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 { RouterProvider } from 'react-router-dom'
import { router } from '@/router' import { router } from '@/router'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { useThemeStore } from '@/store/themeStore'
function App() { function App() {
const { isAuthenticated, fetchUser, setLoading } = useAuthStore() const { isAuthenticated, fetchUser, setLoading } = useAuthStore()
const { theme, setTheme } = useThemeStore()
useEffect(() => { useEffect(() => {
// On app load, check if we have a token and fetch user data // 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} /> return <RouterProvider router={router} />
} }

View File

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

View File

@@ -36,7 +36,7 @@ export function AdminLayout() {
return ( return (
<div className="flex h-[calc(100vh-4rem)]"> <div className="flex h-[calc(100vh-4rem)]">
{/* Desktop sidebar */} {/* 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 /> <AdminSidebar />
</div> </div>
@@ -44,14 +44,14 @@ export function AdminLayout() {
{mobileOpen && ( {mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden"> <div className="fixed inset-0 z-40 md:hidden">
<div <div
className="absolute inset-0 bg-background/80 backdrop-blur-sm" className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={() => setMobileOpen(false)} 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"> <div className="flex h-12 items-center justify-end px-3">
<button <button
onClick={() => setMobileOpen(false)} 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" /> <X className="h-4 w-4" />
</button> </button>
@@ -67,7 +67,7 @@ export function AdminLayout() {
{/* Mobile menu button */} {/* Mobile menu button */}
<button <button
onClick={() => setMobileOpen(true)} 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" /> <Menu className="h-5 w-5" />
</button> </button>

View File

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

View File

@@ -38,7 +38,7 @@ export function CategoryRow({
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
className={cn( 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' isDragging && 'opacity-50'
)} )}
> >
@@ -47,7 +47,7 @@ export function CategoryRow({
type="button" type="button"
{...attributes} {...attributes}
{...listeners} {...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" aria-label="Drag to reorder"
> >
<GripVertical className="h-5 w-5" /> <GripVertical className="h-5 w-5" />
@@ -56,17 +56,17 @@ export function CategoryRow({
{/* Category Info */} {/* Category Info */}
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2"> <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 && ( {!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 Archived
</span> </span>
)} )}
</div> </div>
{category.description && ( {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' : ''} {stepCount} step{stepCount !== 1 ? 's' : ''}
</p> </p>
</div> </div>
@@ -77,8 +77,8 @@ export function CategoryRow({
type="button" type="button"
onClick={() => onEdit(category)} onClick={() => onEdit(category)}
className={cn( className={cn(
'rounded-md border border-input bg-background p-2 text-muted-foreground', 'rounded-md border border-white/10 bg-black/50 p-2 text-white/50',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Edit category" title="Edit category"
> >

View File

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

View File

@@ -50,16 +50,16 @@ export function DataTable<T>({
} }
return ( 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"> <table className="w-full text-sm">
<thead> <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) => ( {columns.map((col) => (
<th <th
key={col.key} key={col.key}
className={cn( className={cn(
'px-4 py-3 text-left font-medium text-muted-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-foreground', col.sortable && 'cursor-pointer select-none hover:text-white',
col.className col.className
)} )}
onClick={col.sortable ? () => handleSort(col.key) : undefined} onClick={col.sortable ? () => handleSort(col.key) : undefined}
@@ -87,10 +87,10 @@ export function DataTable<T>({
<tbody> <tbody>
{isLoading ? ( {isLoading ? (
Array.from({ length: skeletonRows }).map((_, i) => ( 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) => ( {columns.map((col) => (
<td key={col.key} className="px-4 py-3"> <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> </td>
))} ))}
</tr> </tr>
@@ -99,7 +99,7 @@ export function DataTable<T>({
<tr> <tr>
<td colSpan={columns.length} className="px-4 py-12 text-center"> <td colSpan={columns.length} className="px-4 py-12 text-center">
{emptyState || ( {emptyState || (
<span className="text-muted-foreground">No data found</span> <span className="text-white/40">No data found</span>
)} )}
</td> </td>
</tr> </tr>
@@ -107,7 +107,7 @@ export function DataTable<T>({
data.map((item) => ( data.map((item) => (
<tr <tr
key={keyExtractor(item)} 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) => ( {columns.map((col) => (
<td key={col.key} className={cn('px-4 py-3', col.className)}> <td key={col.key} className={cn('px-4 py-3', col.className)}>

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ export function PageHeader({ title, description, action, className }: PageHeader
return ( return (
<div className={cn('flex items-start justify-between gap-4', className)}> <div className={cn('flex items-start justify-between gap-4', className)}>
<div> <div>
<h1 className="font-heading text-2xl font-bold text-foreground">{title}</h1> <h1 className="text-2xl font-bold text-foreground">{title}</h1>
{description && ( {description && (
<p className="mt-1 text-sm text-muted-foreground">{description}</p> <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 ( return (
<div className="flex items-center justify-between gap-4 pt-4"> <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} Showing {start}-{end} of {total}
</span> </span>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
onClick={() => onPageChange(page - 1)} onClick={() => onPageChange(page - 1)}
disabled={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" /> <ChevronLeft className="h-4 w-4" />
</button> </button>
{getPageNumbers().map((p, i) => {getPageNumbers().map((p, i) =>
p === 'ellipsis' ? ( 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 <button
key={p} key={p}
@@ -58,8 +58,8 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
btnBase, btnBase,
'px-2', 'px-2',
p === page p === page
? 'bg-primary text-primary-foreground' ? 'bg-white text-black'
: 'hover:bg-accent text-muted-foreground' : 'text-white/50 hover:bg-white/[0.06] hover:text-white'
)} )}
> >
{p} {p}
@@ -69,7 +69,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
<button <button
onClick={() => onPageChange(page + 1)} onClick={() => onPageChange(page + 1)}
disabled={page >= totalPages} 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" /> <ChevronRight className="h-4 w-4" />
</button> </button>

View File

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

View File

@@ -9,10 +9,10 @@ interface StatusBadgeProps {
} }
const variantClasses: Record<BadgeVariant, string> = { const variantClasses: Record<BadgeVariant, string> = {
success: 'bg-green-500/10 text-green-600 dark:text-green-400', success: 'bg-emerald-400/10 text-emerald-400',
destructive: 'bg-red-500/10 text-red-600 dark:text-red-400', destructive: 'bg-red-400/10 text-red-400',
warning: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400', warning: 'bg-yellow-400/10 text-yellow-400',
default: 'bg-muted text-muted-foreground', default: 'bg-white/10 text-white/70',
} }
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) { 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. * sm (32x32) for header/navbar, lg (80x80) for login/register pages.
*/ */
export function BrandLogo({ size = 'sm', className }: BrandLogoProps) { export function BrandLogo({ size = 'sm', className }: BrandLogoProps) {
const sizeClasses = size === 'sm' ? 'h-8 w-8' : 'h-20 w-20' 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 strokeBase = size === 'sm' ? 1 : 2
const strokeThick = size === 'sm' ? 1.25 : 2.5 const strokeThick = size === 'sm' ? 1.25 : 2.5
const dashArray = size === 'sm' ? '1 1.5' : '2 3' 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 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 } 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 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 ( return (
<svg viewBox={vb} fill="none" className={cn(sizeClasses, className)}> <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 */} {/* 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={7 * s} r={nodeR.outer} fill="white" 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={15 * s} r={nodeR.inner} fill="white" 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={25 * s} r={nodeR.inner} fill="white" 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={33 * s} r={nodeR.outer} fill="white" opacity="0.35" />
{/* Connecting lines */} {/* 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.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="url(#brand-logo-grad)" strokeWidth={strokeBase} strokeLinecap="round" opacity="0.6" /> <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="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="white" 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} ${33 * s}L${14 * s} ${23 * s}`} stroke="white" strokeWidth={strokeBase} strokeLinecap="round" strokeDasharray={dashArray} opacity="0.45" />
{/* Central hub */} {/* 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.glow} fill="white" 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.solid} fill="white" />
{/* Output arrow */} {/* 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> </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. * sm for header/navbar, lg for login/register pages.
*/ */
export function BrandWordmark({ size = 'sm', className }: BrandWordmarkProps) { export function BrandWordmark({ size = 'sm', className }: BrandWordmarkProps) {
return ( return (
<span <span
className={cn( className={cn(
'font-heading font-bold', 'font-semibold tracking-tight text-white',
size === 'sm' ? 'text-xl' : 'text-3xl tracking-tight', size === 'sm' ? 'text-xl' : 'text-3xl',
className className
)} )}
> >
<span className="text-foreground">Resolution</span> ResolutionFlow
<span className="text-gradient-brand">Flow</span>
</span> </span>
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,17 +11,17 @@ export function UpgradePrompt({ feature, className }: UpgradePromptProps) {
return ( return (
<div className={cn( <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 className
)}> )}>
<h3 className="font-semibold text-foreground">Plan Limit Reached</h3> <h3 className="font-semibold text-white">Plan Limit Reached</h3>
<p className="mt-1 text-sm text-muted-foreground"> <p className="mt-1 text-sm text-white/70">
Your {plan} plan doesn't allow you to {feature}. Upgrade your plan to continue. Your {plan} plan doesn't allow you to {feature}. Upgrade your plan to continue.
</p> </p>
<button <button
className={cn( className={cn(
'mt-3 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'mt-3 rounded-xl bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
onClick={() => window.location.href = '/account'} 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 { Link, useLocation, useNavigate, Outlet } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { usePermissions } from '@/hooks/usePermissions' import { usePermissions } from '@/hooks/usePermissions'
import { ThemeToggle } from '@/components/common/ThemeToggle'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark' import { Menu, X, LogOut, User, Shield } from 'lucide-react'
import { Menu, X } from 'lucide-react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function AppLayout() { export function AppLayout() {
@@ -21,7 +19,7 @@ export function AppLayout() {
navigate('/login') navigate('/login')
} }
// Close mobile menu on route change - key-based reset // Close mobile menu on route change
const [prevPath, setPrevPath] = useState(location.pathname) const [prevPath, setPrevPath] = useState(location.pathname)
if (prevPath !== location.pathname) { if (prevPath !== location.pathname) {
setPrevPath(location.pathname) setPrevPath(location.pathname)
@@ -57,68 +55,92 @@ export function AppLayout() {
] ]
return ( 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 */}
<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="container mx-auto flex h-16 items-center justify-between px-4">
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
{/* Mobile hamburger */} {/* Mobile hamburger */}
<button <button
onClick={() => setMobileMenuOpen(true)} 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" aria-label="Open menu"
> >
<Menu className="h-5 w-5" /> <Menu className="h-5 w-5" />
</button> </button>
<Link to="/" className="flex items-center gap-2"> {/* Logo */}
<BrandLogo size="sm" /> <Link to="/" className="flex items-center gap-3 group">
<BrandWordmark size="sm" /> <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> </Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-1 sm:flex"> <nav className="hidden items-center gap-1 sm:flex">
{navItems.map((item) => ( {navItems.map((item) => {
<Link const isActive = item.path === '/'
key={item.path} ? location.pathname === '/'
to={item.path} : location.pathname.startsWith(item.path)
className={cn(
'relative rounded-md px-3 py-2 text-sm font-medium transition-colors', return (
(item.path === '/' ? location.pathname === '/' : location.pathname.startsWith(item.path)) <Link
? 'bg-accent text-accent-foreground' key={item.path}
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground' to={item.path}
)} className={cn(
> 'rounded-xl px-4 py-2 text-sm font-medium transition-all',
{item.label} isActive
</Link> ? 'bg-white/10 text-white border border-white/20'
))} : 'text-white/50 hover:text-white hover:bg-white/[0.06]'
)}
>
{item.label}
</Link>
)
})}
</nav> </nav>
</div> </div>
<div className="flex items-center gap-4"> {/* Right side controls */}
<span className="hidden text-sm text-muted-foreground sm:block"> <div className="flex items-center gap-3">
{user?.name || user?.email} {/* User info */}
</span> <div className="hidden items-center gap-3 sm:flex">
{effectiveRole && effectiveRole !== 'engineer' && ( <div className="flex items-center gap-2 rounded-xl bg-white/[0.06] px-3 py-1.5 border border-white/10">
<span <User className="h-4 w-4 text-white/40" />
className={cn( <span className="text-sm text-white/70">
'hidden rounded-full px-2 py-0.5 text-xs font-medium sm:inline-block', {user?.name || user?.email}
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400', </span>
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400', </div>
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400'
)} {/* Role badge */}
> {effectiveRole && effectiveRole !== 'engineer' && (
{effectiveRole === 'super_admin' ? 'Super Admin' : <div className="px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
effectiveRole === 'owner' ? 'Owner' : <span className="flex items-center gap-1.5 text-xs text-white font-semibold">
'Viewer'} <Shield className="h-3 w-3" />
</span> {effectiveRole === 'super_admin' ? 'Super Admin' :
)} effectiveRole === 'owner' ? 'Owner' :
<ThemeToggle /> 'Viewer'}
</span>
</div>
)}
</div>
{/* Logout button */}
<button <button
onClick={handleLogout} onClick={handleLogout}
className={cn( className={cn(
'hidden rounded-md px-3 py-1.5 text-sm font-medium sm:block', 'hidden items-center gap-2 rounded-xl px-4 py-2 text-sm font-medium sm:flex',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground' '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 Logout
</button> </button>
</div> </div>
@@ -130,21 +152,25 @@ export function AppLayout() {
<div className="fixed inset-0 z-50 sm:hidden"> <div className="fixed inset-0 z-50 sm:hidden">
{/* Backdrop */} {/* Backdrop */}
<div <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)} onClick={() => setMobileMenuOpen(false)}
aria-hidden="true" aria-hidden="true"
/> />
{/* Drawer */} {/* Drawer */}
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-card shadow-xl animate-slide-in-left"> <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-border px-4"> <div className="flex h-16 items-center justify-between border-b border-white/[0.06] px-4">
<Link to="/" className="flex items-center gap-2"> <Link to="/" className="flex items-center gap-3">
<BrandLogo size="sm" /> <div className="w-9 h-9 rounded-xl bg-white flex items-center justify-center">
<BrandWordmark size="sm" /> <BrandLogo size="sm" className="h-5 w-5 invert" />
</div>
<span className="text-xl font-semibold text-white tracking-tight">
ResolutionFlow
</span>
</Link> </Link>
<button <button
onClick={() => setMobileMenuOpen(false)} 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" aria-label="Close menu"
> >
<X className="h-5 w-5" /> <X className="h-5 w-5" />
@@ -153,61 +179,60 @@ export function AppLayout() {
<div className="flex flex-col p-4"> <div className="flex flex-col p-4">
{/* User info */} {/* User info */}
<div className="mb-4 border-b border-border pb-4"> <div className="mb-4 border-b border-white/[0.06] pb-4">
<p className="text-sm font-medium text-foreground"> <div className="flex items-center gap-2 mb-2">
{user?.name || user?.email} <User className="h-4 w-4 text-white/40" />
</p> <p className="text-sm font-medium text-white">
{user?.name || user?.email}
</p>
</div>
{effectiveRole && effectiveRole !== 'engineer' && ( {effectiveRole && effectiveRole !== 'engineer' && (
<span <div className="inline-flex px-3 py-1.5 rounded-xl bg-white/10 border border-white/20">
className={cn( <span className="flex items-center gap-1.5 text-xs text-white font-semibold">
'mt-1 inline-block rounded-full px-2 py-0.5 text-xs font-medium', <Shield className="h-3 w-3" />
effectiveRole === 'super_admin' && 'bg-red-500/10 text-red-600 dark:text-red-400', {effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' && 'bg-blue-500/10 text-blue-600 dark:text-blue-400', effectiveRole === 'owner' ? 'Owner' :
effectiveRole === 'viewer' && 'bg-gray-500/10 text-gray-600 dark:text-gray-400' 'Viewer'}
)} </span>
> </div>
{effectiveRole === 'super_admin' ? 'Super Admin' :
effectiveRole === 'owner' ? 'Owner' :
'Viewer'}
</span>
)} )}
</div> </div>
{/* Nav items */} {/* Nav items */}
<div className="space-y-1"> <div className="space-y-1">
{navItems.map((item) => ( {navItems.map((item) => {
<Link const isActive = item.path === '/'
key={item.path} ? location.pathname === '/'
to={item.path} : location.pathname.startsWith(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 */} return (
<div className="mt-4 border-t border-border pt-4"> <Link
<div className="flex items-center justify-between px-3 py-2"> key={item.path}
<span className="text-sm text-muted-foreground">Theme</span> to={item.path}
<ThemeToggle /> className={cn(
</div> '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> </div>
{/* Logout */} {/* Logout */}
<div className="mt-2"> <div className="mt-4 border-t border-white/[0.06] pt-4">
<button <button
onClick={handleLogout} onClick={handleLogout}
className={cn( className={cn(
'w-full rounded-md px-3 py-2.5 text-left text-sm font-medium', 'w-full flex items-center gap-2 rounded-xl px-4 py-3 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground' '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 Logout
</button> </button>
</div> </div>
@@ -217,7 +242,7 @@ export function AppLayout() {
)} )}
{/* Main Content */} {/* Main Content */}
<main className="animate-fade-in"> <main className="relative animate-in fade-in duration-500">
<Outlet /> <Outlet />
</main> </main>
</div> </div>

View File

@@ -15,7 +15,7 @@ export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps)
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex h-screen items-center justify-center"> <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> </div>
) )
} }

View File

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

View File

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

View File

@@ -113,8 +113,8 @@ function FolderItem({
onClick={() => onFolderSelect(folder.id)} onClick={() => onFolderSelect(folder.id)}
className={cn( className={cn(
'flex w-full items-center gap-1 rounded-md py-1.5 text-sm', 'flex w-full items-center gap-1 rounded-md py-1.5 text-sm',
'transition-colors hover:bg-accent', 'transition-colors hover:bg-white/[0.06]',
selectedFolderId === folder.id && 'bg-accent font-medium' selectedFolderId === folder.id && 'bg-white/10 text-white font-medium'
)} )}
style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }} style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }}
> >
@@ -125,7 +125,7 @@ function FolderItem({
e.stopPropagation() e.stopPropagation()
onToggleExpand(folder.id) 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 ? ( {isExpanded ? (
<ChevronDown className="h-3 w-3" /> <ChevronDown className="h-3 w-3" />
@@ -138,7 +138,7 @@ function FolderItem({
)} )}
<Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} /> <Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} />
<span className="flex-1 truncate text-left">{folder.name}</span> <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> </button>
{/* Folder menu button - replaces tree count on hover */} {/* Folder menu button - replaces tree count on hover */}
@@ -150,7 +150,7 @@ function FolderItem({
className={cn( className={cn(
'absolute right-1 top-1/2 -translate-y-1/2 rounded p-1', 'absolute right-1 top-1/2 -translate-y-1/2 rounded p-1',
'hidden group-hover:block', 'hidden group-hover:block',
'hover:bg-accent' 'hover:bg-white/[0.06]'
)} )}
> >
<MoreVertical className="h-3 w-3" /> <MoreVertical className="h-3 w-3" />
@@ -160,8 +160,8 @@ function FolderItem({
{menuOpenId === folder.id && ( {menuOpenId === folder.id && (
<div <div
className={cn( className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-input', 'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-white/10',
'bg-popover py-1 shadow-lg' 'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
)} )}
> >
<button <button
@@ -170,7 +170,7 @@ function FolderItem({
onEditFolder(folder) onEditFolder(folder)
onMenuToggle(null) 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" /> <Pencil className="h-3 w-3" />
Edit Edit
@@ -182,7 +182,7 @@ function FolderItem({
onAddSubfolder(folder.id) onAddSubfolder(folder.id)
onMenuToggle(null) 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" /> <FolderPlus className="h-3 w-3" />
Add Subfolder Add Subfolder
@@ -194,7 +194,7 @@ function FolderItem({
onDeleteFolder(folder.id, hasSubfolders) onDeleteFolder(folder.id, hasSubfolders)
onMenuToggle(null) 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" /> <Trash2 className="h-3 w-3" />
Delete Delete
@@ -356,13 +356,13 @@ export function FolderSidebar({
{/* Mobile backdrop */} {/* Mobile backdrop */}
{mobileOpen && ( {mobileOpen && (
<div <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} onClick={onMobileClose}
aria-hidden="true" aria-hidden="true"
/> />
)} )}
<div className={cn( <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', 'hidden md:block',
mobileOpen && 'fixed inset-y-0 left-0 z-50 block animate-slide-in-left md:relative md:animate-none' 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 */} {/* Mobile close button */}
{mobileOpen && ( {mobileOpen && (
<div className="mb-3 flex items-center justify-between md:hidden"> <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 <button
onClick={onMobileClose} 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" aria-label="Close folders"
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@@ -382,7 +382,7 @@ export function FolderSidebar({
)} )}
<button <button
onClick={() => setIsExpanded(!isExpanded)} 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 ? ( {isExpanded ? (
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
@@ -399,8 +399,8 @@ export function FolderSidebar({
onClick={() => onFolderSelect(null)} onClick={() => onFolderSelect(null)}
className={cn( className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm', 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
'transition-colors hover:bg-accent', 'transition-colors hover:bg-white/[0.06]',
selectedFolderId === null && 'bg-accent font-medium' selectedFolderId === null && 'bg-white/10 text-white font-medium'
)} )}
> >
<Folder className="h-4 w-4" /> <Folder className="h-4 w-4" />
@@ -409,7 +409,7 @@ export function FolderSidebar({
{/* Loading state */} {/* Loading state */}
{isLoading ? ( {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) */} {/* User folders (hierarchical) */}
@@ -439,7 +439,7 @@ export function FolderSidebar({
onClick={() => onCreateFolder(null)} onClick={() => onCreateFolder(null)}
className={cn( className={cn(
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm', '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" /> <Plus className="h-4 w-4" />
@@ -454,8 +454,8 @@ export function FolderSidebar({
{contextMenu && ( {contextMenu && (
<div <div
className={cn( className={cn(
'fixed z-50 w-44 rounded-md border border-input', 'fixed z-50 w-44 rounded-md border border-white/10',
'bg-popover py-1 shadow-lg' 'bg-black/90 backdrop-blur-sm py-1 shadow-lg'
)} )}
style={{ left: contextMenu.x, top: contextMenu.y }} style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
@@ -465,7 +465,7 @@ export function FolderSidebar({
onEditFolder(contextMenu.folder) onEditFolder(contextMenu.folder)
closeContextMenu() 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" /> <Pencil className="h-3 w-3" />
Edit Edit
@@ -476,7 +476,7 @@ export function FolderSidebar({
handleAddSubfolder(contextMenu.folder.id) handleAddSubfolder(contextMenu.folder.id)
closeContextMenu() 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" /> <FolderPlus className="h-3 w-3" />
Add Subfolder Add Subfolder
@@ -487,7 +487,7 @@ export function FolderSidebar({
handleDeleteFolder(contextMenu.folder.id, contextMenu.folder.children.length > 0) handleDeleteFolder(contextMenu.folder.id, contextMenu.folder.children.length > 0)
closeContextMenu() 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" /> <Trash2 className="h-3 w-3" />
Delete 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"> <div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */} {/* Backdrop */}
<div <div
className="absolute inset-0 bg-background/80 backdrop-blur-sm" className="absolute inset-0 bg-black/80 backdrop-blur-sm"
onClick={onClose} onClick={onClose}
/> />
{/* Modal */} {/* 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 */} {/* 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">
<h2 className="text-lg font-semibold text-card-foreground">Share Tree</h2> <h2 className="text-lg font-semibold text-white">Share Tree</h2>
<button <button
onClick={onClose} 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" /> <X className="h-5 w-5" />
</button> </button>
@@ -135,9 +135,9 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="px-6 py-4 space-y-6"> <div className="px-6 py-4 space-y-6">
{/* Tree Info */} {/* Tree Info */}
<div> <div>
<h3 className="font-medium text-card-foreground">{tree.name}</h3> <h3 className="font-medium text-white">{tree.name}</h3>
{tree.description && ( {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} {tree.description}
</p> </p>
)} )}
@@ -145,7 +145,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Visibility Settings */} {/* Visibility Settings */}
<div> <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 Visibility
</label> </label>
<div className="space-y-2"> <div className="space-y-2">
@@ -156,19 +156,19 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
className={cn( className={cn(
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors', 'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
visibility === level visibility === level
? 'border-primary bg-primary/5 text-card-foreground' ? 'border-white/20 bg-white/10 text-white'
: 'border-border bg-background text-muted-foreground hover:border-primary/50 hover:bg-accent' : 'border-white/[0.06] bg-transparent text-white/50 hover:border-white/20 hover:bg-white/[0.06]'
)} )}
> >
{getVisibilityIcon(level)} {getVisibilityIcon(level)}
<div className="flex-1"> <div className="flex-1">
<div className="text-sm font-medium capitalize">{level}</div> <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)} {getVisibilityDescription(level)}
</div> </div>
</div> </div>
{visibility === level && ( {visibility === level && (
<div className="h-2 w-2 rounded-full bg-primary" /> <div className="h-2 w-2 rounded-full bg-white" />
)} )}
</button> </button>
))} ))}
@@ -178,7 +178,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Share Link Generation */} {/* Share Link Generation */}
{visibility !== 'private' && ( {visibility !== 'private' && (
<div> <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 Share Link
</label> </label>
@@ -189,11 +189,11 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
id="allow-forking" id="allow-forking"
checked={allowForking} checked={allowForking}
onChange={(e) => setAllowForking(e.target.checked)} 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 <label
htmlFor="allow-forking" 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 Allow recipients to fork this tree
</label> </label>
@@ -205,8 +205,8 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
onClick={handleGenerateLink} onClick={handleGenerateLink}
disabled={isGenerating} disabled={isGenerating}
className={cn( className={cn(
'w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'w-full rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed' 'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
)} )}
> >
{isGenerating ? 'Generating...' : 'Generate Share Link'} {isGenerating ? 'Generating...' : 'Generate Share Link'}
@@ -216,20 +216,20 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
{/* Active Share Link */} {/* Active Share Link */}
{activeShare && ( {activeShare && (
<div className="space-y-2"> <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 <input
type="text" type="text"
value={activeShare.share_url} value={activeShare.share_url}
readOnly readOnly
className="flex-1 bg-transparent text-sm text-foreground outline-none" className="flex-1 bg-transparent text-sm text-white outline-none"
/> />
<button <button
onClick={handleCopyLink} onClick={handleCopyLink}
className={cn( 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 copied
? 'border-green-500 bg-green-500/10 text-green-600' ? 'border-green-500 bg-green-500/10 text-green-400'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground' : 'text-white/60 hover:bg-white/10 hover:text-white'
)} )}
> >
{copied ? ( {copied ? (
@@ -245,13 +245,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
)} )}
</button> </button>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-white/40">
{activeShare.allow_forking {activeShare.allow_forking
? 'Recipients can fork this tree' ? 'Recipients can fork this tree'
: 'Forking disabled for this share'} : 'Forking disabled for this share'}
</p> </p>
{shares.length > 1 && ( {shares.length > 1 && (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-white/40">
{shares.length} active share links {shares.length} active share links
</p> </p>
)} )}
@@ -262,12 +262,12 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
</div> </div>
{/* Footer */} {/* 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 <button
onClick={onClose} onClick={onClose}
className={cn( className={cn(
'rounded-md border border-input px-4 py-2 text-sm font-medium text-muted-foreground', 'rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
> >
Close Close

View File

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

View File

@@ -30,13 +30,13 @@ export function TreeGridView({
{trees.map((tree) => ( {trees.map((tree) => (
<div <div
key={tree.id} 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="mb-2 flex items-start justify-between gap-2">
<div className="flex items-center 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' && ( {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" /> <FileText className="h-3 w-3" />
Draft Draft
</span> </span>
@@ -45,21 +45,21 @@ export function TreeGridView({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{tree.is_public ? ( {tree.is_public ? (
<span title="Public tree"> <span title="Public tree">
<Globe className="h-4 w-4 text-muted-foreground" /> <Globe className="h-4 w-4 text-white/40" />
</span> </span>
) : ( ) : (
<span title="Private tree"> <span title="Private tree">
<Lock className="h-4 w-4 text-muted-foreground" /> <Lock className="h-4 w-4 text-white/40" />
</span> </span>
)} )}
{tree.category_info && ( {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} {tree.category_info.name}
</span> </span>
)} )}
</div> </div>
</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'} {tree.description || 'No description available'}
</p> </p>
@@ -71,7 +71,7 @@ export function TreeGridView({
)} )}
<div className="flex items-center justify-between"> <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 v{tree.version} · {tree.usage_count} uses
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -81,8 +81,8 @@ export function TreeGridView({
type="button" type="button"
onClick={() => onForkTree(tree.id)} onClick={() => onForkTree(tree.id)}
className={cn( className={cn(
'rounded-md border border-input p-2 text-muted-foreground', 'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Fork tree" title="Fork tree"
> >
@@ -93,8 +93,8 @@ export function TreeGridView({
<Link <Link
to={`/trees/${tree.id}/edit`} to={`/trees/${tree.id}/edit`}
className={cn( className={cn(
'rounded-md border border-input p-2 text-muted-foreground', 'rounded-md border border-white/10 p-2 text-white/60',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Edit tree" title="Edit tree"
> >
@@ -106,8 +106,8 @@ export function TreeGridView({
type="button" type="button"
onClick={() => onDeleteTree(tree)} onClick={() => onDeleteTree(tree)}
className={cn( className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground', 'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-destructive/10 hover:text-destructive' 'hover:bg-red-400/10 hover:text-red-400'
)} )}
title="Delete tree" title="Delete tree"
> >
@@ -118,8 +118,8 @@ export function TreeGridView({
type="button" type="button"
onClick={() => onStartSession(tree.id)} onClick={() => onStartSession(tree.id)}
className={cn( className={cn(
'rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground', 'rounded-md bg-white px-3 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Start Session Start Session

View File

@@ -29,29 +29,29 @@ export function TreeListView({
{trees.map((tree) => ( {trees.map((tree) => (
<div <div
key={tree.id} 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 */} {/* Left: Name and Description */}
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <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' && ( {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" /> <FileText className="h-3 w-3" />
Draft Draft
</span> </span>
)} )}
{tree.is_public ? ( {tree.is_public ? (
<span title="Public tree"> <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>
) : ( ) : (
<span title="Private tree"> <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> </span>
)} )}
</div> </div>
<p className="text-sm text-muted-foreground truncate"> <p className="text-sm text-white/70 truncate">
{tree.description || 'No description available'} {tree.description || 'No description available'}
</p> </p>
</div> </div>
@@ -59,7 +59,7 @@ export function TreeListView({
{/* Center: Category and Tags */} {/* Center: Category and Tags */}
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}> <div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
{tree.category_info && ( {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} {tree.category_info.name}
</span> </span>
)} )}
@@ -72,7 +72,7 @@ export function TreeListView({
{/* Right: Metadata and Actions */} {/* Right: Metadata and Actions */}
<div className="flex items-center gap-3 flex-shrink-0"> <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>v{tree.version}</span>
<span>{tree.usage_count} uses</span> <span>{tree.usage_count} uses</span>
</div> </div>
@@ -84,8 +84,8 @@ export function TreeListView({
type="button" type="button"
onClick={() => onForkTree(tree.id)} onClick={() => onForkTree(tree.id)}
className={cn( className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground', 'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Fork tree" title="Fork tree"
> >
@@ -96,8 +96,8 @@ export function TreeListView({
<Link <Link
to={`/trees/${tree.id}/edit`} to={`/trees/${tree.id}/edit`}
className={cn( className={cn(
'rounded-md border border-input p-1.5 text-muted-foreground', 'rounded-md border border-white/10 p-1.5 text-white/60',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Edit tree" title="Edit tree"
> >
@@ -108,8 +108,8 @@ export function TreeListView({
type="button" type="button"
onClick={() => onStartSession(tree.id)} onClick={() => onStartSession(tree.id)}
className={cn( className={cn(
'rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground', 'rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-primary/90 whitespace-nowrap' 'hover:bg-white/90 whitespace-nowrap'
)} )}
> >
Start Start

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -124,8 +124,8 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
onClick={() => setIsCollapsed(false)} onClick={() => setIsCollapsed(false)}
className={cn( className={cn(
'fixed right-2 top-1/2 z-40 -translate-y-1/2 rounded-md p-2.5', 'fixed right-2 top-1/2 z-40 -translate-y-1/2 rounded-md p-2.5',
'bg-card border border-border shadow-md', 'bg-[#0a0a0a] border border-white/[0.06] shadow-md',
'text-muted-foreground hover:bg-accent hover:text-foreground', 'text-white/40 hover:bg-white/10 hover:text-white',
'transition-opacity duration-200', 'transition-opacity duration-200',
isCollapsed ? 'opacity-100' : 'pointer-events-none opacity-0' isCollapsed ? 'opacity-100' : 'pointer-events-none opacity-0'
)} )}
@@ -140,7 +140,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{/* Mobile backdrop */} {/* Mobile backdrop */}
{!isCollapsed && ( {!isCollapsed && (
<div <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)} onClick={() => setIsCollapsed(true)}
aria-hidden="true" aria-hidden="true"
/> />
@@ -152,29 +152,29 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
'fixed z-40', 'fixed z-40',
'inset-0 sm:inset-auto sm:right-2 sm:top-1/2 sm:-translate-y-1/2', '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]', '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', 'transition-transform duration-200 ease-out',
isCollapsed ? 'translate-x-full' : 'translate-x-0' isCollapsed ? 'translate-x-full' : 'translate-x-0'
)} )}
> >
{/* Header */} {/* 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"> <div className="flex items-center gap-2">
<StickyNote className="h-4 w-4 text-muted-foreground" /> <StickyNote className="h-4 w-4 text-white/40" />
<span className="text-sm font-medium text-foreground">Scratchpad</span> <span className="text-sm font-medium text-white">Scratchpad</span>
<span className="text-xs text-muted-foreground/60">Ctrl+/</span> <span className="text-xs text-white/30">Ctrl+/</span>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
onClick={() => setShowPreview(!showPreview)} 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'} title={showPreview ? 'Edit' : 'Preview'}
> >
{showPreview ? <Pencil className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />} {showPreview ? <Pencil className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
</button> </button>
<button <button
onClick={() => setIsCollapsed(true)} 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" title="Close scratchpad"
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
@@ -189,7 +189,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{content.trim() ? ( {content.trim() ? (
<MarkdownContent content={content} className="text-sm" /> <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> </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."} placeholder={"Capture IPs, error codes, server names, user info...\n\nSupports markdown formatting."}
className={cn( className={cn(
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm', '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' 'focus:outline-none focus:ring-0'
)} )}
/> />
@@ -208,25 +208,25 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
</div> </div>
{/* Save Indicator */} {/* 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"> <div className="flex items-center gap-1.5 text-xs">
{saveStatus === 'unsaved' && ( {saveStatus === 'unsaved' && (
<span className="text-muted-foreground">Unsaved changes</span> <span className="text-white/40">Unsaved changes</span>
)} )}
{saveStatus === 'saving' && ( {saveStatus === 'saving' && (
<> <>
<Loader2 className="h-3 w-3 animate-spin text-muted-foreground" /> <Loader2 className="h-3 w-3 animate-spin text-white/40" />
<span className="text-muted-foreground">Saving...</span> <span className="text-white/40">Saving...</span>
</> </>
)} )}
{saveStatus === 'saved' && ( {saveStatus === 'saved' && (
<span className="text-green-600 dark:text-green-400">Saved</span> <span className="text-emerald-400">Saved</span>
)} )}
{saveStatus === 'error' && ( {saveStatus === 'error' && (
<span className="text-destructive">Save failed</span> <span className="text-red-400">Save failed</span>
)} )}
{saveStatus === 'idle' && ( {saveStatus === 'idle' && (
<span className="text-muted-foreground/50">Markdown supported</span> <span className="text-white/30">Markdown supported</span>
)} )}
</div> </div>
</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"> <div className="flex flex-col gap-3 sm:flex-row">
{/* Ticket Number Search */} {/* Ticket Number Search */}
<div className="relative flex-1"> <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 <input
type="text" type="text"
placeholder="Search by ticket number..." placeholder="Search by ticket number..."
value={filters.ticketNumber} value={filters.ticketNumber}
onChange={(e) => handleFilterChange('ticketNumber', e.target.value)} onChange={(e) => handleFilterChange('ticketNumber', e.target.value)}
className={cn( className={cn(
'w-full rounded-md border border-input bg-background py-2 pl-9 pr-3', 'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
/> />
</div> </div>
{/* Client Name Search */} {/* Client Name Search */}
<div className="relative flex-1"> <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 <input
type="text" type="text"
placeholder="Search by client name..." placeholder="Search by client name..."
value={filters.clientName} value={filters.clientName}
onChange={(e) => handleFilterChange('clientName', e.target.value)} onChange={(e) => handleFilterChange('clientName', e.target.value)}
className={cn( className={cn(
'w-full rounded-md border border-input bg-background py-2 pl-9 pr-3', 'w-full rounded-md border border-white/10 bg-black/50 py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
/> />
</div> </div>
@@ -128,8 +128,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
value={filters.treeName} value={filters.treeName}
onChange={(e) => handleFilterChange('treeName', e.target.value)} onChange={(e) => handleFilterChange('treeName', e.target.value)}
className={cn( className={cn(
'rounded-md border border-input bg-background px-3 py-2', 'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', 'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
'sm:min-w-[200px]' 'sm:min-w-[200px]'
)} )}
> >
@@ -148,19 +148,19 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button <button
onClick={() => setShowDatePicker(!showDatePicker)} onClick={() => setShowDatePicker(!showDatePicker)}
className={cn( className={cn(
'flex w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm', 'flex w-full items-center gap-2 rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm',
'text-foreground hover:bg-accent', 'text-white hover:bg-white/10',
filters.dateRange?.from && 'border-primary' filters.dateRange?.from && 'border-white/30'
)} )}
> >
<Calendar className="h-4 w-4 text-muted-foreground" /> <Calendar className="h-4 w-4 text-white/40" />
<span className={cn(!filters.dateRange?.from && 'text-muted-foreground')}> <span className={cn(!filters.dateRange?.from && 'text-white/40')}>
{formatDateRange(filters.dateRange)} {formatDateRange(filters.dateRange)}
</span> </span>
</button> </button>
{showDatePicker && ( {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 */} {/* Date Type Toggle */}
<div className="mb-3 flex gap-2"> <div className="mb-3 flex gap-2">
<button <button
@@ -168,8 +168,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn( className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors', 'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'started' filters.dateType === 'started'
? 'bg-primary text-primary-foreground' ? 'bg-white text-black'
: 'bg-accent text-accent-foreground hover:bg-accent/80' : 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
)} )}
> >
Started Started
@@ -179,8 +179,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn( className={cn(
'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors', 'flex-1 rounded-md px-3 py-1.5 text-sm font-medium transition-colors',
filters.dateType === 'completed' filters.dateType === 'completed'
? 'bg-primary text-primary-foreground' ? 'bg-white text-black'
: 'bg-accent text-accent-foreground hover:bg-accent/80' : 'border border-white/10 text-white/60 hover:bg-white/10 hover:text-white'
)} )}
> >
Completed Completed
@@ -194,8 +194,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
key={preset.value} key={preset.value}
onClick={() => applyDatePreset(preset.value)} onClick={() => applyDatePreset(preset.value)}
className={cn( className={cn(
'rounded-md bg-accent px-3 py-1.5 text-sm font-medium', 'rounded-md bg-white/10 px-3 py-1.5 text-sm font-medium text-white/70',
'hover:bg-accent/80' 'hover:bg-white/20 hover:text-white'
)} )}
> >
{preset.label} {preset.label}
@@ -227,8 +227,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
setShowDatePicker(false) setShowDatePicker(false)
}} }}
className={cn( className={cn(
'flex-1 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground', 'flex-1 rounded-md bg-white px-3 py-1.5 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Apply Apply
@@ -236,8 +236,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button <button
onClick={() => setShowDatePicker(false)} onClick={() => setShowDatePicker(false)}
className={cn( className={cn(
'rounded-md bg-accent px-3 py-1.5 text-sm font-medium', 'rounded-md border border-white/10 px-3 py-1.5 text-sm font-medium text-white/60',
'hover:bg-accent/80' 'hover:bg-white/10 hover:text-white'
)} )}
> >
Cancel Cancel
@@ -252,8 +252,8 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
<button <button
onClick={onClear} onClick={onClear}
className={cn( className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-2 text-sm font-medium', 'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground' 'text-white/60 hover:bg-white/10 hover:text-white'
)} )}
> >
<Filter className="h-4 w-4" /> <Filter className="h-4 w-4" />
@@ -265,46 +265,46 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
{/* Active Filter Chips */} {/* Active Filter Chips */}
{hasActiveFilters && ( {hasActiveFilters && (
<div className="flex flex-wrap items-center gap-2"> <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 && ( {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} Ticket: {filters.ticketNumber}
<button <button
onClick={() => handleFilterChange('ticketNumber', '')} 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" /> <X className="h-3 w-3" />
</button> </button>
</span> </span>
)} )}
{filters.clientName && ( {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} Client: {filters.clientName}
<button <button
onClick={() => handleFilterChange('clientName', '')} 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" /> <X className="h-3 w-3" />
</button> </button>
</span> </span>
)} )}
{filters.treeName && ( {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} Tree: {filters.treeName}
<button <button
onClick={() => handleFilterChange('treeName', '')} 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" /> <X className="h-3 w-3" />
</button> </button>
</span> </span>
)} )}
{filters.dateRange?.from && ( {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}) {formatDateRange(filters.dateRange)} ({filters.dateType})
<button <button
onClick={clearDateRange} 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" /> <X className="h-3 w-3" />
</button> </button>

View File

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

View File

@@ -64,14 +64,14 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
} }
return ( 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="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-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-lg"> <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 */} {/* Header */}
<div className="flex items-center justify-between border-b border-border p-4"> <div className="flex items-center justify-between border-b border-white/[0.06] p-4">
<h2 className="text-lg font-semibold">Add Custom Step</h2> <h2 className="text-lg font-semibold text-white">Add Custom Step</h2>
<button <button
onClick={onClose} 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" aria-label="Close"
> >
<X className="h-5 w-5" /> <X className="h-5 w-5" />
@@ -79,15 +79,15 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="flex border-b border-border"> <div className="flex border-b border-white/[0.06]">
{canCreateSteps && ( {canCreateSteps && (
<button <button
onClick={() => setActiveTab('create')} onClick={() => setActiveTab('create')}
className={cn( className={cn(
'flex-1 px-4 py-3 text-sm font-medium transition-colors', 'flex-1 px-4 py-3 text-sm font-medium transition-colors',
activeTab === 'create' activeTab === 'create'
? 'border-b-2 border-primary bg-primary/5 text-primary' ? 'border-b-2 border-white bg-white/5 text-white'
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground' : 'text-white/40 hover:bg-white/10 hover:text-white'
)} )}
> >
Type My Own Type My Own
@@ -108,7 +108,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{/* Error Display */} {/* Error Display */}
{error && ( {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} {error}
</div> </div>
)} )}
@@ -132,10 +132,10 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{/* Loading Overlay */} {/* Loading Overlay */}
{isSubmitting && ( {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="flex flex-col items-center gap-3">
<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" />
<p className="text-sm text-muted-foreground">Creating step...</p> <p className="text-sm text-white/40">Creating step...</p>
</div> </div>
</div> </div>
)} )}

View File

@@ -15,9 +15,9 @@ const stepTypeIcons = {
} }
const stepTypeColors = { const stepTypeColors = {
decision: 'bg-blue-500/20 text-blue-600 dark:text-blue-400 border-blue-500/30', decision: 'bg-blue-400/10 text-blue-400 border-blue-400/20',
action: 'bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 border-yellow-500/30', action: 'bg-yellow-400/10 text-yellow-400 border-yellow-400/20',
solution: 'bg-green-500/20 text-green-600 dark:text-green-400 border-green-500/30' solution: 'bg-emerald-400/10 text-emerald-400 border-emerald-400/20'
} }
export function StepCard({ step, onPreview, onInsert }: StepCardProps) { export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
@@ -27,7 +27,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
const remainingTags = step.tags.length - 3 const remainingTags = step.tags.length - 3
return ( 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 */} {/* Header */}
<div className="mb-3 flex items-start justify-between gap-2"> <div className="mb-3 flex items-start justify-between gap-2">
<div className="flex-1"> <div className="flex-1">
@@ -45,19 +45,19 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
{/* Featured Badge */} {/* Featured Badge */}
{step.is_featured && ( {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 Featured
</span> </span>
)} )}
</div> </div>
{/* Title */} {/* 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>
</div> </div>
{/* Metadata */} {/* 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 */} {/* Category */}
{step.category_name && ( {step.category_name && (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
@@ -103,7 +103,7 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
{visibleTags.map(tag => ( {visibleTags.map(tag => (
<span <span
key={tag} 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} {tag}
</span> </span>
@@ -121,8 +121,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button <button
onClick={() => onPreview(step)} onClick={() => onPreview(step)}
className={cn( 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', '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-accent hover:text-accent-foreground transition-colors' 'hover:bg-white/10 hover:text-white transition-colors'
)} )}
> >
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
@@ -131,8 +131,8 @@ export function StepCard({ step, onPreview, onInsert }: StepCardProps) {
<button <button
onClick={() => onInsert(step)} onClick={() => onInsert(step)}
className={cn( 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', '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-primary/90 transition-colors' 'hover:bg-white/90 transition-colors'
)} )}
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ export function CheckoutButton({ plan, className }: CheckoutButtonProps) {
disabled disabled
title="Billing coming soon" title="Billing coming soon"
className={cn( 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', 'disabled:opacity-50 disabled:cursor-not-allowed',
className className
)} )}

View File

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

View File

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

View File

@@ -52,8 +52,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
<div className="space-y-4"> <div className="space-y-4">
{/* Title */} {/* Title */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
Title <span className="text-destructive">*</span> Title <span className="text-red-400">*</span>
</label> </label>
<input <input
type="text" type="text"
@@ -62,37 +62,37 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
placeholder="e.g., Restart the Service" placeholder="e.g., Restart the Service"
className={cn( className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm', 'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground', 'bg-black/50 text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
titleError ? 'border-destructive' : 'border-input' titleError ? 'border-red-400' : 'border-white/10'
)} )}
/> />
{titleError && ( {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> </div>
{/* Description */} {/* Description */}
<div> <div>
<div className="flex items-center justify-between"> <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 Description
</label> </label>
{node.description && ( {node.description && (
<button <button
type="button" type="button"
onClick={() => setShowPreview(!showPreview)} onClick={() => setShowPreview(!showPreview)}
className="text-xs text-primary hover:underline" className="text-xs text-white/50 hover:text-white hover:underline"
> >
{showPreview ? 'Edit' : 'Preview'} {showPreview ? 'Edit' : 'Preview'}
</button> </button>
)} )}
</div> </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` Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p> </p>
{showPreview && node.description ? ( {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} /> <MarkdownContent content={node.description} />
</div> </div>
) : ( ) : (
@@ -108,7 +108,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
**Note:** Important information here" **Note:** Important information here"
rows={5} rows={5}
className={cn( 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', 'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)} )}
@@ -118,10 +118,10 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Commands */} {/* Commands */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
Commands Commands
</label> </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 PowerShell or CLI commands to execute
</p> </p>
<DynamicArrayField <DynamicArrayField
@@ -137,7 +137,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => handleUpdateCommand(index, e.target.value)} onChange={(e) => handleUpdateCommand(index, e.target.value)}
placeholder="e.g., Get-Service BrokerAgent" placeholder="e.g., Get-Service BrokerAgent"
className={cn( 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', 'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)} )}
@@ -148,7 +148,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
{/* Expected Outcome */} {/* Expected Outcome */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
Expected Outcome Expected Outcome
</label> </label>
<input <input
@@ -157,7 +157,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
onChange={(e) => onUpdate({ expected_outcome: e.target.value })} onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
placeholder="e.g., Service should show as Running" placeholder="e.g., Service should show as Running"
className={cn( 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', 'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' '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" /> <Play className="h-5 w-5 text-blue-500" />
</div> </div>
<div> <div>
<h3 className="font-semibold text-blue-600 dark:text-blue-400"> <h3 className="font-semibold text-blue-400">
Starting Question Starting Question
</h3> </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. This is the first question users will see when they start this troubleshooting tree.
Each option below creates a different troubleshooting path. Each option below creates a different troubleshooting path.
</p> </p>
@@ -78,11 +78,11 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Question */} {/* Question */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-destructive">*</span> {isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
</label> </label>
{isRootNode && ( {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? What's the main question to diagnose the issue?
</p> </p>
)} )}
@@ -95,19 +95,19 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
: "e.g., Can you ping the server?"} : "e.g., Can you ping the server?"}
className={cn( className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm', 'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground', 'bg-black/50 text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
questionError ? 'border-destructive' : 'border-input' questionError ? 'border-red-400' : 'border-white/10'
)} )}
/> />
{questionError && ( {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> </div>
{/* Help Text */} {/* Help Text */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
Help Text Help Text
</label> </label>
<textarea <textarea
@@ -116,7 +116,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
placeholder="Additional context or instructions for this decision..." placeholder="Additional context or instructions for this decision..."
rows={2} rows={2}
className={cn( 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', 'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
)} )}
@@ -125,20 +125,20 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Options */} {/* Options */}
<div> <div>
<label className="block text-sm font-medium text-foreground"> <label className="block text-sm font-medium text-white">
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-destructive">*</span> {isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
</label> </label>
{isRootNode ? ( {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. Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.
</p> </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. Each option can branch to a different next step.
</p> </p>
)} )}
{optionsError && ( {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"> <div className="mt-2">
<DynamicArrayField <DynamicArrayField
@@ -158,14 +158,14 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
const letter = indexToLetter(index) const letter = indexToLetter(index)
return ( 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"> <div className="mb-2 flex items-center gap-2">
{/* Letter badge */} {/* Letter badge */}
<span className={cn( <span className={cn(
'flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold', 'flex h-6 w-6 items-center justify-center rounded-full text-xs font-bold',
isRootNode isRootNode
? 'bg-blue-500/20 text-blue-600 dark:text-blue-400' ? 'bg-blue-500/20 text-blue-400'
: 'bg-muted text-muted-foreground' : 'bg-white/10 text-white/50'
)}> )}>
{letter} {letter}
</span> </span>
@@ -180,12 +180,12 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
'block flex-1 rounded-md border px-3 py-2 text-sm', 'block flex-1 rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground', 'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', '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> </div>
{optionLabelError && ( {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"> <div className="pl-8">
<NodePicker <NodePicker
@@ -207,7 +207,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
{/* Example hint for root node */} {/* Example hint for root node */}
{isRootNode && (node.options?.length || 0) < 2 && ( {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. <strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other". For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
</div> </div>

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
{/* Left Panel - Form Editor */} {/* Left Panel - Form Editor */}
<div <div
className={cn( 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' 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 */} {/* Right Panel - Preview */}
<div <div
className={cn( className={cn(
'flex-1 overflow-hidden bg-muted/30', 'flex-1 overflow-hidden bg-white/[0.02]',
isMobile ? 'hidden' : 'block' isMobile ? 'hidden' : 'block'
)} )}
> >

View File

@@ -56,13 +56,13 @@ export function TreeMetadataForm() {
) )
return ( return (
<div className="space-y-4 rounded-lg border border-border bg-card p-4"> <div className="space-y-4 glass-card rounded-2xl p-4">
<h2 className="text-sm font-semibold text-card-foreground">Tree Details</h2> <h2 className="text-sm font-semibold text-white">Tree Details</h2>
{/* Name */} {/* Name */}
<div> <div>
<label htmlFor="tree-name" className="block text-sm font-medium text-foreground"> <label htmlFor="tree-name" className="block text-sm font-medium text-white">
Name <span className="text-destructive">*</span> Name <span className="text-red-400">*</span>
</label> </label>
<input <input
id="tree-name" id="tree-name"
@@ -72,17 +72,17 @@ export function TreeMetadataForm() {
placeholder="e.g., VDA Registration Troubleshooting" placeholder="e.g., VDA Registration Troubleshooting"
className={cn( className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm', 'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground', 'bg-black/50 text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20',
nameError ? 'border-destructive' : 'border-input' 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> </div>
{/* Description */} {/* Description */}
<div> <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 Description
</label> </label>
<textarea <textarea
@@ -92,16 +92,16 @@ export function TreeMetadataForm() {
placeholder="Brief description of what this tree troubleshoots..." placeholder="Brief description of what this tree troubleshoots..."
rows={2} rows={2}
className={cn( 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', 'bg-black/50 text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
/> />
</div> </div>
{/* Category */} {/* Category */}
<div> <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 Category
</label> </label>
{!customCategory ? ( {!customCategory ? (
@@ -110,9 +110,9 @@ export function TreeMetadataForm() {
value={categoryId || ''} value={categoryId || ''}
onChange={(e) => handleCategoryChange(e.target.value)} onChange={(e) => handleCategoryChange(e.target.value)}
className={cn( 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', 'bg-black/50 text-white',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
> >
<option value="">No category</option> <option value="">No category</option>
@@ -132,9 +132,9 @@ export function TreeMetadataForm() {
onChange={(e) => setCategory(e.target.value)} onChange={(e) => setCategory(e.target.value)}
placeholder="Enter new category" placeholder="Enter new category"
className={cn( className={cn(
'block flex-1 rounded-md border border-input px-3 py-2 text-sm', 'block flex-1 rounded-md border border-white/10 px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground', 'bg-black/50 text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
autoFocus autoFocus
/> />
@@ -144,7 +144,7 @@ export function TreeMetadataForm() {
setCategory('') setCategory('')
setCategoryId(null) 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 Cancel
</button> </button>
@@ -154,7 +154,7 @@ export function TreeMetadataForm() {
{/* Tags */} {/* Tags */}
<div> <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"> <div className="mt-1">
<TagInput tags={tags} onChange={setTags} maxTags={10} placeholder="Add tags..." /> <TagInput tags={tags} onChange={setTags} maxTags={10} placeholder="Add tags..." />
</div> </div>
@@ -162,13 +162,13 @@ export function TreeMetadataForm() {
{/* Visibility */} {/* Visibility */}
<div> <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"> <div className="mt-2 flex gap-4">
<label <label
className={cn( className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2', 'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors', '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 <input
@@ -185,7 +185,7 @@ export function TreeMetadataForm() {
className={cn( className={cn(
'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2', 'flex cursor-pointer items-center gap-2 rounded-md border px-4 py-2',
'transition-colors', '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 <input
@@ -199,7 +199,7 @@ export function TreeMetadataForm() {
<span className="text-sm">Public</span> <span className="text-sm">Public</span>
</label> </label>
</div> </div>
<p className="mt-1 text-xs text-muted-foreground"> <p className="mt-1 text-xs text-white/40">
{isPublic {isPublic
? 'Anyone can view this tree' ? 'Anyone can view this tree'
: 'Only you and your team 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( className={cn(
'rounded-lg border', 'rounded-lg border',
errorItems.length > 0 errorItems.length > 0
? 'border-destructive/50 bg-destructive/5' ? 'border-red-400/30 bg-red-400/5'
: 'border-yellow-500/50 bg-yellow-50 dark:bg-yellow-900/10' : 'border-yellow-400/30 bg-yellow-400/5'
)} )}
> >
{/* Header */} {/* Header */}
<button <button
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
className={cn( className={cn(
'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5', 'flex w-full items-center justify-between p-3 text-left transition-colors hover:bg-white/5',
errorItems.length > 0 ? 'text-destructive' : 'text-yellow-700 dark:text-yellow-500' errorItems.length > 0 ? 'text-red-400' : 'text-yellow-400'
)} )}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -73,15 +73,15 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
className={cn( className={cn(
'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors', 'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors',
error.nodeId error.nodeId
? 'cursor-pointer hover:bg-destructive/10' ? 'cursor-pointer hover:bg-red-400/10'
: 'cursor-default' : '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"> <div className="flex-1">
<p className="text-destructive">{error.message}</p> <p className="text-red-400">{error.message}</p>
{error.nodeId && ( {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} Click to select node: {error.nodeId}
</p> </p>
)} )}
@@ -97,15 +97,15 @@ export function ValidationSummary({ errors, onSelectNode }: ValidationSummaryPro
className={cn( className={cn(
'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors', 'flex w-full items-start gap-2 rounded p-2 text-left text-sm transition-colors',
warning.nodeId warning.nodeId
? 'cursor-pointer hover:bg-yellow-100 dark:hover:bg-yellow-900/20' ? 'cursor-pointer hover:bg-yellow-400/10'
: 'cursor-default' : '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"> <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 && ( {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} Click to select node: {warning.nodeId}
</p> </p>
)} )}

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
), ),
// Style bold text // Style bold text
strong: ({ children }) => ( strong: ({ children }) => (
<strong className="font-semibold text-foreground">{children}</strong> <strong className="font-semibold text-white">{children}</strong>
), ),
// Style ordered lists // Style ordered lists
ol: ({ children }) => ( ol: ({ children }) => (
@@ -33,7 +33,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
), ),
// Style list items // Style list items
li: ({ children }) => ( li: ({ children }) => (
<li className="text-muted-foreground">{children}</li> <li className="text-white/60">{children}</li>
), ),
// Style inline code // Style inline code
code: ({ className, children, ...props }) => { code: ({ className, children, ...props }) => {
@@ -43,7 +43,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
return ( return (
<code <code
className={cn( 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 className
)} )}
{...props} {...props}
@@ -54,7 +54,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
} }
return ( return (
<code <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} {...props}
> >
{children} {children}
@@ -63,25 +63,25 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
}, },
// Style code blocks (pre) // Style code blocks (pre)
pre: ({ children }) => ( 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} {children}
</pre> </pre>
), ),
// Style headers // Style headers
h1: ({ children }) => ( 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: ({ 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: ({ 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 // Style horizontal rules
hr: () => <hr className="my-4 border-border" />, hr: () => <hr className="my-4 border-white/[0.06]" />,
// Style blockquotes // Style blockquotes
blockquote: ({ children }) => ( 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} {children}
</blockquote> </blockquote>
), ),

View File

@@ -4,50 +4,27 @@
@layer base { @layer base {
:root { :root {
/* Light mode (fallback) */ /* Monochrome Design System — Dark Only */
--background: 0 0% 100%; --background: 0 0% 0%;
--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%;
--foreground: 0 0% 100%; --foreground: 0 0% 100%;
--card: 240 10% 9.4%; --card: 0 0% 4%;
--card-foreground: 0 0% 100%; --card-foreground: 0 0% 100%;
--popover: 240 10% 9.4%; --popover: 0 0% 4%;
--popover-foreground: 0 0% 100%; --popover-foreground: 0 0% 100%;
--primary: 243 75% 59%; --primary: 0 0% 100%;
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 0%;
--secondary: 240 5.9% 15%; --secondary: 0 0% 10%;
--secondary-foreground: 0 0% 100%; --secondary-foreground: 0 0% 100%;
--muted: 240 5.9% 15%; --muted: 0 0% 10%;
--muted-foreground: 240 5% 64.9%; --muted-foreground: 0 0% 50%;
--accent: 240 5.9% 15%; --accent: 0 0% 8%;
--accent-foreground: 0 0% 100%; --accent-foreground: 0 0% 100%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--border: 240 5.9% 15%; --border: 0 0% 12%;
--input: 240 5.9% 15%; --input: 0 0% 12%;
--ring: 243 75% 59%; --ring: 0 0% 100%;
--radius: 0.75rem;
} }
} }
@@ -78,7 +55,7 @@
} }
h1, h2, h3, h4, h5, h6 { 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; font-weight: 700;
letter-spacing: -0.02em; letter-spacing: -0.02em;
} }
@@ -111,10 +88,6 @@
} }
@layer utilities { @layer utilities {
.text-gradient-brand {
@apply bg-gradient-brand bg-clip-text text-transparent;
}
.animate-fade-in { .animate-fade-in {
animation: fade-in 200ms ease-out; animation: fade-in 200ms ease-out;
} }
@@ -135,45 +108,67 @@
animation: scale-in 150ms ease-out; animation: scale-in 150ms ease-out;
} }
/* Button press feedback for primary action buttons */ /* Button press feedback */
.btn-press { .btn-press {
@apply active:scale-[0.98] transition-transform; @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 { @layer components {
/* Base toast styling matching Modal/Card components */
:where([data-sonner-toast]) { :where([data-sonner-toast]) {
@apply bg-card text-card-foreground; @apply bg-card text-card-foreground;
@apply border border-border shadow-lg; @apply border border-border shadow-lg;
@apply rounded-lg; @apply rounded-xl;
font-family: 'Inter', system-ui, sans-serif; font-family: 'Inter', system-ui, sans-serif;
backdrop-filter: blur(10px);
} }
/* Toast title using heading font */
:where([data-sonner-toast]) [data-title] { :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; font-weight: 600;
} }
/* Success toast - uses primary brand color */
:where([data-sonner-toast][data-type="success"]) { :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] { :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"]) { :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] { :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"]) { :where([data-sonner-toast][data-type="info"]) {
@apply border-border; @apply border-border;
} }
@@ -181,26 +176,23 @@
@apply text-muted-foreground; @apply text-muted-foreground;
} }
/* Warning toast - uses amber color */
:where([data-sonner-toast][data-type="warning"]) { :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] { :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] { :where([data-sonner-toast]) [data-close-button] {
@apply text-muted-foreground hover:bg-accent hover:text-accent-foreground; @apply text-muted-foreground hover:bg-accent hover:text-accent-foreground;
@apply rounded-md transition-colors; @apply rounded-md transition-colors;
} }
/* Loading spinner uses primary color */
:where([data-sonner-toast]) [data-icon][data-loading] { :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 { .rdp-custom {
@apply text-foreground; @apply text-foreground;
} }
@@ -242,7 +234,7 @@
} }
.rdp-custom .rdp-day_selected { .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 { .rdp-custom .rdp-day_today {

View File

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

View File

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

View File

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

View File

@@ -108,10 +108,8 @@ export function MyTreesPage() {
return ( return (
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8"> <div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
<div className="mb-6 sm:mb-8"> <div className="mb-6 sm:mb-8">
<h1 className="font-heading text-3xl font-bold sm:text-4xl"> <h1 className="text-2xl font-bold text-white sm:text-3xl">My Trees</h1>
<span className="text-gradient-brand">My Trees</span> <p className="mt-2 text-white/40">
</h1>
<p className="mt-2 text-muted-foreground">
Your forked and custom decision trees Your forked and custom decision trees
</p> </p>
</div> </div>
@@ -119,20 +117,20 @@ export function MyTreesPage() {
{/* Loading State */} {/* Loading State */}
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-12"> <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> </div>
) : trees.length === 0 ? ( ) : trees.length === 0 ? (
<div className="rounded-lg border border-dashed border-border bg-card/50 px-4 py-12 text-center"> <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-muted-foreground opacity-50" /> <FolderTree className="mx-auto mb-4 h-12 w-12 text-white/20" />
<h2 className="mb-2 text-lg font-semibold text-foreground">No personal trees yet</h2> <h2 className="mb-2 text-lg font-semibold text-white">No personal trees yet</h2>
<p className="mb-4 text-sm text-muted-foreground"> <p className="mb-4 text-sm text-white/40">
Fork a tree from the library to customize it for your workflow Fork a tree from the library to customize it for your workflow
</p> </p>
<Link <Link
to="/trees" to="/trees"
className={cn( className={cn(
'inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'inline-flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Browse Trees Browse Trees
@@ -143,32 +141,32 @@ export function MyTreesPage() {
{trees.map((tree) => ( {trees.map((tree) => (
<div <div
key={tree.id} 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 */} {/* Header */}
<div className="mb-3 flex items-start justify-between gap-2"> <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 && ( {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} {tree.category_info.name}
</span> </span>
)} )}
</div> </div>
{/* Description */} {/* 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'} {tree.description || 'No description available'}
</p> </p>
{/* Fork Badge */} {/* Fork Badge */}
{tree.parent_tree_id && ( {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"> <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-muted-foreground" /> <GitBranch className="h-4 w-4 text-white/40" />
<span className="text-muted-foreground"> <span className="text-white/40">
Forked from{' '} Forked from{' '}
<Link <Link
to={`/trees/${tree.parent_tree_id}/navigate`} to={`/trees/${tree.parent_tree_id}/navigate`}
className="font-medium text-primary hover:underline" className="font-medium text-white hover:underline"
> >
original original
</Link> </Link>
@@ -184,7 +182,7 @@ export function MyTreesPage() {
)} )}
{/* Stats */} {/* 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"> <div className="flex items-center gap-1">
<Clock className="h-3.5 w-3.5" /> <Clock className="h-3.5 w-3.5" />
<span>{formatDate(tree.lastUsed)}</span> <span>{formatDate(tree.lastUsed)}</span>
@@ -201,8 +199,8 @@ export function MyTreesPage() {
type="button" type="button"
onClick={() => handleStartSession(tree.id)} onClick={() => handleStartSession(tree.id)}
className={cn( 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', '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-primary/90' 'hover:bg-white/90'
)} )}
> >
<Play className="h-4 w-4" /> <Play className="h-4 w-4" />
@@ -212,8 +210,8 @@ export function MyTreesPage() {
<Link <Link
to={`/trees/${tree.id}/edit`} to={`/trees/${tree.id}/edit`}
className={cn( className={cn(
'rounded-md border border-input p-2 text-muted-foreground', 'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Edit tree" title="Edit tree"
> >
@@ -227,8 +225,8 @@ export function MyTreesPage() {
setShowShareModal(true) setShowShareModal(true)
}} }}
className={cn( className={cn(
'rounded-md border border-input p-2 text-muted-foreground', 'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-accent hover:text-accent-foreground' 'hover:bg-white/10 hover:text-white'
)} )}
title="Share tree" title="Share tree"
> >
@@ -241,8 +239,8 @@ export function MyTreesPage() {
setShowDeleteConfirm(true) setShowDeleteConfirm(true)
}} }}
className={cn( className={cn(
'rounded-md border border-input p-2 text-muted-foreground', 'rounded-md border border-white/10 p-2 text-white/40',
'hover:bg-destructive/10 hover:text-destructive' 'hover:bg-red-400/10 hover:text-red-400'
)} )}
title="Delete tree" 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 { useState, useEffect, useRef } from 'react'
import { useNavigate, Link } from 'react-router-dom' 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 { treesApi } from '@/api/trees'
import { sessionsApi } from '@/api/sessions' import { sessionsApi } from '@/api/sessions'
import type { TreeListItem } from '@/types' import type { TreeListItem } from '@/types'
import type { Session } from '@/types/session' import type { Session } from '@/types/session'
import { cn } from '@/lib/utils'
function timeAgo(dateStr: string): string { function timeAgo(dateStr: string): string {
const now = Date.now() const now = Date.now()
@@ -107,39 +107,61 @@ export function QuickStartPage() {
}, []) }, [])
return ( return (
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-12">
{/* Hero Section */} {/* Hero Section */}
<div className="mx-auto max-w-2xl text-center"> <div className="mb-16 text-center max-w-4xl mx-auto">
<h1 className="font-heading text-3xl font-bold text-foreground"> {/* Badge */}
What are you troubleshooting? <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> </h1>
<div ref={searchRef} className="relative mt-6">
<div className="relative"> {/* Description */}
<Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted-foreground" /> <p className="text-lg text-white/40 mb-10 max-w-2xl mx-auto leading-relaxed">
<input Search our library of proven decision trees or continue where you left off
type="text" </p>
autoFocus
value={query} {/* Search Bar */}
onChange={(e) => setQuery(e.target.value)} <div ref={searchRef} className="relative max-w-2xl mx-auto group">
onFocus={() => query.length >= 2 && setShowResults(true)} <div className="absolute inset-0 bg-white/5 rounded-2xl blur-2xl opacity-0 group-hover:opacity-100 transition-opacity" />
placeholder="Paste ticket subject or search for a tree..." <div className="relative glass-card rounded-2xl p-1">
className={cn( <div className="flex items-center bg-black/50 rounded-xl">
'w-full rounded-lg border border-border bg-card py-3 pl-12 pr-4 text-lg', <Search className="ml-5 w-5 h-5 text-blue-400" />
'text-foreground placeholder:text-muted-foreground', <input
'focus:outline-none focus:ring-2 focus:ring-primary/50' 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> </div>
{/* Search Results Dropdown */} {/* Search Results Dropdown */}
{showResults && ( {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 ? ( {isSearching ? (
<div className="flex items-center justify-center py-6"> <div className="flex items-center justify-center py-8">
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" /> <Loader2 className="h-5 w-5 animate-spin text-white/40" />
</div> </div>
) : searchResults.length === 0 ? ( ) : 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 No results found
</div> </div>
) : ( ) : (
@@ -148,13 +170,13 @@ export function QuickStartPage() {
<li key={tree.id}> <li key={tree.id}>
<button <button
onClick={() => navigate(`/trees/${tree.id}/navigate`)} 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} {tree.name}
</div> </div>
{tree.description && ( {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} {tree.description}
</div> </div>
)} )}
@@ -170,65 +192,115 @@ export function QuickStartPage() {
{/* Continue Session Section */} {/* Continue Session Section */}
{activeSessions.length > 0 && ( {activeSessions.length > 0 && (
<div className="mx-auto mt-12 max-w-4xl"> <div className="mx-auto max-w-4xl mb-12">
<h2 className="font-heading text-lg font-semibold text-foreground"> <div className="flex items-center justify-between mb-6">
Continue Session <h2 className="text-2xl font-bold text-white">Active Sessions</h2>
</h2> </div>
<div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{activeSessions.map((session) => ( {/* 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 <button
key={session.id}
onClick={() => onClick={() =>
navigate(`/trees/${session.tree_id}/navigate`, { navigate(`/trees/${activeSessions[0].tree_id}/navigate`, {
state: { sessionId: session.id }, 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"> Continue
<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>
</button> </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> </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> </div>
)} )}
{/* Recent Trees Section */} {/* Recent Trees Section */}
{!isLoading && recentTrees.length > 0 && ( {!isLoading && recentTrees.length > 0 && (
<div className="mx-auto mt-10 max-w-4xl"> <div className="mx-auto max-w-4xl mb-12">
<h2 className="font-heading text-lg font-semibold text-foreground"> <div className="flex items-center justify-between mb-6">
Recent Trees <h2 className="text-2xl font-bold text-white">Recent Trees</h2>
</h2> <Link
<div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-3"> 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) => ( {recentTrees.map((tree) => (
<button <button
key={tree.tree_id} key={tree.tree_id}
onClick={() => navigate(`/trees/${tree.tree_id}/navigate`)} 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} {tree.name}
</div> </div>
<div className="mt-1 flex items-center gap-1 text-xs text-muted-foreground"> <div className="flex items-center gap-1.5 text-xs text-white/30">
<Clock className="h-3 w-3" /> <Clock className="h-3.5 w-3.5" />
<span>{timeAgo(tree.lastUsed)}</span> <span>Last used {timeAgo(tree.lastUsed)}</span>
</div> </div>
</button> </button>
))} ))}
@@ -237,10 +309,10 @@ export function QuickStartPage() {
)} )}
{/* Footer */} {/* Footer */}
<div className="mx-auto mt-12 max-w-4xl text-center"> <div className="mx-auto max-w-4xl text-center">
<Link <Link
to="/trees" 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 Browse All Trees
<ArrowRight className="h-4 w-4" /> <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 { useAuthStore } from '@/store/authStore'
import { inviteApi } from '@/api' import { inviteApi } from '@/api'
import { BrandLogo } from '@/components/common/BrandLogo' import { BrandLogo } from '@/components/common/BrandLogo'
import { BrandWordmark } from '@/components/common/BrandWordmark'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
export function RegisterPage() { export function RegisterPage() {
@@ -76,33 +75,38 @@ export function RegisterPage() {
} }
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background px-4"> <div className="flex min-h-screen items-center justify-center bg-black px-4">
<div className="w-full max-w-md space-y-8"> {/* 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="text-center">
<div className="mb-4 flex justify-center sm:mb-6"> <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> </div>
<h1> <h1 className="text-3xl font-bold text-white tracking-tight">
<BrandWordmark size="lg" /> ResolutionFlow
</h1> </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 Decision Tree Platform
</p> </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 Create your account
</p> </p>
</div> </div>
<form onSubmit={handleSubmit} className="mt-8 space-y-6"> <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) && ( {(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} {localError || error}
</div> </div>
)} )}
<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 Invite code
</label> </label>
<input <input
@@ -116,29 +120,29 @@ export function RegisterPage() {
}} }}
onBlur={(e) => validateInviteCode(e.target.value)} onBlur={(e) => validateInviteCode(e.target.value)}
className={cn( className={cn(
'mt-1 block w-full rounded-md border bg-background px-3 py-2 font-mono tracking-wider', 'mt-1 block w-full rounded-xl border bg-black/50 px-3 py-2 font-mono tracking-wider',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/30',
'focus:outline-none focus:ring-1', 'focus:outline-none focus:ring-1',
inviteCodeStatus === 'valid' && 'border-green-500 focus:border-green-500 focus:ring-green-500', inviteCodeStatus === 'valid' && 'border-emerald-400/50 focus:border-emerald-400 focus:ring-emerald-400/30',
inviteCodeStatus === 'invalid' && 'border-destructive focus:border-destructive focus:ring-destructive', inviteCodeStatus === 'invalid' && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/30',
inviteCodeStatus === 'idle' && 'border-input focus:border-primary focus:ring-primary', inviteCodeStatus === 'idle' && 'border-white/10 focus:border-white/30 focus:ring-white/20',
inviteCodeStatus === 'checking' && 'border-input focus:border-primary focus:ring-primary' inviteCodeStatus === 'checking' && 'border-white/10 focus:border-white/30 focus:ring-white/20'
)} )}
placeholder="ABCD1234" placeholder="ABCD1234"
/> />
{inviteCodeStatus === 'checking' && ( {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' && ( {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' && ( {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>
<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 Full name
</label> </label>
<input <input
@@ -150,16 +154,16 @@ export function RegisterPage() {
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
className={cn( className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2', 'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/30',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
placeholder="John Smith" placeholder="John Smith"
/> />
</div> </div>
<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 Email address
</label> </label>
<input <input
@@ -171,16 +175,16 @@ export function RegisterPage() {
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className={cn( className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2', 'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/30',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
placeholder="you@example.com" placeholder="you@example.com"
/> />
</div> </div>
<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 Password
</label> </label>
<input <input
@@ -192,19 +196,19 @@ export function RegisterPage() {
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className={cn( className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2', 'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/30',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
placeholder="••••••••••" 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 Must be at least 10 characters
</p> </p>
</div> </div>
<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 Confirm password
</label> </label>
<input <input
@@ -216,9 +220,9 @@ export function RegisterPage() {
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
className={cn( className={cn(
'mt-1 block w-full rounded-md border border-input bg-background px-3 py-2', 'mt-1 block w-full rounded-xl border border-white/10 bg-black/50 px-3 py-2',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/30',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
placeholder="••••••••••" placeholder="••••••••••"
/> />
@@ -228,20 +232,20 @@ export function RegisterPage() {
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className={cn( className={cn(
'w-full rounded-md px-4 py-2.5 text-sm font-semibold text-white btn-press', 'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
'bg-gradient-brand hover:bg-gradient-brand-hover', 'bg-white text-black hover:bg-white/90',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2', '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', 'disabled:cursor-not-allowed disabled:opacity-50',
'shadow-lg shadow-primary/20' 'transition-all'
)} )}
> >
{isLoading ? 'Creating account...' : 'Create account'} {isLoading ? 'Creating account...' : 'Create account'}
</button> </button>
</div> </div>
<p className="text-center text-sm text-muted-foreground"> <p className="text-center text-sm text-white/40">
Already have an account?{' '} 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 Sign in
</Link> </Link>
</p> </p>

View File

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

View File

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

View File

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

View File

@@ -248,7 +248,7 @@ export function TreeEditorPage() {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex h-64 items-center justify-center"> <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> </div>
) )
} }
@@ -257,16 +257,16 @@ export function TreeEditorPage() {
if (isMobile) { if (isMobile) {
return ( return (
<div className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center px-6 text-center"> <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" /> <Monitor className="mb-4 h-12 w-12 text-white/50" />
<h2 className="mb-2 text-xl font-semibold text-foreground">Desktop Required</h2> <h2 className="mb-2 text-xl font-semibold text-white">Desktop Required</h2>
<p className="mb-6 max-w-sm text-sm text-muted-foreground"> <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. The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.
</p> </p>
<button <button
onClick={() => navigate('/trees')} onClick={() => navigate('/trees')}
className={cn( 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',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Back to Library Back to Library
@@ -280,18 +280,18 @@ export function TreeEditorPage() {
{/* Draft Restore Prompt */} {/* Draft Restore Prompt */}
{showDraftPrompt && ( {showDraftPrompt && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm"> <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 rounded-lg border border-border bg-card p-6 shadow-lg"> <div className="glass-card rounded-2xl w-full max-w-md p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold">Restore Draft?</h2> <h2 className="mb-2 text-lg font-semibold text-white">Restore Draft?</h2>
<p className="mb-4 text-sm text-muted-foreground"> <p className="mb-4 text-sm text-white/40">
You have an unsaved draft from a previous session. Would you like to restore it? You have an unsaved draft from a previous session. Would you like to restore it?
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={handleRestoreDraft} onClick={handleRestoreDraft}
className={cn( className={cn(
'flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Restore Draft Restore Draft
@@ -299,8 +299,8 @@ export function TreeEditorPage() {
<button <button
onClick={handleDiscardDraft} onClick={handleDiscardDraft}
className={cn( className={cn(
'flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium', 'flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-white/60',
'hover:bg-accent' 'hover:bg-white/10 hover:text-white'
)} )}
> >
Start Fresh Start Fresh
@@ -312,18 +312,18 @@ export function TreeEditorPage() {
{/* Unsaved Changes Dialog */} {/* Unsaved Changes Dialog */}
{blocker.state === 'blocked' && ( {blocker.state === 'blocked' && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm"> <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 rounded-lg border border-border bg-card p-6 shadow-lg"> <div className="glass-card rounded-2xl w-full max-w-md p-6 shadow-lg">
<h2 className="mb-2 text-lg font-semibold">Unsaved Changes</h2> <h2 className="mb-2 text-lg font-semibold text-white">Unsaved Changes</h2>
<p className="mb-4 text-sm text-muted-foreground"> <p className="mb-4 text-sm text-white/40">
You have unsaved changes. Are you sure you want to leave? You have unsaved changes. Are you sure you want to leave?
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={handleBlockerReset} onClick={handleBlockerReset}
className={cn( className={cn(
'flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'flex-1 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Stay Stay
@@ -331,8 +331,8 @@ export function TreeEditorPage() {
<button <button
onClick={handleBlockerProceed} onClick={handleBlockerProceed}
className={cn( className={cn(
'flex-1 rounded-md border border-input bg-background px-4 py-2 text-sm font-medium text-destructive', 'flex-1 rounded-md border border-white/10 px-4 py-2 text-sm font-medium text-red-400',
'hover:bg-accent' 'hover:bg-white/10'
)} )}
> >
Leave Without Saving Leave Without Saving
@@ -343,17 +343,17 @@ export function TreeEditorPage() {
)} )}
{/* Toolbar */} {/* 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"> <div className="flex items-center gap-4">
<button <button
onClick={() => navigate('/trees')} onClick={() => navigate('/trees')}
className="text-sm text-muted-foreground hover:text-foreground" className="text-sm text-white/50 hover:text-white"
> >
Back to Library Back to Library
</button> </button>
<h1 className="text-lg font-semibold"> <h1 className="text-lg font-semibold text-white">
{isEditMode ? 'Edit Tree' : 'Create New Tree'} {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> </h1>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{treeStatus === 'draft' && ( {treeStatus === 'draft' && (
@@ -372,7 +372,7 @@ export function TreeEditorPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Undo/Redo */} {/* 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 <button
type="button" type="button"
onClick={() => undo()} onClick={() => undo()}
@@ -381,13 +381,13 @@ export function TreeEditorPage() {
className={cn( className={cn(
'rounded-l-md p-2 transition-colors', 'rounded-l-md p-2 transition-colors',
pastStates.length > 0 pastStates.length > 0
? 'text-foreground hover:bg-accent' ? 'text-white hover:bg-white/[0.06]'
: 'text-muted-foreground/40 cursor-not-allowed' : 'text-white/20 cursor-not-allowed'
)} )}
> >
<Undo2 className="h-4 w-4" /> <Undo2 className="h-4 w-4" />
</button> </button>
<div className="h-6 w-px bg-border" /> <div className="h-6 w-px bg-white/[0.06]" />
<button <button
type="button" type="button"
onClick={() => redo()} onClick={() => redo()}
@@ -396,15 +396,15 @@ export function TreeEditorPage() {
className={cn( className={cn(
'rounded-r-md p-2 transition-colors', 'rounded-r-md p-2 transition-colors',
futureStates.length > 0 futureStates.length > 0
? 'text-foreground hover:bg-accent' ? 'text-white hover:bg-white/[0.06]'
: 'text-muted-foreground/40 cursor-not-allowed' : 'text-white/20 cursor-not-allowed'
)} )}
> >
<Redo2 className="h-4 w-4" /> <Redo2 className="h-4 w-4" />
</button> </button>
</div> </div>
<div className="mx-2 h-6 w-px bg-border" /> <div className="mx-2 h-6 w-px bg-white/[0.06]" />
{/* Validate */} {/* Validate */}
<button <button
@@ -412,8 +412,8 @@ export function TreeEditorPage() {
disabled={isSaving} disabled={isSaving}
title="Validate tree structure (checks for errors and warnings)" title="Validate tree structure (checks for errors and warnings)"
className={cn( className={cn(
'flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm font-medium', '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-accent disabled:opacity-50' 'hover:bg-white/10 hover:text-white disabled:opacity-50'
)} )}
> >
<CheckCircle2 className="h-4 w-4" /> <CheckCircle2 className="h-4 w-4" />
@@ -426,8 +426,8 @@ export function TreeEditorPage() {
disabled={isSaving || !isDirty} disabled={isSaving || !isDirty}
title="Save as draft (Ctrl+S when draft or has errors)" title="Save as draft (Ctrl+S when draft or has errors)"
className={cn( className={cn(
'flex items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-sm font-medium', '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-accent disabled:opacity-50 disabled:cursor-not-allowed' 'hover:bg-white/10 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed'
)} )}
> >
<Save className="h-4 w-4" /> <Save className="h-4 w-4" />
@@ -440,8 +440,8 @@ export function TreeEditorPage() {
disabled={isSaving || !isDirty || hasBlockingErrors} disabled={isSaving || !isDirty || hasBlockingErrors}
title={hasBlockingErrors ? 'Fix validation errors before publishing (Ctrl+S when no errors)' : 'Publish tree (Ctrl+S when no errors)'} title={hasBlockingErrors ? 'Fix validation errors before publishing (Ctrl+S when no errors)' : 'Publish tree (Ctrl+S when no errors)'}
className={cn( className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed' 'hover:bg-white/90 disabled:opacity-50 disabled:cursor-not-allowed'
)} )}
> >
<CheckCircle2 className="h-4 w-4" /> <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="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 className="mb-6 flex flex-col gap-4 sm:mb-8 sm:flex-row sm:items-start sm:justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-foreground sm:text-3xl">Decision Trees</h1> <h1 className="text-2xl font-bold text-white sm:text-3xl">Decision Trees</h1>
<p className="mt-2 text-muted-foreground"> <p className="mt-2 text-white/40">
Select a troubleshooting tree to start a new session Select a troubleshooting tree to start a new session
</p> </p>
</div> </div>
@@ -208,8 +208,8 @@ export function TreeLibraryPage() {
<Link <Link
to="/trees/new" to="/trees/new"
className={cn( className={cn(
'flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground', 'flex items-center gap-2 rounded-md bg-white px-4 py-2 text-sm font-medium text-black',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
@@ -225,9 +225,9 @@ export function TreeLibraryPage() {
<button <button
onClick={() => setMobileFolderOpen(true)} onClick={() => setMobileFolderOpen(true)}
className={cn( className={cn(
'flex items-center gap-2 rounded-md border border-input px-3 py-2 text-sm font-medium md:hidden', 'flex items-center gap-2 rounded-md border border-white/10 px-3 py-2 text-sm font-medium md:hidden',
'text-muted-foreground hover:bg-accent hover:text-accent-foreground', 'text-white/40 hover:bg-white/10 hover:text-white',
selectedFolderId && 'border-primary text-primary' selectedFolderId && 'border-white/30 text-white'
)} )}
> >
<FolderOpen className="h-4 w-4" /> <FolderOpen className="h-4 w-4" />
@@ -241,16 +241,16 @@ export function TreeLibraryPage() {
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()} onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
className={cn( className={cn(
'flex-1 rounded-md border border-input bg-background px-3 py-2', 'flex-1 rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-foreground placeholder:text-muted-foreground', 'text-white placeholder:text-white/40',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
/> />
<button <button
onClick={handleSearch} onClick={handleSearch}
className={cn( 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',
'hover:bg-primary/90' 'hover:bg-white/90'
)} )}
> >
Search Search
@@ -262,8 +262,8 @@ export function TreeLibraryPage() {
onChange={(e) => setSelectedCategoryId(e.target.value)} onChange={(e) => setSelectedCategoryId(e.target.value)}
aria-label="Filter by category" aria-label="Filter by category"
className={cn( className={cn(
'rounded-md border border-input bg-background px-3 py-2', 'rounded-md border border-white/10 bg-black/50 px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary' 'text-white focus:border-white/30 focus:outline-none focus:ring-1 focus:ring-white/20'
)} )}
> >
<option value="">All Categories</option> <option value="">All Categories</option>
@@ -284,9 +284,9 @@ export function TreeLibraryPage() {
type="checkbox" type="checkbox"
checked={showDrafts} checked={showDrafts}
onChange={(e) => setShowDrafts(e.target.checked)} 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> </label>
</div> </div>
<ViewToggle view={treeLibraryView} onChange={setTreeLibraryView} /> <ViewToggle view={treeLibraryView} onChange={setTreeLibraryView} />
@@ -296,24 +296,24 @@ export function TreeLibraryPage() {
{/* Active Filters */} {/* Active Filters */}
{hasActiveFilters && ( {hasActiveFilters && (
<div className="mb-6 flex flex-wrap items-center gap-2"> <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 && ( {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 Folder
<button <button
onClick={() => setSelectedFolderId(null)} 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" /> <X className="h-3 w-3" />
</button> </button>
</span> </span>
)} )}
{selectedCategoryId && ( {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} {categories.find((c) => c.id === selectedCategoryId)?.name}
<button <button
onClick={() => setSelectedCategoryId('')} 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" /> <X className="h-3 w-3" />
</button> </button>
@@ -322,12 +322,12 @@ export function TreeLibraryPage() {
{selectedTags.map((tag) => ( {selectedTags.map((tag) => (
<span <span
key={tag} 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} {tag}
<button <button
onClick={() => removeTagFilter(tag)} 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" /> <X className="h-3 w-3" />
</button> </button>
@@ -335,7 +335,7 @@ export function TreeLibraryPage() {
))} ))}
<button <button
onClick={clearAllFilters} onClick={clearAllFilters}
className="text-sm text-muted-foreground hover:text-foreground" className="text-sm text-white/40 hover:text-white"
> >
Clear all Clear all
</button> </button>
@@ -345,10 +345,10 @@ export function TreeLibraryPage() {
{/* Loading State */} {/* Loading State */}
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-12"> <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> </div>
) : trees.length === 0 ? ( ) : trees.length === 0 ? (
<div className="py-12 text-center text-muted-foreground"> <div className="py-12 text-center text-white/40">
No trees found.{' '} No trees found.{' '}
{(searchQuery || hasActiveFilters) && 'Try adjusting your filters.'} {(searchQuery || hasActiveFilters) && 'Try adjusting your filters.'}
</div> </div>

View File

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

View File

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

View File

@@ -14,13 +14,13 @@ interface MetricCardProps {
function MetricCard({ label, value, icon }: MetricCardProps) { function MetricCard({ label, value, icon }: MetricCardProps) {
return ( 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 className="flex items-center justify-between">
<div> <div>
<p className="text-sm text-muted-foreground">{label}</p> <p className="text-sm text-white/40">{label}</p>
<p className="mt-1 text-3xl font-bold text-foreground">{value}</p> <p className="mt-1 text-3xl font-bold text-white">{value}</p>
</div> </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>
</div> </div>
) )
@@ -56,7 +56,7 @@ export function DashboardPage() {
{loading ? ( {loading ? (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => ( {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> </div>
) : metrics && ( ) : metrics && (
@@ -71,18 +71,18 @@ export function DashboardPage() {
{/* Recent Activity */} {/* Recent Activity */}
{activity.length > 0 && ( {activity.length > 0 && (
<div> <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"> <div className="mt-3 space-y-2">
{activity.slice(0, 10).map((entry) => ( {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> <div>
<span className="font-medium text-foreground">{entry.action}</span> <span className="font-medium text-white">{entry.action}</span>
<span className="ml-2 text-muted-foreground">{entry.resource_type}</span> <span className="ml-2 text-white/40">{entry.resource_type}</span>
{entry.user_email && ( {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> </div>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-white/40">
{new Date(entry.created_at).toLocaleString()} {new Date(entry.created_at).toLocaleString()}
</span> </span>
</div> </div>
@@ -93,18 +93,18 @@ export function DashboardPage() {
{/* Quick Links */} {/* Quick Links */}
<div> <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"> <div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
{quickLinks.map((link) => ( {quickLinks.map((link) => (
<Link <Link
key={link.to} key={link.to}
to={link.to} to={link.to}
className={cn( 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',
'text-sm font-medium text-foreground transition-colors hover:bg-accent' '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.label}
</Link> </Link>
))} ))}

View File

@@ -93,11 +93,11 @@ export function FeatureFlagsPage() {
const flagColumns: Column<FeatureFlagResponse>[] = [ const flagColumns: Column<FeatureFlagResponse>[] = [
{ key: 'name', header: 'Name', render: (f) => ( { key: 'name', header: 'Name', render: (f) => (
<div> <div>
<div className="font-medium text-foreground">{f.display_name}</div> <div className="font-medium text-white">{f.display_name}</div>
<div className="text-xs text-muted-foreground">{f.flag_key}</div> <div className="text-xs text-white/40">{f.flag_key}</div>
</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 => ({ ...PLANS.map(plan => ({
key: plan, key: plan,
header: plan.charAt(0).toUpperCase() + plan.slice(1), header: plan.charAt(0).toUpperCase() + plan.slice(1),
@@ -109,7 +109,7 @@ export function FeatureFlagsPage() {
onClick={() => handleTogglePlan(f.id, plan, enabled)} onClick={() => handleTogglePlan(f.id, plan, enabled)}
className={cn( className={cn(
'h-6 w-10 rounded-full transition-colors', 'h-6 w-10 rounded-full transition-colors',
enabled ? 'bg-green-500' : 'bg-muted' enabled ? 'bg-emerald-400' : 'bg-white/10'
)} )}
> >
<div className={cn( <div className={cn(
@@ -131,10 +131,10 @@ export function FeatureFlagsPage() {
] ]
const overrideColumns: Column<AccountFeatureOverrideResponse>[] = [ 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: '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-muted-foreground">{o.flag_display_name || o.flag_key || o.flag_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: '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', key: 'actions', header: '', className: 'w-12',
render: (o) => ( 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 ( return (
<div className="space-y-8"> <div className="space-y-8">
@@ -153,7 +153,7 @@ export function FeatureFlagsPage() {
title="Feature Flags" title="Feature Flags"
description="Manage feature availability per plan and account" description="Manage feature availability per plan and account"
action={ 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" /> <Plus className="h-4 w-4" />
Create Flag Create Flag
</button> </button>
@@ -161,7 +161,7 @@ export function FeatureFlagsPage() {
/> />
<div> <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"> <div className="mt-3">
<DataTable columns={flagColumns} data={flags} keyExtractor={(f) => f.id} isLoading={loading} <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." />} 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>
<div className="flex items-center justify-between"> <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={() => 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')}> <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" /> <Plus className="h-4 w-4" />
Add Override Add Override
</button> </button>
@@ -188,22 +188,22 @@ export function FeatureFlagsPage() {
<Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Feature Flag" size="sm" <Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Feature Flag" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</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>
} }
> >
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <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>
<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} /> <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>
<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} /> <input type="text" value={createForm.description ?? ''} onChange={(e) => setCreateForm({ ...createForm, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div> </div>
</div> </div>
@@ -213,29 +213,29 @@ export function FeatureFlagsPage() {
<Modal isOpen={overrideOpen} onClose={() => setOverrideOpen(false)} title="Add Account Override" size="sm" <Modal isOpen={overrideOpen} onClose={() => setOverrideOpen(false)} title="Add Account Override" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</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>
} }
> >
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <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>
<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}> <select value={overrideForm.flag_id} onChange={(e) => setOverrideForm({ ...overrideForm, flag_id: e.target.value })} className={inputCn}>
<option value="">Select a flag...</option> <option value="">Select a flag...</option>
{flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)} {flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
</select> </select>
</div> </div>
<div className="flex items-center gap-2"> <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" /> <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-foreground">Enabled</label> <label htmlFor="override-enabled" className="text-sm font-medium text-white">Enabled</label>
</div> </div>
<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} /> <input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason" className={inputCn} />
</div> </div>
</div> </div>

View File

@@ -72,10 +72,10 @@ export function GlobalCategoriesPage() {
} }
const columns: Column<AdminCategory>[] = [ const columns: Column<AdminCategory>[] = [
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-foreground">{c.name}</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-muted-foreground">{c.slug}</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-muted-foreground">{c.description || '-'}</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-muted-foreground">{c.tree_count}</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', key: 'actions', header: '', className: 'w-12',
render: (c) => ( 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 ( return (
<div className="space-y-6"> <div className="space-y-6">
@@ -95,7 +95,7 @@ export function GlobalCategoriesPage() {
title="Global Categories" title="Global Categories"
description="Manage tree categories available to all accounts" description="Manage tree categories available to all accounts"
action={ 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" /> <Plus className="h-4 w-4" />
Create Category Create Category
</button> </button>
@@ -118,22 +118,22 @@ export function GlobalCategoriesPage() {
size="sm" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</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>
} }
> >
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <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>
<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} /> <input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" className={inputCn} />
</div> </div>
<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} /> <input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div> </div>
</div> </div>
@@ -147,22 +147,22 @@ export function GlobalCategoriesPage() {
size="sm" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Save</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>
} }
> >
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="e.g. Networking" className={inputCn} />
</div> </div>
<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} /> <input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" className={inputCn} />
</div> </div>
<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} /> <input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" className={inputCn} />
</div> </div>
</div> </div>

View File

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

View File

@@ -75,16 +75,16 @@ export function PlanLimitsPage() {
} }
const planColumns: Column<PlanLimitConfig>[] = [ const planColumns: Column<PlanLimitConfig>[] = [
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-foreground capitalize">{p.plan}</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-muted-foreground">{p.max_trees ?? 'Unlimited'}</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-muted-foreground">{p.max_sessions_per_month ?? '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-muted-foreground">{p.max_users ?? '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', key: 'actions', header: '', className: 'w-12',
render: (p) => ( render: (p) => (
<button <button
onClick={() => setEditPlan({ ...p })} 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 Edit
</button> </button>
@@ -93,11 +93,11 @@ export function PlanLimitsPage() {
] ]
const overrideColumns: Column<AccountOverrideResponse>[] = [ 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: '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-muted-foreground">{o.override_max_trees ?? '-'}</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-muted-foreground">{o.override_max_sessions_per_month ?? '-'}</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-muted-foreground">{o.override_max_users ?? '-'}</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-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', key: 'actions', header: '', className: 'w-12',
render: (o) => ( render: (o) => (
@@ -109,8 +109,8 @@ export function PlanLimitsPage() {
] ]
const inputCn = cn( const inputCn = cn(
'w-full rounded-md border border-border bg-background px-3 py-2 text-sm', 'w-full rounded-md border border-white/10 bg-black/50 px-3 py-2 text-sm text-white',
'placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring' 'placeholder:text-white/40 focus:outline-none focus:border-white/30 focus:ring-2 focus:ring-white/20'
) )
return ( return (
@@ -118,7 +118,7 @@ export function PlanLimitsPage() {
<PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" /> <PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" />
<div> <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"> <div className="mt-3">
<DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} /> <DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} />
</div> </div>
@@ -126,10 +126,10 @@ export function PlanLimitsPage() {
<div> <div>
<div className="flex items-center justify-between"> <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 <button
onClick={() => setCreateOverride(true)} 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" /> <Plus className="h-4 w-4" />
Add Override Add Override
@@ -154,23 +154,23 @@ export function PlanLimitsPage() {
size="sm" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90">Save</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> </div>
} }
> >
{editPlan && ( {editPlan && (
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <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>
<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} /> <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>
<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} /> <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>
</div> </div>
@@ -185,30 +185,30 @@ export function PlanLimitsPage() {
size="sm" size="sm"
footer={ footer={
<div className="flex justify-end gap-3"> <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={() => 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-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50">Create</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>
} }
> >
<div className="space-y-4"> <div className="space-y-4">
<div> <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} /> <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>
<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} /> <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>
<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} /> <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>
<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} /> <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>
<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} /> <input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason for override" className={inputCn} />
</div> </div>
</div> </div>

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
darkMode: ["class"],
content: [ content: [
"./index.html", "./index.html",
"./src/**/*.{js,ts,jsx,tsx}", "./src/**/*.{js,ts,jsx,tsx}",
@@ -8,25 +7,7 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
// ResolutionFlow Brand Colors // shadcn/ui color system (monochrome)
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
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
@@ -68,12 +49,6 @@ export default {
}, },
fontFamily: { fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'], 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%)',
}, },
}, },
}, },