feat: implement monochrome design system across entire frontend

Migrate all 84 frontend files from the old themed/colored design to a
monochrome glass-morphism design system. Pure black backgrounds, white
text with opacity levels, glass-card components with backdrop-blur, and
functional color reserved for status indicators only.

Foundation: remap CSS variables to monochrome, simplify Tailwind config,
remove theme toggle, convert brand logo/wordmark to white. Pages: all
14 pages updated. Components: all common, library, session, step-library,
tree-editor, tree-preview, admin, and subscription components converted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-09 21:41:29 -05:00
parent 1381aaae99
commit f4ce1595d6
88 changed files with 2976 additions and 1596 deletions

View File

@@ -0,0 +1,290 @@
# Component Migration Examples
## Common Component Transformations
### 1. Card Component
**BEFORE (Old Design):**
```tsx
<div className="bg-slate-900 border border-slate-800 rounded-lg p-6">
<h3 className="text-slate-200">Title</h3>
<p className="text-slate-400">Content</p>
</div>
```
**AFTER (New Design):**
```tsx
<div className="bg-gradient-to-br from-white/[0.04] to-white/[0.01] border border-white/8 backdrop-blur-xl rounded-2xl p-6 hover:from-white/[0.06] hover:to-white/[0.02] hover:border-white/12 transition-all">
<h3 className="text-white font-bold">Title</h3>
<p className="text-white/40">Content</p>
</div>
```
---
### 2. Active/Highlighted Card
**BEFORE:**
```tsx
<div className="bg-purple-900/20 border border-purple-700 rounded-lg p-8">
{/* content */}
</div>
```
**AFTER (Bright Glow):**
```tsx
<div className="bg-gradient-to-br from-white/[0.08] to-white/[0.04] border border-white/20 rounded-2xl p-8 shadow-[0_0_40px_rgba(255,255,255,0.1)]">
{/* content */}
</div>
```
---
### 3. Icon in Card
**BEFORE:**
```tsx
<div className="w-10 h-10 rounded-lg bg-purple-600 flex items-center justify-center">
<Icon className="w-5 h-5 text-white" />
</div>
```
**AFTER (with subtle color):**
```tsx
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
<Icon className="w-6 h-6 text-blue-400" />
</div>
```
---
### 4. Badge/Status
**BEFORE:**
```tsx
<span className="px-3 py-1 bg-purple-900 text-purple-200 rounded-md text-sm">
Admin
</span>
```
**AFTER:**
```tsx
<div className="px-4 py-2 rounded-xl bg-white/10 border border-white/20">
<span className="text-sm text-white font-semibold">Admin</span>
</div>
```
---
### 5. Primary Button
**BEFORE:**
```tsx
<button className="px-6 py-3 bg-purple-600 hover:bg-purple-700 text-white rounded-lg">
Click Me
</button>
```
**AFTER:**
```tsx
<button className="px-6 py-3 bg-white text-black font-semibold rounded-xl hover:bg-white/90 transition-all hover:scale-105">
Click Me
</button>
```
---
### 6. Secondary Button
**BEFORE:**
```tsx
<button className="px-6 py-3 border border-slate-700 text-slate-300 hover:bg-slate-800 rounded-lg">
Cancel
</button>
```
**AFTER:**
```tsx
<button className="px-6 py-3 bg-white/10 border border-white/20 text-white font-medium rounded-xl hover:bg-white/20 transition-all">
Cancel
</button>
```
---
### 7. Input/Search
**BEFORE:**
```tsx
<input
className="bg-slate-800 border border-slate-700 text-white placeholder-slate-400 rounded-lg px-4 py-3"
placeholder="Search..."
/>
```
**AFTER:**
```tsx
<div className="bg-gradient-to-br from-white/[0.04] to-white/[0.01] border border-white/8 backdrop-blur-xl rounded-2xl p-1">
<div className="flex items-center bg-black/50 rounded-xl">
<svg className="ml-5 w-5 h-5 text-blue-400">{/* search icon */}</svg>
<input
className="flex-1 bg-transparent py-4 px-4 text-white placeholder:text-white/30 focus:outline-none"
placeholder="Search..."
/>
</div>
</div>
```
---
### 8. Progress Bar
**BEFORE:**
```tsx
<div className="h-2 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-purple-600 rounded-full" style={{width: '60%'}}></div>
</div>
```
**AFTER:**
```tsx
<div className="h-2 bg-white/10 rounded-full overflow-hidden">
<div className="h-full bg-white rounded-full" style={{width: '60%'}}></div>
</div>
```
---
### 9. Stat Card with Trend
**BEFORE:**
```tsx
<div className="bg-slate-900 border border-slate-800 rounded-lg p-6">
<div className="text-slate-400 text-sm">Active Users</div>
<div className="text-2xl font-bold text-white">1,234</div>
</div>
```
**AFTER:**
```tsx
<div className="bg-[rgba(20,20,25,0.5)] border border-white/[0.06] backdrop-blur-xl rounded-2xl p-6 hover:scale-105 transition-transform">
<div className="text-sm text-white/40 mb-2 font-medium">Active Users</div>
<div className="text-4xl font-bold text-white mb-1">1,234</div>
<div className="flex items-center gap-1 text-xs text-emerald-400 font-medium">
<svg className="w-3 h-3">{/* up arrow */}</svg>
12% vs last week
</div>
</div>
```
---
### 10. Section Header
**BEFORE:**
```tsx
<div className="mb-8">
<h2 className="text-2xl font-bold text-slate-100">Recent Trees</h2>
<p className="text-slate-400 mt-1">Your recently accessed decision trees</p>
</div>
```
**AFTER:**
```tsx
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">Recent Trees</h2>
<button className="text-sm text-white/60 hover:text-white font-medium transition-colors">
View all
</button>
</div>
```
---
## Icon Color Guidelines
### AI/Automation Icons
```tsx
<svg className="w-5 h-5 text-cyan-400">{/* sparkle/star */}</svg>
```
### Search Icons
```tsx
<svg className="w-5 h-5 text-blue-400">{/* magnifying glass */}</svg>
```
### Active/Playing State
```tsx
<svg className="w-6 h-6 text-violet-400">{/* play button */}</svg>
```
### Network Category
```tsx
<svg className="w-6 h-6 text-blue-400">{/* wifi/network */}</svg>
```
### Printer Category
```tsx
<svg className="w-6 h-6 text-indigo-400">{/* printer */}</svg>
```
### Email Category
```tsx
<svg className="w-6 h-6 text-cyan-400">{/* envelope */}</svg>
```
### Success Indicators
```tsx
<svg className="w-4 h-4 text-emerald-400">{/* check/arrow up */}</svg>
```
### Error Indicators
```tsx
<svg className="w-4 h-4 text-red-400">{/* x/arrow down */}</svg>
```
### Time/Clock (NO COLOR - keep gray)
```tsx
<svg className="w-4 h-4 text-white/30">{/* clock */}</svg>
```
---
## Common Mistakes to Avoid
**DON'T:**
- Use colored backgrounds on cards
- Use gradient text
- Color ALL icons
- Use slate-900, slate-800 (use white/opacity instead)
- Use purple gradients anywhere
**DO:**
- Use white/black with opacity for all backgrounds
- Keep text white (with varying opacity)
- Only color functional icons
- Use backdrop-blur on cards
- Use white for primary buttons
---
## Quick Reference: Opacity Levels
**Text:**
- Primary: `text-white`
- Secondary: `text-white/70`
- Tertiary: `text-white/40`
- Placeholder: `text-white/30`
**Backgrounds:**
- Card: `bg-white/[0.04]` to `bg-white/[0.01]`
- Card hover: `bg-white/[0.06]` to `bg-white/[0.02]`
- Active card: `bg-white/[0.08]` to `bg-white/[0.04]`
- Button secondary: `bg-white/10`
- Badge: `bg-white/10`
**Borders:**
- Subtle: `border-white/8`
- Normal: `border-white/10`
- Prominent: `border-white/20`
- Active: `border-white/30`

View File

