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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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