@@ -0,0 +1,528 @@
# ResolutionFlow Design System Implementation Guide
## Overview
This guide provides everything needed to implement the new monochrome design with subtle icon accents across the entire ResolutionFlow application.
## Design Philosophy
- **95% Monochrome**: Pure black backgrounds, white text, transparent card overlays
- **5% Color**: Subtle colors ONLY on functional icons
- **Inspiration**: Plasma + Aspect templates (minimalist, modern, enterprise-grade)
- **Key Principle**: Restraint and sophistication over flashy gradients
---
## Color Palette
### Background Colors
```css
/* Main background - pure black with subtle gradient */
background: linear-gradient(to bottom, #000000 0%, #0a0a0a 50%, #000000 100%);
/* Subtle radial overlays (optional, adds depth) */
background: radial-gradient(circle at 50% 0%, rgba(100, 100, 120, 0.03), transparent 50%),
radial-gradient(circle at 80% 80%, rgba(80, 80, 100, 0.02), transparent 50%);
```
### Text Colors
```css
/* Primary text */
color: white;
/* Secondary text */
color: rgba(255, 255, 255, 0.7); /* white/70 */
/* Tertiary text */
color: rgba(255, 255, 255, 0.4); /* white/40 */
/* Subtle text */
color: rgba(255, 255, 255, 0.3); /* white/30 */
```
### Card/Surface Colors
```css
/* Standard cards */
background: linear-gradient(135deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.01) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
/* Card hover state */
background: linear-gradient(135deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.02) 100%);
border-color: rgba(255, 255, 255, 0.12);
/* Stat cards */
background: rgba(20, 20, 25, 0.5);
border: 1px solid rgba(255, 255, 255, 0.06);
backdrop-filter: blur(10px);
/* Active/Highlighted card (Bright Glow) */
background: linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 100%);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 0 40px rgba(255, 255, 255, 0.1);
```
### Icon Colors (THE ONLY COLORS IN THE APP)
```css
/* AI/Sparkle icons */
color: #22d3ee; /* cyan-400 */
/* Search icons */
color: #60a5fa; /* blue-400 */
/* Active/Play icons */
color: #a78bfa; /* violet-400 */
/* Network category */
color: #60a5fa; /* blue-400 */
/* Printer category */
color: #818cf8; /* indigo-400 */
/* Email category */
color: #22d3ee; /* cyan-400 */
/* Success/Up trends */
color: #34d399; /* emerald-400 */
/* Failure/Down trends */
color: #f87171; /* red-400 */
/* Neutral/Time icons (NO COLOR) */
color: rgba(255, 255, 255, 0.5); /* gray */
```
### Button Colors
```css
/* Primary button (white) */
background: white;
color: black;
/* Primary button hover */
background: rgba(255, 255, 255, 0.9);
/* Secondary button */
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
/* Secondary button hover */
background: rgba(255, 255, 255, 0.2);
```
---
## Typography
### Font Family
```css
font-family: 'Inter', sans-serif;
```
### Font Weights
- Regular: 400
- Medium: 500
- Semibold: 600
- Bold: 700
- Extrabold: 800
### Type Scale
```css
/* Hero heading */
font-size: 3.5rem; /* 56px */
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
/* Section heading */
font-size: 2rem; /* 32px */
font-weight: 700;
line-height: 1.2;
/* Card title */
font-size: 1.25rem; /* 20px */
font-weight: 700;
line-height: 1.3;
/* Body large */
font-size: 1.25rem; /* 20px */
font-weight: 400;
line-height: 1.6;
/* Body */
font-size: 1rem; /* 16px */
font-weight: 400;
line-height: 1.5;
/* Small */
font-size: 0.875rem; /* 14px */
font-weight: 500;
line-height: 1.4;
/* Extra small */
font-size: 0.75rem; /* 12px */
font-weight: 500;
line-height: 1.3;
```
---
## Component Patterns
### Header/Navigation
```jsx
<header className="mb-16">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{/* White logo icon */}
<div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center">
<svg className="w-5 h-5 text-black" /* icon */></svg>
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
</div>
<div className="flex items-center gap-4">
<div className="px-4 py-2 rounded-xl bg-white/10 border border-white/20">
<span className="text-sm text-white font-semibold">Admin</span>
</div>
</div>
</div>
</header>
```
### Hero Section
```jsx
<div className="mb-20 text-center max-w-4xl mx-auto">
{/* Badge with colored icon */}
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 mb-8">
<svg className="w-4 h-4 text-cyan-400" /* sparkle icon */></svg>
<span className="text-sm text-white/70 font-medium">AI-POWERED TROUBLESHOOTING</span>
</div>
{/* Main heading */}
<h1 className="text-5xl md:text-7xl font-bold text-white mb-6 tracking-tight leading-tight">
All Your Tickets in One<br>
<span className="text-white/60">Unified Dashboard</span>
</h1>
{/* Description */}
<p className="text-xl text-white/40 mb-12 max-w-2xl mx-auto leading-relaxed">
Search our library of proven decision trees or continue where you left off
</p>
</div>
```
### Search Bar
```jsx
<div className="relative max-w-2xl mx-auto group">
<div className="absolute inset-0 bg-white/5 rounded-2xl blur-2xl opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className="relative bg-gradient-to-br from-white/[0.04] to-white/[0.01] border border-white/8 backdrop-blur-xl rounded-2xl p-1">
<div className="flex items-center bg-black/50 rounded-xl">
{/* Blue search icon */}
<svg className="ml-5 w-5 h-5 text-blue-400" /* search icon */></svg>
<input
type="text"
placeholder="Paste ticket subject or search for a tree..."
className="flex-1 bg-transparent py-4 px-4 text-white placeholder:text-white/30 focus:outline-none"
/>
{/* White button */}
<button className="mr-2 px-5 py-2.5 bg-white text-black font-semibold rounded-lg hover:bg-white/90 transition-all">
Search
</button>
</div>
</div>
</div>
```
### Stat Card
```jsx
<div className="bg-[rgba(20,20,25,0.5)] border border-white/[0.06] backdrop-blur-xl rounded-2xl p-6 hover:scale-105 transition-transform">
<div className="text-sm text-white/40 mb-2 font-medium">Active Sessions</div>
<div className="text-4xl font-bold text-white mb-1">18</div>
<div className="flex items-center gap-1 text-xs text-emerald-400 font-medium">
{/* Green up arrow icon */}
<svg className="w-3 h-3" /* arrow up icon */></svg>
12% vs last week
</div>
</div>
```
### Active Session Card (Bright Glow)
```jsx
<div className="bg-gradient-to-br from-white/[0.08] to-white/[0.04] border border-white/20 backdrop-blur-xl rounded-2xl p-8 shadow-[0_0_40px_rgba(255,255,255,0.1)]">
<div className="flex items-start justify-between mb-6">
<div>
<div className="flex items-center gap-3 mb-3">
{/* Icon with violet color */}
<div className="w-12 h-12 rounded-xl bg-white/15 border border-white/30 flex items-center justify-center">
<svg className="w-6 h-6 text-violet-400" /* play icon */></svg>
</div>
<div>
<div className="text-xs text-white/70 font-semibold uppercase tracking-wider mb-1">
Active Session
</div>
<h3 className="text-2xl font-bold text-white">Email Delivery Issues</h3>
</div>
</div>
<p className="text-sm text-white/50">Ticket #5847 Started 2h ago 11 of 15 steps</p>
</div>
<button className="px-4 py-2 bg-white text-black rounded-xl font-semibold hover:bg-white/90 transition-all">
Continue
</button>
</div>
{/* Progress bar */}
<div>
<div className="flex items-center justify-between text-sm text-white/70 mb-3 font-medium">
<span>Progress</span>
<span>72%</span>
</div>
<div className="h-2 bg-white/10 rounded-full overflow-hidden">
<div className="h-full bg-white rounded-full" style={{width: '72%'}}></div>
</div>
</div>
</div>
```
### Tree Card
```jsx
<div className="bg-gradient-to-br from-white/[0.04] to-white/[0.01] border border-white/8 backdrop-blur-xl rounded-2xl p-6 hover:scale-105 hover:from-white/[0.06] hover:to-white/[0.02] hover:border-white/12 transition-all cursor-pointer">
<div className="flex items-start justify-between mb-4">
{/* Icon with category-specific color */}
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
<svg className="w-6 h-6 text-blue-400" /* network icon */></svg>
</div>
<div className="px-2 py-1 rounded-lg bg-white/10 border border-white/20">
<span className="text-xs text-white/80 font-semibold">Available</span>
</div>
</div>
<h4 className="text-lg font-bold text-white mb-2">Network Connectivity</h4>
<p className="text-sm text-white/40 mb-4">Diagnose and resolve network issues</p>
<div className="flex items-center gap-1.5 text-xs text-white/30">
{/* Gray clock icon (NO COLOR) */}
<svg className="w-3.5 h-3.5 text-slate-500" /* clock icon */></svg>
Last used 5h ago
</div>
</div>
```
### Primary Button (White)
```jsx
<button className="px-8 py-4 bg-white text-black font-bold rounded-xl hover:bg-white/90 transition-all hover:scale-105">
Get Started Free
</button>
```
### Secondary Button (Transparent)
```jsx
<button className="px-8 py-4 bg-white/10 border border-white/20 text-white font-semibold rounded-xl hover:bg-white/20 transition-all">
Book a Demo
</button>
```
---
## Icon Color Mapping
### Where to use each color:
**Cyan (#22d3ee / cyan-400):**
- AI/Sparkle icons
- Email-related icons
- Magic/automation indicators
**Blue (#60a5fa / blue-400):**
- Search icons
- Network-related icons
- General tech icons
**Violet (#a78bfa / violet-400):**
- Active/playing state icons
- Current session indicators
- Progress-related icons
**Indigo (#818cf8 / indigo-400):**
- Printer-related icons
- Hardware icons
**Emerald (#34d399 / emerald-400):**
- Success indicators
- Up arrows/trends
- Positive metrics
**Red (#f87171 / red-400):**
- Error indicators
- Down arrows/trends
- Negative metrics
**Gray (rgba(255,255,255,0.5) / white/50):**
- Time/clock icons
- Neutral informational icons
- Non-critical icons
---
## Tailwind Classes Reference
### Background Patterns
```
bg-black
bg-white
bg-white/5 (5% opacity)
bg-white/10 (10% opacity)
bg-white/15 (15% opacity)
```
### Text Opacity
```
text-white
text-white/70
text-white/60
text-white/50
text-white/40
text-white/30
```
### Border Opacity
```
border-white/8
border-white/10
border-white/12
border-white/20
border-white/30
```
### Icon Colors
```
text-cyan-400
text-blue-400
text-violet-400
text-indigo-400
text-emerald-400
text-red-400
text-slate-500
```
### Rounded Corners
```
rounded-xl (12px - cards, buttons)
rounded-2xl (16px - large cards)
rounded-3xl (24px - hero sections)
rounded-full (perfect circle - badges, dots)
rounded-lg (8px - small elements)
```
### Spacing
```
gap-1, gap-2, gap-3, gap-4, gap-6, gap-8
p-4, p-6, p-8, p-12
mb-2, mb-3, mb-4, mb-6, mb-8, mb-12, mb-16, mb-20
```
---
## Animation & Transitions
### Standard Transition
```css
transition: all 0.2s ease;
```
### Hover Scale
```css
hover:scale-105
transition-transform
```
### Hover Opacity
```css
hover:opacity-100
transition-opacity
```
### Card Hover Glow
```css
hover:shadow-[0_0_40px_rgba(255,255,255,0.15)]
```
---
## Implementation Checklist
### Phase 1: Global Styles
- [ ] Update background color to pure black gradient
- [ ] Set font-family to Inter
- [ ] Add subtle radial gradient overlays
### Phase 2: Component Updates
- [ ] Header/Navigation
- [ ] Hero sections
- [ ] Search bars
- [ ] Stat cards
- [ ] Active session cards (Bright Glow)
- [ ] Tree/item cards
- [ ] Buttons (primary & secondary)
- [ ] Forms/inputs
- [ ] Modals
- [ ] Empty states
### Phase 3: Icon Colors
- [ ] Map all icons to appropriate colors
- [ ] Keep time/clock icons gray
- [ ] Add category colors to tree icons
### Phase 4: Polish
- [ ] Add hover states to all interactive elements
- [ ] Test backdrop-filter blur support
- [ ] Verify contrast ratios for accessibility
- [ ] Test on dark displays
---
## Notes for Claude Code
1. **Preserve existing functionality** - Only change visual styling, not logic
2. **Use Tailwind classes** - Avoid custom CSS where possible
3. **Keep shadcn/ui components** - Just reskin them with new colors
4. **Test incrementally** - Update one component type at a time
5. **Icon mapping is critical** - Different tree types get different icon colors
## Questions to Resolve
1. Should all buttons use the same white style, or do you want variations?
2. Do you want the subtle radial gradient overlays, or pure black background?
3. Should stat cards always show trend indicators (up/down arrows)?
4. Any specific components that should NOT follow this design?
---
## Example Files to Update
**Priority 1 (Core UI):**
- `AppLayout.tsx` - Main layout wrapper
- `QuickStartPage.tsx` - Homepage/dashboard
- `Header.tsx` / `Navbar.tsx` - Navigation
**Priority 2 (Features):**
- Tree list components
- Session components
- Search components
- Stats/metrics components
**Priority 3 (Supporting):**
- Modals
- Forms
- Settings pages
- Admin panels
---
## Testing Checklist
After implementation:
- [ ] Dark mode looks correct
- [ ] All text is readable (contrast check)
- [ ] Icons have correct colors
- [ ] Hover states work
- [ ] Cards have proper depth/hierarchy
- [ ] Buttons are prominent
- [ ] Layout is consistent across pages

View File

@@ -2,11 +2,9 @@ import { useEffect } from 'react'
import { RouterProvider } from 'react-router-dom'
import { router } from '@/router'
import { useAuthStore } from '@/store/authStore'
import { useThemeStore } from '@/store/themeStore'
function App() {
const { isAuthenticated, fetchUser, setLoading } = useAuthStore()
const { theme, setTheme } = useThemeStore()
useEffect(() => {
// On app load, check if we have a token and fetch user data
@@ -20,19 +18,6 @@ function App() {
}
}, [])
// Listen for system theme changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = () => {
if (theme === 'system') {
setTheme('system') // Re-apply to update resolvedTheme
}
}
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [theme, setTheme])
return <RouterProvider router={router} />
}

View File

@@ -53,8 +53,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
ref={buttonRef}
onClick={() => setOpen(!open)}
className={cn(
'rounded-md p-1.5 text-muted-foreground transition-colors',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md p-1.5 text-white/50 transition-colors',
'hover:bg-white/10 hover:text-white'
)}
>
<MoreHorizontal className="h-4 w-4" />
@@ -63,8 +63,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
<div
ref={menuRef}
className={cn(
'fixed z-50 min-w-[160px] rounded-md border border-border',
'bg-card py-1 shadow-lg animate-scale-in'
'fixed z-50 min-w-[160px] rounded-md border border-white/10',
'bg-black py-1 shadow-lg animate-scale-in'
)}
style={{
top: `${menuPosition.top}px`,
@@ -80,8 +80,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
'flex w-full items-center gap-2 px-3 py-2 text-sm transition-colors',
'disabled:opacity-50 disabled:pointer-events-none',
item.destructive
? 'text-destructive hover:bg-destructive/10'
: 'text-foreground hover:bg-accent'
? 'text-red-400 hover:bg-red-400/10'
: 'text-white/70 hover:bg-white/[0.06]'
)}
>
{item.icon}

View File

@@ -36,7 +36,7 @@ export function AdminLayout() {
return (
<div className="flex h-[calc(100vh-4rem)]">
{/* Desktop sidebar */}
<div className="hidden w-60 flex-shrink-0 border-r border-border bg-card md:block">
<div className="hidden w-60 flex-shrink-0 border-r border-white/[0.06] bg-black md:block">
<AdminSidebar />
</div>
@@ -44,14 +44,14 @@ export function AdminLayout() {
{mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden">
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={() => setMobileOpen(false)}
/>
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
<div className="absolute inset-y-0 left-0 w-60 border-r border-white/[0.06] bg-black shadow-xl">
<div className="flex h-12 items-center justify-end px-3">
<button
onClick={() => setMobileOpen(false)}
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
className="rounded-md p-1.5 text-white/50 hover:bg-white/[0.06]"
>
<X className="h-4 w-4" />
</button>
@@ -67,7 +67,7 @@ export function AdminLayout() {
{/* Mobile menu button */}
<button
onClick={() => setMobileOpen(true)}
className="mb-4 rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden"
className="mb-4 rounded-md p-2 text-white/50 hover:bg-white/[0.06] md:hidden"
>
<Menu className="h-5 w-5" />
</button>

View File

@@ -39,7 +39,7 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
return (
<aside className={cn('flex h-full flex-col', className)}>
<div className="p-4">
<h2 className="font-heading text-lg font-bold text-foreground">Admin Panel</h2>
<h2 className="text-lg font-bold text-white">Admin Panel</h2>
</div>
<nav className="flex-1 space-y-1 px-3">
{navItems.map((item) => (
@@ -50,8 +50,8 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
isActive(item.path, item.end)
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
? 'bg-white/10 text-white'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
>
<item.icon className="h-4 w-4" />
@@ -59,13 +59,13 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
</Link>
))}
</nav>
<div className="border-t border-border p-3">
<div className="border-t border-white/[0.06] p-3">
<Link
to="/trees"
onClick={onNavigate}
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
>
<ArrowLeft className="h-4 w-4" />

View File

@@ -38,7 +38,7 @@ export function CategoryRow({
ref={setNodeRef}
style={style}
className={cn(
'flex items-center gap-3 rounded-lg border border-border bg-card p-4',
'flex items-center gap-3 glass-card rounded-2xl p-4',
isDragging && 'opacity-50'
)}
>
@@ -47,7 +47,7 @@ export function CategoryRow({
type="button"
{...attributes}
{...listeners}
className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
className="cursor-grab touch-none text-white/50 hover:text-white active:cursor-grabbing"
aria-label="Drag to reorder"
>
<GripVertical className="h-5 w-5" />
@@ -56,17 +56,17 @@ export function CategoryRow({
{/* Category Info */}
<div className="flex-1">
<div className="flex items-center gap-2">
<h3 className="font-medium text-foreground">{category.name}</h3>
<h3 className="font-medium text-white">{category.name}</h3>
{!category.is_active && (
<span className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground">
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs font-medium text-white/70">
Archived
</span>
)}
</div>
{category.description && (
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
<p className="mt-1 text-sm text-white/40">{category.description}</p>
)}
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{stepCount} step{stepCount !== 1 ? 's' : ''}
</p>
</div>
@@ -77,8 +77,8 @@ export function CategoryRow({
type="button"
onClick={() => onEdit(category)}
className={cn(
'rounded-md border border-input bg-background p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 bg-black/50 p-2 text-white/50',
'hover:bg-white/10 hover:text-white'
)}
title="Edit category"
>

View File

@@ -58,15 +58,15 @@ export function CreateCategoryModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">Create Category</h2>
<h2 className="text-lg font-semibold text-white">Create Category</h2>
<button
onClick={handleClose}
disabled={isSaving}
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -76,15 +76,15 @@ export function CreateCategoryModal({
<form onSubmit={handleSubmit} className="space-y-4">
{/* Error Message */}
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-md bg-red-400/10 p-3 text-sm text-red-400">
{error}
</div>
)}
{/* Name Field */}
<div>
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-destructive">*</span>
<label htmlFor="name" className="mb-1 block text-sm font-medium text-white">
Category Name <span className="text-red-400">*</span>
</label>
<input
id="name"
@@ -96,21 +96,21 @@ export function CreateCategoryModal({
placeholder="e.g., Network Troubleshooting"
required
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{name.length}/100 characters
</p>
</div>
{/* Description Field */}
<div>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
</label>
<textarea
id="description"
@@ -120,9 +120,9 @@ export function CreateCategoryModal({
rows={3}
placeholder="Brief description of this category..."
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
@@ -135,8 +135,8 @@ export function CreateCategoryModal({
onClick={handleClose}
disabled={isSaving}
className={cn(
'rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
Cancel
@@ -145,8 +145,8 @@ export function CreateCategoryModal({
type="submit"
disabled={isSaving || !name.trim()}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
{isSaving ? 'Creating...' : 'Create Category'}

View File

@@ -50,16 +50,16 @@ export function DataTable<T>({
}
return (
<div className="overflow-x-auto rounded-lg border border-border">
<div className="overflow-x-auto rounded-lg border border-white/[0.06]">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border bg-muted/50">
<tr className="border-b border-white/[0.06] bg-white/[0.02]">
{columns.map((col) => (
<th
key={col.key}
className={cn(
'px-4 py-3 text-left font-medium text-muted-foreground',
col.sortable && 'cursor-pointer select-none hover:text-foreground',
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-white/50',
col.sortable && 'cursor-pointer select-none hover:text-white',
col.className
)}
onClick={col.sortable ? () => handleSort(col.key) : undefined}
@@ -87,10 +87,10 @@ export function DataTable<T>({
<tbody>
{isLoading ? (
Array.from({ length: skeletonRows }).map((_, i) => (
<tr key={i} className="border-b border-border last:border-0">
<tr key={i} className="border-b border-white/[0.06] last:border-0">
{columns.map((col) => (
<td key={col.key} className="px-4 py-3">
<div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
<div className="h-4 w-3/4 animate-pulse rounded bg-white/10" />
</td>
))}
</tr>
@@ -99,7 +99,7 @@ export function DataTable<T>({
<tr>
<td colSpan={columns.length} className="px-4 py-12 text-center">
{emptyState || (
<span className="text-muted-foreground">No data found</span>
<span className="text-white/40">No data found</span>
)}
</td>
</tr>
@@ -107,7 +107,7 @@ export function DataTable<T>({
data.map((item) => (
<tr
key={keyExtractor(item)}
className="border-b border-border last:border-0 hover:bg-muted/30 transition-colors"
className="border-b border-white/[0.06] last:border-0 hover:bg-white/[0.04] transition-colors"
>
{columns.map((col) => (
<td key={col.key} className={cn('px-4 py-3', col.className)}>

View File

@@ -67,15 +67,15 @@ export function EditCategoryModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">Edit Category</h2>
<h2 className="text-lg font-semibold text-white">Edit Category</h2>
<button
onClick={handleClose}
disabled={isSaving}
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
className="rounded-full p-1 text-white/50 hover:bg-white/10 hover:text-white disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -85,15 +85,15 @@ export function EditCategoryModal({
<form onSubmit={handleSubmit} className="space-y-4">
{/* Error Message */}
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-md bg-red-400/10 p-3 text-sm text-red-400">
{error}
</div>
)}
{/* Name Field */}
<div>
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
Category Name <span className="text-destructive">*</span>
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-white">
Category Name <span className="text-red-400">*</span>
</label>
<input
id="edit-name"
@@ -105,21 +105,21 @@ export function EditCategoryModal({
placeholder="e.g., Network Troubleshooting"
required
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{name.length}/100 characters
</p>
</div>
{/* Description Field */}
<div>
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
</label>
<textarea
id="edit-description"
@@ -129,9 +129,9 @@ export function EditCategoryModal({
rows={3}
placeholder="Brief description of this category..."
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
@@ -144,8 +144,8 @@ export function EditCategoryModal({
onClick={handleClose}
disabled={isSaving}
className={cn(
'rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
Cancel
@@ -154,8 +154,8 @@ export function EditCategoryModal({
type="submit"
disabled={isSaving || !name.trim()}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
{isSaving ? 'Saving...' : 'Save Changes'}

View File

@@ -12,10 +12,10 @@ interface EmptyStateProps {
export function EmptyState({ icon, title, description, action, className }: EmptyStateProps) {
return (
<div className={cn('flex flex-col items-center justify-center py-12 text-center', className)}>
{icon && <div className="mb-4 text-muted-foreground">{icon}</div>}
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
{icon && <div className="mb-4 text-white/50">{icon}</div>}
<h3 className="text-lg font-semibold text-white">{title}</h3>
{description && (
<p className="mt-1 max-w-sm text-sm text-muted-foreground">{description}</p>
<p className="mt-1 max-w-sm text-sm text-white/40">{description}</p>
)}
{action && <div className="mt-4">{action}</div>}
</div>

View File

@@ -12,7 +12,7 @@ export function PageHeader({ title, description, action, className }: PageHeader
return (
<div className={cn('flex items-start justify-between gap-4', className)}>
<div>
<h1 className="font-heading text-2xl font-bold text-foreground">{title}</h1>
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
{description && (
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
)}

View File

@@ -36,20 +36,20 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
return (
<div className="flex items-center justify-between gap-4 pt-4">
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
Showing {start}-{end} of {total}
</span>
<div className="flex items-center gap-1">
<button
onClick={() => onPageChange(page - 1)}
disabled={page <= 1}
className={cn(btnBase, 'px-2 hover:bg-accent')}
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
>
<ChevronLeft className="h-4 w-4" />
</button>
{getPageNumbers().map((p, i) =>
p === 'ellipsis' ? (
<span key={`e${i}`} className="px-1 text-muted-foreground">...</span>
<span key={`e${i}`} className="px-1 text-white/40">...</span>
) : (
<button
key={p}
@@ -58,8 +58,8 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
btnBase,
'px-2',
p === page
? 'bg-primary text-primary-foreground'
: 'hover:bg-accent text-muted-foreground'
? 'bg-white text-black'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
>
{p}
@@ -69,7 +69,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
<button
onClick={() => onPageChange(page + 1)}
disabled={page >= totalPages}
className={cn(btnBase, 'px-2 hover:bg-accent')}
className={cn(btnBase, 'px-2 text-white/50 hover:bg-white/[0.06] hover:text-white')}
>
<ChevronRight className="h-4 w-4" />
</button>

View File

@@ -40,21 +40,21 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
return (
<div className={cn('relative', className)}>
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/50" />
<input
type="text"
value={localValue}
onChange={handleChange}
placeholder={placeholder}
className={cn(
'h-9 w-full rounded-md border border-border bg-background pl-9 pr-8 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'h-9 w-full rounded-md border border-white/10 bg-black/50 pl-9 pr-8 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
{localValue && (
<button
onClick={handleClear}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground"
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-white/50 hover:text-white"
>
<X className="h-3.5 w-3.5" />
</button>

View File

@@ -9,10 +9,10 @@ interface StatusBadgeProps {
}
const variantClasses: Record<BadgeVariant, string> = {
success: 'bg-green-500/10 text-green-600 dark:text-green-400',
destructive: 'bg-red-500/10 text-red-600 dark:text-red-400',
warning: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400',
default: 'bg-muted text-muted-foreground',
success: 'bg-emerald-400/10 text-emerald-400',
destructive: 'bg-red-400/10 text-red-400',
warning: 'bg-yellow-400/10 text-yellow-400',
default: 'bg-white/10 text-white/70',
}
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {

View File

@@ -6,51 +6,41 @@ interface BrandLogoProps {
}
/**
* ResolutionFlow brand logo icon.
* ResolutionFlow brand logo icon — white monochrome.
* sm (32x32) for header/navbar, lg (80x80) for login/register pages.
*/
export function BrandLogo({ size = 'sm', className }: BrandLogoProps) {
const sizeClasses = size === 'sm' ? 'h-8 w-8' : 'h-20 w-20'
// The SVG scales via viewBox - same paths work at any size.
// Stroke widths are tuned per size for visual clarity.
const strokeBase = size === 'sm' ? 1 : 2
const strokeThick = size === 'sm' ? 1.25 : 2.5
const dashArray = size === 'sm' ? '1 1.5' : '2 3'
const nodeR = size === 'sm' ? { outer: 2.5, inner: 2.75 } : { outer: 5, inner: 5.5 }
const hubR = size === 'sm' ? { glow: 5, solid: 3.5 } : { glow: 10, solid: 7 }
// Positions scale with viewBox
const vb = size === 'sm' ? '0 0 40 40' : '0 0 80 80'
const s = size === 'sm' ? 1 : 2 // scale factor
const s = size === 'sm' ? 1 : 2
return (
<svg viewBox={vb} fill="none" className={cn(sizeClasses, className)}>
<defs>
<linearGradient id="brand-logo-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#818cf8" />
<stop offset="100%" stopColor="#a78bfa" />
</linearGradient>
</defs>
{/* Input nodes */}
<circle cx={5 * s} cy={7 * s} r={nodeR.outer} fill="url(#brand-logo-grad)" opacity="0.35" />
<circle cx={5 * s} cy={15 * s} r={nodeR.inner} fill="url(#brand-logo-grad)" opacity="0.5" />
<circle cx={5 * s} cy={25 * s} r={nodeR.inner} fill="url(#brand-logo-grad)" opacity="0.5" />
<circle cx={5 * s} cy={33 * s} r={nodeR.outer} fill="url(#brand-logo-grad)" opacity="0.35" />
<circle cx={5 * s} cy={7 * s} r={nodeR.outer} fill="white" opacity="0.35" />
<circle cx={5 * s} cy={15 * s} r={nodeR.inner} fill="white" opacity="0.5" />
<circle cx={5 * s} cy={25 * s} r={nodeR.inner} fill="white" opacity="0.5" />
<circle cx={5 * s} cy={33 * s} r={nodeR.outer} fill="white" opacity="0.35" />
{/* Connecting lines */}
<path d={`M${7.5 * s} ${7 * s}L${14 * s} ${17 * s}`} stroke="url(#brand-logo-grad)" strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.45" />
<path d={`M${7.75 * s} ${15 * s}L${14 * s} ${19 * s}`} stroke="url(#brand-logo-grad)" strokeWidth={strokeBase} strokeLinecap="round" opacity="0.6" />
<path d={`M${7.75 * s} ${25 * s}L${14 * s} ${21 * s}`} stroke="url(#brand-logo-grad)" strokeWidth={strokeBase} strokeLinecap="round" opacity="0.6" />
<path d={`M${7.5 * s} ${33 * s}L${14 * s} ${23 * s}`} stroke="url(#brand-logo-grad)" strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.45" />
<path d={`M${7.5 * s} ${7 * s}L${14 * s} ${17 * s}`} stroke="white" strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.45" />
<path d={`M${7.75 * s} ${15 * s}L${14 * s} ${19 * s}`} stroke="white" strokeWidth={strokeBase} strokeLinecap="round" opacity="0.6" />
<path d={`M${7.75 * s} ${25 * s}L${14 * s} ${21 * s}`} stroke="white" strokeWidth={strokeBase} strokeLinecap="round" opacity="0.6" />
<path d={`M${7.5 * s} ${33 * s}L${14 * s} ${23 * s}`} stroke="white" strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.45" />
{/* Central hub */}
<circle cx={18 * s} cy={20 * s} r={hubR.glow} fill="url(#brand-logo-grad)" opacity="0.15" />
<circle cx={18 * s} cy={20 * s} r={hubR.solid} fill="url(#brand-logo-grad)" />
<circle cx={18 * s} cy={20 * s} r={hubR.glow} fill="white" opacity="0.15" />
<circle cx={18 * s} cy={20 * s} r={hubR.solid} fill="white" />
{/* Output arrow */}
<path d={`M${21.5 * s} ${20 * s}H${35 * s}M${35 * s} ${20 * s}L${30 * s} ${15 * s}M${35 * s} ${20 * s}L${30 * s} ${25 * s}`} stroke="url(#brand-logo-grad)" strokeWidth={strokeThick} strokeLinecap="round" strokeLinejoin="round" />
<path d={`M${21.5 * s} ${20 * s}H${35 * s}M${35 * s} ${20 * s}L${30 * s} ${15 * s}M${35 * s} ${20 * s}L${30 * s} ${25 * s}`} stroke="white" strokeWidth={strokeThick} strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}

View File

@@ -6,20 +6,19 @@ interface BrandWordmarkProps {
}
/**
* ResolutionFlow wordmark with gradient "Flow" text.
* ResolutionFlow wordmark — clean white text.
* sm for header/navbar, lg for login/register pages.
*/
export function BrandWordmark({ size = 'sm', className }: BrandWordmarkProps) {
return (
<span
className={cn(
'font-heading font-bold',
size === 'sm' ? 'text-xl' : 'text-3xl tracking-tight',
'font-semibold tracking-tight text-white',
size === 'sm' ? 'text-xl' : 'text-3xl',
className
)}
>
<span className="text-foreground">Resolution</span>
<span className="text-gradient-brand">Flow</span>
ResolutionFlow
</span>
)
}

View File

@@ -34,8 +34,8 @@ export function ConfirmDialog({
onClick={onClose}
disabled={isLoading}
className={cn(
'rounded-md border border-border px-4 py-2 text-sm font-medium',
'text-card-foreground hover:bg-accent',
'rounded-xl border border-white/10 px-4 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
@@ -45,11 +45,11 @@ export function ConfirmDialog({
onClick={onConfirm}
disabled={isLoading}
className={cn(
'rounded-md px-4 py-2 text-sm font-medium text-white',
'rounded-xl px-4 py-2 text-sm font-medium',
'disabled:opacity-50 disabled:cursor-not-allowed',
confirmVariant === 'destructive'
? 'bg-destructive hover:bg-destructive/90'
: 'bg-primary hover:bg-primary/90'
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20'
: 'bg-white text-black hover:bg-white/90'
)}
>
{isLoading ? 'Processing...' : confirmLabel}
@@ -57,7 +57,7 @@ export function ConfirmDialog({
</div>
}
>
<p className="text-sm text-muted-foreground">{message}</p>
<p className="text-sm text-white/70">{message}</p>
</Modal>
)
}

View File

@@ -34,22 +34,22 @@ export class ErrorBoundary extends Component<Props, State> {
return (
<div className="flex min-h-[400px] flex-col items-center justify-center p-8">
<div className="max-w-md text-center">
<h2 className="mb-2 text-xl font-semibold text-foreground">
<h2 className="mb-2 text-xl font-semibold text-red-400">
Something went wrong
</h2>
<p className="mb-4 text-muted-foreground">
<p className="mb-4 text-white/70">
An unexpected error occurred. Please try refreshing the page.
</p>
{this.state.error && (
<pre className="mb-4 overflow-auto rounded bg-muted p-3 text-left text-xs text-muted-foreground">
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-white/[0.06] p-3 text-left text-xs text-red-400">
{this.state.error.message}
</pre>
)}
<button
onClick={() => window.location.reload()}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Refresh Page

View File

@@ -52,7 +52,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
>
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={onClose}
aria-hidden="true"
/>
@@ -60,23 +60,23 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
{/* Modal Content */}
<div
className={cn(
'relative flex w-full flex-col border border-border bg-card shadow-lg',
'max-h-[100vh] rounded-t-lg sm:max-h-[85vh] sm:rounded-lg',
'relative flex w-full flex-col border border-white/[0.06] bg-[#0a0a0a] shadow-lg',
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
'animate-scale-in',
sizeClasses[size]
)}
>
{/* Header - Fixed at top */}
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
<h2 id="modal-title" className="text-lg font-semibold text-card-foreground">
<div className="flex flex-shrink-0 items-center justify-between border-b border-white/[0.06] px-4 py-3 sm:px-6 sm:py-4">
<h2 id="modal-title" className="text-lg font-semibold text-white">
{title}
</h2>
<button
onClick={onClose}
className={cn(
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
'hover:bg-accent hover:text-accent-foreground',
'focus:outline-none focus:ring-2 focus:ring-ring'
'rounded-md p-1.5 text-white/40 transition-colors sm:p-1',
'hover:bg-white/10 hover:text-white',
'focus:outline-none focus:ring-2 focus:ring-white/20'
)}
aria-label="Close modal"
>
@@ -91,7 +91,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md' }:
{/* Footer - Fixed at bottom */}
{footer && (
<div className="flex-shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
<div className="flex-shrink-0 border-t border-white/[0.06] px-4 py-3 sm:px-6 sm:py-4">
{footer}
</div>
)}

View File

@@ -1,9 +1,9 @@
export function PageLoader() {
return (
<div className="flex h-screen items-center justify-center bg-background">
<div className="flex h-screen items-center justify-center bg-black">
<div className="flex flex-col items-center gap-4">
<div className="h-12 w-12 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<p className="text-sm text-muted-foreground">Loading...</p>
<div className="h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-white" />
<p className="text-sm text-white/40">Loading...</p>
</div>
</div>
)

View File

@@ -17,19 +17,19 @@ export function RouteError() {
}
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
<div className="flex min-h-screen flex-col items-center justify-center bg-black p-8">
<div className="max-w-md text-center">
<h1 className="mb-2 text-4xl font-bold text-foreground">Oops!</h1>
<h2 className="mb-2 text-xl font-semibold text-foreground">{errorMessage}</h2>
<h1 className="mb-2 text-4xl font-bold text-white">Oops!</h1>
<h2 className="mb-2 text-xl font-semibold text-red-400">{errorMessage}</h2>
{errorDetails && (
<p className="mb-4 text-muted-foreground">{errorDetails}</p>
<p className="mb-4 text-white/70">{errorDetails}</p>
)}
<div className="flex justify-center gap-4">
<button
onClick={() => navigate(-1)}
className={cn(
'rounded-md border border-input px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground'
'rounded-xl border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
Go Back
@@ -37,8 +37,8 @@ export function RouteError() {
<button
onClick={() => navigate('/trees')}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Go Home

View File

@@ -48,14 +48,14 @@ export function StarRating({
sizeClasses[size],
star <= value
? 'fill-yellow-400 text-yellow-400'
: 'fill-none text-muted-foreground',
: 'fill-none text-white/30',
!readonly && 'hover:text-yellow-300'
)}
/>
</button>
))}
{showCount && (
<span className="ml-1 text-sm text-muted-foreground">
<span className="ml-1 text-sm text-white/40">
({value}/5)
</span>
)}

View File

@@ -37,8 +37,8 @@ export function TagBadges({
'rounded-full transition-colors',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
variant === 'default'
? 'bg-primary/10 text-primary hover:bg-primary/20'
: 'bg-muted text-muted-foreground hover:bg-muted/80',
? 'bg-white/10 text-white/70 hover:bg-white/15'
: 'bg-white/5 text-white/40 hover:bg-white/10',
!onTagClick && 'cursor-default'
)}
>
@@ -50,7 +50,7 @@ export function TagBadges({
className={cn(
'rounded-full',
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
'bg-muted text-muted-foreground'
'bg-white/5 text-white/40'
)}
title={tags.slice(maxVisible).join(', ')}
>

View File

@@ -118,11 +118,11 @@ export function TagInput({
<div ref={wrapperRef} className="relative">
<div
className={cn(
'flex flex-wrap gap-1.5 rounded-md border px-2 py-1.5',
'bg-background text-foreground',
'focus-within:border-primary focus-within:ring-1 focus-within:ring-primary',
'flex flex-wrap gap-1.5 rounded-xl border px-2 py-1.5',
'bg-black/50 text-white',
'focus-within:border-white/30 focus-within:ring-1 focus-within:ring-white/20',
disabled ? 'cursor-not-allowed opacity-50' : '',
'border-input'
'border-white/10'
)}
onClick={() => inputRef.current?.focus()}
>
@@ -132,7 +132,7 @@ export function TagInput({
key={tag}
className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs',
'bg-primary/10 text-primary'
'bg-white/10 text-white/70'
)}
>
{tag}
@@ -143,7 +143,7 @@ export function TagInput({
e.stopPropagation()
removeTag(tag)
}}
className="rounded-full p-0.5 hover:bg-primary/20"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
@@ -167,8 +167,8 @@ export function TagInput({
placeholder={tags.length === 0 ? placeholder : ''}
disabled={disabled}
className={cn(
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm',
'placeholder:text-muted-foreground',
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-white',
'placeholder:text-white/40',
'focus:outline-none focus:ring-0'
)}
/>
@@ -179,8 +179,8 @@ export function TagInput({
{showSuggestions && suggestions.length > 0 && (
<div
className={cn(
'absolute z-10 mt-1 w-full rounded-md border border-input',
'bg-popover shadow-lg'
'absolute z-10 mt-1 w-full rounded-xl border border-white/[0.06]',
'bg-[#0a0a0a] shadow-lg'
)}
>
{suggestions.map((suggestion, index) => (
@@ -189,13 +189,13 @@ export function TagInput({
type="button"
onClick={() => addTag(suggestion.name)}
className={cn(
'flex w-full items-center justify-between px-3 py-2 text-sm',
'hover:bg-accent',
index === selectedIndex && 'bg-accent'
'flex w-full items-center justify-between px-3 py-2 text-sm text-white/70',
'hover:bg-white/10',
index === selectedIndex && 'bg-white/10'
)}
>
<span>{suggestion.name}</span>
<span className="text-xs text-muted-foreground">
<span className="text-xs text-white/40">
{suggestion.usage_count} trees
</span>
</button>
@@ -208,8 +208,8 @@ export function TagInput({
type="button"
onClick={() => addTag(inputValue)}
className={cn(
'flex w-full items-center gap-2 border-t border-input px-3 py-2 text-sm',
'hover:bg-accent text-primary'
'flex w-full items-center gap-2 border-t border-white/[0.06] px-3 py-2 text-sm',
'hover:bg-white/10 text-white'
)}
>
<Plus className="h-4 w-4" />
@@ -220,7 +220,7 @@ export function TagInput({
)}
{/* Helper text */}
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{tags.length}/{maxTags} tags. Press Enter or comma to add.
</p>
</div>

View File

@@ -11,17 +11,17 @@ export function UpgradePrompt({ feature, className }: UpgradePromptProps) {
return (
<div className={cn(
'rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-4',
'glass-card rounded-2xl border border-white/[0.06] p-4',
className
)}>
<h3 className="font-semibold text-foreground">Plan Limit Reached</h3>
<p className="mt-1 text-sm text-muted-foreground">
<h3 className="font-semibold text-white">Plan Limit Reached</h3>
<p className="mt-1 text-sm text-white/70">
Your {plan} plan doesn't allow you to {feature}. Upgrade your plan to continue.
</p>
<button
className={cn(
'mt-3 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'mt-3 rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
onClick={() => window.location.href = '/account'}
>

View File

@@ -0,0 +1,227 @@
import { useState, useEffect, useCallback } from 'react'
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { ThemeToggle } from '@/components/common/ThemeToggle'
import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { Menu, X } from 'lucide-react'
import { cn } from '@/lib/utils'
export function AppLayout() {
const location = useLocation()
const navigate = useNavigate()
const { user, logout } = useAuthStore()
const { effectiveRole, isSuperAdmin } = usePermissions()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const handleLogout = async () => {
setMobileMenuOpen(false)
await logout()
navigate('/login')
}
// Close mobile menu on route change - key-based reset
const [prevPath, setPrevPath] = useState(location.pathname)
if (prevPath !== location.pathname) {
setPrevPath(location.pathname)
if (mobileMenuOpen) setMobileMenuOpen(false)
}
// Close on Escape
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') setMobileMenuOpen(false)
}, [])
useEffect(() => {
if (mobileMenuOpen) {
document.addEventListener('keydown', handleKeyDown)
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.removeEventListener('keydown', handleKeyDown)
document.body.style.overflow = ''
}
}, [mobileMenuOpen, handleKeyDown])
const navItems = [
{ path: '/', label: 'Home' },
{ path: '/trees', label: 'Trees' },
{ path: '/my-trees', label: 'My Trees' },
{ path: '/sessions', label: 'Sessions' },
{ path: '/account', label: 'Account' },
{ path: '/settings', label: 'Settings' },
...(isSuperAdmin ? [{ path: '/admin', label: 'Admin Panel' }] : []),
]
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="sticky top-0 z-50 border-b border-border bg-card backdrop-blur-sm">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-8">
{/* Mobile hamburger */}
<button
onClick={() => setMobileMenuOpen(true)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground sm:hidden"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
</Link>
<nav className="hidden items-center gap-1 sm:flex">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'relative rounded-md px-3 py-2 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
</nav>
</div>
<div className="flex items-center gap-4">
<span className="hidden text-sm text-muted-foreground sm:block">
{user?.name || user?.email}
</span>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-block',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
<ThemeToggle />
<button
onClick={handleLogout}
className={cn(
'hidden rounded-md px-3 py-1.5 text-sm font-medium sm:block',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
Logout
</button>
</div>
</div>
</header>
{/* Mobile Nav Drawer */}
{mobileMenuOpen && (
<div className="fixed inset-0 z-50 sm:hidden">
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm animate-fade-in"
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
{/* Drawer */}
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-card shadow-xl animate-slide-in-left">
<div className="flex h-16 items-center justify-between border-b border-border px-4">
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
</Link>
<button
onClick={() => setMobileMenuOpen(false)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
aria-label="Close menu"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="flex flex-col p-4">
{/* User info */}
<div className="mb-4 border-b border-border pb-4">
<p className="text-sm font-medium text-foreground">
{user?.name || user?.email}
</p>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-medium',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
</div>
{/* Nav items */}
<div className="space-y-1">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'block rounded-md px-3 py-2.5 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
</div>
{/* Theme toggle */}
<div className="mt-4 border-t border-border pt-4">
<div className="flex items-center justify-between px-3 py-2">
<span className="text-sm text-muted-foreground">Theme</span>
<ThemeToggle />
</div>
</div>
{/* Logout */}
<div className="mt-2">
<button
onClick={handleLogout}
className={cn(
'w-full rounded-md px-3 py-2.5 text-left text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
Logout
</button>
</div>
</div>
</nav>
</div>
)}
{/* Main Content */}
<main className="animate-fade-in">
<Outlet />
</main>
</div>
)
}
export default AppLayout

View File

@@ -2,10 +2,8 @@ import { useState, useEffect, useCallback } from 'react'
import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions'
import { ThemeToggle } from '@/components/common/ThemeToggle'
import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { Menu, X } from 'lucide-react'
import { Menu, X, LogOut, User, Shield } from 'lucide-react'
import { cn } from '@/lib/utils'
export function AppLayout() {
@@ -21,7 +19,7 @@ export function AppLayout() {
navigate('/login')
}
// Close mobile menu on route change - key-based reset
// Close mobile menu on route change
const [prevPath, setPrevPath] = useState(location.pathname)
if (prevPath !== location.pathname) {
setPrevPath(location.pathname)
@@ -57,68 +55,92 @@ export function AppLayout() {
]
return (
<div className="min-h-screen bg-background">
<div className="min-h-screen bg-black">
{/* Subtle radial overlay for depth */}
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(100,100,120,0.03),transparent_50%),radial-gradient(circle_at_80%_80%,rgba(80,80,100,0.02),transparent_50%)]" />
{/* Header */}
<header className="sticky top-0 z-50 border-b border-border bg-card backdrop-blur-sm">
<header className="sticky top-0 z-50 border-b border-white/[0.06] bg-black/80 backdrop-blur-xl">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-8">
{/* Mobile hamburger */}
<button
onClick={() => setMobileMenuOpen(true)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground sm:hidden"
className="rounded-xl p-2 text-white/50 hover:bg-white/10 hover:text-white transition-all sm:hidden"
aria-label="Open menu"
>
<Menu className="h-5 w-5" />
</button>
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
{/* Logo */}
<Link to="/" className="flex items-center gap-3 group">
<div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center transition-transform group-hover:scale-105">
<BrandLogo size="sm" className="h-5 w-5 invert" />
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-1 sm:flex">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'relative rounded-md px-3 py-2 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
{navItems.map((item) => {
const isActive = item.path === '/'
? location.pathname === '/'
: location.pathname.startsWith(item.path)
return (
<Link
key={item.path}
to={item.path}
className={cn(
'rounded-xl px-4 py-2 text-sm font-medium transition-all',
isActive
? 'bg-white/10 text-white border border-white/20'
: 'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{item.label}
</Link>
)
})}
</nav>
</div>
<div className="flex items-center gap-4">
<span className="hidden text-sm text-muted-foreground sm:block">
{user?.name || user?.email}
</span>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-block',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)}
<ThemeToggle />
{/* Right side controls */}
<div className="flex items-center gap-3">
{/* User info */}
<div className="hidden items-center gap-3 sm:flex">
<div className="flex items-center gap-2 rounded-xl bg-white/[0.06] px-3 py-1.5 border border-white/10">
<User className="h-4 w-4 text-white/40" />
<span className="text-sm text-white/70">
{user?.name || user?.email}
</span>
</div>
{/* Role badge */}
{effectiveRole && effectiveRole !== 'engineer' && (
<div className="px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
<span className="flex items-center gap-1.5 text-xs text-white font-semibold">
<Shield className="h-3 w-3" />
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
</div>
)}
</div>
{/* Logout button */}
<button
onClick={handleLogout}
className={cn(
'hidden rounded-md px-3 py-1.5 text-sm font-medium sm:block',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
'hidden items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium sm:flex',
'text-white/50 hover:text-white hover:bg-white/10 transition-all',
'border border-white/10 hover:border-white/20'
)}
>
<LogOut className="h-4 w-4" />
Logout
</button>
</div>
@@ -130,21 +152,25 @@ export function AppLayout() {
<div className="fixed inset-0 z-50 sm:hidden">
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm animate-fade-in"
className="absolute inset-0 bg-black/80 backdrop-blur-sm animate-in fade-in duration-200"
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
{/* Drawer */}
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-card shadow-xl animate-slide-in-left">
<div className="flex h-16 items-center justify-between border-b border-border px-4">
<Link to="/" className="flex items-center gap-2">
<BrandLogo size="sm" />
<BrandWordmark size="sm" />
<nav className="absolute inset-y-0 left-0 w-72 border-r border-white/[0.06] bg-black shadow-2xl animate-in slide-in-from-left duration-300">
<div className="flex h-16 items-center justify-between border-b border-white/[0.06] px-4">
<Link to="/" className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center">
<BrandLogo size="sm" className="h-5 w-5 invert" />
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
</Link>
<button
onClick={() => setMobileMenuOpen(false)}
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
className="rounded-xl p-2 text-white/50 hover:bg-white/10 hover:text-white transition-all"
aria-label="Close menu"
>
<X className="h-5 w-5" />
@@ -153,61 +179,60 @@ export function AppLayout() {
<div className="flex flex-col p-4">
{/* User info */}
<div className="mb-4 border-b border-border pb-4">
<p className="text-sm font-medium text-foreground">
{user?.name || user?.email}
</p>
<div className="mb-4 border-b border-white/[0.06] pb-4">
<div className="flex items-center gap-2 mb-2">
<User className="h-4 w-4 text-white/40" />
<p className="text-sm font-medium text-white">
{user?.name || user?.email}
</p>
</div>
{effectiveRole && effectiveRole !== 'engineer' && (
<span
className={cn(
'mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-medium',
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400',
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400',
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)}
>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
<div className="inline-flex px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
<span className="flex items-center gap-1.5 text-xs text-white font-semibold">
<Shield className="h-3 w-3" />
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
</div>
)}
</div>
{/* Nav items */}
<div className="space-y-1">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={cn(
'block rounded-md px-3 py-2.5 text-sm font-medium transition-colors',
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path))
? 'bg-accent text-accent-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
)}
>
{item.label}
</Link>
))}
</div>
{navItems.map((item) => {
const isActive = item.path === '/'
? location.pathname === '/'
: location.pathname.startsWith(item.path)
{/* Theme toggle */}
<div className="mt-4 border-t border-border pt-4">
<div className="flex items-center justify-between px-3 py-2">
<span className="text-sm text-muted-foreground">Theme</span>
<ThemeToggle />
</div>
return (
<Link
key={item.path}
to={item.path}
className={cn(
'block rounded-xl px-4 py-3 text-sm font-medium transition-all',
isActive
? 'bg-white/10 text-white border border-white/20'
: 'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{item.label}
</Link>
)
})}
</div>
{/* Logout */}
<div className="mt-2">
<div className="mt-4 border-t border-white/[0.06] pt-4">
<button
onClick={handleLogout}
className={cn(
'w-full rounded-md px-3 py-2.5 text-left text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
'w-full flex items-center gap-2 rounded-xl px-4 py-3 text-sm font-medium',
'text-white/50 hover:text-white hover:bg-white/10 transition-all',
'border border-white/10 hover:border-white/20'
)}
>
<LogOut className="h-4 w-4" />
Logout
</button>
</div>
@@ -217,7 +242,7 @@ export function AppLayout() {
)}
{/* Main Content */}
<main className="animate-fade-in">
<main className="relative animate-in fade-in duration-500">
<Outlet />
</main>
</div>

View File

@@ -15,7 +15,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}

View File

@@ -89,8 +89,8 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
setIsOpen(!isOpen)
}}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Add to folder"
>
@@ -100,14 +100,14 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
{isOpen && (
<div
className={cn(
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-input',
'bg-popover py-1 shadow-lg'
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
)}
>
{isLoading ? (
<div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
<div className="px-3 py-2 text-sm text-white/40">Loading...</div>
) : folders.length === 0 ? (
<div className="px-3 py-2 text-sm text-muted-foreground">No folders yet</div>
<div className="px-3 py-2 text-sm text-white/40">No folders yet</div>
) : (
folders.map((folder) => (
<button
@@ -116,7 +116,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
e.stopPropagation()
toggleFolder(folder.id)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<div
className="h-3 w-3 rounded-sm"
@@ -124,13 +124,13 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
/>
<span className="flex-1 truncate text-left">{folder.name}</span>
{treeFolderIds.has(folder.id) && (
<Check className="h-4 w-4 text-primary" />
<Check className="h-4 w-4 text-white" />
)}
</button>
))
)}
<div className="border-t border-input my-1" />
<div className="border-t border-white/10 my-1" />
<button
onClick={(e) => {
@@ -138,7 +138,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
setIsOpen(false)
onFolderCreated?.()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-primary hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<Plus className="h-4 w-4" />
Create new folder

View File

@@ -174,15 +174,15 @@ export function FolderEditModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div className="absolute inset-0 bg-background/80 backdrop-blur-sm" onClick={onClose} />
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
{/* Modal */}
<div className="relative z-10 w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg">
<div className="relative z-10 w-full max-w-md glass-card rounded-2xl p-6 shadow-lg">
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-card-foreground">
<h2 className="text-lg font-semibold text-white">
{isEditMode ? 'Edit Folder' : initialParentId ? 'Create Subfolder' : 'Create Folder'}
</h2>
<button onClick={onClose} className="rounded-md p-1 hover:bg-accent">
<button onClick={onClose} className="rounded-md p-1 text-white/40 hover:bg-white/[0.06] hover:text-white">
<X className="h-5 w-5" />
</button>
</div>
@@ -190,7 +190,7 @@ export function FolderEditModal({
<form onSubmit={handleSubmit}>
{/* Name input */}
<div className="mb-4">
<label htmlFor="folder-name" className="block text-sm font-medium text-foreground">
<label htmlFor="folder-name" className="block text-sm font-medium text-white">
Name
</label>
<input
@@ -201,9 +201,9 @@ export function FolderEditModal({
placeholder="e.g., Citrix Issues"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'border-input'
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'border-white/10'
)}
autoFocus
/>
@@ -211,7 +211,7 @@ export function FolderEditModal({
{/* Parent folder dropdown */}
<div className="mb-4">
<label htmlFor="folder-parent" className="block text-sm font-medium text-foreground">
<label htmlFor="folder-parent" className="block text-sm font-medium text-white">
Parent Folder
</label>
<select
@@ -220,9 +220,9 @@ export function FolderEditModal({
onChange={(e) => setParentId(e.target.value || null)}
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'border-input'
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'border-white/10'
)}
>
<option value="">None (root level)</option>
@@ -232,14 +232,14 @@ export function FolderEditModal({
</option>
))}
</select>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
Folders can be nested up to 3 levels deep.
</p>
</div>
{/* Color picker */}
<div className="mb-6">
<label className="block text-sm font-medium text-foreground">Color</label>
<label className="block text-sm font-medium text-white">Color</label>
<div className="mt-2 flex flex-wrap gap-2">
{FOLDER_COLORS.map((c) => (
<button
@@ -248,7 +248,7 @@ export function FolderEditModal({
onClick={() => setColor(c)}
className={cn(
'h-8 w-8 rounded-full transition-transform',
color === c && 'ring-2 ring-offset-2 ring-offset-background ring-primary scale-110'
color === c && 'ring-2 ring-offset-2 ring-offset-black ring-white/50 scale-110'
)}
style={{ backgroundColor: c }}
title={c}
@@ -262,7 +262,7 @@ export function FolderEditModal({
<button
type="button"
onClick={onClose}
className={cn('rounded-md border border-input px-4 py-2 text-sm', 'hover:bg-accent')}
className={cn('rounded-md border border-white/10 px-4 py-2 text-sm text-white/60', 'hover:bg-white/10 hover:text-white')}
>
Cancel
</button>
@@ -270,8 +270,8 @@ export function FolderEditModal({
type="submit"
disabled={isSubmitting}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90',
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90',
'disabled:opacity-50'
)}
>

View File

@@ -113,8 +113,8 @@ function FolderItem({
onClick={() => onFolderSelect(folder.id)}
className={cn(
'flex w-full items-center gap-1 rounded-md py-1.5 text-sm',
'transition-colors hover:bg-accent',
selectedFolderId === folder.id && 'bg-accent font-medium'
'transition-colors hover:bg-white/[0.06]',
selectedFolderId === folder.id && 'bg-white/10 text-white font-medium'
)}
style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }}
>
@@ -125,7 +125,7 @@ function FolderItem({
e.stopPropagation()
onToggleExpand(folder.id)
}}
className="shrink-0 p-0.5 hover:bg-accent rounded"
className="shrink-0 p-0.5 hover:bg-white/[0.06] rounded"
>
{isExpanded ? (
<ChevronDown className="h-3 w-3" />
@@ -138,7 +138,7 @@ function FolderItem({
)}
<Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} />
<span className="flex-1 truncate text-left">{folder.name}</span>
<span className="text-xs text-muted-foreground group-hover:hidden">{folder.tree_count}</span>
<span className="text-xs text-white/40 group-hover:hidden">{folder.tree_count}</span>
</button>
{/* Folder menu button - replaces tree count on hover */}
@@ -150,7 +150,7 @@ function FolderItem({
className={cn(
'absolute right-1 top-1/2 -translate-y-1/2 rounded p-1',
'hidden group-hover:block',
'hover:bg-accent'
'hover:bg-white/[0.06]'
)}
>
<MoreVertical className="h-3 w-3" />
@@ -160,8 +160,8 @@ function FolderItem({
{menuOpenId === folder.id && (
<div
className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-input',
'bg-popover py-1 shadow-lg'
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
)}
>
<button
@@ -170,7 +170,7 @@ function FolderItem({
onEditFolder(folder)
onMenuToggle(null)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<Pencil className="h-3 w-3" />
Edit
@@ -182,7 +182,7 @@ function FolderItem({
onAddSubfolder(folder.id)
onMenuToggle(null)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<FolderPlus className="h-3 w-3" />
Add Subfolder
@@ -194,7 +194,7 @@ function FolderItem({
onDeleteFolder(folder.id, hasSubfolders)
onMenuToggle(null)
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-destructive hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-red-400 hover:bg-red-400/10"
>
<Trash2 className="h-3 w-3" />
Delete
@@ -356,13 +356,13 @@ export function FolderSidebar({
{/* Mobile backdrop */}
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-background/80 backdrop-blur-sm md:hidden"
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-sm md:hidden"
onClick={onMobileClose}
aria-hidden="true"
/>
)}
<div className={cn(
'w-56 shrink-0 border-r border-border bg-card',
'w-56 shrink-0 border-r border-white/[0.06] bg-transparent',
'hidden md:block',
mobileOpen && 'fixed inset-y-0 left-0 z-50 block animate-slide-in-left md:relative md:animate-none'
)}>
@@ -370,10 +370,10 @@ export function FolderSidebar({
{/* Mobile close button */}
{mobileOpen && (
<div className="mb-3 flex items-center justify-between md:hidden">
<span className="text-sm font-medium text-card-foreground">Folders</span>
<span className="text-sm font-medium text-white">Folders</span>
<button
onClick={onMobileClose}
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
className="rounded-md p-1.5 text-white/40 hover:bg-white/[0.06]"
aria-label="Close folders"
>
<X className="h-4 w-4" />
@@ -382,7 +382,7 @@ export function FolderSidebar({
)}
<button
onClick={() => setIsExpanded(!isExpanded)}
className="flex w-full items-center gap-2 text-sm font-medium text-card-foreground"
className="flex w-full items-center gap-2 text-sm font-medium text-white"
>
{isExpanded ? (
<ChevronDown className="h-4 w-4" />
@@ -399,8 +399,8 @@ export function FolderSidebar({
onClick={() => onFolderSelect(null)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
'transition-colors hover:bg-accent',
selectedFolderId === null && 'bg-accent font-medium'
'transition-colors hover:bg-white/[0.06]',
selectedFolderId === null && 'bg-white/10 text-white font-medium'
)}
>
<Folder className="h-4 w-4" />
@@ -409,7 +409,7 @@ export function FolderSidebar({
{/* Loading state */}
{isLoading ? (
<div className="px-2 py-1.5 text-sm text-muted-foreground">Loading...</div>
<div className="px-2 py-1.5 text-sm text-white/40">Loading...</div>
) : (
<>
{/* User folders (hierarchical) */}
@@ -439,7 +439,7 @@ export function FolderSidebar({
onClick={() => onCreateFolder(null)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground'
'text-white/50 transition-colors hover:bg-white/[0.06] hover:text-white'
)}
>
<Plus className="h-4 w-4" />
@@ -454,8 +454,8 @@ export function FolderSidebar({
{contextMenu && (
<div
className={cn(
'fixed z-50 w-44 rounded-md border border-input',
'bg-popover py-1 shadow-lg'
'fixed z-50 w-44 rounded-md border border-white/10',
'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
)}
style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()}
@@ -465,7 +465,7 @@ export function FolderSidebar({
onEditFolder(contextMenu.folder)
closeContextMenu()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<Pencil className="h-3 w-3" />
Edit
@@ -476,7 +476,7 @@ export function FolderSidebar({
handleAddSubfolder(contextMenu.folder.id)
closeContextMenu()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-white/70 hover:bg-white/[0.06] hover:text-white"
>
<FolderPlus className="h-3 w-3" />
Add Subfolder
@@ -487,7 +487,7 @@ export function FolderSidebar({
handleDeleteFolder(contextMenu.folder.id, contextMenu.folder.children.length > 0)
closeContextMenu()
}}
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-destructive hover:bg-accent"
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-red-400 hover:bg-red-400/10"
>
<Trash2 className="h-3 w-3" />
Delete

View File

@@ -114,18 +114,18 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative w-full max-w-lg rounded-lg border border-border bg-card shadow-lg">
<div className="relative w-full max-w-lg glass-card rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<h2 className="text-lg font-semibold text-card-foreground">Share Tree</h2>
<div className="flex items-center justify-between border-b border-white/[0.06] px-6 py-4">
<h2 className="text-lg font-semibold text-white">Share Tree</h2>
<button
onClick={onClose}
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
className="rounded-md p-1 text-white/40 hover:bg-white/[0.06] hover:text-white"
>
<X className="h-5 w-5" />
</button>
@@ -135,9 +135,9 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="px-6 py-4 space-y-6">
{/* Tree Info */}
<div>
<h3 className="font-medium text-card-foreground">{tree.name}</h3>
<h3 className="font-medium text-white">{tree.name}</h3>
{tree.description && (
<p className="mt-1 text-sm text-muted-foreground line-clamp-2">
<p className="mt-1 text-sm text-white/70 line-clamp-2">
{tree.description}
</p>
)}
@@ -145,7 +145,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Visibility Settings */}
<div>
<label className="mb-2 block text-sm font-medium text-card-foreground">
<label className="mb-2 block text-sm font-medium text-white">
Visibility
</label>
<div className="space-y-2">
@@ -156,19 +156,19 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
className={cn(
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
visibility === level
? 'border-primary bg-primary/5 text-card-foreground'
: 'border-border bg-background text-muted-foreground hover:border-primary/50 hover:bg-accent'
? 'border-white/20 bg-white/10 text-white'
: 'border-white/[0.06] bg-transparent text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
)}
>
{getVisibilityIcon(level)}
<div className="flex-1">
<div className="text-sm font-medium capitalize">{level}</div>
<div className="text-xs text-muted-foreground">
<div className="text-xs text-white/40">
{getVisibilityDescription(level)}
</div>
</div>
{visibility === level && (
<div className="h-2 w-2 rounded-full bg-primary" />
<div className="h-2 w-2 rounded-full bg-white" />
)}
</button>
))}
@@ -178,7 +178,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Share Link Generation */}
{visibility !== 'private' && (
<div>
<label className="mb-2 block text-sm font-medium text-card-foreground">
<label className="mb-2 block text-sm font-medium text-white">
Share Link
</label>
@@ -189,11 +189,11 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
id="allow-forking"
checked={allowForking}
onChange={(e) => setAllowForking(e.target.checked)}
className="h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2"
className="h-4 w-4 rounded border-white/10 bg-black/50 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-2 focus:ring-offset-black"
/>
<label
htmlFor="allow-forking"
className="text-sm text-muted-foreground cursor-pointer"
className="text-sm text-white/70 cursor-pointer"
>
Allow recipients to fork this tree
</label>
@@ -205,8 +205,8 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
onClick={handleGenerateLink}
disabled={isGenerating}
className={cn(
'w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed'
'w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
{isGenerating ? 'Generating...' : 'Generate Share Link'}
@@ -216,20 +216,20 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Active Share Link */}
{activeShare && (
<div className="space-y-2">
<div className="flex items-center gap-2 rounded-md border border-border bg-background p-3">
<div className="flex items-center gap-2 rounded-md border border-white/10 bg-black/50 p-3">
<input
type="text"
value={activeShare.share_url}
readOnly
className="flex-1 bg-transparent text-sm text-foreground outline-none"
className="flex-1 bg-transparent text-sm text-white outline-none"
/>
<button
onClick={handleCopyLink}
className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-1.5 text-sm font-medium transition-colors',
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-1.5 text-sm font-medium transition-colors',
copied
? 'border-green-500 bg-green-500/10 text-green-600'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
? 'border-green-500 bg-green-500/10 text-green-400'
: 'text-white/60 hover:bg-white/10 hover:text-white'
)}
>
{copied ? (
@@ -245,13 +245,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
)}
</button>
</div>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-white/40">
{activeShare.allow_forking
? 'Recipients can fork this tree'
: 'Forking disabled for this share'}
</p>
{shares.length > 1 && (
<p className="text-xs text-muted-foreground">
<p className="text-xs text-white/40">
{shares.length} active share links
</p>
)}
@@ -262,12 +262,12 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
</div>
{/* Footer */}
<div className="flex justify-end gap-3 border-t border-border px-6 py-4">
<div className="flex justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
<button
onClick={onClose}
className={cn(
'rounded-md border border-input px-4 py-2 text-sm font-medium text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
Close

View File

@@ -21,7 +21,7 @@ const sortOptions: { value: SortBy; label: string }[] = [
export function SortDropdown({ value, onChange, className }: SortDropdownProps) {
return (
<div className={cn('relative inline-flex items-center', className)}>
<span className="mr-2 flex items-center gap-1.5 text-sm text-muted-foreground">
<span className="mr-2 flex items-center gap-1.5 text-sm text-white/40">
<ArrowUpDown className="h-4 w-4" />
<span className="hidden sm:inline">Sort:</span>
</span>
@@ -29,8 +29,8 @@ export function SortDropdown({ value, onChange, className }: SortDropdownProps)
value={value}
onChange={(e) => onChange(e.target.value as SortBy)}
className={cn(
'rounded-md border border-input bg-background px-3 py-1.5 text-sm',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'rounded-md border border-white/10 bg-black/50 px-3 py-1.5 text-sm',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
{sortOptions.map((option) => (

View File

@@ -30,13 +30,13 @@ export function TreeGridView({
{trees.map((tree) => (
<div
key={tree.id}
className="rounded-lg border border-border bg-card p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
className="glass-card rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-white/20 hover:shadow-md sm:p-6"
>
<div className="mb-2 flex items-start justify-between gap-2">
<div className="flex items-center gap-2">
<h3 className="font-semibold text-card-foreground">{tree.name}</h3>
<h3 className="font-semibold text-white">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
<FileText className="h-3 w-3" />
Draft
</span>
@@ -45,21 +45,21 @@ export function TreeGridView({
<div className="flex items-center gap-2">
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-4 w-4 text-muted-foreground" />
<Globe className="h-4 w-4 text-white/40" />
</span>
) : (
<span title="Private tree">
<Lock className="h-4 w-4 text-muted-foreground" />
<Lock className="h-4 w-4 text-white/40" />
</span>
)}
{tree.category_info && (
<span className="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
{tree.category_info.name}
</span>
)}
</div>
</div>
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
<p className="mb-3 text-sm text-white/70 line-clamp-2">
{tree.description || 'No description available'}
</p>
@@ -71,7 +71,7 @@ export function TreeGridView({
)}
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
<span className="text-xs text-white/40">
v{tree.version} · {tree.usage_count} uses
</span>
<div className="flex items-center gap-2">
@@ -81,8 +81,8 @@ export function TreeGridView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-input p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Fork tree"
>
@@ -93,8 +93,8 @@ export function TreeGridView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-input p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Edit tree"
>
@@ -106,8 +106,8 @@ export function TreeGridView({
type="button"
onClick={() => onDeleteTree(tree)}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-destructive/10 hover:text-destructive'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-red-400/10 hover:text-red-400'
)}
title="Delete tree"
>
@@ -118,8 +118,8 @@ export function TreeGridView({
type="button"
onClick={() => onStartSession(tree.id)}
className={cn(
'rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Start Session

View File

@@ -29,29 +29,29 @@ export function TreeListView({
{trees.map((tree) => (
<div
key={tree.id}
className="flex items-center gap-4 rounded-lg border border-border bg-card p-4 transition-all hover:border-primary/30 hover:shadow-sm"
className="flex items-center gap-4 glass-card rounded-2xl p-4 transition-all hover:border-white/20 hover:shadow-sm"
>
{/* Left: Name and Description */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-card-foreground truncate">{tree.name}</h3>
<h3 className="font-semibold text-white truncate">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
<FileText className="h-3 w-3" />
Draft
</span>
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
</span>
)}
</div>
<p className="text-sm text-muted-foreground truncate">
<p className="text-sm text-white/70 truncate">
{tree.description || 'No description available'}
</p>
</div>
@@ -59,7 +59,7 @@ export function TreeListView({
{/* Center: Category and Tags */}
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
{tree.category_info && (
<span className="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground whitespace-nowrap">
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70 whitespace-nowrap">
{tree.category_info.name}
</span>
)}
@@ -72,7 +72,7 @@ export function TreeListView({
{/* Right: Metadata and Actions */}
<div className="flex items-center gap-3 flex-shrink-0">
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
<div className="hidden sm:flex flex-col items-end text-xs text-white/40">
<span>v{tree.version}</span>
<span>{tree.usage_count} uses</span>
</div>
@@ -84,8 +84,8 @@ export function TreeListView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Fork tree"
>
@@ -96,8 +96,8 @@ export function TreeListView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Edit tree"
>
@@ -108,8 +108,8 @@ export function TreeListView({
type="button"
onClick={() => onStartSession(tree.id)}
className={cn(
'rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 whitespace-nowrap'
'rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-white/90 whitespace-nowrap'
)}
>
Start

View File

@@ -69,12 +69,12 @@ export function TreeTableView({
}
return (
<div className="overflow-x-auto rounded-lg border border-border">
<div className="overflow-x-auto rounded-2xl border border-white/[0.06]">
<table className="w-full">
<thead className="bg-muted/50 sticky top-0 z-10">
<tr className="border-b border-border">
<thead className="bg-white/[0.02] sticky top-0 z-10">
<tr className="border-b border-white/[0.06]">
<th
className="px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
className="px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
onClick={() => handleSort('name')}
>
<div className="flex items-center gap-1">
@@ -82,11 +82,11 @@ export function TreeTableView({
{getSortIcon('name')}
</div>
</th>
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-white/50">
Description
</th>
<th
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
onClick={() => handleSort('category')}
>
<div className="flex items-center gap-1">
@@ -94,11 +94,11 @@ export function TreeTableView({
{getSortIcon('category')}
</div>
</th>
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-white/50">
Tags
</th>
<th
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-white/50 cursor-pointer hover:text-white"
onClick={() => handleSort('version')}
>
<div className="flex items-center justify-center gap-1">
@@ -107,7 +107,7 @@ export function TreeTableView({
</div>
</th>
<th
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-white/50 cursor-pointer hover:text-white"
onClick={() => handleSort('usage')}
>
<div className="flex items-center justify-center gap-1">
@@ -116,7 +116,7 @@ export function TreeTableView({
</div>
</th>
<th
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-white/50 cursor-pointer hover:text-white"
onClick={() => handleSort('updated')}
>
<div className="flex items-center gap-1">
@@ -124,44 +124,44 @@ export function TreeTableView({
{getSortIcon('updated')}
</div>
</th>
<th className="px-4 py-3 text-right text-sm font-medium text-muted-foreground">
<th className="px-4 py-3 text-right text-sm font-medium text-white/50">
Actions
</th>
</tr>
</thead>
<tbody className="bg-card">
<tbody className="bg-transparent">
{trees.map((tree) => (
<tr key={tree.id} className="border-b border-border last:border-0 hover:bg-accent/50">
<tr key={tree.id} className="border-b border-white/[0.06] last:border-0 hover:bg-white/[0.04]">
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="font-medium text-card-foreground truncate max-w-[200px]">
<span className="font-medium text-white truncate max-w-[200px]">
{tree.name}
</span>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
<FileText className="h-3 w-3" />
Draft
</span>
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-white/40 flex-shrink-0" />
</span>
)}
</div>
</td>
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
<td className="hidden md:table-cell px-4 py-3 text-sm text-white/70">
<span className="truncate block max-w-[250px]">
{tree.description || 'No description'}
</span>
</td>
<td className="hidden lg:table-cell px-4 py-3">
{tree.category_info && (
<span className="inline-block rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">
<span className="inline-block rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
{tree.category_info.name}
</span>
)}
@@ -171,13 +171,13 @@ export function TreeTableView({
<TagBadges tags={tree.tags} maxVisible={2} onTagClick={onTagClick} />
)}
</td>
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-white/70">
v{tree.version}
</td>
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-white/70">
{tree.usage_count}
</td>
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
<td className="hidden md:table-cell px-4 py-3 text-sm text-white/70">
{formatDate(tree.updated_at)}
</td>
<td className="px-4 py-3">
@@ -188,8 +188,8 @@ export function TreeTableView({
type="button"
onClick={() => onForkTree(tree.id)}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Fork tree"
>
@@ -200,8 +200,8 @@ export function TreeTableView({
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-white/10 hover:text-white'
)}
title="Edit tree"
>
@@ -212,8 +212,8 @@ export function TreeTableView({
type="button"
onClick={() => onStartSession(tree.id)}
className={cn(
'rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground',
'hover:bg-primary/90 whitespace-nowrap'
'rounded-md bg-white px-3 py-1.5 text-xs font-medium text-black',
'hover:bg-white/90 whitespace-nowrap'
)}
>
Start

View File

@@ -11,15 +11,15 @@ interface ViewToggleProps {
export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
return (
<div className={cn('flex items-center gap-1 rounded-md border border-input p-1', className)}>
<div className={cn('flex items-center gap-1 rounded-md border border-white/10 p-1', className)}>
<button
type="button"
onClick={() => onChange('grid')}
className={cn(
'rounded p-1.5 transition-colors',
view === 'grid'
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
title="Grid view"
>
@@ -31,8 +31,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
className={cn(
'rounded p-1.5 transition-colors',
view === 'list'
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
title="List view"
>
@@ -44,8 +44,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
className={cn(
'rounded p-1.5 transition-colors',
view === 'table'
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
? 'bg-white/10 text-white border-white/20'
: 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)}
title="Table view"
>

View File

@@ -45,7 +45,7 @@ export function ContinuationModal({
{/* Descendant Selection */}
{hasDescendants && (
<div>
<p className="mb-4 text-sm text-muted-foreground">
<p className="mb-4 text-sm text-white/70">
Select the next step in your troubleshooting path:
</p>
@@ -56,20 +56,20 @@ export function ContinuationModal({
onClick={() => onSelectNode(node.id)}
title={`From: ${node.parentOptionLabel}`}
className={cn(
'flex w-full items-center gap-3 rounded-lg border border-border p-3 text-left transition-colors',
'hover:border-primary hover:bg-accent'
'flex w-full items-center gap-3 rounded-lg border border-white/[0.06] p-3 text-left transition-colors',
'hover:border-white/20 hover:bg-white/10'
)}
>
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-muted">
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-white/10">
{nodeTypeIcons[node.type]}
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">{node.label}</p>
<p className="text-xs text-muted-foreground">
<p className="truncate font-medium text-white">{node.label}</p>
<p className="text-xs text-white/40">
{nodeTypeLabels[node.type]}
</p>
</div>
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<ArrowRight className="h-4 w-4 flex-shrink-0 text-white/40" />
</button>
))}
</div>
@@ -79,11 +79,11 @@ export function ContinuationModal({
{/* Divider */}
{hasDescendants && (
<div className="flex items-center gap-4">
<div className="h-px flex-1 bg-border" />
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
<div className="h-px flex-1 bg-white/[0.06]" />
<span className="text-xs font-medium uppercase tracking-wide text-white/40">
Or
</span>
<div className="h-px flex-1 bg-border" />
<div className="h-px flex-1 bg-white/[0.06]" />
</div>
)}
@@ -100,17 +100,17 @@ export function ContinuationModal({
<GitBranch className="h-5 w-5 text-amber-500" />
</div>
<div className="flex-1">
<p className="font-medium">Build Custom Branch</p>
<p className="text-sm text-muted-foreground">
<p className="font-medium text-white">Build Custom Branch</p>
<p className="text-sm text-white/70">
Create your own troubleshooting path with custom steps
</p>
</div>
</button>
{/* Warning */}
<div className="mt-3 flex items-start gap-2 rounded-md bg-amber-500/10 p-3">
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-500" />
<p className="text-sm text-amber-700 dark:text-amber-400">
<div className="mt-3 flex items-start gap-2 rounded-md bg-yellow-400/10 p-3">
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
<p className="text-sm text-yellow-400">
You'll need to complete this branch manually or mark the issue as resolved.
Custom branches can be saved as a personal tree when your session ends.
</p>

View File

@@ -46,9 +46,9 @@ export function ExportPreviewModal({
return (
<Modal isOpen={isOpen} onClose={handleClose} title="Export Preview" size="xl">
{/* Filename and format info */}
<p className="mb-3 text-sm text-muted-foreground">
Filename: <span className="font-mono text-foreground">{filename}</span>
<span className="ml-3 rounded bg-secondary px-2 py-0.5 text-xs">
<p className="mb-3 text-sm text-white/70">
Filename: <span className="font-mono text-white">{filename}</span>
<span className="ml-3 rounded bg-white/10 px-2 py-0.5 text-xs text-white/70">
{format === 'markdown' ? 'Markdown' : format === 'html' ? 'HTML' : 'Plain Text'}
</span>
</p>
@@ -56,8 +56,8 @@ export function ExportPreviewModal({
{/* Content Preview */}
<div
className={cn(
'max-h-96 overflow-auto rounded-md border border-input bg-muted/50 p-4',
'font-mono text-sm text-foreground'
'max-h-96 overflow-auto rounded-md border border-white/10 bg-black/50 p-4',
'font-mono text-sm text-white'
)}
>
<pre className="whitespace-pre-wrap">{content}</pre>
@@ -68,14 +68,14 @@ export function ExportPreviewModal({
<button
onClick={handleCopy}
className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-2 text-sm font-medium',
'bg-background text-foreground hover:bg-accent',
'focus:outline-none focus:ring-2 focus:ring-ring'
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white',
'focus:outline-none focus:ring-2 focus:ring-white/20'
)}
>
{copied ? (
<>
<Check className="h-4 w-4 text-green-500" />
<Check className="h-4 w-4 text-emerald-400" />
Copied!
</>
) : (
@@ -88,8 +88,8 @@ export function ExportPreviewModal({
<button
onClick={handleDownload}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring'
'flex items-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90 focus:outline-none focus:ring-2 focus:ring-white/20'
)}
>
<Download className="h-4 w-4" />

View File

@@ -49,7 +49,7 @@ export function ForkTreeModal({
disabled={isSaving}
className={cn(
'rounded-md px-4 py-2 text-sm font-medium transition-colors',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
'text-white/60 hover:bg-white/10 hover:text-white',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -59,8 +59,8 @@ export function ForkTreeModal({
onClick={handleFork}
disabled={isSaving || !name.trim()}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors',
'hover:bg-primary/90',
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black transition-colors',
'hover:bg-white/90',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -82,13 +82,13 @@ export function ForkTreeModal({
return (
<Modal isOpen={isOpen} onClose={onClose} title="Save Custom Tree?" footer={footer}>
<div className="space-y-4">
<div className="flex items-start gap-3 rounded-lg bg-accent/50 p-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-primary/10">
<GitFork className="h-5 w-5 text-primary" />
<div className="flex items-start gap-3 rounded-lg bg-white/5 p-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-white/10">
<GitFork className="h-5 w-5 text-white" />
</div>
<div>
<p className="font-medium">You've created a custom troubleshooting path!</p>
<p className="mt-1 text-sm text-muted-foreground">
<p className="font-medium text-white">You've created a custom troubleshooting path!</p>
<p className="mt-1 text-sm text-white/70">
Save it as your own personal tree to reuse this troubleshooting flow in the future.
</p>
</div>
@@ -96,8 +96,8 @@ export function ForkTreeModal({
<div className="space-y-4">
<div>
<label htmlFor="tree-name" className="mb-1.5 block text-sm font-medium">
Tree Name <span className="text-destructive">*</span>
<label htmlFor="tree-name" className="mb-1.5 block text-sm font-medium text-white">
Tree Name <span className="text-red-400">*</span>
</label>
<input
id="tree-name"
@@ -106,15 +106,15 @@ export function ForkTreeModal({
onChange={(e) => setName(e.target.value)}
placeholder="My Custom Tree"
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20'
)}
/>
</div>
<div>
<label htmlFor="tree-description" className="mb-1.5 block text-sm font-medium">
Description <span className="text-muted-foreground">(optional)</span>
<label htmlFor="tree-description" className="mb-1.5 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
</label>
<textarea
id="tree-description"
@@ -123,8 +123,8 @@ export function ForkTreeModal({
placeholder="Describe what this tree helps troubleshoot..."
rows={3}
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'focus:outline-none focus:ring-2 focus:ring-ring',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
'resize-none'
)}
/>
@@ -132,10 +132,10 @@ export function ForkTreeModal({
</div>
{error && (
<p className="text-sm text-destructive">{error}</p>
<p className="text-sm text-red-400">{error}</p>
)}
<p className="text-xs text-muted-foreground">
<p className="text-xs text-white/40">
The new tree will include your custom steps and will be saved to your personal tree library.
</p>
</div>

View File

@@ -28,8 +28,8 @@ export function PostStepActionModal({
return (
<Modal isOpen={isOpen} onClose={onClose} title="What would you like to do?">
<div className="space-y-3">
<p className="mb-4 text-sm text-muted-foreground">
You've created: <strong className="text-foreground">{step.title}</strong>
<p className="mb-4 text-sm text-white/70">
You've created: <strong className="text-white">{step.title}</strong>
</p>
{/* Save for Later - Only show if not already from library */}
@@ -48,8 +48,8 @@ export function PostStepActionModal({
<Bookmark className="h-5 w-5 text-blue-500" />
</div>
<div>
<p className="font-medium">Save for Later</p>
<p className="text-sm text-muted-foreground">
<p className="font-medium text-white">Save for Later</p>
<p className="text-sm text-white/70">
Add to your step library for future use
</p>
</div>
@@ -62,8 +62,8 @@ export function PostStepActionModal({
onClick={onUseNow}
disabled={isSaving}
className={cn(
'w-full rounded-lg border border-border p-4 text-left transition-colors',
'hover:border-primary hover:bg-accent',
'w-full rounded-lg border border-white/[0.06] p-4 text-left transition-colors',
'hover:border-white/20 hover:bg-white/10',
'disabled:cursor-not-allowed disabled:opacity-50'
)}
>
@@ -96,8 +96,8 @@ export function PostStepActionModal({
<BookmarkPlus className="h-5 w-5 text-purple-500" />
</div>
<div>
<p className="font-medium">Do Both</p>
<p className="text-sm text-muted-foreground">
<p className="font-medium text-white">Do Both</p>
<p className="text-sm text-white/70">
Save to library AND use in this session
</p>
</div>
@@ -106,7 +106,7 @@ export function PostStepActionModal({
)}
{isSaving && (
<p className="text-center text-sm text-muted-foreground">Saving...</p>
<p className="text-center text-sm text-white/40">Saving...</p>
)}
</div>
</Modal>

View File

@@ -33,22 +33,22 @@ export function SaveSessionAsTreeModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="w-full max-w-lg rounded-lg border border-border bg-card p-6 shadow-lg">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="glass-card w-full max-w-lg rounded-2xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-lg font-semibold text-foreground">Save Session as Tree</h2>
<h2 className="text-lg font-semibold text-white">Save Session as Tree</h2>
<button
onClick={onClose}
disabled={isSaving}
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
className="rounded-full p-1 text-white/40 hover:bg-white/10 hover:text-white disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Info */}
<p className="mb-4 text-sm text-muted-foreground">
<p className="mb-4 text-sm text-white/70">
Create a new tree from this session's path. The tree will be linked to the original tree as a fork.
</p>
@@ -56,8 +56,8 @@ export function SaveSessionAsTreeModal({
<form onSubmit={handleSubmit} className="space-y-4">
{/* Tree Name */}
<div>
<label htmlFor="treeName" className="mb-1 block text-sm font-medium text-foreground">
Tree Name <span className="text-muted-foreground">(optional)</span>
<label htmlFor="treeName" className="mb-1 block text-sm font-medium text-white">
Tree Name <span className="text-white/40">(optional)</span>
</label>
<input
id="treeName"
@@ -68,9 +68,9 @@ export function SaveSessionAsTreeModal({
disabled={isSaving}
maxLength={255}
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
@@ -78,8 +78,8 @@ export function SaveSessionAsTreeModal({
{/* Description */}
<div>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
Description <span className="text-muted-foreground">(optional)</span>
<label htmlFor="description" className="mb-1 block text-sm font-medium text-white">
Description <span className="text-white/40">(optional)</span>
</label>
<textarea
id="description"
@@ -89,9 +89,9 @@ export function SaveSessionAsTreeModal({
disabled={isSaving}
rows={3}
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
@@ -99,7 +99,7 @@ export function SaveSessionAsTreeModal({
{/* Status */}
<div>
<label className="mb-2 block text-sm font-medium text-foreground">Status</label>
<label className="mb-2 block text-sm font-medium text-white">Status</label>
<div className="flex gap-4">
<label className="flex cursor-pointer items-center gap-2">
<input
@@ -109,9 +109,9 @@ export function SaveSessionAsTreeModal({
checked={status === 'draft'}
onChange={() => setStatus('draft')}
disabled={isSaving}
className="h-4 w-4 border-input text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2"
className="h-4 w-4 border-white/10 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-0"
/>
<span className="text-sm text-foreground">Draft</span>
<span className="text-sm text-white">Draft</span>
</label>
<label className="flex cursor-pointer items-center gap-2">
<input
@@ -121,9 +121,9 @@ export function SaveSessionAsTreeModal({
checked={status === 'published'}
onChange={() => setStatus('published')}
disabled={isSaving}
className="h-4 w-4 border-input text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2"
className="h-4 w-4 border-white/10 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-0"
/>
<span className="text-sm text-foreground">Published</span>
<span className="text-sm text-white">Published</span>
</label>
</div>
</div>
@@ -135,8 +135,8 @@ export function SaveSessionAsTreeModal({
onClick={onClose}
disabled={isSaving}
className={cn(
'rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
Cancel
@@ -145,8 +145,8 @@ export function SaveSessionAsTreeModal({
type="submit"
disabled={isSaving}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
{isSaving ? 'Saving...' : 'Save as Tree'}

View File

@@ -124,8 +124,8 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
onClick={() => setIsCollapsed(false)}
className={cn(
'fixed right-2 top-1/2 z-40 -translate-y-1/2 rounded-md p-2.5',
'bg-card border border-border shadow-md',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'bg-[#0a0a0a] border border-white/[0.06] shadow-md',
'text-white/40 hover:bg-white/10 hover:text-white',
'transition-opacity duration-200',
isCollapsed ? 'opacity-100' : 'pointer-events-none opacity-0'
)}
@@ -140,7 +140,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{/* Mobile backdrop */}
{!isCollapsed && (
<div
className="fixed inset-0 z-30 bg-background/80 backdrop-blur-sm sm:hidden"
className="fixed inset-0 z-30 bg-black/80 backdrop-blur-sm sm:hidden"
onClick={() => setIsCollapsed(true)}
aria-hidden="true"
/>
@@ -152,29 +152,29 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
'fixed z-40',
'inset-0 sm:inset-auto sm:right-2 sm:top-1/2 sm:-translate-y-1/2',
'flex w-full flex-col sm:h-[55vh] sm:w-[420px]',
'border-border bg-card shadow-xl sm:rounded-lg sm:border',
'border-white/[0.06] bg-[#0a0a0a]/95 backdrop-blur-md shadow-xl sm:rounded-lg sm:border',
'transition-transform duration-200 ease-out',
isCollapsed ? 'translate-x-full' : 'translate-x-0'
)}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<div className="flex items-center justify-between border-b border-white/[0.06] px-3 py-2">
<div className="flex items-center gap-2">
<StickyNote className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">Scratchpad</span>
<span className="text-xs text-muted-foreground/60">Ctrl+/</span>
<StickyNote className="h-4 w-4 text-white/40" />
<span className="text-sm font-medium text-white">Scratchpad</span>
<span className="text-xs text-white/30">Ctrl+/</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => setShowPreview(!showPreview)}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
title={showPreview ? 'Edit' : 'Preview'}
>
{showPreview ? <Pencil className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
</button>
<button
onClick={() => setIsCollapsed(true)}
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
title="Close scratchpad"
>
<X className="h-4 w-4" />
@@ -189,7 +189,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{content.trim() ? (
<MarkdownContent content={content} className="text-sm" />
) : (
<p className="text-sm italic text-muted-foreground">Nothing to preview</p>
<p className="text-sm italic text-white/40">Nothing to preview</p>
)}
</div>
) : (
@@ -200,7 +200,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
placeholder={"Capture IPs, error codes, server names, user info...\n\nSupports markdown formatting."}
className={cn(
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm',
'text-foreground placeholder:text-muted-foreground',
'text-white placeholder:text-white/40',
'focus:outline-none focus:ring-0'
)}
/>
@@ -208,25 +208,25 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
</div>
{/* Save Indicator */}
<div className="border-t border-border px-3 py-1.5">
<div className="border-t border-white/[0.06] px-3 py-1.5">
<div className="flex items-center gap-1.5 text-xs">
{saveStatus === 'unsaved' && (
<span className="text-muted-foreground">Unsaved changes</span>
<span className="text-white/40">Unsaved changes</span>
)}
{saveStatus === 'saving' && (
<>
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" />
<span className="text-muted-foreground">Saving...</span>
<Loader2 className="h-3 w-3 animate-spin text-white/40" />
<span className="text-white/40">Saving...</span>
</>
)}
{saveStatus === 'saved' && (
<span className="text-green-600 dark:text-green-400">Saved</span>
<span className="text-emerald-400">Saved</span>
)}
{saveStatus === 'error' && (
<span className="text-destructive">Save failed</span>
<span className="text-red-400">Save failed</span>
)}
{saveStatus === 'idle' && (
<span className="text-muted-foreground/50">Markdown supported</span>
<span className="text-white/30">Markdown supported</span>
)}
</div>
</div>

View File

@@ -93,32 +93,32 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<div className="flex flex-col gap-3 sm:flex-row">
{/* Ticket Number Search */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<input
type="text"
placeholder="Search by ticket number..."
value={filters.ticketNumber}
onChange={(e) => handleFilterChange('ticketNumber', e.target.value)}
className={cn(
'w-full rounded-md border border-input bg-background py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
{/* Client Name Search */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<input
type="text"
placeholder="Search by client name..."
value={filters.clientName}
onChange={(e) => handleFilterChange('clientName', e.target.value)}
className={cn(
'w-full rounded-md border border-input bg-background py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
@@ -128,8 +128,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
value={filters.treeName}
onChange={(e) => handleFilterChange('treeName', e.target.value)}
className={cn(
'rounded-md border border-input bg-background px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'sm:min-w-[200px]'
)}
>
@@ -148,19 +148,19 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={() => setShowDatePicker(!showDatePicker)}
className={cn(
'flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm',
'text-foreground hover:bg-accent',
filters.dateRange?.from && 'border-primary'
'flex w-full items-center gap-2 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm',
'text-white hover:bg-white/10',
filters.dateRange?.from && 'border-white/30'
)}
>
<Calendar className="h-4 w-4 text-muted-foreground" />
<span className={cn(!filters.dateRange?.from && 'text-muted-foreground')}>
<Calendar className="h-4 w-4 text-white/40" />
<span className={cn(!filters.dateRange?.from && 'text-white/40')}>
{formatDateRange(filters.dateRange)}
</span>
</button>
{showDatePicker && (
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-border bg-popover p-4 shadow-lg">
<div className="absolute left-0 top-full z-50 mt-2 rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4 shadow-lg">
{/* Date Type Toggle */}
<div className="mb-3 flex gap-2">
<button
@@ -168,8 +168,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'started'
? 'bg-primary text-primary-foreground'
: 'bg-accent text-accent-foreground hover:bg-accent/80'
? 'bg-white text-black'
: 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
)}
>
Started
@@ -179,8 +179,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'completed'
? 'bg-primary text-primary-foreground'
: 'bg-accent text-accent-foreground hover:bg-accent/80'
? 'bg-white text-black'
: 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
)}
>
Completed
@@ -194,8 +194,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
key={preset.value}
onClick={() => applyDatePreset(preset.value)}
className={cn(
'rounded-md bg-accent px-3 py-1.5 text-sm font-medium',
'hover:bg-accent/80'
'rounded-md bg-white/10 px-3 py-1.5 text-sm font-medium text-white/70',
'hover:bg-white/20 hover:text-white'
)}
>
{preset.label}
@@ -227,8 +227,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
setShowDatePicker(false)
}}
className={cn(
'flex-1 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex-1 rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Apply
@@ -236,8 +236,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={() => setShowDatePicker(false)}
className={cn(
'rounded-md bg-accent px-3 py-1.5 text-sm font-medium',
'hover:bg-accent/80'
'rounded-md border border-white/10 px-3 py-1.5 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
Cancel
@@ -252,8 +252,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button
onClick={onClear}
className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white'
)}
>
<Filter className="h-4 w-4" />
@@ -265,46 +265,46 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
{/* Active Filter Chips */}
{hasActiveFilters && (
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-muted-foreground">Active filters:</span>
<span className="text-sm text-white/40">Active filters:</span>
{filters.ticketNumber && (
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
Ticket: {filters.ticketNumber}
<button
onClick={() => handleFilterChange('ticketNumber', '')}
className="rounded-full p-0.5 hover:bg-accent-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.clientName && (
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
Client: {filters.clientName}
<button
onClick={() => handleFilterChange('clientName', '')}
className="rounded-full p-0.5 hover:bg-accent-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.treeName && (
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
Tree: {filters.treeName}
<button
onClick={() => handleFilterChange('treeName', '')}
className="rounded-full p-0.5 hover:bg-accent-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{filters.dateRange?.from && (
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white/70">
{formatDateRange(filters.dateRange)} ({filters.dateType})
<button
onClick={clearDateRange}
className="rounded-full p-0.5 hover:bg-accent-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>

View File

@@ -77,20 +77,20 @@ export function StepRatingModal({
const getRating = (stepId: string) => ratings.get(stepId)
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm p-4">
<div className="w-full max-w-2xl max-h-[90vh] flex flex-col rounded-lg border border-border bg-card shadow-lg">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div className="glass-card w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
<div className="flex items-center justify-between border-b border-white/[0.06] px-6 py-4">
<div>
<h2 className="text-lg font-semibold text-foreground">Rate Your Experience</h2>
<p className="mt-1 text-sm text-muted-foreground">
<h2 className="text-lg font-semibold text-white">Rate Your Experience</h2>
<p className="mt-1 text-sm text-white/70">
Help others by rating the steps you used ({librarySteps.length} step{librarySteps.length !== 1 ? 's' : ''})
</p>
</div>
<button
onClick={onClose}
disabled={isSaving}
className="rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
className="rounded-full p-1 text-white/40 hover:bg-white/10 hover:text-white disabled:opacity-50"
>
<X className="h-5 w-5" />
</button>
@@ -102,14 +102,14 @@ export function StepRatingModal({
{librarySteps.map((step) => {
const rating = getRating(step.id)
return (
<div key={step.id} className="rounded-lg border border-border bg-background p-4">
<div key={step.id} className="rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4">
{/* Step Title */}
<h3 className="font-medium text-foreground">{step.title}</h3>
<p className="mt-1 text-sm text-muted-foreground capitalize">{step.step_type}</p>
<h3 className="font-medium text-white">{step.title}</h3>
<p className="mt-1 text-sm text-white/40 capitalize">{step.step_type}</p>
{/* Star Rating */}
<div className="mt-3">
<label className="mb-1 block text-sm font-medium text-foreground">
<label className="mb-1 block text-sm font-medium text-white">
Rating
</label>
<StarRating
@@ -121,7 +121,7 @@ export function StepRatingModal({
{/* Was this helpful? */}
<div className="mt-3">
<label className="mb-2 block text-sm font-medium text-foreground">
<label className="mb-2 block text-sm font-medium text-white">
Was this helpful?
</label>
<div className="flex gap-2">
@@ -132,8 +132,8 @@ export function StepRatingModal({
className={cn(
'flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors',
rating?.helpful === true
? 'border-green-500 bg-green-500/10 text-green-600 dark:text-green-400'
: 'border-input bg-background text-foreground hover:bg-accent',
? 'border-emerald-400/20 bg-emerald-400/10 text-emerald-400'
: 'border-white/10 text-white/60 hover:bg-white/10 hover:text-white',
'disabled:opacity-50'
)}
>
@@ -147,8 +147,8 @@ export function StepRatingModal({
className={cn(
'flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors',
rating?.helpful === false
? 'border-red-500 bg-red-500/10 text-red-600 dark:text-red-400'
: 'border-input bg-background text-foreground hover:bg-accent',
? 'border-red-400/20 bg-red-400/10 text-red-400'
: 'border-white/10 text-white/60 hover:bg-white/10 hover:text-white',
'disabled:opacity-50'
)}
>
@@ -160,8 +160,8 @@ export function StepRatingModal({
{/* Optional Review */}
<div className="mt-3">
<label htmlFor={`review-${step.id}`} className="mb-1 block text-sm font-medium text-foreground">
Review <span className="text-muted-foreground">(optional)</span>
<label htmlFor={`review-${step.id}`} className="mb-1 block text-sm font-medium text-white">
Review <span className="text-white/40">(optional)</span>
</label>
<textarea
id={`review-${step.id}`}
@@ -172,13 +172,13 @@ export function StepRatingModal({
rows={2}
placeholder="Share your experience with this step..."
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'disabled:opacity-50'
)}
/>
<p className="mt-1 text-xs text-muted-foreground text-right">
<p className="mt-1 text-xs text-white/40 text-right">
{rating?.review?.length || 0}/500
</p>
</div>
@@ -189,14 +189,14 @@ export function StepRatingModal({
</div>
{/* Footer */}
<div className="flex justify-end gap-2 border-t border-border px-6 py-4">
<div className="flex justify-end gap-2 border-t border-white/[0.06] px-6 py-4">
<button
type="button"
onClick={onClose}
disabled={isSaving}
className={cn(
'rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
Skip
@@ -206,8 +206,8 @@ export function StepRatingModal({
onClick={handleSubmit}
disabled={isSaving}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
{isSaving ? 'Submitting...' : 'Submit Ratings'}

View File

@@ -64,14 +64,14 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
}
return (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-background/80 backdrop-blur-sm sm:items-center sm:p-4">
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-lg">
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-sm sm:items-center sm:p-4">
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-white/[0.06] bg-[#0a0a0a] shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border p-4">
<h2 className="text-lg font-semibold">Add Custom Step</h2>
<div className="flex items-center justify-between border-b border-white/[0.06] p-4">
<h2 className="text-lg font-semibold text-white">Add Custom Step</h2>
<button
onClick={onClose}
className="rounded-md p-1.5 hover:bg-accent"
className="rounded-md p-1.5 text-white/40 hover:bg-white/10 hover:text-white"
aria-label="Close"
>
<X className="h-5 w-5" />
@@ -79,15 +79,15 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
</div>
{/* Tabs */}
<div className="flex border-b border-border">
<div className="flex border-b border-white/[0.06]">
{canCreateSteps && (
<button
onClick={() => setActiveTab('create')}
className={cn(
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
activeTab === 'create'
? 'border-b-2 border-primary bg-primary/5 text-primary'
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
? 'border-b-2 border-white bg-white/5 text-white'
: 'text-white/40 hover:bg-white/10 hover:text-white'
)}
>
Type My Own
@@ -108,7 +108,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{/* Error Display */}
{error && (
<div className="mx-4 mt-4 rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
<div className="mx-4 mt-4 rounded-lg border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
{error}
</div>
)}
@@ -132,10 +132,10 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{/* Loading Overlay */}
{isSubmitting && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<p className="text-sm text-muted-foreground">Creating step...</p>
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
<p className="text-sm text-white/40">Creating step...</p>
</div>
</div>
)}

View File

@@ -15,9 +15,9 @@ const stepTypeIcons = {
}
const stepTypeColors = {
decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30',
action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/30',
solution: 'bg-green-500/20 text-green-600 dark:text-green-400 border-green-500/30'
decision: 'bg-blue-400/10 text-blue-400 border-blue-400/20',
action: 'bg-yellow-400/10 text-yellow-400 border-yellow-400/20',
solution: 'bg-emerald-400/10 text-emerald-400 border-emerald-400/20'
}
export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
@@ -27,7 +27,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
const remainingTags = step.tags.length - 3
return (
<div className="group rounded-lg border border-border bg-card p-4 transition-shadow hover:shadow-md">
<div className="group rounded-lg border border-white/[0.06] bg-[#0a0a0a] p-4 transition-shadow hover:shadow-md">
{/* Header */}
<div className="mb-3 flex items-start justify-between gap-2">
<div className="flex-1">
@@ -45,19 +45,19 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
{/* Featured Badge */}
{step.is_featured && (
<span className="rounded bg-amber-500/20 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">
<span className="rounded bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
Featured
</span>
)}
</div>
{/* Title */}
<h3 className="font-semibold text-foreground line-clamp-2">{step.title}</h3>
<h3 className="font-semibold text-white line-clamp-2">{step.title}</h3>
</div>
</div>
{/* Metadata */}
<div className="mb-3 space-y-1.5 text-sm text-muted-foreground">
<div className="mb-3 space-y-1.5 text-sm text-white/40">
{/* Category */}
{step.category_name && (
<div className="flex items-center gap-1.5">
@@ -103,7 +103,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
{visibleTags.map(tag => (
<span
key={tag}
className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground"
className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70"
>
{tag}
</span>
@@ -121,8 +121,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button
onClick={() => onPreview(step)}
className={cn(
'flex flex-1 items-center justify-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground transition-colors'
'flex flex-1 items-center justify-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white transition-colors'
)}
>
<Eye className="h-4 w-4" />
@@ -131,8 +131,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button
onClick={() => onInsert(step)}
className={cn(
'flex flex-1 items-center justify-center gap-2 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 transition-colors'
'flex flex-1 items-center justify-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90 transition-colors'
)}
>
<Plus className="h-4 w-4" />

View File

@@ -18,9 +18,9 @@ const stepTypeIcons = {
}
const stepTypeColors = {
decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30',
action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/30',
solution: 'bg-green-500/20 text-green-600 dark:text-green-400 border-green-500/30'
decision: 'bg-blue-400/10 text-blue-400 border-blue-400/20',
action: 'bg-yellow-400/10 text-yellow-400 border-yellow-400/20',
solution: 'bg-emerald-400/10 text-emerald-400 border-emerald-400/20'
}
export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalProps) {
@@ -69,14 +69,14 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
const topReviews = reviews.filter(r => r.review_text).slice(0, 3)
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col rounded-lg border border-border bg-card shadow-lg">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col glass-card rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-start justify-between border-b border-border p-6 pb-4">
<div className="flex items-start justify-between border-b border-white/[0.06] p-6 pb-4">
{isLoading ? (
<div className="h-6 w-48 animate-pulse rounded bg-muted" />
<div className="h-6 w-48 animate-pulse rounded bg-white/10" />
) : error ? (
<h2 className="text-lg font-semibold text-destructive">{error}</h2>
<h2 className="text-lg font-semibold text-red-400">{error}</h2>
) : step ? (
<div className="flex-1">
<div className="mb-2 flex items-center gap-2">
@@ -90,25 +90,25 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{step.step_type}
</span>
{step.category_name && (
<span className="text-xs text-muted-foreground">📁 {step.category_name}</span>
<span className="text-xs text-white/40">📁 {step.category_name}</span>
)}
{step.is_featured && (
<span className="rounded bg-amber-500/20 px-2 py-0.5 text-xs font-medium text-amber-600 dark:text-amber-400">
<span className="rounded bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
Featured
</span>
)}
{step.is_verified && (
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs font-medium text-green-600 dark:text-green-400">
<span className="rounded bg-emerald-400/10 px-2 py-0.5 text-xs font-medium text-emerald-400">
Verified
</span>
)}
</div>
<h2 className="text-xl font-semibold">{step.title}</h2>
<h2 className="text-xl font-semibold text-white">{step.title}</h2>
</div>
) : null}
<button
onClick={onClose}
className="rounded-md p-1 hover:bg-accent"
className="rounded-md p-1 text-white/40 hover:bg-white/10 hover:text-white"
aria-label="Close"
>
<X className="h-5 w-5" />
@@ -119,18 +119,18 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
<div className="flex-1 overflow-y-auto p-6">
{isLoading ? (
<div className="space-y-4">
<div className="h-4 w-full animate-pulse rounded bg-muted" />
<div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
<div className="h-4 w-5/6 animate-pulse rounded bg-muted" />
<div className="h-4 w-full animate-pulse rounded bg-white/10" />
<div className="h-4 w-3/4 animate-pulse rounded bg-white/10" />
<div className="h-4 w-5/6 animate-pulse rounded bg-white/10" />
</div>
) : error ? (
<p className="text-sm text-muted-foreground">{error}</p>
<p className="text-sm text-white/40">{error}</p>
) : step ? (
<div className="space-y-6">
{/* Rating */}
{hasRating && (
<div>
<h3 className="mb-2 text-sm font-semibold">Rating</h3>
<h3 className="mb-2 text-sm font-semibold text-white">Rating</h3>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1">
{[1, 2, 3, 4, 5].map(i => (
@@ -139,13 +139,13 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
className={cn(
'h-4 w-4',
i <= Math.round(step.rating_average)
? 'fill-yellow-500 text-yellow-500'
: 'text-muted-foreground'
? 'fill-yellow-400 text-yellow-400'
: 'text-white/20'
)}
/>
))}
</div>
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/70">
{step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
</span>
</div>
@@ -155,12 +155,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Tags */}
{step.tags.length > 0 && (
<div>
<h3 className="mb-2 text-sm font-semibold">Tags</h3>
<h3 className="mb-2 text-sm font-semibold text-white">Tags</h3>
<div className="flex flex-wrap gap-1.5">
{step.tags.map(tag => (
<span
key={tag}
className="rounded-full bg-muted px-2 py-1 text-xs text-muted-foreground"
className="rounded-full bg-white/10 px-2 py-1 text-xs text-white/70"
>
{tag}
</span>
@@ -172,7 +172,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Instructions */}
<div>
<h3 className="mb-2 text-sm font-semibold">Instructions</h3>
<div className="rounded-lg border border-border bg-muted/30 p-4">
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-4">
<MarkdownContent content={step.content.instructions} />
</div>
</div>
@@ -180,8 +180,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Help Text */}
{step.content.help_text && (
<div>
<h3 className="mb-2 text-sm font-semibold">Help Text</h3>
<div className="rounded-lg border border-border bg-blue-500/5 p-4 text-sm">
<h3 className="mb-2 text-sm font-semibold text-white">Help Text</h3>
<div className="rounded-lg border border-white/[0.06] bg-blue-400/5 p-4 text-sm">
<MarkdownContent content={step.content.help_text} />
</div>
</div>
@@ -190,19 +190,19 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{/* Commands */}
{step.content.commands && step.content.commands.length > 0 && (
<div>
<h3 className="mb-2 text-sm font-semibold">Commands</h3>
<h3 className="mb-2 text-sm font-semibold text-white">Commands</h3>
<div className="space-y-2">
{step.content.commands.map((cmd, index) => (
<div key={index} className="group relative">
<div className="mb-1 flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground">{cmd.label}</span>
<span className="text-xs font-medium text-white/40">{cmd.label}</span>
<button
onClick={() => handleCopyCommand(cmd.command, index)}
className={cn(
'flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors',
copiedCommandIndex === index
? 'bg-green-500/20 text-green-600 dark:text-green-400'
: 'bg-muted text-muted-foreground hover:bg-muted/80'
? 'bg-emerald-400/10 text-emerald-400'
: 'bg-white/10 text-white/40 hover:bg-white/20 hover:text-white'
)}
>
{copiedCommandIndex === index ? (
@@ -218,7 +218,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
</button>
</div>
<pre className="overflow-x-auto rounded bg-muted p-3 text-xs">
<pre className="overflow-x-auto rounded bg-black/50 p-3 text-xs text-white">
<code>{cmd.command}</code>
</pre>
</div>
@@ -231,22 +231,22 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
{topReviews.length > 0 && (
<div>
<div className="mb-2 flex items-center justify-between">
<h3 className="text-sm font-semibold">Reviews</h3>
<h3 className="text-sm font-semibold text-white">Reviews</h3>
{reviews.length > 3 && (
<button className="text-xs text-primary hover:underline">
<button className="text-xs text-white/70 hover:text-white hover:underline">
See all {reviews.length} reviews
</button>
)}
</div>
<div className="space-y-3">
{topReviews.map(review => (
<div key={review.id} className="rounded-lg border border-border bg-muted/30 p-3">
<div key={review.id} className="rounded-lg border border-white/[0.06] bg-white/5 p-3">
<div className="mb-2 flex items-center justify-between">
<div className="flex items-center gap-2 text-sm">
<User className="h-3.5 w-3.5" />
<span className="font-medium">{review.user_name || 'Anonymous'}</span>
<span className="font-medium text-white">{review.user_name || 'Anonymous'}</span>
{review.verified_use && (
<span className="rounded bg-green-500/20 px-1.5 py-0.5 text-xs text-green-600 dark:text-green-400">
<span className="rounded bg-emerald-400/10 px-1.5 py-0.5 text-xs text-emerald-400">
Verified Use
</span>
)}
@@ -258,15 +258,15 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
className={cn(
'h-3 w-3',
i <= review.rating
? 'fill-yellow-500 text-yellow-500'
: 'text-muted-foreground'
? 'fill-yellow-400 text-yellow-400'
: 'text-white/20'
)}
/>
))}
</div>
</div>
<p className="text-sm text-muted-foreground">{review.review_text}</p>
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
<p className="text-sm text-white/70">{review.review_text}</p>
<div className="mt-2 flex items-center gap-2 text-xs text-white/40">
<Calendar className="h-3 w-3" />
{new Date(review.created_at).toLocaleDateString()}
</div>
@@ -277,22 +277,22 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
)}
{/* Metadata */}
<div className="rounded-lg border border-border bg-muted/30 p-4">
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-4">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-muted-foreground">Author:</span>
<span className="ml-2 font-medium">{step.author_name || 'Unknown'}</span>
<span className="text-white/40">Author:</span>
<span className="ml-2 font-medium text-white">{step.author_name || 'Unknown'}</span>
</div>
<div>
<span className="text-muted-foreground">Usage Count:</span>
<span className="ml-2 font-medium">{step.usage_count}</span>
<span className="text-white/40">Usage Count:</span>
<span className="ml-2 font-medium text-white">{step.usage_count}</span>
</div>
<div>
<span className="text-muted-foreground">Created:</span>
<span className="ml-2 font-medium">{new Date(step.created_at).toLocaleDateString()}</span>
<span className="text-white/40">Created:</span>
<span className="ml-2 font-medium text-white">{new Date(step.created_at).toLocaleDateString()}</span>
</div>
<div>
<span className="text-muted-foreground">Visibility:</span>
<span className="text-white/40">Visibility:</span>
<span className="ml-2 font-medium capitalize">{step.visibility}</span>
</div>
</div>
@@ -302,10 +302,10 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
</div>
{/* Footer - Actions */}
<div className="flex gap-2 border-t border-border p-4">
<div className="flex gap-2 border-t border-white/[0.06] p-4">
<button
onClick={onClose}
className="flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent"
className="flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
@@ -313,8 +313,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
onClick={handleInsert}
disabled={!step}
className={cn(
'flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
Insert Into Session

View File

@@ -136,8 +136,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
<form onSubmit={handleSubmit} className="space-y-6">
{/* Step Type */}
<div>
<label className="mb-2 block text-sm font-medium">
Step Type <span className="text-destructive">*</span>
<label className="mb-2 block text-sm font-medium text-white">
Step Type <span className="text-red-400">*</span>
</label>
<div className="grid grid-cols-3 gap-2">
{stepTypeOptions.map(option => {
@@ -150,15 +150,15 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
className={cn(
'rounded-lg border p-3 text-left transition-colors',
stepType === option.value
? 'border-primary bg-primary/10 ring-2 ring-primary'
: 'border-border hover:border-primary/50'
? 'border-white/20 bg-white/10 ring-2 ring-white/20'
: 'border-white/[0.06] hover:border-white/20'
)}
>
<div className="mb-1 flex items-center gap-2">
<Icon className="h-4 w-4" />
<span className="font-medium text-sm">{option.label}</span>
<span className="font-medium text-sm text-white">{option.label}</span>
</div>
<p className="text-xs text-muted-foreground">{option.description}</p>
<p className="text-xs text-white/40">{option.description}</p>
</button>
)
})}
@@ -167,8 +167,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Title */}
<div>
<label htmlFor="title" className="mb-2 block text-sm font-medium">
Title <span className="text-destructive">*</span>
<label htmlFor="title" className="mb-2 block text-sm font-medium text-white">
Title <span className="text-red-400">*</span>
</label>
<input
id="title"
@@ -177,20 +177,20 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter step title"
className={cn(
'w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',
errors.title ? 'border-destructive' : 'border-input'
'w-full rounded-md border bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
errors.title ? 'border-red-400/50' : 'border-white/10'
)}
/>
{errors.title && (
<p className="mt-1 text-xs text-destructive">{errors.title}</p>
<p className="mt-1 text-xs text-red-400">{errors.title}</p>
)}
</div>
{/* Instructions */}
<div>
<label htmlFor="instructions" className="mb-2 block text-sm font-medium">
Instructions <span className="text-destructive">*</span>
<span className="ml-2 text-xs font-normal text-muted-foreground">(Markdown supported)</span>
<label htmlFor="instructions" className="mb-2 block text-sm font-medium text-white">
Instructions <span className="text-red-400">*</span>
<span className="ml-2 text-xs font-normal text-white/40">(Markdown supported)</span>
</label>
<textarea
id="instructions"
@@ -199,19 +199,19 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
placeholder="Describe what to do in this step..."
rows={6}
className={cn(
'w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',
errors.instructions ? 'border-destructive' : 'border-input'
'w-full rounded-md border bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20',
errors.instructions ? 'border-red-400/50' : 'border-white/10'
)}
/>
{errors.instructions && (
<p className="mt-1 text-xs text-destructive">{errors.instructions}</p>
<p className="mt-1 text-xs text-red-400">{errors.instructions}</p>
)}
</div>
{/* Help Text */}
<div>
<label htmlFor="helpText" className="mb-2 block text-sm font-medium">
Help Text <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-white">
Help Text <span className="text-xs font-normal text-white/40">(Optional)</span>
</label>
<textarea
id="helpText"
@@ -219,20 +219,20 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setHelpText(e.target.value)}
placeholder="Additional context or tips..."
rows={3}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
/>
</div>
{/* Commands */}
<div>
<div className="mb-2 flex items-center justify-between">
<label className="text-sm font-medium">
Commands <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
<label className="text-sm font-medium text-white">
Commands <span className="text-xs font-normal text-white/40">(Optional)</span>
</label>
<button
type="button"
onClick={addCommand}
className="flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-xs font-medium hover:bg-muted/80"
className="flex items-center gap-1 rounded-md bg-white/10 px-2 py-1 text-xs font-medium text-white/70 hover:bg-white/20 hover:text-white"
>
<Plus className="h-3 w-3" />
Add Command
@@ -241,13 +241,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{commands.length > 0 && (
<div className="space-y-3">
{commands.map((cmd, index) => (
<div key={index} className="rounded-lg border border-border bg-muted/30 p-3">
<div key={index} className="rounded-lg border border-white/[0.06] bg-white/5 p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground">Command {index + 1}</span>
<span className="text-xs font-medium text-white/40">Command {index + 1}</span>
<button
type="button"
onClick={() => removeCommand(index)}
className="rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
className="rounded p-1 text-white/40 hover:bg-red-400/10 hover:text-red-400"
>
<X className="h-3 w-3" />
</button>
@@ -259,8 +259,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => updateCommand(index, 'label', e.target.value)}
placeholder="Command label (e.g., 'Restart service')"
className={cn(
'w-full rounded-md border bg-background px-3 py-1.5 text-sm',
errors[`command_${index}_label`] ? 'border-destructive' : 'border-input'
'w-full rounded-md border bg-black/50 px-3 py-1.5 text-sm text-white',
errors[`command_${index}_label`] ? 'border-red-400/50' : 'border-white/10'
)}
/>
<input
@@ -269,12 +269,12 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => updateCommand(index, 'command', e.target.value)}
placeholder="Command (e.g., 'systemctl restart nginx')"
className={cn(
'w-full rounded-md border bg-background px-3 py-1.5 font-mono text-sm',
errors[`command_${index}_command`] ? 'border-destructive' : 'border-input'
'w-full rounded-md border bg-black/50 px-3 py-1.5 font-mono text-sm text-white',
errors[`command_${index}_command`] ? 'border-red-400/50' : 'border-white/10'
)}
/>
{(errors[`command_${index}_label`] || errors[`command_${index}_command`]) && (
<p className="text-xs text-destructive">
<p className="text-xs text-red-400">
{errors[`command_${index}_label`] || errors[`command_${index}_command`]}
</p>
)}
@@ -287,14 +287,14 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Category */}
<div>
<label htmlFor="category" className="mb-2 block text-sm font-medium">
Category <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
<label htmlFor="category" className="mb-2 block text-sm font-medium text-white">
Category <span className="text-xs font-normal text-white/40">(Optional)</span>
</label>
<select
id="category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="">None</option>
{categories.map(cat => (
@@ -305,8 +305,8 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Tags */}
<div>
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium">
Tags <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium text-white">
Tags <span className="text-xs font-normal text-white/40">(Optional)</span>
</label>
<div className="flex gap-2">
<input
@@ -316,12 +316,12 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleTagInputKeyDown}
placeholder="Type tag and press Enter"
className="flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
/>
<button
type="button"
onClick={addTag}
className="rounded-md bg-muted px-4 py-2 text-sm font-medium hover:bg-muted/80"
className="rounded-md bg-white/10 px-4 py-2 text-sm font-medium text-white/70 hover:bg-white/20 hover:text-white"
>
Add
</button>
@@ -331,13 +331,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{tags.map(tag => (
<span
key={tag}
className="flex items-center gap-1 rounded-full bg-primary/10 px-2.5 py-1 text-xs text-primary"
className="flex items-center gap-1 rounded-full bg-white/10 px-2.5 py-1 text-xs text-white/70"
>
{tag}
<button
type="button"
onClick={() => removeTag(tag)}
className="rounded-full hover:bg-primary/20"
className="rounded-full hover:bg-white/20"
aria-label={`Remove tag ${tag}`}
>
<X className="h-3 w-3" />
@@ -350,14 +350,14 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
{/* Visibility */}
<div>
<label htmlFor="visibility" className="mb-2 block text-sm font-medium">
<label htmlFor="visibility" className="mb-2 block text-sm font-medium text-white">
Visibility
</label>
<select
id="visibility"
value={visibility}
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="private">Private (only me)</option>
<option value="team">Team (my team members)</option>
@@ -370,13 +370,13 @@ export function StepForm({ onSubmit, onCancel, initialData }: StepFormProps) {
<button
type="button"
onClick={onCancel}
className="flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium hover:bg-accent"
className="flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
<button
type="submit"
className="flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
className="flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
>
Insert Step
</button>

View File

@@ -132,16 +132,16 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
return (
<div className="flex h-full flex-col">
{/* Header - Filters */}
<div className="space-y-4 border-b border-border p-4">
<div className="space-y-4 border-b border-white/[0.06] p-4">
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/40" />
<input
type="text"
placeholder="Search steps..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full rounded-md border border-input bg-background py-2 pl-10 pr-4 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="w-full rounded-md border border-white/10 bg-black/50 py-2 pl-10 pr-4 text-sm text-white placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
/>
</div>
@@ -152,7 +152,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by category"
value={selectedCategoryId || ''}
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="">All Categories</option>
{categories.map(cat => (
@@ -165,7 +165,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by step type"
value={selectedStepType || ''}
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="">All Types</option>
<option value="decision">Decision</option>
@@ -178,7 +178,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by minimum rating"
value={minRating?.toString() || ''}
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="">Any Rating</option>
<option value="4">4+ Stars</option>
@@ -191,7 +191,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Sort steps by"
value={sortBy}
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
className="rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
className="rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white focus:outline-none focus:border-white/30 focus:ring-1 focus:ring-white/20"
>
<option value="recent">Most Recent</option>
<option value="popular">Most Popular</option>
@@ -203,7 +203,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{/* Popular Tags */}
{popularTags.length > 0 && (
<div>
<div className="mb-2 text-xs font-medium text-muted-foreground">Popular Tags:</div>
<div className="mb-2 text-xs font-medium text-white/40">Popular Tags:</div>
<div className="flex flex-wrap gap-1.5">
{popularTags.map(tag => (
<button
@@ -212,8 +212,8 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
className={cn(
'rounded-full px-2.5 py-1 text-xs transition-colors',
selectedTag === tag.tag
? 'bg-primary text-primary-foreground'
: 'bg-muted text-muted-foreground hover:bg-muted/80'
? 'bg-white text-black'
: 'bg-white/10 text-white/70 hover:bg-white/20'
)}
>
{tag.tag} ({tag.count})
@@ -227,7 +227,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{hasActiveFilters && (
<button
onClick={clearFilters}
className="text-sm text-primary hover:underline"
className="text-sm text-white/70 hover:text-white hover:underline"
>
Clear all filters
</button>
@@ -238,16 +238,16 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
<div className="flex-1 overflow-y-auto p-4">
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<Loader2 className="h-8 w-8 animate-spin text-white/40" />
</div>
) : error ? (
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-center text-sm text-destructive">
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center text-sm text-red-400">
{error}
</div>
) : steps.length === 0 ? (
<div className="rounded-lg border border-border bg-muted/30 p-12 text-center">
<p className="mb-2 text-lg font-medium">No steps found</p>
<p className="text-sm text-muted-foreground">
<div className="rounded-lg border border-white/[0.06] bg-white/5 p-12 text-center">
<p className="mb-2 text-lg font-medium text-white">No steps found</p>
<p className="text-sm text-white/40">
{hasActiveFilters ? 'Try adjusting your filters' : 'Create your first step to get started!'}
</p>
</div>
@@ -260,7 +260,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('private')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold">My Steps ({groupedSteps.private.length})</h3>
<h3 className="text-sm font-semibold text-white">My Steps ({groupedSteps.private.length})</h3>
{collapsedSections.private ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -289,7 +289,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('team')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold">Team Steps ({groupedSteps.team.length})</h3>
<h3 className="text-sm font-semibold text-white">Team Steps ({groupedSteps.team.length})</h3>
{collapsedSections.team ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -318,7 +318,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
onClick={() => toggleSection('public')}
className="mb-3 flex w-full items-center justify-between"
>
<h3 className="text-sm font-semibold">Community ({groupedSteps.public.length})</h3>
<h3 className="text-sm font-semibold text-white">Community ({groupedSteps.public.length})</h3>
{collapsedSections.public ? (
<ChevronDown className="h-4 w-4" />
) : (
@@ -345,10 +345,10 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
{/* Footer - Optional Create Button */}
{showCreateButton && onCreateNew && (
<div className="border-t border-border p-4">
<div className="border-t border-white/[0.06] p-4">
<button
onClick={onCreateNew}
className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
className="w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
>
+ Create New Step
</button>

View File

@@ -13,7 +13,7 @@ export function CheckoutButton({ plan, className }: CheckoutButtonProps) {
disabled
title="Billing coming soon"
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'disabled:opacity-50 disabled:cursor-not-allowed',
className
)}

View File

@@ -51,7 +51,7 @@ export function DynamicArrayField<T>({
type="button"
onClick={() => handleMoveUp(index)}
disabled={index === 0}
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-30"
className="rounded p-0.5 text-white/50 hover:bg-white/[0.06] hover:text-white disabled:opacity-30"
title="Move up"
>
<ChevronUp className="h-3 w-3" />
@@ -60,7 +60,7 @@ export function DynamicArrayField<T>({
type="button"
onClick={() => handleMoveDown(index)}
disabled={index === items.length - 1}
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:opacity-30"
className="rounded p-0.5 text-white/50 hover:bg-white/[0.06] hover:text-white disabled:opacity-30"
title="Move down"
>
<ChevronDown className="h-3 w-3" />
@@ -76,7 +76,7 @@ export function DynamicArrayField<T>({
<button
type="button"
onClick={() => onRemove(index)}
className="mt-1 rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
className="mt-1 rounded p-1 text-white/50 hover:bg-red-400/20 hover:text-red-400"
title="Remove"
>
<Trash2 className="h-4 w-4" />
@@ -91,9 +91,9 @@ export function DynamicArrayField<T>({
type="button"
onClick={onAdd}
className={cn(
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-input',
'px-3 py-2 text-sm text-muted-foreground',
'hover:border-primary hover:text-primary'
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-white/10',
'px-3 py-2 text-sm text-white/50',
'hover:border-white/30 hover:text-white'
)}
>
<Plus className="h-4 w-4" />
@@ -103,7 +103,7 @@ export function DynamicArrayField<T>({
{/* Empty state */}
{items.length === 0 && !canAdd && (
<p className="text-center text-sm text-muted-foreground">No items</p>
<p className="text-center text-sm text-white/40">No items</p>
)}
</div>
)

View File

@@ -68,14 +68,14 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
<button
type="button"
onClick={handleCancel}
className="rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent"
className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
<button
type="button"
onClick={handleSave}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
>
Done
</button>
@@ -85,8 +85,8 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
return (
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent}>
{/* Node ID display */}
<div className="mb-4 text-xs text-muted-foreground">
Node ID: <code className="rounded bg-muted px-1 py-0.5">{node.id}</code>
<div className="mb-4 text-xs text-white/40">
Node ID: <code className="rounded bg-white/10 px-1 py-0.5">{node.id}</code>
</div>
{/* Validation errors */}
@@ -97,8 +97,8 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
key={i}
className={`rounded-md px-3 py-2 text-sm ${
error.severity === 'error'
? 'bg-destructive/10 text-destructive'
: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400'
? 'bg-red-400/10 text-red-400'
: 'bg-yellow-400/10 text-yellow-400'
}`}
>
{error.message}

View File

@@ -52,8 +52,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
<div className="space-y-4">
{/* Title */}
<div>
<label className="block text-sm font-medium text-foreground">
Title <span className="text-destructive">*</span>
<label className="block text-sm font-medium text-white">
Title <span className="text-red-400">*</span>
</label>
<input
type="text"
@@ -62,37 +62,37 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
placeholder="e.g., Restart the Service"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
titleError ? 'border-destructive' : 'border-input'
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
titleError ? 'border-red-400' : 'border-white/10'
)}
/>
{titleError && (
<p className="mt-1 text-xs text-destructive">{titleError.message}</p>
<p className="mt-1 text-xs text-red-400">{titleError.message}</p>
)}
</div>
{/* Description */}
<div>
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Description
</label>
{node.description && (
<button
type="button"
onClick={() => setShowPreview(!showPreview)}
className="text-xs text-primary hover:underline"
className="text-xs text-white/50 hover:text-white hover:underline"
>
{showPreview ? 'Edit' : 'Preview'}
</button>
)}
</div>
<p className="mb-1 text-xs text-muted-foreground">
<p className="mb-1 text-xs text-white/40">
Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
{showPreview && node.description ? (
<div className="mt-1 rounded-md border border-input bg-muted/50 p-3 text-sm">
<div className="mt-1 rounded-md border border-white/10 bg-white/[0.04] p-3 text-sm">
<MarkdownContent content={node.description} />
</div>
) : (
@@ -108,7 +108,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
**Note:** Important information here"
rows={5}
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -118,10 +118,10 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Commands */}
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Commands
</label>
<p className="mb-2 text-xs text-muted-foreground">
<p className="mb-2 text-xs text-white/40">
PowerShell or CLI commands to execute
</p>
<DynamicArrayField
@@ -137,7 +137,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => handleUpdateCommand(index, e.target.value)}
placeholder="e.g., Get-Service BrokerAgent"
className={cn(
'block w-full rounded-md border border-input px-3 py-2 font-mono text-sm',
'block w-full rounded-md border border-white/10 px-3 py-2 font-mono text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -148,7 +148,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Expected Outcome */}
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Expected Outcome
</label>
<input
@@ -157,7 +157,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
placeholder="e.g., Service should show as Running"
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}

View File

@@ -64,10 +64,10 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
<Play className="h-5 w-5 text-blue-500" />
</div>
<div>
<h3 className="font-semibold text-blue-600 dark:text-blue-400">
<h3 className="font-semibold text-blue-400">
Starting Question
</h3>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-white/40">
This is the first question users will see when they start this troubleshooting tree.
Each option below creates a different troubleshooting path.
</p>
@@ -78,11 +78,11 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Question */}
<div>
<label className="block text-sm font-medium text-foreground">
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-destructive">*</span>
<label className="block text-sm font-medium text-white">
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
</label>
{isRootNode && (
<p className="mt-0.5 text-xs text-muted-foreground">
<p className="mt-0.5 text-xs text-white/40">
What's the main question to diagnose the issue?
</p>
)}
@@ -95,19 +95,19 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
: "e.g., Can you ping the server?"}
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
questionError ? 'border-destructive' : 'border-input'
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
questionError ? 'border-red-400' : 'border-white/10'
)}
/>
{questionError && (
<p className="mt-1 text-xs text-destructive">{questionError.message}</p>
<p className="mt-1 text-xs text-red-400">{questionError.message}</p>
)}
</div>
{/* Help Text */}
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Help Text
</label>
<textarea
@@ -116,7 +116,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
placeholder="Additional context or instructions for this decision..."
rows={2}
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -125,20 +125,20 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Options */}
<div>
<label className="block text-sm font-medium text-foreground">
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-destructive">*</span>
<label className="block text-sm font-medium text-white">
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
</label>
{isRootNode ? (
<p className="mt-0.5 text-xs text-muted-foreground">
<p className="mt-0.5 text-xs text-white/40">
Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.
</p>
) : (
<p className="mt-0.5 text-xs text-muted-foreground">
<p className="mt-0.5 text-xs text-white/40">
Each option can branch to a different next step.
</p>
)}
{optionsError && (
<p className="mt-1 text-xs text-destructive">{optionsError.message}</p>
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
)}
<div className="mt-2">
<DynamicArrayField
@@ -158,14 +158,14 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
const letter = indexToLetter(index)
return (
<div className="rounded-md border border-input bg-muted/30 p-3">
<div className="rounded-md border border-white/10 bg-white/[0.04] p-3">
<div className="mb-2 flex items-center gap-2">
{/* Letter badge */}
<span className={cn(
'flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold',
isRootNode
? 'bg-blue-500/20 text-blue-600 dark:text-blue-400'
: 'bg-muted text-muted-foreground'
? 'bg-blue-500/20 text-blue-400'
: 'bg-white/10 text-white/50'
)}>
{letter}
</span>
@@ -180,12 +180,12 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
'block flex-1 rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
optionLabelError ? 'border-destructive' : 'border-input'
optionLabelError ? 'border-red-400' : 'border-white/10'
)}
/>
</div>
{optionLabelError && (
<p className="mb-2 text-xs text-destructive">{optionLabelError.message}</p>
<p className="mb-2 text-xs text-red-400">{optionLabelError.message}</p>
)}
<div className="pl-8">
<NodePicker
@@ -207,7 +207,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Example hint for root node */}
{isRootNode && (node.options?.length || 0) < 2 && (
<div className="mt-3 rounded-md border border-dashed border-muted-foreground/30 bg-muted/20 p-3 text-xs text-muted-foreground">
<div className="mt-3 rounded-md border border-dashed border-white/10 bg-white/[0.02] p-3 text-xs text-white/40">
<strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
</div>

View File

@@ -47,8 +47,8 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
<div className="space-y-4">
{/* Title */}
<div>
<label className="block text-sm font-medium text-foreground">
Title <span className="text-destructive">*</span>
<label className="block text-sm font-medium text-white">
Title <span className="text-red-400">*</span>
</label>
<input
type="text"
@@ -57,37 +57,37 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
placeholder="e.g., VDA Successfully Registered"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
titleError ? 'border-destructive' : 'border-input'
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
titleError ? 'border-red-400' : 'border-white/10'
)}
/>
{titleError && (
<p className="mt-1 text-xs text-destructive">{titleError.message}</p>
<p className="mt-1 text-xs text-red-400">{titleError.message}</p>
)}
</div>
{/* Description */}
<div>
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Description
</label>
{node.description && (
<button
type="button"
onClick={() => setShowPreview(!showPreview)}
className="text-xs text-primary hover:underline"
className="text-xs text-white/50 hover:text-white hover:underline"
>
{showPreview ? 'Edit' : 'Preview'}
</button>
)}
</div>
<p className="mb-1 text-xs text-muted-foreground">
<p className="mb-1 text-xs text-white/40">
Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
{showPreview && node.description ? (
<div className="mt-1 rounded-md border border-input bg-muted/50 p-3 text-sm">
<div className="mt-1 rounded-md border border-white/10 bg-white/[0.04] p-3 text-sm">
<MarkdownContent content={node.description} />
</div>
) : (
@@ -102,7 +102,7 @@ Document what was done and the outcome.
**Close ticket as:** Resolved"
rows={5}
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -112,10 +112,10 @@ Document what was done and the outcome.
{/* Resolution Steps */}
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Resolution Steps
</label>
<p className="mb-2 text-xs text-muted-foreground">
<p className="mb-2 text-xs text-white/40">
Step-by-step instructions for resolving the issue
</p>
<DynamicArrayField
@@ -126,7 +126,7 @@ Document what was done and the outcome.
addLabel="Add Step"
renderItem={(step, index) => (
<div className="flex items-start gap-2">
<span className="mt-2 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/10 text-xs font-medium text-primary">
<span className="mt-2 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-400/10 text-xs font-medium text-emerald-400">
{index + 1}
</span>
<input
@@ -135,7 +135,7 @@ Document what was done and the outcome.
onChange={(e) => handleUpdateStep(index, e.target.value)}
placeholder={`Step ${index + 1}`}
className={cn(
'block w-full rounded-md border border-input px-3 py-2 text-sm',
'block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)}
@@ -146,7 +146,7 @@ Document what was done and the outcome.
</div>
{/* Note about terminal node */}
<div className="rounded-md bg-green-500/10 p-3 text-sm text-green-600 dark:text-green-400">
<div className="rounded-md bg-emerald-400/10 p-3 text-sm text-emerald-400">
<strong>Note:</strong> Solution nodes are terminal - they end the troubleshooting flow.
The session will be marked complete when the user reaches this node.
</div>

View File

@@ -139,7 +139,7 @@ export function NodePicker({
return (
<div className={className}>
{label && (
<label className="mb-1 block text-sm font-medium text-foreground">
<label className="mb-1 block text-sm font-medium text-white">
{label}
</label>
)}
@@ -147,8 +147,8 @@ export function NodePicker({
{/* Inline node creation UI */}
{creatingNodeType ? (
<div className="space-y-2">
<div className="flex items-center gap-2 rounded-md border border-primary bg-primary/5 p-2">
<span className="text-xs font-medium text-primary">
<div className="flex items-center gap-2 rounded-md border border-white/20 bg-white/[0.04] p-2">
<span className="text-xs font-medium text-white">
New {NODE_TYPE_LABELS[creatingNodeType]}:
</span>
<input
@@ -159,9 +159,9 @@ export function NodePicker({
onKeyDown={handleKeyDown}
placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'}
className={cn(
'flex-1 rounded-md border border-input px-2 py-1 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'flex-1 rounded-md border border-white/10 px-2 py-1 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
@@ -169,7 +169,7 @@ export function NodePicker({
<button
type="button"
onClick={handleCancelCreate}
className="flex-1 rounded-md border border-input px-3 py-1.5 text-xs font-medium hover:bg-accent"
className="flex-1 rounded-md border border-white/10 px-3 py-1.5 text-xs font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
@@ -179,7 +179,7 @@ export function NodePicker({
disabled={!newNodeTitle.trim()}
className={cn(
'flex-1 rounded-md px-3 py-1.5 text-xs font-medium',
'bg-primary text-primary-foreground hover:bg-primary/90',
'bg-white text-black hover:bg-white/90',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
@@ -194,9 +194,9 @@ export function NodePicker({
onChange={(e) => handleChange(e.target.value)}
className={cn(
'block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
error ? 'border-destructive' : 'border-input'
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
error ? 'border-red-400' : 'border-white/10'
)}
>
<option value="">{placeholder}</option>
@@ -242,14 +242,14 @@ export function NodePicker({
{/* Show what's selected */}
{value && selectedNode && (
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{selectedNode.label}
</p>
)}
</>
)}
{error && <p className="mt-1 text-xs text-destructive">{error}</p>}
{error && <p className="mt-1 text-xs text-red-400">{error}</p>}
</div>
)
}

View File

@@ -18,7 +18,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
{/* Left Panel - Form Editor */}
<div
className={cn(
'flex flex-col overflow-y-auto border-border bg-background',
'flex flex-col overflow-y-auto border-white/[0.06]',
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
)}
>
@@ -31,7 +31,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
{/* Right Panel - Preview */}
<div
className={cn(
'flex-1 overflow-hidden bg-muted/30',
'flex-1 overflow-hidden bg-white/[0.02]',
isMobile ? 'hidden' : 'block'
)}
>

View File

@@ -56,13 +56,13 @@ export function TreeMetadataForm() {
)
return (
<div className="space-y-4 rounded-lg border border-border bg-card p-4">
<h2 className="text-sm font-semibold text-card-foreground">Tree Details</h2>
<div className="space-y-4 glass-card rounded-2xl p-4">
<h2 className="text-sm font-semibold text-white">Tree Details</h2>
{/* Name */}
<div>
<label htmlFor="tree-name" className="block text-sm font-medium text-foreground">
Name <span className="text-destructive">*</span>
<label htmlFor="tree-name" className="block text-sm font-medium text-white">
Name <span className="text-red-400">*</span>
</label>
<input
id="tree-name"
@@ -72,17 +72,17 @@ export function TreeMetadataForm() {
placeholder="e.g., VDA Registration Troubleshooting"
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
nameError ? 'border-destructive' : 'border-input'
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
nameError ? 'border-red-400' : 'border-white/10'
)}
/>
{nameError && <p className="mt-1 text-xs text-destructive">{nameError.message}</p>}
{nameError && <p className="mt-1 text-xs text-red-400">{nameError.message}</p>}
</div>
{/* Description */}
<div>
<label htmlFor="tree-description" className="block text-sm font-medium text-foreground">
<label htmlFor="tree-description" className="block text-sm font-medium text-white">
Description
</label>
<textarea
@@ -92,16 +92,16 @@ export function TreeMetadataForm() {
placeholder="Brief description of what this tree troubleshoots..."
rows={2}
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
{/* Category */}
<div>
<label htmlFor="tree-category" className="block text-sm font-medium text-foreground">
<label htmlFor="tree-category" className="block text-sm font-medium text-white">
Category
</label>
{!customCategory ? (
@@ -110,9 +110,9 @@ export function TreeMetadataForm() {
value={categoryId || ''}
onChange={(e) => handleCategoryChange(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-input px-3 py-2 text-sm',
'bg-background text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
<option value="">No category</option>
@@ -132,9 +132,9 @@ export function TreeMetadataForm() {
onChange={(e) => setCategory(e.target.value)}
placeholder="Enter new category"
className={cn(
'block flex-1 rounded-md border border-input px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'block flex-1 rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-black/50 text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
autoFocus
/>
@@ -144,7 +144,7 @@ export function TreeMetadataForm() {
setCategory('')
setCategoryId(null)
}}
className="rounded-md border border-input px-3 py-2 text-sm hover:bg-accent"
className="rounded-md border border-white/10 px-3 py-2 text-sm text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
@@ -154,7 +154,7 @@ export function TreeMetadataForm() {
{/* Tags */}
<div>
<label className="block text-sm font-medium text-foreground">Tags</label>
<label className="block text-sm font-medium text-white">Tags</label>
<div className="mt-1">
<TagInput tags={tags} onChange={setTags} maxTags={10} placeholder="Add tags..." />
</div>
@@ -162,13 +162,13 @@ export function TreeMetadataForm() {
{/* Visibility */}
<div>
<label className="block text-sm font-medium text-foreground">Visibility</label>
<label className="block text-sm font-medium text-white">Visibility</label>
<div className="mt-2 flex gap-4">
<label
className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors',
!isPublic ? 'border-primary bg-primary/10' : 'border-input hover:bg-accent'
!isPublic ? 'border-white/30 bg-white/10 text-white' : 'border-white/10 text-white/60 hover:bg-white/[0.06]'
)}
>
<input
@@ -185,7 +185,7 @@ export function TreeMetadataForm() {
className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors',
isPublic ? 'border-primary bg-primary/10' : 'border-input hover:bg-accent'
isPublic ? 'border-white/30 bg-white/10 text-white' : 'border-white/10 text-white/60 hover:bg-white/[0.06]'
)}
>
<input
@@ -199,7 +199,7 @@ export function TreeMetadataForm() {
<span className="text-sm">Public</span>
</label>
</div>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/40">
{isPublic
? 'Anyone can view this tree'
: 'Only you and your team can view this tree'}

View File

@@ -27,16 +27,16 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
className={cn(
'rounded-lg border',
errorItems.length > 0
? 'border-destructive/50 bg-destructive/5'
: 'border-yellow-500/50 bg-yellow-50 dark:bg-yellow-900/10'
? 'border-red-400/30 bg-red-400/5'
: 'border-yellow-400/30 bg-yellow-400/5'
)}
>
{/* Header */}
<button
onClick={() => setIsExpanded(!isExpanded)}
className={cn(
'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5',
errorItems.length > 0 ? 'text-destructive' : 'text-yellow-700 dark:text-yellow-500'
'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-white/5',
errorItems.length > 0 ? 'text-red-400' : 'text-yellow-400'
)}
>
<div className="flex items-center gap-2">
@@ -73,15 +73,15 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
className={cn(
'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors',
error.nodeId
? 'cursor-pointer hover:bg-destructive/10'
? 'cursor-pointer hover:bg-red-400/10'
: 'cursor-default'
)}
>
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-destructive" />
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-400" />
<div className="flex-1">
<p className="text-destructive">{error.message}</p>
<p className="text-red-400">{error.message}</p>
{error.nodeId && (
<p className="mt-0.5 text-xs text-muted-foreground">
<p className="mt-0.5 text-xs text-white/40">
Click to select node: {error.nodeId}
</p>
)}
@@ -97,15 +97,15 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
className={cn(
'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors',
warning.nodeId
? 'cursor-pointer hover:bg-yellow-100 dark:hover:bg-yellow-900/20'
? 'cursor-pointer hover:bg-yellow-400/10'
: 'cursor-default'
)}
>
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-600 dark:text-yellow-500" />
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
<div className="flex-1">
<p className="text-yellow-700 dark:text-yellow-500">{warning.message}</p>
<p className="text-yellow-400">{warning.message}</p>
{warning.nodeId && (
<p className="mt-0.5 text-xs text-muted-foreground">
<p className="mt-0.5 text-xs text-white/40">
Click to select node: {warning.nodeId}
</p>
)}

View File

@@ -154,8 +154,8 @@ export function TreePreviewNode({
<div className="relative">
{/* From option label */}
{fromOption && (
<div className="mb-1 text-xs font-medium text-muted-foreground">
<span className="rounded bg-muted px-1.5 py-0.5">{fromOption}</span>
<div className="mb-1 text-xs font-medium text-white/40">
<span className="rounded bg-white/10 px-1.5 py-0.5">{fromOption}</span>
</div>
)}
@@ -194,7 +194,7 @@ export function TreePreviewNode({
<div className="rounded-full bg-blue-500/30 p-1.5">
<Play className="h-4 w-4 text-blue-500" />
</div>
<span className="text-xs font-bold uppercase tracking-wide text-blue-600 dark:text-blue-400">
<span className="text-xs font-bold uppercase tracking-wide text-blue-400">
Starting Question
</span>
</div>
@@ -206,12 +206,12 @@ export function TreePreviewNode({
<button
type="button"
onClick={toggleCollapse}
className="mt-0.5 rounded p-0.5 hover:bg-muted"
className="mt-0.5 rounded p-0.5 hover:bg-white/10"
>
{isCollapsed ? (
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<ChevronRight className="h-4 w-4 text-white/50" />
) : (
<ChevronDown className="h-4 w-4 text-muted-foreground" />
<ChevronDown className="h-4 w-4 text-white/50" />
)}
</button>
) : (
@@ -222,14 +222,14 @@ export function TreePreviewNode({
{isRootNode && <HelpCircle className="h-4 w-4 text-blue-500" />}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground leading-tight">
<p className="text-sm font-medium text-white leading-tight">
{getNodeLabel()}
</p>
{/* Node ID with copy button */}
<div className="flex items-center gap-1 mt-1">
<span
className="text-xs text-muted-foreground cursor-help"
className="text-xs text-white/40 cursor-help"
title={`Full ID: ${node.id}`}
>
{node.id === 'root' ? 'root' : node.id.slice(0, 8) + '...'}
@@ -237,7 +237,7 @@ export function TreePreviewNode({
<button
type="button"
onClick={handleCopyId}
className="rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
className="rounded p-0.5 text-white/40 hover:bg-white/10 hover:text-white"
title="Copy full ID"
>
{copiedId ? (
@@ -252,8 +252,8 @@ export function TreePreviewNode({
{/* Show options for decision nodes */}
{node.type === 'decision' && node.options && node.options.length > 0 && (
<div className="mt-2 space-y-1 border-t border-border/50 pt-2">
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">Options:</p>
<div className="mt-2 space-y-1 border-t border-white/[0.06] pt-2">
<p className="text-[10px] uppercase tracking-wide text-white/40">Options:</p>
{node.options.map((opt, i) => {
const leadsToSolution = nodeLeadsToSolution(opt.next_node_id)
return (
@@ -261,15 +261,15 @@ export function TreePreviewNode({
key={opt.id}
className={cn(
'flex items-center gap-1 text-xs rounded px-1 py-0.5 -mx-1',
opt.next_node_id && 'hover:bg-muted cursor-pointer'
opt.next_node_id && 'hover:bg-white/[0.06] cursor-pointer'
)}
onMouseEnter={() => opt.next_node_id && onHoverNodeId?.(opt.next_node_id)}
onMouseLeave={() => onHoverNodeId?.(null)}
>
<span className="inline-flex h-4 w-4 items-center justify-center rounded bg-muted text-[10px] font-medium">
<span className="inline-flex h-4 w-4 items-center justify-center rounded bg-white/10 text-[10px] font-medium text-white/50">
{i + 1}
</span>
<span className="truncate text-foreground">{opt.label || 'Untitled'}</span>
<span className="truncate text-white/70">{opt.label || 'Untitled'}</span>
<span className="ml-auto flex items-center gap-1">
{leadsToSolution && (
<span title="Leads to solution">
@@ -279,7 +279,7 @@ export function TreePreviewNode({
{opt.next_node_id ? (
<span className="text-blue-500"></span>
) : (
<span className="text-muted-foreground/50 text-[10px]">(no link)</span>
<span className="text-white/30 text-[10px]">(no link)</span>
)}
</span>
</div>
@@ -308,12 +308,12 @@ export function TreePreviewNode({
return (
<div
className="mt-2 text-xs border-t border-border/50 pt-2 hover:bg-muted/50 cursor-pointer rounded px-1 -mx-1"
className="mt-2 text-xs border-t border-white/[0.06] pt-2 hover:bg-white/[0.04] cursor-pointer rounded px-1 -mx-1"
onMouseEnter={() => onHoverNodeId?.(node.next_node_id!)}
onMouseLeave={() => onHoverNodeId?.(null)}
>
<div className="flex items-center gap-1.5">
<span className="text-muted-foreground">Next:</span>
<span className="text-white/40">Next:</span>
{isSharedTarget && (
<span title={sharedTooltip} className="flex items-center">
<Users className="h-3 w-3 text-purple-500" />
@@ -321,7 +321,7 @@ export function TreePreviewNode({
)}
<span className={cn(
'truncate',
nextNode?.type === 'solution' ? 'text-green-500 font-medium' : 'text-foreground'
nextNode?.type === 'solution' ? 'text-green-500 font-medium' : 'text-white/70'
)}>
{nextNodeLabel.slice(0, 30)}{nextNodeLabel.length > 30 ? '...' : ''}
</span>
@@ -347,7 +347,7 @@ export function TreePreviewNode({
{/* Children - show as branches */}
{hasChildren && !isCollapsed && (
<div className="relative mt-3 ml-6 pl-6 border-l-2 border-border">
<div className="relative mt-3 ml-6 pl-6 border-l-2 border-white/[0.06]">
<div className="space-y-4">
{node.children!.map((child) => {
const optionLabel = getOptionLabelForChild(child.id)
@@ -355,7 +355,7 @@ export function TreePreviewNode({
return (
<div key={child.id} className="relative">
{/* Horizontal connector line */}
<div className="absolute -left-6 top-6 h-0.5 w-6 bg-border" />
<div className="absolute -left-6 top-6 h-0.5 w-6 bg-white/[0.06]" />
<TreePreviewNode
node={child}
@@ -377,8 +377,8 @@ export function TreePreviewNode({
{/* Show collapsed indicator */}
{hasChildren && isCollapsed && (
<div className="mt-2 ml-6 text-xs text-muted-foreground">
<span className="rounded bg-muted px-2 py-1">
<div className="mt-2 ml-6 text-xs text-white/40">
<span className="rounded bg-white/10 px-2 py-1">
{node.children!.length} child node{node.children!.length !== 1 ? 's' : ''} hidden
</span>
</div>

View File

@@ -56,7 +56,7 @@ export function TreePreviewPanel() {
if (!treeStructure) {
return (
<div className="flex h-full items-center justify-center p-4 text-sm text-muted-foreground">
<div className="flex h-full items-center justify-center p-4 text-sm text-white/40">
No tree structure to preview
</div>
)
@@ -65,11 +65,11 @@ export function TreePreviewPanel() {
return (
<div className="flex h-full flex-col">
{/* Header */}
<div className="border-b border-border bg-background px-4 py-2">
<h3 className="text-sm font-semibold text-foreground">
<div className="border-b border-white/[0.06] px-4 py-2">
<h3 className="text-sm font-semibold text-white">
Preview: {name || 'Untitled Tree'}
</h3>
<p className="text-xs text-muted-foreground">
<p className="text-xs text-white/40">
Click a node to select Hover options to highlight targets
</p>
</div>
@@ -91,8 +91,8 @@ export function TreePreviewPanel() {
</div>
{/* Legend */}
<div className="border-t border-border bg-background px-4 py-2">
<div className="flex flex-wrap gap-4 text-xs text-muted-foreground">
<div className="border-t border-white/[0.06] px-4 py-2">
<div className="flex flex-wrap gap-4 text-xs text-white/40">
<div className="flex items-center gap-1">
<div className="h-3 w-3 rounded bg-blue-500/50" />
<span>Decision</span>

View File

@@ -21,7 +21,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
// Style bold text
strong: ({ children }) => (
<strong className="font-semibold text-foreground">{children}</strong>
<strong className="font-semibold text-white">{children}</strong>
),
// Style ordered lists
ol: ({ children }) => (
@@ -33,7 +33,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
),
// Style list items
li: ({ children }) => (
<li className="text-muted-foreground">{children}</li>
<li className="text-white/60">{children}</li>
),
// Style inline code
code: ({ className, children, ...props }) => {
@@ -43,7 +43,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return (
<code
className={cn(
'block rounded bg-muted p-3 font-mono text-sm overflow-x-auto',
'block rounded bg-white/10 p-3 font-mono text-sm overflow-x-auto',
className
)}
{...props}
@@ -54,7 +54,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
}
return (
<code
className="rounded bg-muted px-1.5 py-0.5 font-mono text-sm"
className="rounded bg-white/10 px-1.5 py-0.5 font-mono text-sm"
{...props}
>
{children}
@@ -63,25 +63,25 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
},
// Style code blocks (pre)
pre: ({ children }) => (
<pre className="mb-3 overflow-x-auto rounded bg-muted p-0 last:mb-0">
<pre className="mb-3 overflow-x-auto rounded bg-white/10 p-0 last:mb-0">
{children}
</pre>
),
// Style headers
h1: ({ children }) => (
<h1 className="mb-3 text-lg font-bold text-foreground">{children}</h1>
<h1 className="mb-3 text-lg font-bold text-white">{children}</h1>
),
h2: ({ children }) => (
<h2 className="mb-2 text-base font-bold text-foreground">{children}</h2>
<h2 className="mb-2 text-base font-bold text-white">{children}</h2>
),
h3: ({ children }) => (
<h3 className="mb-2 text-sm font-bold text-foreground">{children}</h3>
<h3 className="mb-2 text-sm font-bold text-white">{children}</h3>
),
// Style horizontal rules
hr: () => <hr className="my-4 border-border" />,
hr: () => <hr className="my-4 border-white/[0.06]" />,
// Style blockquotes
blockquote: ({ children }) => (
<blockquote className="mb-3 border-l-4 border-primary/50 pl-4 italic text-muted-foreground last:mb-0">
<blockquote className="mb-3 border-l-4 border-white/20 pl-4 italic text-white/50 last:mb-0">
{children}
</blockquote>
),

View File

@@ -4,50 +4,27 @@
@layer base {
:root {
/* Light mode (fallback) */
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 243 75% 59%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 243 75% 59%;
--radius: 0.75rem;
}
.dark {
/* ResolutionFlow Dark Theme */
--background: 240 10% 3.9%;
/* Monochrome Design System — Dark Only */
--background: 0 0% 0%;
--foreground: 0 0% 100%;
--card: 240 10% 9.4%;
--card: 0 0% 4%;
--card-foreground: 0 0% 100%;
--popover: 240 10% 9.4%;
--popover: 0 0% 4%;
--popover-foreground: 0 0% 100%;
--primary: 243 75% 59%;
--primary-foreground: 0 0% 100%;
--secondary: 240 5.9% 15%;
--primary: 0 0% 100%;
--primary-foreground: 0 0% 0%;
--secondary: 0 0% 10%;
--secondary-foreground: 0 0% 100%;
--muted: 240 5.9% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 5.9% 15%;
--muted: 0 0% 10%;
--muted-foreground: 0 0% 50%;
--accent: 0 0% 8%;
--accent-foreground: 0 0% 100%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 100%;
--border: 240 5.9% 15%;
--input: 240 5.9% 15%;
--ring: 243 75% 59%;
--border: 0 0% 12%;
--input: 0 0% 12%;
--ring: 0 0% 100%;
--radius: 0.75rem;
}
}
@@ -78,7 +55,7 @@
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
font-family: 'Inter', system-ui, sans-serif;
font-weight: 700;
letter-spacing: -0.02em;
}
@@ -111,10 +88,6 @@
}
@layer utilities {
.text-gradient-brand {
@apply bg-gradient-brand bg-clip-text text-transparent;
}
.animate-fade-in {
animation: fade-in 200ms ease-out;
}
@@ -135,45 +108,67 @@
animation: scale-in 150ms ease-out;
}
/* Button press feedback for primary action buttons */
/* Button press feedback */
.btn-press {
@apply active:scale-[0.98] transition-transform;
}
/* Glass card effect */
.glass-card {
background: linear-gradient(135deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.01) 100%);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
}
.glass-card-hover {
background: linear-gradient(135deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.02) 100%);
border-color: rgba(255, 255, 255, 0.12);
}
/* Active/highlighted card glow */
.glass-card-glow {
background: linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.04) 100%);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 0 40px rgba(255, 255, 255, 0.1);
}
/* Stat card */
.glass-stat {
background: rgba(20, 20, 25, 0.5);
border: 1px solid rgba(255, 255, 255, 0.06);
backdrop-filter: blur(10px);
}
}
/* Sonner Toast Customization - ResolutionFlow Design System */
/* Sonner Toast Customization */
@layer components {
/* Base toast styling matching Modal/Card components */
:where([data-sonner-toast]) {
@apply bg-card text-card-foreground;
@apply border border-border shadow-lg;
@apply rounded-lg;
@apply rounded-xl;
font-family: 'Inter', system-ui, sans-serif;
backdrop-filter: blur(10px);
}
/* Toast title using heading font */
:where([data-sonner-toast]) [data-title] {
font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
font-family: 'Inter', system-ui, sans-serif;
font-weight: 600;
}
/* Success toast - uses primary brand color */
:where([data-sonner-toast][data-type="success"]) {
@apply border-primary/30;
border-color: rgba(52, 211, 153, 0.3);
}
:where([data-sonner-toast][data-type="success"]) [data-icon] {
@apply text-primary;
color: #34d399;
}
/* Error toast - uses destructive color */
:where([data-sonner-toast][data-type="error"]) {
@apply border-destructive/30;
border-color: rgba(248, 113, 113, 0.3);
}
:where([data-sonner-toast][data-type="error"]) [data-icon] {
@apply text-destructive;
color: #f87171;
}
/* Info toast - uses muted color */
:where([data-sonner-toast][data-type="info"]) {
@apply border-border;
}
@@ -181,26 +176,23 @@
@apply text-muted-foreground;
}
/* Warning toast - uses amber color */
:where([data-sonner-toast][data-type="warning"]) {
border-color: hsl(38 92% 50% / 0.3);
border-color: rgba(251, 191, 36, 0.3);
}
:where([data-sonner-toast][data-type="warning"]) [data-icon] {
color: hsl(38 92% 50%);
color: #fbbf24;
}
/* Close button matching Modal close button */
:where([data-sonner-toast]) [data-close-button] {
@apply text-muted-foreground hover:bg-accent hover:text-accent-foreground;
@apply rounded-md transition-colors;
}
/* Loading spinner uses primary color */
:where([data-sonner-toast]) [data-icon][data-loading] {
@apply text-primary;
@apply text-white;
}
/* React Day Picker Customization - ResolutionFlow Design System */
/* React Day Picker Customization */
.rdp-custom {
@apply text-foreground;
}
@@ -242,7 +234,7 @@
}
.rdp-custom .rdp-day_selected {
@apply bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground;
@apply bg-white text-black hover:bg-white/90 hover:text-black;
}
.rdp-custom .rdp-day_today {

View File

@@ -110,7 +110,7 @@ export function AccountSettingsPage() {
if (isLoading) {
return (
<div className="flex justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}
@@ -118,7 +118,7 @@ export function AccountSettingsPage() {
if (error) {
return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
<div className="rounded-md border border-red-400/20 bg-red-400/10 p-4 text-red-400">
<div className="flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
{error}
@@ -134,23 +134,23 @@ export function AccountSettingsPage() {
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-8">
<div className="flex items-center gap-3">
<Building2 className="h-8 w-8 text-primary" />
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">Account Settings</h1>
<Building2 className="h-8 w-8 text-white/50" />
<h1 className="text-2xl font-bold text-white sm:text-3xl">Account Settings</h1>
</div>
<p className="mt-2 text-muted-foreground">
<p className="mt-2 text-white/40">
Manage your account, subscription, and team
</p>
</div>
<div className="max-w-3xl space-y-6">
{/* Account Info Section */}
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<h2 className="text-lg font-semibold text-card-foreground">Account Information</h2>
<div className="glass-card rounded-2xl p-4 sm:p-6">
<h2 className="text-lg font-semibold text-white">Account Information</h2>
<div className="mt-4 space-y-4">
{/* Account Name */}
<div>
<label className="block font-label text-sm font-medium text-card-foreground">
<label className="block text-sm font-medium text-white">
Account Name
</label>
{isEditingName ? (
@@ -160,9 +160,9 @@ export function AccountSettingsPage() {
value={editedName}
onChange={(e) => setEditedName(e.target.value)}
className={cn(
'flex-1 rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
autoFocus
onKeyDown={(e) => {
@@ -177,8 +177,8 @@ export function AccountSettingsPage() {
onClick={handleSaveName}
disabled={isSavingName}
className={cn(
'rounded-md bg-primary p-2 text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'rounded-md bg-white p-2 text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
{isSavingName ? (
@@ -192,18 +192,18 @@ export function AccountSettingsPage() {
setEditedName(account?.name ?? '')
setIsEditingName(false)
}}
className="rounded-md border border-input p-2 text-muted-foreground hover:bg-accent"
className="rounded-md border border-white/10 p-2 text-white/40 hover:bg-white/10"
>
<X className="h-4 w-4" />
</button>
</div>
) : (
<div className="mt-1 flex items-center gap-2">
<span className="text-sm text-foreground">{account?.name}</span>
<span className="text-sm text-white">{account?.name}</span>
{isAccountOwner && (
<button
onClick={() => setIsEditingName(true)}
className="text-xs text-primary hover:underline"
className="text-xs text-white hover:underline"
>
Edit
</button>
@@ -214,10 +214,10 @@ export function AccountSettingsPage() {
{/* Display Code */}
<div>
<label className="block font-label text-sm font-medium text-card-foreground">
<label className="block text-sm font-medium text-white">
Display Code
</label>
<p className="mt-1 text-sm font-mono text-muted-foreground">
<p className="mt-1 text-sm font-mono text-white/40">
{account?.display_code}
</p>
</div>
@@ -225,8 +225,8 @@ export function AccountSettingsPage() {
</div>
{/* Subscription Section */}
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<h2 className="text-lg font-semibold text-card-foreground">Subscription</h2>
<div className="glass-card rounded-2xl p-4 sm:p-6">
<h2 className="text-lg font-semibold text-white">Subscription</h2>
<div className="mt-4 space-y-4">
{/* Plan & Status */}
@@ -234,9 +234,9 @@ export function AccountSettingsPage() {
<span
className={cn(
'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium',
plan === 'free' && 'bg-secondary text-secondary-foreground',
plan === 'pro' && 'bg-primary/10 text-primary',
plan === 'team' && 'bg-primary/20 text-primary'
plan === 'free' && 'bg-white/10 text-white/70',
plan === 'pro' && 'bg-white/10 text-white',
plan === 'team' && 'bg-white/10 text-white'
)}
>
<Crown className="h-3.5 w-3.5" />
@@ -246,11 +246,11 @@ export function AccountSettingsPage() {
<span
className={cn(
'inline-flex rounded-full px-2.5 py-0.5 text-xs font-medium',
sub.status === 'active' && 'bg-green-500/10 text-green-600',
sub.status === 'trialing' && 'bg-blue-500/10 text-blue-600',
sub.status === 'past_due' && 'bg-yellow-500/10 text-yellow-600',
sub.status === 'canceled' && 'bg-destructive/10 text-destructive',
sub.status === 'orphaned' && 'bg-muted text-muted-foreground'
sub.status === 'active' && 'bg-green-500/10 text-emerald-400',
sub.status === 'trialing' && 'bg-blue-500/10 text-blue-400',
sub.status === 'past_due' && 'bg-yellow-500/10 text-yellow-400',
sub.status === 'canceled' && 'bg-red-400/10 text-red-400',
sub.status === 'orphaned' && 'bg-white/10 text-white/40'
)}
>
{sub.status.charAt(0).toUpperCase() + sub.status.slice(1).replace('_', ' ')}
@@ -259,7 +259,7 @@ export function AccountSettingsPage() {
</div>
{sub?.current_period_end && (
<p className="text-sm text-muted-foreground">
<p className="text-sm text-white/40">
Current period ends: {new Date(sub.current_period_end).toLocaleDateString()}
</p>
)}
@@ -302,45 +302,45 @@ export function AccountSettingsPage() {
{/* Team Members Section (owners only) */}
{isAccountOwner && (
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<div className="glass-card rounded-2xl p-4 sm:p-6">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold text-card-foreground">Team Members</h2>
<Users className="h-5 w-5 text-white/50" />
<h2 className="text-lg font-semibold text-white">Team Members</h2>
</div>
{members.length === 0 ? (
<p className="mt-4 text-sm text-muted-foreground">No team members yet.</p>
<p className="mt-4 text-sm text-white/40">No team members yet.</p>
) : (
<div className="mt-4 divide-y divide-border">
<div className="mt-4 divide-y divide-white/[0.06]">
{members.map((member) => (
<div
key={member.id}
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
>
<div>
<p className="text-sm font-medium text-foreground">{member.name}</p>
<p className="text-xs text-muted-foreground">{member.email}</p>
<p className="text-sm font-medium text-white">{member.name}</p>
<p className="text-xs text-white/40">{member.email}</p>
</div>
<div className="flex items-center gap-3">
<span
className={cn(
'rounded-full px-2.5 py-0.5 text-xs font-medium',
member.account_role === 'owner' && 'bg-primary/10 text-primary',
member.account_role === 'engineer' && 'bg-secondary text-secondary-foreground',
member.account_role === 'viewer' && 'bg-muted text-muted-foreground'
member.account_role === 'owner' && 'bg-white/10 text-white',
member.account_role === 'engineer' && 'bg-white/10 text-white/70',
member.account_role === 'viewer' && 'bg-white/10 text-white/40'
)}
>
{member.account_role}
</span>
{!member.is_active && (
<span className="rounded-full bg-destructive/10 px-2 py-0.5 text-xs text-destructive">
<span className="rounded-full bg-red-400/10 px-2 py-0.5 text-xs text-red-400">
Inactive
</span>
)}
{member.account_role !== 'owner' && (
<button
onClick={() => handleRemoveMember(member.id)}
className="text-muted-foreground hover:text-destructive"
className="text-white/40 hover:text-red-400"
title="Remove member"
>
<X className="h-4 w-4" />
@@ -356,10 +356,10 @@ export function AccountSettingsPage() {
{/* Invite Member Section (owners only) */}
{isAccountOwner && (
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<div className="glass-card rounded-2xl p-4 sm:p-6">
<div className="flex items-center gap-2">
<Mail className="h-5 w-5 text-primary" />
<h2 className="text-lg font-semibold text-card-foreground">Invite Member</h2>
<Mail className="h-5 w-5 text-white/50" />
<h2 className="text-lg font-semibold text-white">Invite Member</h2>
</div>
<form onSubmit={handleInvite} className="mt-4 space-y-3">
@@ -371,17 +371,17 @@ export function AccountSettingsPage() {
onChange={(e) => setInviteEmail(e.target.value)}
required
className={cn(
'flex-1 rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
<select
value={inviteRole}
onChange={(e) => setInviteRole(e.target.value)}
className={cn(
'rounded-md border border-input bg-background px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
<option value="engineer">Engineer</option>
@@ -391,8 +391,8 @@ export function AccountSettingsPage() {
type="submit"
disabled={isInviting || !inviteEmail.trim()}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
{isInviting ? (
@@ -407,18 +407,18 @@ export function AccountSettingsPage() {
</div>
{inviteError && (
<p className="text-sm text-destructive">{inviteError}</p>
<p className="text-sm text-red-400">{inviteError}</p>
)}
{inviteSuccess && (
<p className="text-sm text-green-600">{inviteSuccess}</p>
<p className="text-sm text-emerald-400">{inviteSuccess}</p>
)}
</form>
{/* Pending Invites */}
{invites.length > 0 && (
<div className="mt-6">
<h3 className="text-sm font-medium text-card-foreground">Pending Invites</h3>
<div className="mt-2 divide-y divide-border">
<h3 className="text-sm font-medium text-white">Pending Invites</h3>
<div className="mt-2 divide-y divide-white/[0.06]">
{invites
.filter((inv) => !inv.used_at)
.map((invite) => (
@@ -427,12 +427,12 @@ export function AccountSettingsPage() {
className="flex items-center justify-between py-2"
>
<div>
<p className="text-sm text-foreground">{invite.email}</p>
<p className="text-xs text-muted-foreground">
<p className="text-sm text-white">{invite.email}</p>
<p className="text-xs text-white/40">
Expires {new Date(invite.expires_at).toLocaleDateString()}
</p>
</div>
<span className="rounded-full bg-secondary px-2.5 py-0.5 text-xs text-secondary-foreground">
<span className="rounded-full bg-white/10 px-2.5 py-0.5 text-xs text-white/70">
{invite.role}
</span>
</div>
@@ -463,25 +463,25 @@ function UsageStat({
const isAtLimit = !isUnlimited && current >= max
return (
<div className="rounded-md border border-border bg-background p-3">
<p className="text-xs font-medium text-muted-foreground">{label}</p>
<div className="glass-stat rounded-md p-3">
<p className="text-xs font-medium text-white/40">{label}</p>
<p
className={cn(
'mt-1 text-lg font-semibold',
isAtLimit ? 'text-destructive' : isNearLimit ? 'text-yellow-600' : 'text-foreground'
isAtLimit ? 'text-red-400' : isNearLimit ? 'text-yellow-400' : 'text-white'
)}
>
{current}
<span className="text-sm font-normal text-muted-foreground">
<span className="text-sm font-normal text-white/40">
{' '}/ {isUnlimited ? 'Unlimited' : max}
</span>
</p>
{!isUnlimited && (
<div className="mt-2 h-1.5 overflow-hidden rounded-full bg-muted">
<div className="mt-2 h-1.5 overflow-hidden rounded-full bg-white/10">
<div
className={cn(
'h-full rounded-full transition-all',
isAtLimit ? 'bg-destructive' : isNearLimit ? 'bg-yellow-500' : 'bg-primary'
isAtLimit ? 'bg-red-400' : isNearLimit ? 'bg-yellow-500' : 'bg-white'
)}
style={{ width: `${percentage}%` }}
/>

View File

@@ -144,7 +144,7 @@ export function AdminCategoriesPage() {
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}
@@ -154,18 +154,18 @@ export function AdminCategoriesPage() {
{/* Header */}
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">
<h1 className="text-2xl font-bold text-white sm:text-3xl">
Step Categories
</h1>
<p className="mt-2 text-muted-foreground">
<p className="mt-2 text-white/40">
Manage categories for organizing step library
</p>
</div>
<button
onClick={() => setShowCreateModal(true)}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
<Plus className="h-4 w-4" />
@@ -180,16 +180,16 @@ export function AdminCategoriesPage() {
type="checkbox"
checked={includeArchived}
onChange={(e) => setIncludeArchived(e.target.checked)}
className="h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2"
className="h-4 w-4 rounded border-white/10 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-0"
/>
<span className="text-sm text-muted-foreground">Show archived categories</span>
<span className="text-sm text-white/40">Show archived categories</span>
</label>
</div>
{/* Categories List */}
{categories.length === 0 ? (
<div className="rounded-lg border border-border bg-card p-12 text-center">
<p className="text-muted-foreground">
<div className="glass-card rounded-2xl p-12 text-center">
<p className="text-white/40">
No categories found. Create your first category to get started.
</p>
</div>

View File

@@ -2,7 +2,6 @@ import { useState } from 'react'
import { Link, useNavigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { cn } from '@/lib/utils'
export function LoginPage() {
@@ -35,33 +34,38 @@ export function LoginPage() {
}
return (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-md space-y-8">
<div className="flex min-h-screen items-center justify-center bg-black px-4">
{/* Subtle radial overlay */}
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(100,100,120,0.03),transparent_50%)]" />
<div className="relative w-full max-w-md space-y-8">
<div className="text-center">
<div className="mb-4 flex justify-center sm:mb-6">
<BrandLogo size="lg" className="h-12 w-12 sm:h-16 sm:w-16" />
<div className="w-16 h-16 rounded-2xl bg-white flex items-center justify-center sm:w-20 sm:h-20">
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
</div>
</div>
<h1>
<BrandWordmark size="lg" />
<h1 className="text-3xl font-bold text-white tracking-tight">
ResolutionFlow
</h1>
<p className="mt-2 text-base font-medium text-gradient-brand sm:mt-3 sm:text-lg">
<p className="mt-2 text-base font-medium text-white/60 sm:mt-3 sm:text-lg">
Decision Tree Platform
</p>
<p className="mt-1 text-sm text-muted-foreground sm:mt-2">
<p className="mt-1 text-sm text-white/40 sm:mt-2">
Sign in to your account
</p>
</div>
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
<div className="space-y-4 rounded-lg border border-border bg-card p-6 shadow-lg">
<div className="glass-card rounded-2xl p-6 space-y-4">
{(error || localError) && (
<div className="rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
{localError || error}
</div>
)}
<div>
<label htmlFor="email" className="mb-1 block text-sm font-medium text-foreground">
<label htmlFor="email" className="mb-1 block text-sm font-medium text-white">
Email address
</label>
<input
@@ -73,9 +77,9 @@ export function LoginPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
className={cn(
'block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20',
'block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'transition-colors'
)}
placeholder="you@example.com"
@@ -83,7 +87,7 @@ export function LoginPage() {
</div>
<div>
<label htmlFor="password" className="mb-1 block text-sm font-medium text-foreground">
<label htmlFor="password" className="mb-1 block text-sm font-medium text-white">
Password
</label>
<input
@@ -95,9 +99,9 @@ export function LoginPage() {
value={password}
onChange={(e) => setPassword(e.target.value)}
className={cn(
'block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20',
'block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'transition-colors'
)}
placeholder="••••••••••"
@@ -108,20 +112,20 @@ export function LoginPage() {
type="submit"
disabled={isLoading}
className={cn(
'w-full rounded-md px-4 py-2.5 text-sm font-semibold text-white btn-press',
'bg-gradient-brand hover:bg-gradient-brand-hover',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
'bg-white text-black hover:bg-white/90',
'focus:outline-none focus:ring-2 focus:ring-white/30 focus:ring-offset-2 focus:ring-offset-black',
'disabled:cursor-not-allowed disabled:opacity-50',
'shadow-lg shadow-primary/20'
'transition-all'
)}
>
{isLoading ? 'Signing in...' : 'Sign in'}
</button>
</div>
<p className="text-center text-sm text-muted-foreground">
<p className="text-center text-sm text-white/40">
Don't have an account?{' '}
<Link to="/register" className="font-medium text-gradient-brand hover:underline">
<Link to="/register" className="font-medium text-white hover:underline">
Register
</Link>
</p>

View File

@@ -108,10 +108,8 @@ export function MyTreesPage() {
return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-6 sm:mb-8">
<h1 className="font-heading text-3xl font-bold sm:text-4xl">
<span className="text-gradient-brand">My Trees</span>
</h1>
<p className="mt-2 text-muted-foreground">
<h1 className="text-2xl font-bold text-white sm:text-3xl">My Trees</h1>
<p className="mt-2 text-white/40">
Your forked and custom decision trees
</p>
</div>
@@ -119,20 +117,20 @@ export function MyTreesPage() {
{/* Loading State */}
{isLoading ? (
<div className="flex justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
) : trees.length === 0 ? (
<div className="rounded-lg border border-dashed border-border bg-card/50 px-4 py-12 text-center">
<FolderTree className="mx-auto mb-4 h-12 w-12 text-muted-foreground opacity-50" />
<h2 className="mb-2 text-lg font-semibold text-foreground">No personal trees yet</h2>
<p className="mb-4 text-sm text-muted-foreground">
<div className="rounded-lg border border-dashed border-white/10 bg-white/[0.02] px-4 py-12 text-center">
<FolderTree className="mx-auto mb-4 h-12 w-12 text-white/20" />
<h2 className="mb-2 text-lg font-semibold text-white">No personal trees yet</h2>
<p className="mb-4 text-sm text-white/40">
Fork a tree from the library to customize it for your workflow
</p>
<Link
to="/trees"
className={cn(
'inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'inline-flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Browse Trees
@@ -143,32 +141,32 @@ export function MyTreesPage() {
{trees.map((tree) => (
<div
key={tree.id}
className="rounded-lg border border-border bg-card p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
className="glass-card rounded-2xl p-4 transition-all hover:glass-card-hover sm:p-6"
>
{/* Header */}
<div className="mb-3 flex items-start justify-between gap-2">
<h3 className="font-semibold text-card-foreground">{tree.name}</h3>
<h3 className="font-semibold text-white">{tree.name}</h3>
{tree.category_info && (
<span className="rounded-full bg-secondary px-2 py-0.5 text-xs text-secondary-foreground">
<span className="rounded-full bg-white/10 px-2 py-0.5 text-xs text-white/70">
{tree.category_info.name}
</span>
)}
</div>
{/* Description */}
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
<p className="mb-3 text-sm text-white/40 line-clamp-2">
{tree.description || 'No description available'}
</p>
{/* Fork Badge */}
{tree.parent_tree_id && (
<div className="mb-3 flex items-center gap-2 rounded-md bg-accent/50 px-2 py-1.5 text-sm">
<GitBranch className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">
<div className="mb-3 flex items-center gap-2 rounded-md bg-white/5 px-2 py-1.5 text-sm">
<GitBranch className="h-4 w-4 text-white/40" />
<span className="text-white/40">
Forked from{' '}
<Link
to={`/trees/${tree.parent_tree_id}/navigate`}
className="font-medium text-primary hover:underline"
className="font-medium text-white hover:underline"
>
original
</Link>
@@ -184,7 +182,7 @@ export function MyTreesPage() {
)}
{/* Stats */}
<div className="mb-4 flex items-center gap-4 text-xs text-muted-foreground">
<div className="mb-4 flex items-center gap-4 text-xs text-white/30">
<div className="flex items-center gap-1">
<Clock className="h-3.5 w-3.5" />
<span>{formatDate(tree.lastUsed)}</span>
@@ -201,8 +199,8 @@ export function MyTreesPage() {
type="button"
onClick={() => handleStartSession(tree.id)}
className={cn(
'flex flex-1 items-center justify-center gap-2 rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex flex-1 items-center justify-center gap-2 rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
<Play className="h-4 w-4" />
@@ -212,8 +210,8 @@ export function MyTreesPage() {
<Link
to={`/trees/${tree.id}/edit`}
className={cn(
'rounded-md border border-input p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-white/10 hover:text-white'
)}
title="Edit tree"
>
@@ -227,8 +225,8 @@ export function MyTreesPage() {
setShowShareModal(true)
}}
className={cn(
'rounded-md border border-input p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-white/10 hover:text-white'
)}
title="Share tree"
>
@@ -241,8 +239,8 @@ export function MyTreesPage() {
setShowDeleteConfirm(true)
}}
className={cn(
'rounded-md border border-input p-2 text-muted-foreground',
'hover:bg-destructive/10 hover:text-destructive'
'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-red-400/10 hover:text-red-400'
)}
title="Delete tree"
>

View File

@@ -0,0 +1,315 @@
import { useState, useEffect, useRef } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Search, Clock, ArrowRight, Play, Loader2, TrendingUp, Sparkles, Zap } from 'lucide-react'
import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import type { TreeListItem } from '@/types'
import type { Session } from '@/types/session'
import { cn } from '@/lib/utils'
function timeAgo(dateStr: string): string {
const now = Date.now()
const then = new Date(dateStr).getTime()
const diffMs = now - then
const minutes = Math.floor(diffMs / 60000)
if (minutes < 1) return 'just now'
if (minutes < 60) return `${minutes}m ago`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}h ago`
const days = Math.floor(hours / 24)
return `${days}d ago`
}
export function QuickStartPage() {
const navigate = useNavigate()
const [query, setQuery] = useState('')
const [searchResults, setSearchResults] = useState<TreeListItem[]>([])
const [isSearching, setIsSearching] = useState(false)
const [showResults, setShowResults] = useState(false)
const [activeSessions, setActiveSessions] = useState<Session[]>([])
const [recentTrees, setRecentTrees] = useState<{ tree_id: string; name: string; lastUsed: string }[]>([])
const [isLoading, setIsLoading] = useState(true)
const searchRef = useRef<HTMLDivElement>(null)
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
// Load sessions on mount
useEffect(() => {
async function loadData() {
try {
const [active, recent] = await Promise.all([
sessionsApi.list({ completed: false, size: 5 }),
sessionsApi.list({ size: 10 }),
])
setActiveSessions(active.slice(0, 3))
// Deduplicate recent sessions by tree_id, max 5
const seen = new Set<string>()
const deduped: { tree_id: string; name: string; lastUsed: string }[] = []
for (const s of recent) {
if (!seen.has(s.tree_id) && deduped.length < 5) {
seen.add(s.tree_id)
deduped.push({
tree_id: s.tree_id,
name: s.tree_snapshot?.name || 'Unnamed Tree',
lastUsed: s.started_at,
})
}
}
setRecentTrees(deduped)
} catch (err) {
console.error('Failed to load sessions:', err)
} finally {
setIsLoading(false)
}
}
loadData()
}, [])
// Debounced search
useEffect(() => {
if (debounceRef.current) clearTimeout(debounceRef.current)
if (query.length < 2) {
setSearchResults([])
setShowResults(false)
setIsSearching(false)
return
}
setIsSearching(true)
setShowResults(true)
debounceRef.current = setTimeout(async () => {
try {
const results = await treesApi.search(query, 8)
setSearchResults(results)
} catch (err) {
console.error('Search failed:', err)
setSearchResults([])
} finally {
setIsSearching(false)
}
}, 300)
return () => {
if (debounceRef.current) clearTimeout(debounceRef.current)
}
}, [query])
// Close dropdown on outside click
useEffect(() => {
function handleClick(e: MouseEvent) {
if (searchRef.current && !searchRef.current.contains(e.target as Node)) {
setShowResults(false)
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [])
return (
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950">
{/* Animated background grid */}
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(to_right,#4f4f4f12_1px,transparent_1px),linear-gradient(to_bottom,#4f4f4f12_1px,transparent_1px)] bg-[size:64px_64px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_110%)]" />
<div className="relative container mx-auto px-4 py-12">
{/* Hero Section */}
<div className="mx-auto max-w-3xl">
{/* Badge */}
<div className="flex justify-center mb-6 animate-in fade-in slide-in-from-bottom-4 duration-700">
<div className="inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-violet-500/10 to-purple-500/10 px-4 py-2 border border-violet-500/20 backdrop-blur-sm">
<Sparkles className="h-4 w-4 text-violet-400" />
<span className="text-sm font-medium text-violet-300">AI-Powered Troubleshooting</span>
</div>
</div>
{/* Title */}
<h1 className="font-heading text-5xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-br from-white via-slate-200 to-slate-400 leading-tight animate-in fade-in slide-in-from-bottom-4 duration-700 delay-100">
What are you troubleshooting?
</h1>
<p className="text-center text-slate-400 mt-4 text-lg animate-in fade-in slide-in-from-bottom-4 duration-700 delay-200">
Search our library of proven decision trees or continue where you left off
</p>
{/* Enhanced Search Bar */}
<div ref={searchRef} className="relative mt-8 animate-in fade-in slide-in-from-bottom-4 duration-700 delay-300">
<div className="relative group">
{/* Glow effect */}
<div className="absolute -inset-0.5 bg-gradient-to-r from-violet-600 to-purple-600 rounded-xl blur opacity-20 group-hover:opacity-40 transition duration-300" />
<div className="relative">
<Search className="absolute left-5 top-1/2 h-5 w-5 -translate-y-1/2 text-slate-400 transition-colors group-hover:text-violet-400" />
<input
type="text"
autoFocus
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => query.length >= 2 && setShowResults(true)}
placeholder="Paste ticket subject or search for a tree..."
className={cn(
'w-full rounded-xl border border-slate-700/50 bg-slate-900/90 backdrop-blur-xl py-5 pl-14 pr-5 text-lg',
'text-white placeholder:text-slate-500',
'focus:outline-none focus:ring-2 focus:ring-violet-500/50 focus:border-violet-500/50',
'transition-all duration-300'
)}
/>
{query && (
<Zap className="absolute right-5 top-1/2 h-5 w-5 -translate-y-1/2 text-violet-400 animate-pulse" />
)}
</div>
</div>
{/* Enhanced Search Results Dropdown */}
{showResults && (
<div className="absolute z-10 mt-2 w-full rounded-xl border border-slate-700/50 bg-slate-900/95 backdrop-blur-xl shadow-2xl animate-in fade-in slide-in-from-top-2 duration-200">
{isSearching ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-violet-400" />
</div>
) : searchResults.length === 0 ? (
<div className="px-4 py-8 text-center">
<div className="text-slate-400 text-sm">No results found</div>
<div className="text-slate-500 text-xs mt-1">Try a different search term</div>
</div>
) : (
<ul className="max-h-96 overflow-y-auto py-2">
{searchResults.map((tree, idx) => (
<li key={tree.id} style={{ animationDelay: `${idx * 50}ms` }} className="animate-in fade-in slide-in-from-top-1 duration-200">
<button
onClick={() => navigate(`/trees/${tree.id}/navigate`)}
className="w-full px-5 py-4 text-left transition-all hover:bg-slate-800/50 group border-b border-slate-800/50 last:border-0"
>
<div className="flex items-start justify-between gap-3">
<div className="flex-1 min-w-0">
<div className="text-sm font-semibold text-white group-hover:text-violet-300 transition-colors">
{tree.name}
</div>
{tree.description && (
<div className="mt-1 line-clamp-2 text-xs text-slate-400">
{tree.description}
</div>
)}
</div>
<ArrowRight className="h-4 w-4 flex-shrink-0 text-slate-600 group-hover:text-violet-400 group-hover:translate-x-1 transition-all" />
</div>
</button>
</li>
))}
</ul>
)}
</div>
)}
</div>
</div>
{/* Continue Session Section */}
{activeSessions.length > 0 && (
<div className="mx-auto mt-16 max-w-6xl animate-in fade-in slide-in-from-bottom-4 duration-700 delay-500">
<div className="flex items-center gap-3 mb-5">
<div className="flex items-center gap-2">
<div className="h-8 w-1 bg-gradient-to-b from-violet-500 to-purple-600 rounded-full" />
<h2 className="font-heading text-xl font-bold text-white">
Continue Session
</h2>
</div>
<div className="flex-1 h-px bg-gradient-to-r from-slate-700/50 to-transparent" />
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{activeSessions.map((session, idx) => (
<button
key={session.id}
onClick={() =>
navigate(`/trees/${session.tree_id}/navigate`, {
state: { sessionId: session.id },
})
}
style={{ animationDelay: `${(idx + 5) * 100}ms` }}
className="group relative rounded-xl border border-slate-700/50 bg-slate-900/50 backdrop-blur-sm p-5 text-left transition-all hover:border-violet-500/50 hover:shadow-lg hover:shadow-violet-500/10 hover:-translate-y-1 animate-in fade-in slide-in-from-bottom-2 duration-500"
>
{/* Animated corner accent */}
<div className="absolute top-0 right-0 h-16 w-16 bg-gradient-to-bl from-violet-500/20 to-transparent rounded-tr-xl opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1">
<div className="truncate text-base font-semibold text-white group-hover:text-violet-300 transition-colors">
{session.tree_snapshot?.name || 'Unnamed Tree'}
</div>
{(session.ticket_number || session.client_name) && (
<div className="mt-1.5 truncate text-sm text-slate-400">
{[session.ticket_number, session.client_name]
.filter(Boolean)
.join(' • ')}
</div>
)}
</div>
<div className="flex-shrink-0 rounded-full bg-violet-500/10 p-2 group-hover:bg-violet-500/20 transition-colors">
<Play className="h-4 w-4 text-violet-400 group-hover:scale-110 transition-transform" />
</div>
</div>
<div className="mt-4 flex items-center gap-2 text-xs text-slate-500">
<Clock className="h-3.5 w-3.5" />
<span>Started {timeAgo(session.started_at)}</span>
</div>
{/* Progress indicator (optional - you can remove this) */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-slate-800/50 rounded-b-xl overflow-hidden">
<div className="h-full bg-gradient-to-r from-violet-500 to-purple-600 w-2/3" />
</div>
</button>
))}
</div>
</div>
)}
{/* Recent Trees Section */}
{!isLoading && recentTrees.length > 0 && (
<div className="mx-auto mt-12 max-w-6xl animate-in fade-in slide-in-from-bottom-4 duration-700 delay-700">
<div className="flex items-center gap-3 mb-5">
<div className="flex items-center gap-2">
<TrendingUp className="h-5 w-5 text-violet-400" />
<h2 className="font-heading text-xl font-bold text-white">
Recent Trees
</h2>
</div>
<div className="flex-1 h-px bg-gradient-to-r from-slate-700/50 to-transparent" />
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
{recentTrees.map((tree, idx) => (
<button
key={tree.tree_id}
onClick={() => navigate(`/trees/${tree.tree_id}/navigate`)}
style={{ animationDelay: `${(idx + 8) * 100}ms` }}
className="group relative rounded-xl border border-slate-700/50 bg-slate-900/30 backdrop-blur-sm p-4 text-left transition-all hover:border-violet-500/50 hover:bg-slate-900/50 hover:-translate-y-1 animate-in fade-in slide-in-from-bottom-2 duration-500"
>
<div className="truncate text-sm font-medium text-white group-hover:text-violet-300 transition-colors">
{tree.name}
</div>
<div className="mt-2 flex items-center gap-1.5 text-xs text-slate-500">
<Clock className="h-3 w-3" />
<span>{timeAgo(tree.lastUsed)}</span>
</div>
</button>
))}
</div>
</div>
)}
{/* Footer CTA */}
<div className="mx-auto mt-16 max-w-4xl text-center animate-in fade-in duration-700 delay-1000">
<Link
to="/trees"
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-gradient-to-r from-violet-600 to-purple-600 text-white font-medium hover:from-violet-500 hover:to-purple-500 transition-all hover:shadow-lg hover:shadow-violet-500/25 hover:scale-105"
>
Browse All Trees
<ArrowRight className="h-4 w-4" />
</Link>
</div>
</div>
</div>
)
}
export default QuickStartPage

View File

@@ -1,11 +1,11 @@
import { useState, useEffect, useRef } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Search, Clock, ArrowRight, Play, Loader2 } from 'lucide-react'
import { Search, Clock, ArrowRight, Play, Loader2, Sparkles } from 'lucide-react'
import { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions'
import type { TreeListItem } from '@/types'
import type { Session } from '@/types/session'
import { cn } from '@/lib/utils'
function timeAgo(dateStr: string): string {
const now = Date.now()
@@ -107,39 +107,61 @@ export function QuickStartPage() {
}, [])
return (
<div className="container mx-auto px-4 py-8">
<div className="container mx-auto px-4 py-12">
{/* Hero Section */}
<div className="mx-auto max-w-2xl text-center">
<h1 className="font-heading text-3xl font-bold text-foreground">
What are you troubleshooting?
<div className="mb-16 text-center max-w-4xl mx-auto">
{/* Badge */}
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/5 border border-white/10 mb-8">
<Sparkles className="w-4 h-4 text-cyan-400" />
<span className="text-sm text-white/70 font-medium">DECISION TREE PLATFORM</span>
</div>
{/* Main heading */}
<h1 className="text-4xl md:text-6xl font-bold text-white mb-6 tracking-tight leading-tight">
What are you<br />
<span className="text-white/60">troubleshooting?</span>
</h1>
<div ref={searchRef} className="relative mt-6">
<div className="relative">
<Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" />
<input
type="text"
autoFocus
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => query.length >= 2 && setShowResults(true)}
placeholder="Paste ticket subject or search for a tree..."
className={cn(
'w-full rounded-lg border border-border bg-card py-3 pl-12 pr-4 text-lg',
'text-foreground placeholder:text-muted-foreground',
'focus:outline-none focus:ring-2 focus:ring-primary/50'
{/* Description */}
<p className="text-lg text-white/40 mb-10 max-w-2xl mx-auto leading-relaxed">
Search our library of proven decision trees or continue where you left off
</p>
{/* Search Bar */}
<div ref={searchRef} className="relative max-w-2xl mx-auto group">
<div className="absolute inset-0 bg-white/5 rounded-2xl blur-2xl opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative glass-card rounded-2xl p-1">
<div className="flex items-center bg-black/50 rounded-xl">
<Search className="ml-5 w-5 h-5 text-blue-400" />
<input
type="text"
autoFocus
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => query.length >= 2 && setShowResults(true)}
placeholder="Paste ticket subject or search for a tree..."
className="flex-1 bg-transparent py-4 px-4 text-white placeholder:text-white/30 focus:outline-none"
/>
{query.length >= 2 && (
<button
onClick={() => {/* search already fires on type */}}
className="mr-2 px-5 py-2.5 bg-white text-black font-semibold rounded-lg hover:bg-white/90 transition-all"
>
Search
</button>
)}
/>
</div>
</div>
{/* Search Results Dropdown */}
{showResults && (
<div className="absolute z-10 mt-1 w-full rounded-lg border border-border bg-card shadow-lg">
<div className="absolute z-10 mt-2 w-full glass-card rounded-2xl shadow-[0_0_40px_rgba(0,0,0,0.5)] overflow-hidden">
{isSearching ? (
<div className="flex items-center justify-center py-6">
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
<div className="flex items-center justify-center py-8">
<Loader2 className="h-5 w-5 animate-spin text-white/40" />
</div>
) : searchResults.length === 0 ? (
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
<div className="px-4 py-8 text-center text-sm text-white/40">
No results found
</div>
) : (
@@ -148,13 +170,13 @@ export function QuickStartPage() {
<li key={tree.id}>
<button
onClick={() => navigate(`/trees/${tree.id}/navigate`)}
className="w-full px-4 py-3 text-left transition-colors hover:bg-accent"
className="w-full px-5 py-3.5 text-left transition-all hover:bg-white/[0.06]"
>
<div className="text-sm font-medium text-foreground">
<div className="text-sm font-medium text-white">
{tree.name}
</div>
{tree.description && (
<div className="mt-0.5 line-clamp-1 text-xs text-muted-foreground">
<div className="mt-0.5 line-clamp-1 text-xs text-white/40">
{tree.description}
</div>
)}
@@ -170,65 +192,115 @@ export function QuickStartPage() {
{/* Continue Session Section */}
{activeSessions.length > 0 && (
<div className="mx-auto mt-12 max-w-4xl">
<h2 className="font-heading text-lg font-semibold text-foreground">
Continue Session
</h2>
<div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{activeSessions.map((session) => (
<div className="mx-auto max-w-4xl mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">Active Sessions</h2>
</div>
{/* Primary active session — Bright Glow card */}
<div className="glass-card-glow backdrop-blur-xl rounded-2xl p-8 mb-4">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3 mb-3">
<div className="w-12 h-12 rounded-xl bg-white/15 border border-white/30 flex items-center justify-center">
<Play className="w-6 h-6 text-violet-400" />
</div>
<div>
<div className="text-xs text-white/70 font-semibold uppercase tracking-wider mb-1">
Active Session
</div>
<h3 className="text-xl font-bold text-white">
{activeSessions[0].tree_snapshot?.name || 'Unnamed Tree'}
</h3>
</div>
</div>
<button
key={session.id}
onClick={() =>
navigate(`/trees/${session.tree_id}/navigate`, {
state: { sessionId: session.id },
navigate(`/trees/${activeSessions[0].tree_id}/navigate`, {
state: { sessionId: activeSessions[0].id },
})
}
className="rounded-lg border border-border bg-card p-4 text-left transition-colors hover:border-primary/50 hover:bg-accent"
className="px-5 py-2.5 bg-white text-black rounded-xl font-semibold hover:bg-white/90 transition-all hover:scale-105"
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-medium text-foreground">
{session.tree_snapshot?.name || 'Unnamed Tree'}
</div>
{(session.ticket_number || session.client_name) && (
<div className="mt-1 truncate text-xs text-muted-foreground">
{[session.ticket_number, session.client_name]
.filter(Boolean)
.join(' - ')}
</div>
)}
</div>
<Play className="mt-0.5 h-4 w-4 flex-shrink-0 text-primary" />
</div>
<div className="mt-2 flex items-center gap-1 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
<span>{timeAgo(session.started_at)}</span>
</div>
Continue
</button>
))}
</div>
<p className="text-sm text-white/50 mt-1">
{[activeSessions[0].ticket_number, activeSessions[0].client_name]
.filter(Boolean)
.join(' \u2022 ')}
{activeSessions[0].started_at && ` \u2022 Started ${timeAgo(activeSessions[0].started_at)}`}
</p>
</div>
{/* Additional active sessions */}
{activeSessions.length > 1 && (
<div className="grid gap-3 sm:grid-cols-2">
{activeSessions.slice(1).map((session) => (
<button
key={session.id}
onClick={() =>
navigate(`/trees/${session.tree_id}/navigate`, {
state: { sessionId: session.id },
})
}
className="glass-card hover:glass-card-hover rounded-2xl p-5 text-left transition-all hover:scale-[1.02] cursor-pointer"
>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<div className="truncate text-sm font-bold text-white">
{session.tree_snapshot?.name || 'Unnamed Tree'}
</div>
{(session.ticket_number || session.client_name) && (
<div className="mt-1 truncate text-xs text-white/40">
{[session.ticket_number, session.client_name]
.filter(Boolean)
.join(' - ')}
</div>
)}
</div>
<Play className="mt-0.5 h-4 w-4 flex-shrink-0 text-violet-400" />
</div>
<div className="mt-3 flex items-center gap-1.5 text-xs text-white/30">
<Clock className="h-3.5 w-3.5" />
<span>{timeAgo(session.started_at)}</span>
</div>
</button>
))}
</div>
)}
</div>
)}
{/* Recent Trees Section */}
{!isLoading && recentTrees.length > 0 && (
<div className="mx-auto mt-10 max-w-4xl">
<h2 className="font-heading text-lg font-semibold text-foreground">
Recent Trees
</h2>
<div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
<div className="mx-auto max-w-4xl mb-12">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold text-white">Recent Trees</h2>
<Link
to="/trees"
className="text-sm text-white/60 hover:text-white font-medium transition-colors"
>
View all
</Link>
</div>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{recentTrees.map((tree) => (
<button
key={tree.tree_id}
onClick={() => navigate(`/trees/${tree.tree_id}/navigate`)}
className="rounded-lg border border-border bg-card p-4 text-left transition-colors hover:border-primary/50 hover:bg-accent"
className="glass-card hover:glass-card-hover rounded-2xl p-5 text-left transition-all hover:scale-[1.02] cursor-pointer"
>
<div className="truncate text-sm font-medium text-foreground">
<div className="flex items-start justify-between mb-3">
<div className="w-10 h-10 rounded-xl bg-white/5 border border-white/10 flex items-center justify-center">
<Search className="w-5 h-5 text-blue-400" />
</div>
</div>
<div className="truncate text-sm font-bold text-white mb-2">
{tree.name}
</div>
<div className="mt-1 flex items-center gap-1 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
<span>{timeAgo(tree.lastUsed)}</span>
<div className="flex items-center gap-1.5 text-xs text-white/30">
<Clock className="h-3.5 w-3.5" />
<span>Last used {timeAgo(tree.lastUsed)}</span>
</div>
</button>
))}
@@ -237,10 +309,10 @@ export function QuickStartPage() {
)}
{/* Footer */}
<div className="mx-auto mt-12 max-w-4xl text-center">
<div className="mx-auto max-w-4xl text-center">
<Link
to="/trees"
className="inline-flex items-center gap-1.5 text-sm font-medium text-primary hover:underline"
className="inline-flex items-center gap-2 px-6 py-3 bg-white/10 border border-white/20 text-white font-medium rounded-xl hover:bg-white/20 transition-all"
>
Browse All Trees
<ArrowRight className="h-4 w-4" />

View File

@@ -3,7 +3,6 @@ import { Link, useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { inviteApi } from '@/api'
import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { cn } from '@/lib/utils'
export function RegisterPage() {
@@ -76,33 +75,38 @@ export function RegisterPage() {
}
return (
<div className="flex min-h-screen items-center justify-center bg-background px-4">
<div className="w-full max-w-md space-y-8">
<div className="flex min-h-screen items-center justify-center bg-black px-4">
{/* Subtle radial overlay */}
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(100,100,120,0.03),transparent_50%)]" />
<div className="relative w-full max-w-md space-y-8">
<div className="text-center">
<div className="mb-4 flex justify-center sm:mb-6">
<BrandLogo size="lg" className="h-12 w-12 sm:h-16 sm:w-16" />
<div className="w-16 h-16 rounded-2xl bg-white flex items-center justify-center sm:w-20 sm:h-20">
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
</div>
</div>
<h1>
<BrandWordmark size="lg" />
<h1 className="text-3xl font-bold text-white tracking-tight">
ResolutionFlow
</h1>
<p className="mt-2 text-base font-medium text-gradient-brand sm:mt-3 sm:text-lg">
<p className="mt-2 text-base font-medium text-white/60 sm:mt-3 sm:text-lg">
Decision Tree Platform
</p>
<p className="mt-1 text-sm text-muted-foreground sm:mt-2">
<p className="mt-1 text-sm text-white/40 sm:mt-2">
Create your account
</p>
</div>
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
<div className="space-y-4 rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="glass-card rounded-2xl p-6 space-y-4">
{(error || localError) && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
{localError || error}
</div>
)}
<div>
<label htmlFor="inviteCode" className="block text-sm font-medium text-foreground">
<label htmlFor="inviteCode" className="block text-sm font-medium text-white">
Invite code
</label>
<input
@@ -116,29 +120,29 @@ export function RegisterPage() {
}}
onBlur={(e) => validateInviteCode(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border bg-background px-3 py-2 font-mono tracking-wider',
'text-foreground placeholder:text-muted-foreground',
'mt-1 block w-full rounded-xl border bg-black/50 px-3 py-2 font-mono tracking-wider',
'text-white placeholder:text-white/30',
'focus:outline-none focus:ring-1',
inviteCodeStatus === 'valid' && 'border-green-500 focus:border-green-500 focus:ring-green-500',
inviteCodeStatus === 'invalid' && 'border-destructive focus:border-destructive focus:ring-destructive',
inviteCodeStatus === 'idle' && 'border-input focus:border-primary focus:ring-primary',
inviteCodeStatus === 'checking' && 'border-input focus:border-primary focus:ring-primary'
inviteCodeStatus === 'valid' && 'border-emerald-400/50 focus:border-emerald-400 focus:ring-emerald-400/30',
inviteCodeStatus === 'invalid' && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/30',
inviteCodeStatus === 'idle' && 'border-white/10 focus:border-white/30 focus:ring-white/20',
inviteCodeStatus === 'checking' && 'border-white/10 focus:border-white/30 focus:ring-white/20'
)}
placeholder="ABCD1234"
/>
{inviteCodeStatus === 'checking' && (
<p className="mt-1 text-xs text-muted-foreground">Validating...</p>
<p className="mt-1 text-xs text-white/40">Validating...</p>
)}
{inviteCodeStatus === 'valid' && (
<p className="mt-1 text-xs text-green-600">{inviteCodeMessage}</p>
<p className="mt-1 text-xs text-emerald-400">{inviteCodeMessage}</p>
)}
{inviteCodeStatus === 'invalid' && (
<p className="mt-1 text-xs text-destructive">{inviteCodeMessage}</p>
<p className="mt-1 text-xs text-red-400">{inviteCodeMessage}</p>
)}
</div>
<div>
<label htmlFor="name" className="block text-sm font-medium text-foreground">
<label htmlFor="name" className="block text-sm font-medium text-white">
Full name
</label>
<input
@@ -150,16 +154,16 @@ export function RegisterPage() {
value={name}
onChange={(e) => setName(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
placeholder="John Smith"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-foreground">
<label htmlFor="email" className="block text-sm font-medium text-white">
Email address
</label>
<input
@@ -171,16 +175,16 @@ export function RegisterPage() {
value={email}
onChange={(e) => setEmail(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
placeholder="you@example.com"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-foreground">
<label htmlFor="password" className="block text-sm font-medium text-white">
Password
</label>
<input
@@ -192,19 +196,19 @@ export function RegisterPage() {
value={password}
onChange={(e) => setPassword(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
placeholder="••••••••••"
/>
<p className="mt-1 text-xs text-muted-foreground">
<p className="mt-1 text-xs text-white/30">
Must be at least 10 characters
</p>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-foreground">
<label htmlFor="confirmPassword" className="block text-sm font-medium text-white">
Confirm password
</label>
<input
@@ -216,9 +220,9 @@ export function RegisterPage() {
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/30',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
placeholder="••••••••••"
/>
@@ -228,20 +232,20 @@ export function RegisterPage() {
type="submit"
disabled={isLoading}
className={cn(
'w-full rounded-md px-4 py-2.5 text-sm font-semibold text-white btn-press',
'bg-gradient-brand hover:bg-gradient-brand-hover',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
'bg-white text-black hover:bg-white/90',
'focus:outline-none focus:ring-2 focus:ring-white/30 focus:ring-offset-2 focus:ring-offset-black',
'disabled:cursor-not-allowed disabled:opacity-50',
'shadow-lg shadow-primary/20'
'transition-all'
)}
>
{isLoading ? 'Creating account...' : 'Create account'}
</button>
</div>
<p className="text-center text-sm text-muted-foreground">
<p className="text-center text-sm text-white/40">
Already have an account?{' '}
<Link to="/login" className="font-medium text-gradient-brand hover:underline">
<Link to="/login" className="font-medium text-white hover:underline">
Sign in
</Link>
</p>

View File

@@ -223,7 +223,7 @@ export function SessionDetailPage() {
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}
@@ -231,12 +231,12 @@ export function SessionDetailPage() {
if (error || !session) {
return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
<div className="rounded-md border border-red-400/20 bg-red-400/10 p-4 text-red-400">
{error || 'Session not found'}
</div>
<button
onClick={() => navigate('/sessions')}
className="mt-4 text-primary hover:underline"
className="mt-4 text-white hover:underline"
>
Back to sessions
</button>
@@ -252,18 +252,18 @@ export function SessionDetailPage() {
<div>
<button
onClick={() => navigate('/sessions')}
className="mb-2 text-sm text-muted-foreground hover:text-foreground"
className="mb-2 text-sm text-white/40 hover:text-white"
>
Back to sessions
</button>
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">
<h1 className="text-2xl font-bold text-white sm:text-3xl">
{session.ticket_number || 'Session Details'}
</h1>
<div className="mt-2 flex items-center gap-4 text-sm text-muted-foreground">
<div className="mt-2 flex items-center gap-4 text-sm text-white/40">
<span
className={cn(
'flex items-center gap-1',
session.completed_at ? 'text-green-600' : 'text-yellow-600'
session.completed_at ? 'text-emerald-400' : 'text-yellow-400'
)}
>
<span
@@ -286,8 +286,8 @@ export function SessionDetailPage() {
onClick={() => setShowSaveAsTreeModal(true)}
disabled={isSavingTree}
className={cn(
'flex items-center gap-2 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'flex items-center gap-2 rounded-md border border-white/10 bg-transparent px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
<Save className="h-4 w-4" />
@@ -299,8 +299,8 @@ export function SessionDetailPage() {
<button
onClick={handleCopyForTicket}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
{copiedPsa ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
@@ -314,8 +314,8 @@ export function SessionDetailPage() {
onChange={(e) => setExportFormat(e.target.value as typeof exportFormat)}
aria-label="Export format"
className={cn(
'w-full rounded-md border border-input bg-background px-3 py-2 text-sm sm:w-auto',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white sm:w-auto',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
<option value="markdown">Markdown</option>
@@ -328,18 +328,18 @@ export function SessionDetailPage() {
disabled={isExporting}
title="Copy to clipboard"
className={cn(
'rounded-md border border-input bg-background p-2 text-muted-foreground',
'hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
'rounded-md border border-white/10 bg-transparent p-2 text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
</button>
<button
onClick={handlePreview}
disabled={isExporting}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50'
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50'
)}
>
<Eye className="h-4 w-4" />
@@ -352,37 +352,37 @@ export function SessionDetailPage() {
{/* Timeline */}
<div className="mb-8">
<h2 className="mb-4 text-lg font-semibold text-foreground">Decision Timeline</h2>
<h2 className="mb-4 text-lg font-semibold text-white">Decision Timeline</h2>
<div className="space-y-4">
<div className="flex items-center gap-3 text-sm">
<span className="h-3 w-3 rounded-full bg-primary" />
<span className="text-muted-foreground">
<span className="h-3 w-3 rounded-full bg-white" />
<span className="text-white/40">
Session started: {formatDate(session.started_at)}
</span>
</div>
{session.decisions.map((decision, index) => (
<div key={index} className="ml-1 border-l-2 border-border pl-6">
<div key={index} className="ml-1 border-l-2 border-white/[0.06] pl-6">
<div className="relative">
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-border" />
<div className="rounded-lg border border-border bg-card p-4">
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-white/20" />
<div className="glass-card rounded-xl p-4">
{decision.question && (
<p className="font-medium text-card-foreground">{decision.question}</p>
<p className="font-medium text-white">{decision.question}</p>
)}
{decision.answer && (
<p className="mt-1 text-sm text-primary">Answer: {decision.answer}</p>
<p className="mt-1 text-sm text-white">Answer: {decision.answer}</p>
)}
{decision.action_performed && (
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-white/40">
Action: {decision.action_performed}
</p>
)}
{decision.notes && (
<p className="mt-2 rounded bg-muted/50 p-2 text-sm text-muted-foreground">
<p className="mt-2 rounded bg-white/5 p-2 text-sm text-white/40">
Notes: {decision.notes}
</p>
)}
<p className="mt-2 text-xs text-muted-foreground">
<p className="mt-2 text-xs text-white/40">
{formatDate(decision.timestamp)}
</p>
</div>
@@ -393,7 +393,7 @@ export function SessionDetailPage() {
{session.completed_at && (
<div className="flex items-center gap-3 text-sm">
<span className="h-3 w-3 rounded-full bg-green-500" />
<span className="text-green-600">
<span className="text-emerald-400">
Session completed: {formatDate(session.completed_at)}
</span>
</div>

View File

@@ -142,14 +142,14 @@ export function SessionHistoryPage() {
return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-8">
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">Session History</h1>
<p className="mt-2 text-muted-foreground">
<h1 className="text-2xl font-bold text-white sm:text-3xl">Session History</h1>
<p className="mt-2 text-white/40">
Search and filter your troubleshooting sessions
</p>
</div>
{/* Filter Tabs */}
<div className="mb-6 flex gap-2 border-b border-border">
<div className="mb-6 flex gap-2 border-b border-white/[0.06]">
{(['all', 'active', 'completed'] as const).map((tab) => (
<button
key={tab}
@@ -157,8 +157,8 @@ export function SessionHistoryPage() {
className={cn(
'px-4 py-2 text-sm font-medium transition-colors',
filter === tab
? 'border-b-2 border-primary text-primary'
: 'text-muted-foreground hover:text-foreground'
? 'border-b-2 border-white text-white'
: 'text-white/40 hover:text-white'
)}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
@@ -179,22 +179,22 @@ export function SessionHistoryPage() {
{/* Loading State */}
{isLoading ? (
<div className="flex justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
) : sessions.length === 0 ? (
<div className="py-12 text-center text-muted-foreground">
<div className="py-12 text-center text-white/40">
No sessions found.{' '}
{filters.ticketNumber || filters.clientName || filters.treeName || filters.dateRange?.from ? (
<button
onClick={handleClearFilters}
className="text-primary hover:underline"
className="text-white hover:underline"
>
Clear filters
</button>
) : (
<button
onClick={() => navigate('/trees')}
className="text-primary hover:underline"
className="text-white hover:underline"
>
Start a new session
</button>
@@ -205,7 +205,7 @@ export function SessionHistoryPage() {
{sessions.map((session) => (
<div
key={session.id}
className="rounded-lg border border-border bg-card p-4 shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md"
className="glass-card rounded-2xl p-4 transition-all hover:glass-card-hover"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="flex-1">
@@ -217,23 +217,23 @@ export function SessionHistoryPage() {
session.completed_at ? 'bg-green-500' : 'bg-yellow-500'
)}
/>
<span className="font-medium text-card-foreground">
<span className="font-medium text-white">
{session.ticket_number || 'No ticket'}
</span>
{session.client_name && (
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium">
<span className="rounded-full bg-white/10 px-2.5 py-0.5 text-xs font-medium text-white">
{session.client_name}
</span>
)}
</div>
{/* Tree Name */}
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-white/40">
<span className="font-medium">Tree:</span> {getTreeName(session)}
</p>
{/* Timestamps */}
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-white/40">
Started: {formatDate(session.started_at)}
{session.completed_at && (
<> · Completed: {formatDate(session.completed_at)}</>
@@ -241,7 +241,7 @@ export function SessionHistoryPage() {
</p>
{/* Stats */}
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-white/40">
{session.decisions.length} decision{session.decisions.length !== 1 ? 's' : ''} recorded
{session.scratchpad && session.scratchpad.trim() && (
<span> · Has notes</span>
@@ -254,8 +254,8 @@ export function SessionHistoryPage() {
<button
onClick={() => navigate(`/sessions/${session.id}`)}
className={cn(
'rounded-md border border-input px-3 py-2 text-sm font-medium',
'hover:bg-accent hover:text-accent-foreground'
'rounded-md border border-white/10 px-3 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
View Details
@@ -264,8 +264,8 @@ export function SessionHistoryPage() {
<button
onClick={() => navigate(`/trees/${session.tree_id}/navigate`, { state: { sessionId: session.id } })}
className={cn(
'rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Resume

View File

@@ -1,13 +1,10 @@
import { Settings } from 'lucide-react'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { useThemeStore } from '@/store/themeStore'
import { cn } from '@/lib/utils'
import { ThemeToggle } from '@/components/common/ThemeToggle'
import { toast } from '@/lib/toast'
export function SettingsPage() {
const { defaultExportFormat, setDefaultExportFormat } = useUserPreferencesStore()
const { theme } = useThemeStore()
const handleExportFormatChange = (format: 'markdown' | 'text' | 'html') => {
setDefaultExportFormat(format)
@@ -18,50 +15,30 @@ export function SettingsPage() {
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-8">
<div className="flex items-center gap-3">
<Settings className="h-8 w-8 text-primary" />
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">Settings</h1>
<Settings className="h-8 w-8 text-white/50" />
<h1 className="text-2xl font-bold text-white sm:text-3xl">Settings</h1>
</div>
<p className="mt-2 text-muted-foreground">
<p className="mt-2 text-white/40">
Manage your application preferences
</p>
</div>
<div className="max-w-2xl space-y-6">
{/* Appearance Section */}
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<h2 className="text-lg font-semibold text-card-foreground">Appearance</h2>
<p className="mt-1 text-sm text-muted-foreground">
Customize how ResolutionFlow looks on your device
</p>
<div className="mt-4">
<label className="block font-label text-sm font-medium text-card-foreground">
Theme
</label>
<p className="text-sm text-muted-foreground">
Current: {theme.charAt(0).toUpperCase() + theme.slice(1)}
</p>
<div className="mt-2">
<ThemeToggle />
</div>
</div>
</div>
{/* Export Preferences Section */}
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<h2 className="text-lg font-semibold text-card-foreground">Export Preferences</h2>
<p className="mt-1 text-sm text-muted-foreground">
<div className="glass-card rounded-2xl p-4 sm:p-6">
<h2 className="text-lg font-semibold text-white">Export Preferences</h2>
<p className="mt-1 text-sm text-white/40">
Configure default settings for session exports
</p>
<div className="mt-4">
<label
htmlFor="export-format"
className="block font-label text-sm font-medium text-card-foreground"
className="block text-sm font-medium text-white"
>
Default Export Format
</label>
<p className="text-sm text-muted-foreground">
<p className="text-sm text-white/40">
This format will be pre-selected when exporting sessions
</p>
<select
@@ -69,9 +46,9 @@ export function SettingsPage() {
value={defaultExportFormat}
onChange={(e) => handleExportFormatChange(e.target.value as 'markdown' | 'text' | 'html')}
className={cn(
'mt-2 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-2 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-sm text-white',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
<option value="markdown">Markdown (.md)</option>
@@ -82,12 +59,12 @@ export function SettingsPage() {
</div>
{/* About Section */}
<div className="rounded-lg border border-border bg-card p-4 shadow-sm sm:p-6">
<h2 className="text-lg font-semibold text-card-foreground">About</h2>
<p className="mt-1 text-sm text-muted-foreground">
<div className="glass-card rounded-2xl p-4 sm:p-6">
<h2 className="text-lg font-semibold text-white">About</h2>
<p className="mt-1 text-sm text-white/40">
ResolutionFlow - Decision Tree Platform
</p>
<p className="mt-2 text-sm text-muted-foreground">
<p className="mt-2 text-sm text-white/40">
Transform troubleshooting into guided workflows
</p>
</div>

View File

@@ -248,7 +248,7 @@ export function TreeEditorPage() {
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}
@@ -257,16 +257,16 @@ export function TreeEditorPage() {
if (isMobile) {
return (
<div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center px-6 text-center">
<Monitor className="mb-4 h-12 w-12 text-muted-foreground" />
<h2 className="mb-2 text-xl font-semibold text-foreground">Desktop Required</h2>
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
<Monitor className="mb-4 h-12 w-12 text-white/50" />
<h2 className="mb-2 text-xl font-semibold text-white">Desktop Required</h2>
<p className="mb-6 max-w-sm text-sm text-white/40">
The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.
</p>
<button
onClick={() => navigate('/trees')}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Back to Library
@@ -280,18 +280,18 @@ export function TreeEditorPage() {
{/* Draft Restore Prompt */}
{showDraftPrompt && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold">Restore Draft?</h2>
<p className="mb-4 text-sm text-muted-foreground">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="glass-card rounded-2xl w-full max-w-md p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold text-white">Restore Draft?</h2>
<p className="mb-4 text-sm text-white/40">
You have an unsaved draft from a previous session. Would you like to restore it?
</p>
<div className="flex gap-2">
<button
onClick={handleRestoreDraft}
className={cn(
'flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Restore Draft
@@ -299,8 +299,8 @@ export function TreeEditorPage() {
<button
onClick={handleDiscardDraft}
className={cn(
'flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium',
'hover:bg-accent'
'flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
Start Fresh
@@ -312,18 +312,18 @@ export function TreeEditorPage() {
{/* Unsaved Changes Dialog */}
{blocker.state === 'blocked' && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold">Unsaved Changes</h2>
<p className="mb-4 text-sm text-muted-foreground">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="glass-card rounded-2xl w-full max-w-md p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold text-white">Unsaved Changes</h2>
<p className="mb-4 text-sm text-white/40">
You have unsaved changes. Are you sure you want to leave?
</p>
<div className="flex gap-2">
<button
onClick={handleBlockerReset}
className={cn(
'flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Stay
@@ -331,8 +331,8 @@ export function TreeEditorPage() {
<button
onClick={handleBlockerProceed}
className={cn(
'flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-destructive',
'hover:bg-accent'
'flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-red-400',
'hover:bg-white/10'
)}
>
Leave Without Saving
@@ -343,17 +343,17 @@ export function TreeEditorPage() {
)}
{/* Toolbar */}
<div className="flex items-center justify-between border-b border-border bg-card px-4 py-2">
<div className="flex items-center justify-between border-b border-white/[0.06] bg-black px-4 py-2">
<div className="flex items-center gap-4">
<button
onClick={() => navigate('/trees')}
className="text-sm text-muted-foreground hover:text-foreground"
className="text-sm text-white/50 hover:text-white"
>
Back to Library
</button>
<h1 className="text-lg font-semibold">
<h1 className="text-lg font-semibold text-white">
{isEditMode ? 'Edit Tree' : 'Create New Tree'}
{name && <span className="ml-2 text-muted-foreground">- {name}</span>}
{name && <span className="ml-2 text-white/40">- {name}</span>}
</h1>
<div className="flex items-center gap-2">
{treeStatus === 'draft' && (
@@ -372,7 +372,7 @@ export function TreeEditorPage() {
<div className="flex items-center gap-2">
{/* Undo/Redo */}
<div className="flex items-center rounded-md border border-border">
<div className="flex items-center rounded-md border border-white/[0.06]">
<button
type="button"
onClick={() => undo()}
@@ -381,13 +381,13 @@ export function TreeEditorPage() {
className={cn(
'rounded-l-md p-2 transition-colors',
pastStates.length > 0
? 'text-foreground hover:bg-accent'
: 'text-muted-foreground/40 cursor-not-allowed'
? 'text-white hover:bg-white/[0.06]'
: 'text-white/20 cursor-not-allowed'
)}
>
<Undo2 className="h-4 w-4" />
</button>
<div className="h-6 w-px bg-border" />
<div className="h-6 w-px bg-white/[0.06]" />
<button
type="button"
onClick={() => redo()}
@@ -396,15 +396,15 @@ export function TreeEditorPage() {
className={cn(
'rounded-r-md p-2 transition-colors',
futureStates.length > 0
? 'text-foreground hover:bg-accent'
: 'text-muted-foreground/40 cursor-not-allowed'
? 'text-white hover:bg-white/[0.06]'
: 'text-white/20 cursor-not-allowed'
)}
>
<Redo2 className="h-4 w-4" />
</button>
</div>
<div className="mx-2 h-6 w-px bg-border" />
<div className="mx-2 h-6 w-px bg-white/[0.06]" />
{/* Validate */}
<button
@@ -412,8 +412,8 @@ export function TreeEditorPage() {
disabled={isSaving}
title="Validate tree structure (checks for errors and warnings)"
className={cn(
'flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm font-medium',
'hover:bg-accent disabled:opacity-50'
'flex items-center gap-2 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50'
)}
>
<CheckCircle2 className="h-4 w-4" />
@@ -426,8 +426,8 @@ export function TreeEditorPage() {
disabled={isSaving || !isDirty}
title="Save as draft (Ctrl+S when draft or has errors)"
className={cn(
'flex items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium',
'hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed'
'flex items-center gap-2 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
<Save className="h-4 w-4" />
@@ -440,8 +440,8 @@ export function TreeEditorPage() {
disabled={isSaving || !isDirty || hasBlockingErrors}
title={hasBlockingErrors ? 'Fix validation errors before publishing (Ctrl+S when no errors)' : 'Publish tree (Ctrl+S when no errors)'}
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed'
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
<CheckCircle2 className="h-4 w-4" />

View File

@@ -199,8 +199,8 @@ export function TreeLibraryPage() {
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-6 flex flex-col gap-4 sm:mb-8 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">Decision Trees</h1>
<p className="mt-2 text-muted-foreground">
<h1 className="text-2xl font-bold text-white sm:text-3xl">Decision Trees</h1>
<p className="mt-2 text-white/40">
Select a troubleshooting tree to start a new session
</p>
</div>
@@ -208,8 +208,8 @@ export function TreeLibraryPage() {
<Link
to="/trees/new"
className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
<Plus className="h-4 w-4" />
@@ -225,9 +225,9 @@ export function TreeLibraryPage() {
<button
onClick={() => setMobileFolderOpen(true)}
className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-2 text-sm font-medium md:hidden',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
selectedFolderId && 'border-primary text-primary'
'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium md:hidden',
'text-white/40 hover:bg-white/10 hover:text-white',
selectedFolderId && 'border-white/30 text-white'
)}
>
<FolderOpen className="h-4 w-4" />
@@ -241,16 +241,16 @@ export function TreeLibraryPage() {
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className={cn(
'flex-1 rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
<button
onClick={handleSearch}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Search
@@ -262,8 +262,8 @@ export function TreeLibraryPage() {
onChange={(e) => setSelectedCategoryId(e.target.value)}
aria-label="Filter by category"
className={cn(
'rounded-md border border-input bg-background px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
>
<option value="">All Categories</option>
@@ -284,9 +284,9 @@ export function TreeLibraryPage() {
type="checkbox"
checked={showDrafts}
onChange={(e) => setShowDrafts(e.target.checked)}
className="h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-primary focus:ring-offset-2"
className="h-4 w-4 rounded border-white/20 text-white focus:ring-2 focus:ring-white/20 focus:ring-offset-2"
/>
<span className="text-sm text-muted-foreground">Show my drafts</span>
<span className="text-sm text-white/40">Show my drafts</span>
</label>
</div>
<ViewToggle view={treeLibraryView} onChange={setTreeLibraryView} />
@@ -296,24 +296,24 @@ export function TreeLibraryPage() {
{/* Active Filters */}
{hasActiveFilters && (
<div className="mb-6 flex flex-wrap items-center gap-2">
<span className="text-sm text-muted-foreground">Filters:</span>
<span className="text-sm text-white/40">Filters:</span>
{selectedFolderId && (
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white">
Folder
<button
onClick={() => setSelectedFolderId(null)}
className="rounded-full p-0.5 hover:bg-accent-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
</span>
)}
{selectedCategoryId && (
<span className="inline-flex items-center gap-1 rounded-full bg-secondary px-3 py-1 text-sm">
<span className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white">
{categories.find((c) => c.id === selectedCategoryId)?.name}
<button
onClick={() => setSelectedCategoryId('')}
className="rounded-full p-0.5 hover:bg-secondary-foreground/10"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
@@ -322,12 +322,12 @@ export function TreeLibraryPage() {
{selectedTags.map((tag) => (
<span
key={tag}
className="inline-flex items-center gap-1 rounded-full bg-primary/10 px-3 py-1 text-sm text-primary"
className="inline-flex items-center gap-1 rounded-full bg-white/10 px-3 py-1 text-sm text-white"
>
{tag}
<button
onClick={() => removeTagFilter(tag)}
className="rounded-full p-0.5 hover:bg-primary/20"
className="rounded-full p-0.5 hover:bg-white/20"
>
<X className="h-3 w-3" />
</button>
@@ -335,7 +335,7 @@ export function TreeLibraryPage() {
))}
<button
onClick={clearAllFilters}
className="text-sm text-muted-foreground hover:text-foreground"
className="text-sm text-white/40 hover:text-white"
>
Clear all
</button>
@@ -345,10 +345,10 @@ export function TreeLibraryPage() {
{/* Loading State */}
{isLoading ? (
<div className="flex justify-center py-12">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
) : trees.length === 0 ? (
<div className="py-12 text-center text-muted-foreground">
<div className="py-12 text-center text-white/40">
No trees found.{' '}
{(searchQuery || hasActiveFilters) && 'Try adjusting your filters.'}
</div>

View File

@@ -267,7 +267,7 @@ export function TreeNavigationPage() {
if (isLoading) {
return (
<div className="flex h-64 items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
<div className="h-8 w-8 animate-spin rounded-full border-4 border-white/20 border-t-white" />
</div>
)
}
@@ -275,12 +275,12 @@ export function TreeNavigationPage() {
if (error || !tree) {
return (
<div className="container mx-auto px-4 py-8">
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
<div className="rounded-md bg-red-400/10 p-4 text-red-400">
{error || 'Tree not found'}
</div>
<button
onClick={() => navigate('/trees')}
className="mt-4 text-primary hover:underline"
className="mt-4 text-white/50 hover:text-white hover:underline"
>
Back to trees
</button>
@@ -292,17 +292,17 @@ export function TreeNavigationPage() {
if (showMetadataForm) {
return (
<div className="container mx-auto max-w-lg px-4 py-8">
<h1 className="mb-2 text-2xl font-bold text-foreground">{tree.name}</h1>
<p className="mb-6 text-muted-foreground">{tree.description}</p>
<h1 className="mb-2 text-2xl font-bold text-white">{tree.name}</h1>
<p className="mb-6 text-white/40">{tree.description}</p>
<div className="space-y-4 rounded-lg border border-border bg-card p-6">
<h2 className="font-semibold text-card-foreground">Session Details</h2>
<p className="text-sm text-muted-foreground">
<div className="glass-card rounded-2xl space-y-4 p-6">
<h2 className="font-semibold text-white">Session Details</h2>
<p className="text-sm text-white/40">
Optional: Add ticket and client info for easier tracking
</p>
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Ticket Number
</label>
<input
@@ -311,15 +311,15 @@ export function TreeNavigationPage() {
onChange={(e) => setTicketNumber(e.target.value)}
placeholder="e.g., INC0012345"
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground">
<label className="block text-sm font-medium text-white">
Client Name
</label>
<input
@@ -328,9 +328,9 @@ export function TreeNavigationPage() {
onChange={(e) => setClientName(e.target.value)}
placeholder="e.g., Acme Corp"
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
@@ -338,8 +338,8 @@ export function TreeNavigationPage() {
<button
onClick={startSession}
className={cn(
'w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Start Troubleshooting
@@ -352,7 +352,7 @@ export function TreeNavigationPage() {
if (!currentNode && !currentCustomStep) {
return (
<div className="container mx-auto px-4 py-8">
<div className="rounded-md bg-destructive/10 p-4 text-destructive">
<div className="rounded-md bg-red-400/10 p-4 text-red-400">
Invalid tree structure
</div>
</div>
@@ -367,9 +367,9 @@ export function TreeNavigationPage() {
{/* Header */}
<div className="mb-6 flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-foreground">{tree.name}</h1>
<h1 className="text-xl font-bold text-white">{tree.name}</h1>
{(ticketNumber || clientName) && (
<p className="text-sm text-muted-foreground">
<p className="text-sm text-white/40">
{ticketNumber && `Ticket: ${ticketNumber}`}
{ticketNumber && clientName && ' · '}
{clientName && `Client: ${clientName}`}
@@ -378,7 +378,7 @@ export function TreeNavigationPage() {
</div>
<button
onClick={() => navigate('/sessions')}
className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded-md px-3 py-1.5 text-sm text-white/50 hover:bg-white/[0.06] hover:text-white"
>
Exit
</button>
@@ -392,12 +392,12 @@ export function TreeNavigationPage() {
const label = node?.question || node?.title || customStep?.step_data.title || nodeId
return (
<span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
{index > 0 && <span className="text-muted-foreground"></span>}
{index > 0 && <span className="text-white/40"></span>}
<span
className={cn(
index === pathTaken.length - 1
? 'font-medium text-foreground'
: 'text-muted-foreground'
? 'font-medium text-white'
: 'text-white/40'
)}
>
{label.length > 30 ? `${label.slice(0, 30)}...` : label}
@@ -408,15 +408,15 @@ export function TreeNavigationPage() {
</div>
{/* Current Node */}
<div className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="glass-card rounded-2xl p-6 shadow-sm">
{/* Decision Node */}
{currentNode && currentNode.type === 'decision' && (
<>
<h2 className="mb-2 text-xl font-semibold text-card-foreground">
<h2 className="mb-2 text-xl font-semibold text-white">
{currentNode.question}
</h2>
{currentNode.help_text && (
<div className="mb-4 text-sm text-muted-foreground">
<div className="mb-4 text-sm text-white/50">
<MarkdownContent content={currentNode.help_text} />
</div>
)}
@@ -426,13 +426,13 @@ export function TreeNavigationPage() {
key={option.id}
onClick={() => handleSelectOption(option.id, option.label, option.next_node_id)}
className={cn(
'w-full rounded-md border border-input p-3 text-left transition-colors',
'hover:border-primary hover:bg-accent',
'w-full rounded-md border border-white/10 p-3 text-left text-white transition-colors',
'hover:border-white/30 hover:bg-white/[0.06]',
'flex items-center gap-3'
)}
>
{index < 9 && (
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-muted text-xs font-medium text-muted-foreground">
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10 text-xs font-medium text-white/50">
{index + 1}
</span>
)}
@@ -443,7 +443,7 @@ export function TreeNavigationPage() {
{/* Previously-created custom steps at this node */}
{customStepFlow.customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
<div className="mt-2 space-y-2">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
<p className="text-xs font-medium uppercase tracking-wide text-white/40">
Your Custom Steps
</p>
{customStepFlow.customSteps
@@ -454,13 +454,12 @@ export function TreeNavigationPage() {
key={cs.id}
onClick={() => customStepFlow.handleNavigateToCustomStep(cs)}
className={cn(
'w-full rounded-md border border-purple-300 bg-purple-50 p-3 text-left transition-colors',
'hover:border-purple-500 hover:bg-purple-100',
'dark:border-purple-700 dark:bg-purple-900/20 dark:hover:border-purple-500 dark:hover:bg-purple-900/40',
'w-full rounded-md border border-purple-700 bg-purple-900/20 p-3 text-left text-white transition-colors',
'hover:border-purple-500 hover:bg-purple-900/40',
'flex items-center gap-3'
)}
>
<span className="flex-shrink-0 rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-100">
<span className="flex-shrink-0 rounded-full bg-purple-900 px-2 py-0.5 text-xs font-medium text-purple-100">
Custom
</span>
<span>{cs.step_data.title}</span>
@@ -472,7 +471,7 @@ export function TreeNavigationPage() {
{/* Add Custom Step Button */}
<button
onClick={() => customStepFlow.setShowCustomStepModal(true)}
className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-primary hover:bg-primary/10"
className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-white/50 hover:bg-white/10 hover:text-white"
>
<Plus className="h-3.5 w-3.5" />
Add Custom Step
@@ -482,18 +481,18 @@ export function TreeNavigationPage() {
{/* Custom Step Node */}
{currentCustomStep && (
<div className="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-800 dark:bg-purple-900/20">
<div className="rounded-lg border border-purple-800 bg-purple-900/20 p-4">
{/* Custom Step Badge */}
<span className="mb-2 inline-block rounded-full bg-purple-100 px-2 py-1 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-100">
<span className="mb-2 inline-block rounded-full bg-purple-900 px-2 py-1 text-xs font-medium text-purple-100">
Custom Step
</span>
<h2 className="mb-2 text-xl font-semibold text-card-foreground">
<h2 className="mb-2 text-xl font-semibold text-white">
{currentCustomStep.step_data.title}
</h2>
{currentCustomStep.step_data.content.instructions && (
<div className="mb-4 text-muted-foreground">
<div className="mb-4 text-white/60">
<MarkdownContent content={currentCustomStep.step_data.content.instructions} />
</div>
)}
@@ -506,12 +505,12 @@ export function TreeNavigationPage() {
{currentCustomStep.step_data.content.commands && currentCustomStep.step_data.content.commands.length > 0 && (
<div className="mb-4">
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
<p className="mb-2 text-sm font-medium text-white">Commands:</p>
<div className="space-y-2">
{currentCustomStep.step_data.content.commands.map((cmd, index) => (
<div key={index}>
<p className="mb-1 text-xs text-muted-foreground">{cmd.label}</p>
<code className="block rounded bg-muted p-2 text-sm font-mono">
<p className="mb-1 text-xs text-white/40">{cmd.label}</p>
<code className="block rounded bg-white/10 p-2 text-sm font-mono">
{cmd.command}
</code>
</div>
@@ -525,13 +524,13 @@ export function TreeNavigationPage() {
const targetNode = findNode(customStepFlow.pendingContinuationNodeId, tree?.tree_structure)
const targetLabel = targetNode?.question || targetNode?.title || 'next step'
return (
<div className="mt-6 border-t border-purple-200 pt-4 dark:border-purple-700">
<div className="mt-6 border-t border-purple-700 pt-4">
<button
type="button"
onClick={customStepFlow.handleContinueToDescendant}
className={cn(
'flex w-full items-center justify-between rounded-md bg-primary px-4 py-3 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'flex w-full items-center justify-between rounded-md bg-white px-4 py-3 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
<span>Continue to: {targetLabel.length > 50 ? `${targetLabel.slice(0, 50)}...` : targetLabel}</span>
@@ -543,16 +542,16 @@ export function TreeNavigationPage() {
{/* Custom Branch Controls */}
{customStepFlow.customBranchMode && (
<div className="mt-6 border-t border-purple-200 pt-4 dark:border-purple-700">
<p className="mb-3 text-sm text-amber-600 dark:text-amber-400">
<div className="mt-6 border-t border-purple-700 pt-4">
<p className="mb-3 text-sm text-amber-400">
Building custom branch - add steps until the issue is resolved
</p>
<div className="flex flex-wrap gap-3">
<button
onClick={() => customStepFlow.setShowCustomStepModal(true)}
className={cn(
'flex items-center gap-2 rounded-md border border-input px-4 py-2 text-sm font-medium',
'bg-background hover:bg-accent hover:text-accent-foreground'
'flex items-center gap-2 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-white/10 hover:text-white'
)}
>
<Plus className="h-4 w-4" />
@@ -578,22 +577,22 @@ export function TreeNavigationPage() {
{/* Action Node */}
{currentNode && currentNode.type === 'action' && (
<>
<h2 className="mb-2 text-xl font-semibold text-card-foreground">
<h2 className="mb-2 text-xl font-semibold text-white">
{currentNode.title}
</h2>
{currentNode.description && (
<div className="mb-4 text-muted-foreground">
<div className="mb-4 text-white/60">
<MarkdownContent content={currentNode.description} />
</div>
)}
{currentNode.commands && currentNode.commands.length > 0 && (
<div className="mb-4">
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
<p className="mb-2 text-sm font-medium text-white">Commands:</p>
<div className="space-y-1">
{currentNode.commands.map((cmd, index) => (
<code
key={index}
className="block rounded bg-muted p-2 text-sm font-mono"
className="block rounded bg-white/10 p-2 text-sm font-mono"
>
{cmd}
</code>
@@ -602,7 +601,7 @@ export function TreeNavigationPage() {
</div>
)}
{currentNode.expected_outcome && (
<p className="mb-4 text-sm text-muted-foreground">
<p className="mb-4 text-sm text-white/40">
<strong>Expected outcome:</strong> {currentNode.expected_outcome}
</p>
)}
@@ -610,8 +609,8 @@ export function TreeNavigationPage() {
<button
onClick={() => handleContinue()}
className={cn(
'rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground',
'hover:bg-primary/90'
'rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-white/90'
)}
>
Continue
@@ -624,22 +623,22 @@ export function TreeNavigationPage() {
{currentNode && currentNode.type === 'solution' && (
<>
<div className="mb-4 flex items-center gap-2">
<span className="rounded-full bg-green-100 px-2 py-1 text-xs font-medium text-green-800">
<span className="rounded-full bg-green-900/30 px-2 py-1 text-xs font-medium text-green-400">
Solution
</span>
</div>
<h2 className="mb-2 text-xl font-semibold text-card-foreground">
<h2 className="mb-2 text-xl font-semibold text-white">
{currentNode.title}
</h2>
{currentNode.description && (
<div className="mb-4 text-muted-foreground">
<div className="mb-4 text-white/60">
<MarkdownContent content={currentNode.description} />
</div>
)}
{currentNode.resolution_steps && currentNode.resolution_steps.length > 0 && (
<div className="mb-4">
<p className="mb-2 text-sm font-medium text-foreground">Resolution steps:</p>
<ol className="list-inside list-decimal space-y-1 text-sm text-muted-foreground">
<p className="mb-2 text-sm font-medium text-white">Resolution steps:</p>
<ol className="list-inside list-decimal space-y-1 text-sm text-white/40">
{currentNode.resolution_steps.map((step, index) => (
<li key={index}>{step}</li>
))}
@@ -660,8 +659,8 @@ export function TreeNavigationPage() {
)}
{/* Notes */}
<div className="mt-6 border-t border-border pt-4">
<label className="block text-sm font-medium text-foreground">
<div className="mt-6 border-t border-white/[0.06] pt-4">
<label className="block text-sm font-medium text-white">
Notes (optional)
</label>
<textarea
@@ -670,9 +669,9 @@ export function TreeNavigationPage() {
placeholder="Add any notes for this step..."
rows={2}
className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'mt-1 block w-full rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-white placeholder:text-white/40',
'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)}
/>
</div>
@@ -681,7 +680,7 @@ export function TreeNavigationPage() {
{pathTaken.length > 1 && (
<button
onClick={handleGoBack}
className="mt-4 text-sm text-muted-foreground hover:text-foreground"
className="mt-4 text-sm text-white/50 hover:text-white"
>
Go back
</button>
@@ -689,7 +688,7 @@ export function TreeNavigationPage() {
{/* Keyboard Shortcuts Hint */}
{currentNode && (
<div className="mt-4 border-t border-border pt-3 text-xs text-muted-foreground">
<div className="mt-4 border-t border-white/[0.06] pt-3 text-xs text-white/40">
<span className="font-medium">Keyboard:</span>{' '}
{currentNode.type === 'decision' && currentOptions.length > 0 && (
<span>1-{Math.min(currentOptions.length, 9)} select option</span>

View File

@@ -77,16 +77,16 @@ export function TeamCategoriesPage() {
setForm({ name: cat.name, slug: cat.slug, description: cat.description || '' })
}
const inputCn = cn('w-full rounded-md border border-border bg-background px-3 py-2 text-sm', 'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring')
const inputCn = cn('w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white', 'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20')
return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-6 flex items-center justify-between">
<div>
<h1 className="font-heading text-2xl font-bold text-foreground">Team Categories</h1>
<p className="mt-1 text-sm text-muted-foreground">Manage tree categories for your team</p>
<h1 className="text-2xl font-bold text-white">Team Categories</h1>
<p className="mt-1 text-sm text-white/40">Manage tree categories for your team</p>
</div>
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-primary-foreground hover:bg-primary/90')}>
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-white text-black hover:bg-white/90')}>
<Plus className="h-4 w-4" />
Create Category
</button>
@@ -95,30 +95,30 @@ export function TeamCategoriesPage() {
{loading ? (
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-16 animate-pulse rounded-lg bg-muted" />
<div key={i} className="h-16 animate-pulse rounded-lg bg-white/10" />
))}
</div>
) : categories.length === 0 ? (
<div className="flex flex-col items-center justify-center rounded-lg border border-border bg-card py-16">
<FolderTree className="h-12 w-12 text-muted-foreground/50" />
<h3 className="mt-4 font-medium text-foreground">No team categories</h3>
<p className="mt-1 text-sm text-muted-foreground">Create categories to organize your team's trees.</p>
<div className="flex flex-col items-center justify-center glass-card rounded-2xl py-16">
<FolderTree className="h-12 w-12 text-white/30" />
<h3 className="mt-4 font-medium text-white">No team categories</h3>
<p className="mt-1 text-sm text-white/40">Create categories to organize your team's trees.</p>
</div>
) : (
<div className="space-y-2">
{categories.map((cat) => (
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-border bg-card px-4 py-3">
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-white/[0.06] px-4 py-3">
<div>
<span className="font-medium text-foreground">{cat.name}</span>
<span className="ml-3 text-sm text-muted-foreground">{cat.slug}</span>
{cat.description && <span className="ml-3 text-sm text-muted-foreground">- {cat.description}</span>}
<span className="ml-3 text-xs text-muted-foreground">{cat.tree_count} trees</span>
<span className="font-medium text-white">{cat.name}</span>
<span className="ml-3 text-sm text-white/40">{cat.slug}</span>
{cat.description && <span className="ml-3 text-sm text-white/40">- {cat.description}</span>}
<span className="ml-3 text-xs text-white/40">{cat.tree_count} trees</span>
</div>
<div className="flex items-center gap-1">
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground">
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-white/50 hover:bg-white/[0.06] hover:text-white">
<Pencil className="h-4 w-4" />
</button>
<button onClick={() => handleDelete(cat.id)} className="rounded-md p-1.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive">
<button onClick={() => handleDelete(cat.id)} className="rounded-md p-1.5 text-white/50 hover:bg-red-400/10 hover:text-red-400">
<Trash2 className="h-4 w-4" />
</button>
</div>
@@ -131,22 +131,22 @@ export function TeamCategoriesPage() {
<Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Category" size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleCreate} disabled={!form.name || !form.slug} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</button>
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleCreate} disabled={!form.name || !form.slug} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Create</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
<label className="mb-1 block text-sm font-medium text-white">Name</label>
<input type="text" value={form.name} onChange={(e) => { const name = e.target.value; setForm(f => ({ ...f, name, slug: generateSlug(name) })) }} placeholder="e.g. Networking" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
<label className="mb-1 block text-sm font-medium text-white">Slug</label>
<input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
<label className="mb-1 block text-sm font-medium text-white">Description</label>
<input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" className={inputCn} />
</div>
</div>
@@ -156,22 +156,22 @@ export function TeamCategoriesPage() {
<Modal isOpen={!!editCategory} onClose={() => setEditCategory(null)} title="Edit Category" size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setEditCategory(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleUpdate} disabled={!form.name || !form.slug} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Save</button>
<button onClick={() => setEditCategory(null)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleUpdate} disabled={!form.name || !form.slug} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Save</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
<label className="mb-1 block text-sm font-medium text-white">Name</label>
<input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
<label className="mb-1 block text-sm font-medium text-white">Slug</label>
<input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
<label className="mb-1 block text-sm font-medium text-white">Description</label>
<input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" className={inputCn} />
</div>
</div>

View File

@@ -64,7 +64,7 @@ export function AuditLogsPage() {
render: (log) => (
<button
onClick={() => setExpandedId(expandedId === log.id ? null : log.id)}
className="p-1 text-muted-foreground hover:text-foreground"
className="p-1 text-white/50 hover:text-white"
>
{expandedId === log.id ? (
<ChevronDown className="h-4 w-4" />
@@ -78,14 +78,14 @@ export function AuditLogsPage() {
key: 'action',
header: 'Action',
render: (log) => (
<span className="text-sm font-medium text-foreground">{log.action}</span>
<span className="text-sm font-medium text-white">{log.action}</span>
),
},
{
key: 'resource',
header: 'Resource',
render: (log) => (
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
{log.resource_type}{log.resource_id ? ` (${log.resource_id.slice(0, 8)}...)` : ''}
</span>
),
@@ -94,14 +94,14 @@ export function AuditLogsPage() {
key: 'user',
header: 'User',
render: (log) => (
<span className="text-sm text-muted-foreground">{log.user_email || 'System'}</span>
<span className="text-sm text-white/40">{log.user_email || 'System'}</span>
),
},
{
key: 'created_at',
header: 'Time',
render: (log) => (
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
{new Date(log.created_at).toLocaleString()}
</span>
),
@@ -117,8 +117,8 @@ export function AuditLogsPage() {
<button
onClick={handleExport}
className={cn(
'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium',
'text-card-foreground hover:bg-accent'
'flex items-center gap-2 rounded-md border border-white/10 px-4 py-2 text-sm font-medium',
'text-white/60 hover:bg-white/10 hover:text-white'
)}
>
<Download className="h-4 w-4" />
@@ -134,8 +134,8 @@ export function AuditLogsPage() {
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
placeholder="Filter by action..."
className={cn(
'h-9 rounded-md border border-border bg-background px-3 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'h-9 rounded-md border border-white/10 bg-black/50 px-3 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
<input
@@ -144,8 +144,8 @@ export function AuditLogsPage() {
onChange={(e) => { setResourceFilter(e.target.value); setPage(1) }}
placeholder="Filter by resource type..."
className={cn(
'h-9 rounded-md border border-border bg-background px-3 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'h-9 rounded-md border border-white/10 bg-black/50 px-3 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
</div>
@@ -166,9 +166,9 @@ export function AuditLogsPage() {
{/* Expanded details row */}
{expandedId && logs.find(l => l.id === expandedId)?.details && (
<div className="rounded-md border border-border bg-muted/30 p-4">
<h4 className="mb-2 text-sm font-medium text-foreground">Details</h4>
<pre className="overflow-x-auto rounded bg-muted p-3 text-xs text-muted-foreground">
<div className="rounded-md border border-white/[0.06] bg-white/[0.02] p-4">
<h4 className="mb-2 text-sm font-medium text-white">Details</h4>
<pre className="overflow-x-auto rounded bg-black/50 p-3 text-xs text-white/40">
{JSON.stringify(logs.find(l => l.id === expandedId)?.details, null, 2)}
</pre>
</div>

View File

@@ -14,13 +14,13 @@ interface MetricCardProps {
function MetricCard({ label, value, icon }: MetricCardProps) {
return (
<div className="rounded-lg border border-border bg-card p-6">
<div className="glass-card rounded-2xl p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-muted-foreground">{label}</p>
<p className="mt-1 text-3xl font-bold text-foreground">{value}</p>
<p className="text-sm text-white/40">{label}</p>
<p className="mt-1 text-3xl font-bold text-white">{value}</p>
</div>
<div className="rounded-lg bg-muted/50 p-3 text-muted-foreground">{icon}</div>
<div className="rounded-lg bg-white/[0.06] p-3 text-white/50">{icon}</div>
</div>
</div>
)
@@ -56,7 +56,7 @@ export function DashboardPage() {
{loading ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-32 animate-pulse rounded-lg bg-muted" />
<div key={i} className="h-32 animate-pulse rounded-lg bg-white/10" />
))}
</div>
) : metrics && (
@@ -71,18 +71,18 @@ export function DashboardPage() {
{/* Recent Activity */}
{activity.length > 0 && (
<div>
<h2 className="font-heading text-lg font-semibold text-foreground">Recent Activity</h2>
<h2 className="text-lg font-semibold text-white">Recent Activity</h2>
<div className="mt-3 space-y-2">
{activity.slice(0, 10).map((entry) => (
<div key={entry.id} className="flex items-center justify-between rounded-md border border-border bg-card px-4 py-3 text-sm">
<div key={entry.id} className="flex items-center justify-between rounded-md border border-white/[0.06] px-4 py-3 text-sm">
<div>
<span className="font-medium text-foreground">{entry.action}</span>
<span className="ml-2 text-muted-foreground">{entry.resource_type}</span>
<span className="font-medium text-white">{entry.action}</span>
<span className="ml-2 text-white/40">{entry.resource_type}</span>
{entry.user_email && (
<span className="ml-2 text-muted-foreground">by {entry.user_email}</span>
<span className="ml-2 text-white/40">by {entry.user_email}</span>
)}
</div>
<span className="text-xs text-muted-foreground">
<span className="text-xs text-white/40">
{new Date(entry.created_at).toLocaleString()}
</span>
</div>
@@ -93,18 +93,18 @@ export function DashboardPage() {
{/* Quick Links */}
<div>
<h2 className="font-heading text-lg font-semibold text-foreground">Quick Links</h2>
<h2 className="text-lg font-semibold text-white">Quick Links</h2>
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
{quickLinks.map((link) => (
<Link
key={link.to}
to={link.to}
className={cn(
'flex items-center gap-3 rounded-lg border border-border bg-card p-4',
'text-sm font-medium text-foreground transition-colors hover:bg-accent'
'flex items-center gap-3 glass-card rounded-2xl p-4',
'text-sm font-medium text-white transition-colors hover:bg-white/[0.06]'
)}
>
<link.icon className="h-5 w-5 text-muted-foreground" />
<link.icon className="h-5 w-5 text-white/50" />
{link.label}
</Link>
))}

View File

@@ -93,11 +93,11 @@ export function FeatureFlagsPage() {
const flagColumns: Column<FeatureFlagResponse>[] = [
{ key: 'name', header: 'Name', render: (f) => (
<div>
<div className="font-medium text-foreground">{f.display_name}</div>
<div className="text-xs text-muted-foreground">{f.flag_key}</div>
<div className="font-medium text-white">{f.display_name}</div>
<div className="text-xs text-white/40">{f.flag_key}</div>
</div>
)},
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-muted-foreground">{f.description || '-'}</span> },
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-white/40">{f.description || '-'}</span> },
...PLANS.map(plan => ({
key: plan,
header: plan.charAt(0).toUpperCase() + plan.slice(1),
@@ -109,7 +109,7 @@ export function FeatureFlagsPage() {
onClick={() => handleTogglePlan(f.id, plan, enabled)}
className={cn(
'h-6 w-10 rounded-full transition-colors',
enabled ? 'bg-green-500' : 'bg-muted'
enabled ? 'bg-emerald-400' : 'bg-white/10'
)}
>
<div className={cn(
@@ -131,10 +131,10 @@ export function FeatureFlagsPage() {
]
const overrideColumns: Column<AccountFeatureOverrideResponse>[] = [
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-muted-foreground">{o.flag_display_name || o.flag_key || o.flag_id.slice(0, 8)}</span> },
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-white">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-white/40">{o.flag_display_name || o.flag_key || o.flag_id.slice(0, 8)}</span> },
{ key: 'enabled', header: 'Enabled', render: (o) => <StatusBadge variant={o.enabled ? 'success' : 'destructive'}>{o.enabled ? 'Yes' : 'No'}</StatusBadge> },
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-muted-foreground">{o.note || '-'}</span> },
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-white/40">{o.note || '-'}</span> },
{
key: 'actions', header: '', className: 'w-12',
render: (o) => (
@@ -145,7 +145,7 @@ export function FeatureFlagsPage() {
},
]
const inputCn = cn('w-full rounded-md border border-border bg-background px-3 py-2 text-sm', 'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring')
const inputCn = cn('w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white', 'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20')
return (
<div className="space-y-8">
@@ -153,7 +153,7 @@ export function FeatureFlagsPage() {
title="Feature Flags"
description="Manage feature availability per plan and account"
action={
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-primary-foreground hover:bg-primary/90')}>
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-white text-black hover:bg-white/90')}>
<Plus className="h-4 w-4" />
Create Flag
</button>
@@ -161,7 +161,7 @@ export function FeatureFlagsPage() {
/>
<div>
<h2 className="font-heading text-lg font-semibold text-foreground">Feature Matrix</h2>
<h2 className="text-lg font-semibold text-white">Feature Matrix</h2>
<div className="mt-3">
<DataTable columns={flagColumns} data={flags} keyExtractor={(f) => f.id} isLoading={loading}
emptyState={<EmptyState icon={<ToggleLeft className="h-12 w-12" />} title="No feature flags" description="Create feature flags to control availability per plan." />}
@@ -171,8 +171,8 @@ export function FeatureFlagsPage() {
<div>
<div className="flex items-center justify-between">
<h2 className="font-heading text-lg font-semibold text-foreground">Account Overrides</h2>
<button onClick={() => setOverrideOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-primary-foreground hover:bg-primary/90')}>
<h2 className="text-lg font-semibold text-white">Account Overrides</h2>
<button onClick={() => setOverrideOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-white text-black hover:bg-white/90')}>
<Plus className="h-4 w-4" />
Add Override
</button>
@@ -188,22 +188,22 @@ export function FeatureFlagsPage() {
<Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Feature Flag" size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleCreate} disabled={!createForm.flag_key || !createForm.display_name} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</button>
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleCreate} disabled={!createForm.flag_key || !createForm.display_name} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Create</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Flag Key</label>
<label className="mb-1 block text-sm font-medium text-white">Flag Key</label>
<input type="text" value={createForm.flag_key} onChange={(e) => setCreateForm({ ...createForm, flag_key: e.target.value })} placeholder="e.g. custom_branding" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Display Name</label>
<label className="mb-1 block text-sm font-medium text-white">Display Name</label>
<input type="text" value={createForm.display_name} onChange={(e) => setCreateForm({ ...createForm, display_name: e.target.value })} placeholder="e.g. Custom Branding" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
<label className="mb-1 block text-sm font-medium text-white">Description</label>
<input type="text" value={createForm.description ?? ''} onChange={(e) => setCreateForm({ ...createForm, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div>
</div>
@@ -213,29 +213,29 @@ export function FeatureFlagsPage() {
<Modal isOpen={overrideOpen} onClose={() => setOverrideOpen(false)} title="Add Account Override" size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setOverrideOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleCreateOverride} disabled={!overrideForm.account_display_code || !overrideForm.flag_id} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</button>
<button onClick={() => setOverrideOpen(false)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleCreateOverride} disabled={!overrideForm.account_display_code || !overrideForm.flag_id} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Create</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
<label className="mb-1 block text-sm font-medium text-white">Account Display Code</label>
<input type="text" value={overrideForm.account_display_code} onChange={(e) => setOverrideForm({ ...overrideForm, account_display_code: e.target.value })} placeholder="e.g. ABC-1234" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Feature Flag</label>
<label className="mb-1 block text-sm font-medium text-white">Feature Flag</label>
<select value={overrideForm.flag_id} onChange={(e) => setOverrideForm({ ...overrideForm, flag_id: e.target.value })} className={inputCn}>
<option value="">Select a flag...</option>
{flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
</select>
</div>
<div className="flex items-center gap-2">
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-border" />
<label htmlFor="override-enabled" className="text-sm font-medium text-foreground">Enabled</label>
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-white/10" />
<label htmlFor="override-enabled" className="text-sm font-medium text-white">Enabled</label>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
<label className="mb-1 block text-sm font-medium text-white">Note</label>
<input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason" className={inputCn} />
</div>
</div>

View File

@@ -72,10 +72,10 @@ export function GlobalCategoriesPage() {
}
const columns: Column<AdminCategory>[] = [
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-foreground">{c.name}</span> },
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-muted-foreground">{c.slug}</span> },
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-muted-foreground">{c.description || '-'}</span> },
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-muted-foreground">{c.tree_count}</span> },
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-white">{c.name}</span> },
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-white/40">{c.slug}</span> },
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-white/40">{c.description || '-'}</span> },
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-white/40">{c.tree_count}</span> },
{
key: 'actions', header: '', className: 'w-12',
render: (c) => (
@@ -87,7 +87,7 @@ export function GlobalCategoriesPage() {
},
]
const inputCn = cn('w-full rounded-md border border-border bg-background px-3 py-2 text-sm', 'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring')
const inputCn = cn('w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white', 'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20')
return (
<div className="space-y-6">
@@ -95,7 +95,7 @@ export function GlobalCategoriesPage() {
title="Global Categories"
description="Manage tree categories available to all accounts"
action={
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-primary-foreground hover:bg-primary/90')}>
<button onClick={() => setCreateOpen(true)} className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-white text-black hover:bg-white/90')}>
<Plus className="h-4 w-4" />
Create Category
</button>
@@ -118,22 +118,22 @@ export function GlobalCategoriesPage() {
size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleCreate} disabled={!form.name || !form.slug} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</button>
<button onClick={() => setCreateOpen(false)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleCreate} disabled={!form.name || !form.slug} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Create</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
<label className="mb-1 block text-sm font-medium text-white">Name</label>
<input type="text" value={form.name} onChange={(e) => { const name = e.target.value; setForm(f => ({ ...f, name, slug: generateSlug(name) })) }} placeholder="e.g. Networking" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
<label className="mb-1 block text-sm font-medium text-white">Slug</label>
<input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
<label className="mb-1 block text-sm font-medium text-white">Description</label>
<input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div>
</div>
@@ -147,22 +147,22 @@ export function GlobalCategoriesPage() {
size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setEditCategory(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleUpdate} disabled={!form.name || !form.slug} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Save</button>
<button onClick={() => setEditCategory(null)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleUpdate} disabled={!form.name || !form.slug} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Save</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
<label className="mb-1 block text-sm font-medium text-white">Name</label>
<input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="e.g. Networking" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
<label className="mb-1 block text-sm font-medium text-white">Slug</label>
<input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
<label className="mb-1 block text-sm font-medium text-white">Description</label>
<input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div>
</div>

View File

@@ -72,7 +72,7 @@ export function InviteCodesPage() {
key: 'code',
header: 'Code',
render: (c) => (
<code className="rounded bg-muted px-2 py-1 text-sm font-mono">{c.code}</code>
<code className="rounded bg-white/10 px-2 py-1 text-sm font-mono text-white/70">{c.code}</code>
),
},
{
@@ -89,7 +89,7 @@ export function InviteCodesPage() {
key: 'expires_at',
header: 'Expires',
render: (c) => (
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
{c.expires_at ? new Date(c.expires_at).toLocaleDateString() : 'Never'}
</span>
),
@@ -98,7 +98,7 @@ export function InviteCodesPage() {
key: 'created_at',
header: 'Created',
render: (c) => (
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
{new Date(c.created_at).toLocaleDateString()}
</span>
),
@@ -135,7 +135,7 @@ export function InviteCodesPage() {
onClick={() => setCreateOpen(true)}
className={cn(
'flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium',
'bg-primary text-primary-foreground hover:bg-primary/90'
'bg-white text-black hover:bg-white/90'
)}
>
<Plus className="h-4 w-4" />
@@ -167,13 +167,13 @@ export function InviteCodesPage() {
<div className="flex justify-end gap-3">
<button
onClick={() => setCreateOpen(false)}
className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent"
className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
<button
onClick={handleCreate}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
>
Create
</button>
@@ -182,15 +182,15 @@ export function InviteCodesPage() {
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Expires in (days)</label>
<label className="mb-1 block text-sm font-medium text-white">Expires in (days)</label>
<input
type="number"
value={expiresInDays}
onChange={(e) => setExpiresInDays(e.target.value)}
placeholder="Leave empty for no expiry"
className={cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
</div>

View File

@@ -75,16 +75,16 @@ export function PlanLimitsPage() {
}
const planColumns: Column<PlanLimitConfig>[] = [
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-foreground capitalize">{p.plan}</span> },
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-muted-foreground">{p.max_trees ?? 'Unlimited'}</span> },
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-muted-foreground">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-muted-foreground">{p.max_users ?? 'Unlimited'}</span> },
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-white capitalize">{p.plan}</span> },
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-white/40">{p.max_trees ?? 'Unlimited'}</span> },
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-white/40">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-white/40">{p.max_users ?? 'Unlimited'}</span> },
{
key: 'actions', header: '', className: 'w-12',
render: (p) => (
<button
onClick={() => setEditPlan({ ...p })}
className="rounded-md px-3 py-1 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
className="rounded-md px-3 py-1 text-sm text-white/50 hover:bg-white/[0.06] hover:text-white"
>
Edit
</button>
@@ -93,11 +93,11 @@ export function PlanLimitsPage() {
]
const overrideColumns: Column<AccountOverrideResponse>[] = [
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
{ key: 'max_trees', header: 'Max Trees', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_trees ?? '-'}</span> },
{ key: 'max_sessions', header: 'Sessions/Month', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_sessions_per_month ?? '-'}</span> },
{ key: 'max_users', header: 'Max Users', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_users ?? '-'}</span> },
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-muted-foreground">{o.note || '-'}</span> },
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-white">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
{ key: 'max_trees', header: 'Max Trees', render: (o) => <span className="text-sm text-white/40">{o.override_max_trees ?? '-'}</span> },
{ key: 'max_sessions', header: 'Sessions/Month', render: (o) => <span className="text-sm text-white/40">{o.override_max_sessions_per_month ?? '-'}</span> },
{ key: 'max_users', header: 'Max Users', render: (o) => <span className="text-sm text-white/40">{o.override_max_users ?? '-'}</span> },
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-white/40">{o.note || '-'}</span> },
{
key: 'actions', header: '', className: 'w-12',
render: (o) => (
@@ -109,8 +109,8 @@ export function PlanLimitsPage() {
]
const inputCn = cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)
return (
@@ -118,7 +118,7 @@ export function PlanLimitsPage() {
<PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" />
<div>
<h2 className="font-heading text-lg font-semibold text-foreground">Plan Defaults</h2>
<h2 className="text-lg font-semibold text-white">Plan Defaults</h2>
<div className="mt-3">
<DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} />
</div>
@@ -126,10 +126,10 @@ export function PlanLimitsPage() {
<div>
<div className="flex items-center justify-between">
<h2 className="font-heading text-lg font-semibold text-foreground">Account Overrides</h2>
<h2 className="text-lg font-semibold text-white">Account Overrides</h2>
<button
onClick={() => setCreateOverride(true)}
className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-primary text-primary-foreground hover:bg-primary/90')}
className={cn('flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium', 'bg-white text-black hover:bg-white/90')}
>
<Plus className="h-4 w-4" />
Add Override
@@ -154,23 +154,23 @@ export function PlanLimitsPage() {
size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setEditPlan(null)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleSavePlan} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90">Save</button>
<button onClick={() => setEditPlan(null)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleSavePlan} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90">Save</button>
</div>
}
>
{editPlan && (
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Trees (empty = unlimited)</label>
<label className="mb-1 block text-sm font-medium text-white">Max Trees (empty = unlimited)</label>
<input type="number" value={editPlan.max_trees ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_trees: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Sessions/Month (empty = unlimited)</label>
<label className="mb-1 block text-sm font-medium text-white">Max Sessions/Month (empty = unlimited)</label>
<input type="number" value={editPlan.max_sessions_per_month ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_sessions_per_month: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Users (empty = unlimited)</label>
<label className="mb-1 block text-sm font-medium text-white">Max Users (empty = unlimited)</label>
<input type="number" value={editPlan.max_users ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_users: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
</div>
@@ -185,30 +185,30 @@ export function PlanLimitsPage() {
size="sm"
footer={
<div className="flex justify-end gap-3">
<button onClick={() => setCreateOverride(false)} className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent">Cancel</button>
<button onClick={handleCreateOverride} disabled={!overrideForm.account_display_code} className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</button>
<button onClick={() => setCreateOverride(false)} className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white">Cancel</button>
<button onClick={handleCreateOverride} disabled={!overrideForm.account_display_code} className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50">Create</button>
</div>
}
>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
<label className="mb-1 block text-sm font-medium text-white">Account Display Code</label>
<input type="text" value={overrideForm.account_display_code} onChange={(e) => setOverrideForm({ ...overrideForm, account_display_code: e.target.value })} placeholder="e.g. ABC-1234" className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Trees Override</label>
<label className="mb-1 block text-sm font-medium text-white">Max Trees Override</label>
<input type="number" value={overrideForm.override_max_trees ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_trees: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Sessions/Month Override</label>
<label className="mb-1 block text-sm font-medium text-white">Max Sessions/Month Override</label>
<input type="number" value={overrideForm.override_max_sessions_per_month ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_sessions_per_month: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Max Users Override</label>
<label className="mb-1 block text-sm font-medium text-white">Max Users Override</label>
<input type="number" value={overrideForm.override_max_users ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_users: e.target.value ? parseInt(e.target.value) : null })} className={inputCn} />
</div>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
<label className="mb-1 block text-sm font-medium text-white">Note</label>
<input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason for override" className={inputCn} />
</div>
</div>

View File

@@ -36,7 +36,7 @@ export function SettingsPage() {
return (
<div className="space-y-6">
<PageHeader title="Platform Settings" description="Global platform configuration" />
<div className="h-40 animate-pulse rounded-lg bg-muted" />
<div className="h-40 animate-pulse rounded-lg bg-white/10" />
</div>
)
}
@@ -45,11 +45,11 @@ export function SettingsPage() {
<div className="space-y-6">
<PageHeader title="Platform Settings" description="Global platform configuration" />
<div className="max-w-xl space-y-6 rounded-lg border border-border bg-card p-6">
<div className="max-w-xl space-y-6 glass-card rounded-2xl p-6">
<div className="flex items-center justify-between">
<div>
<h3 className="font-medium text-foreground">Maintenance Mode</h3>
<p className="text-sm text-muted-foreground">
<h3 className="font-medium text-white">Maintenance Mode</h3>
<p className="text-sm text-white/40">
When enabled, users will see a maintenance message instead of the app.
</p>
</div>
@@ -57,7 +57,7 @@ export function SettingsPage() {
onClick={() => setSettings({ ...settings, maintenance_mode: !maintenanceMode })}
className={cn(
'h-6 w-10 rounded-full transition-colors',
maintenanceMode ? 'bg-destructive' : 'bg-muted'
maintenanceMode ? 'bg-red-400' : 'bg-white/10'
)}
>
<div className={cn(
@@ -69,27 +69,27 @@ export function SettingsPage() {
{maintenanceMode && (
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Maintenance Message</label>
<label className="mb-1 block text-sm font-medium text-white">Maintenance Message</label>
<textarea
value={maintenanceMessage}
onChange={(e) => setSettings({ ...settings, maintenance_message: e.target.value })}
rows={3}
placeholder="We're performing scheduled maintenance. Please check back later."
className={cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
</div>
)}
<div className="border-t border-border pt-4">
<div className="border-t border-white/[0.06] pt-4">
<button
onClick={handleSave}
disabled={saving}
className={cn(
'rounded-md px-4 py-2 text-sm font-medium',
'bg-primary text-primary-foreground hover:bg-primary/90',
'bg-white text-black hover:bg-white/90',
'disabled:opacity-50'
)}
>

View File

@@ -98,8 +98,8 @@ export function UsersPage() {
sortable: true,
render: (u) => (
<div>
<div className="font-medium text-foreground">{u.name}</div>
<div className="text-xs text-muted-foreground">{u.email}</div>
<div className="font-medium text-white">{u.name}</div>
<div className="text-xs text-white/40">{u.email}</div>
</div>
),
},
@@ -129,7 +129,7 @@ export function UsersPage() {
header: 'Joined',
sortable: true,
render: (u) => (
<span className="text-sm text-muted-foreground">
<span className="text-sm text-white/40">
{new Date(u.created_at).toLocaleDateString()}
</span>
),
@@ -197,13 +197,13 @@ export function UsersPage() {
<div className="flex justify-end gap-3">
<button
onClick={() => setRoleModalUser(null)}
className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent"
className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
<button
onClick={handleRoleChange}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90"
>
Save
</button>
@@ -211,15 +211,15 @@ export function UsersPage() {
}
>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Changing role for <span className="font-medium text-foreground">{roleModalUser?.name}</span>
<p className="text-sm text-white/70">
Changing role for <span className="font-medium text-white">{roleModalUser?.name}</span>
</p>
<select
value={newRole}
onChange={(e) => setNewRole(e.target.value)}
className={cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm',
'focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
>
<option value="engineer">Engineer</option>
@@ -238,14 +238,14 @@ export function UsersPage() {
<div className="flex justify-end gap-3">
<button
onClick={() => setMoveModalUser(null)}
className="rounded-md border border-border px-4 py-2 text-sm font-medium text-card-foreground hover:bg-accent"
className="rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60 hover:bg-white/10 hover:text-white"
>
Cancel
</button>
<button
onClick={handleMoveAccount}
disabled={!displayCode}
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
className="rounded-md bg-white px-4 py-2 text-sm font-medium text-black hover:bg-white/90 disabled:opacity-50"
>
Move
</button>
@@ -253,19 +253,19 @@ export function UsersPage() {
}
>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Moving <span className="font-medium text-foreground">{moveModalUser?.name}</span> to a new account.
<p className="text-sm text-white/70">
Moving <span className="font-medium text-white">{moveModalUser?.name}</span> to a new account.
</p>
<div>
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
<label className="mb-1 block text-sm font-medium text-white">Account Display Code</label>
<input
type="text"
value={displayCode}
onChange={(e) => setDisplayCode(e.target.value)}
placeholder="e.g. ABC-1234"
className={cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
)}
/>
</div>

View File

@@ -1,6 +1,5 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
@@ -8,25 +7,7 @@ export default {
theme: {
extend: {
colors: {
// ResolutionFlow Brand Colors
brand: {
gradient: {
from: '#818cf8',
to: '#a78bfa',
},
dark: {
DEFAULT: '#09090b',
card: '#18181b',
surface: '#12121c',
},
text: {
primary: '#ffffff',
secondary: '#a1a1aa',
muted: '#52525b',
},
border: '#27272a',
},
// shadcn/ui color system
// shadcn/ui color system (monochrome)
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
@@ -68,12 +49,6 @@ export default {
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
heading: ['Plus Jakarta Sans', 'system-ui', 'sans-serif'],
label: ['Outfit', 'system-ui', 'sans-serif'],
},
backgroundImage: {
'gradient-brand': 'linear-gradient(90deg, #818cf8 0%, #a78bfa 100%)',
'gradient-brand-hover': 'linear-gradient(90deg, #6366f1 0%, #9333ea 100%)',
},
},
},