refactor: migrate remaining components to Design System v4
111 files across 14 directories: common, tree-editor, kb-accelerator, copilot, assistant, analytics, library, procedural, procedural-editor, public, script-editor, ui, admin, step-library. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,8 +53,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
|||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md p-1.5 text-muted-foreground transition-colors',
|
'rounded-md p-1.5 text-[#848b9b] transition-colors',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
@@ -63,8 +63,8 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
|||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed z-50 min-w-[160px] rounded-md border border-border',
|
'fixed z-50 min-w-[160px] rounded-md border border-[#1e2130]',
|
||||||
'bg-card py-1 shadow-lg animate-scale-in'
|
'bg-[#14161d] py-1 shadow-lg animate-scale-in'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
top: `${menuPosition.top}px`,
|
top: `${menuPosition.top}px`,
|
||||||
@@ -81,7 +81,7 @@ export function ActionMenu({ items }: ActionMenuProps) {
|
|||||||
'disabled:opacity-50 disabled:pointer-events-none',
|
'disabled:opacity-50 disabled:pointer-events-none',
|
||||||
item.destructive
|
item.destructive
|
||||||
? 'text-red-400 hover:bg-red-400/10'
|
? 'text-red-400 hover:bg-red-400/10'
|
||||||
: 'text-muted-foreground hover:bg-accent'
|
: 'text-[#848b9b] hover:bg-accent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function AdminLayout() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full">
|
<div className="flex h-full">
|
||||||
{/* Desktop sidebar */}
|
{/* Desktop sidebar */}
|
||||||
<div className="hidden w-60 shrink-0 border-r border-border bg-card md:block">
|
<div className="hidden w-60 shrink-0 border-r border-[#1e2130] bg-[#14161d] md:block">
|
||||||
<AdminSidebar />
|
<AdminSidebar />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,14 +44,14 @@ export function AdminLayout() {
|
|||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div className="fixed inset-0 z-40 md:hidden">
|
<div className="fixed inset-0 z-40 md:hidden">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-card/80 backdrop-blur-xs"
|
className="absolute inset-0 bg-[#14161d]/80"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
|
<div className="absolute inset-y-0 left-0 w-60 border-r border-[#1e2130] bg-[#14161d] shadow-xl">
|
||||||
<div className="flex h-12 items-center justify-end px-3">
|
<div className="flex h-12 items-center justify-end px-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
|
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -67,7 +67,7 @@ export function AdminLayout() {
|
|||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileOpen(true)}
|
onClick={() => setMobileOpen(true)}
|
||||||
className="mb-4 rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden"
|
className="mb-4 rounded-md p-2 text-[#848b9b] hover:bg-accent md:hidden"
|
||||||
>
|
>
|
||||||
<Menu className="h-5 w-5" />
|
<Menu className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
|||||||
return (
|
return (
|
||||||
<aside className={cn('flex h-full flex-col', className)}>
|
<aside className={cn('flex h-full flex-col', className)}>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h2 className="text-lg font-bold text-foreground">Admin Panel</h2>
|
<h2 className="text-lg font-bold text-[#e2e5eb]">Admin Panel</h2>
|
||||||
</div>
|
</div>
|
||||||
<nav className="flex-1 space-y-1 px-3">
|
<nav className="flex-1 space-y-1 px-3">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
@@ -56,8 +56,8 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||||
isActive(item.path, item.end)
|
isActive(item.path, item.end)
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<item.icon className="h-4 w-4" />
|
<item.icon className="h-4 w-4" />
|
||||||
@@ -65,13 +65,13 @@ export function AdminSidebar({ className, onNavigate }: AdminSidebarProps) {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
<div className="border-t border-border p-3">
|
<div className="border-t border-[#1e2130] p-3">
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
to="/trees"
|
||||||
onClick={onNavigate}
|
onClick={onNavigate}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
|
||||||
'text-muted-foreground hover:bg-accent hover:text-foreground'
|
'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-4 w-4" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function CategoryRow({
|
|||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 bg-card border border-border rounded-xl p-4',
|
'flex items-center gap-3 bg-[#14161d] border border-[#1e2130] rounded-xl p-4',
|
||||||
isDragging && 'opacity-50'
|
isDragging && 'opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -47,7 +47,7 @@ export function CategoryRow({
|
|||||||
type="button"
|
type="button"
|
||||||
{...attributes}
|
{...attributes}
|
||||||
{...listeners}
|
{...listeners}
|
||||||
className="cursor-grab touch-none text-muted-foreground hover:text-foreground active:cursor-grabbing"
|
className="cursor-grab touch-none text-[#848b9b] hover:text-[#e2e5eb] active:cursor-grabbing"
|
||||||
aria-label="Drag to reorder"
|
aria-label="Drag to reorder"
|
||||||
>
|
>
|
||||||
<GripVertical className="h-5 w-5" />
|
<GripVertical className="h-5 w-5" />
|
||||||
@@ -56,17 +56,17 @@ export function CategoryRow({
|
|||||||
{/* Category Info */}
|
{/* Category Info */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-medium text-foreground">{category.name}</h3>
|
<h3 className="font-medium text-[#e2e5eb]">{category.name}</h3>
|
||||||
{!category.is_active && (
|
{!category.is_active && (
|
||||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-[#848b9b]">
|
||||||
Archived
|
Archived
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{category.description && (
|
{category.description && (
|
||||||
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
|
<p className="mt-1 text-sm text-[#848b9b]">{category.description}</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{stepCount} step{stepCount !== 1 ? 's' : ''}
|
{stepCount} step{stepCount !== 1 ? 's' : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,8 +77,8 @@ export function CategoryRow({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEdit(category)}
|
onClick={() => onEdit(category)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] bg-[#14161d] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Edit category"
|
title="Edit category"
|
||||||
aria-label="Edit category"
|
aria-label="Edit category"
|
||||||
@@ -91,7 +91,7 @@ export function CategoryRow({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onArchive(category.id)}
|
onClick={() => onArchive(category.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-input bg-background p-2 text-muted-foreground',
|
'rounded-md border border-input bg-[#0c0d10] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-accent-foreground'
|
'hover:bg-accent hover:text-accent-foreground'
|
||||||
)}
|
)}
|
||||||
title="Archive category"
|
title="Archive category"
|
||||||
@@ -104,7 +104,7 @@ export function CategoryRow({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onRestore(category.id)}
|
onClick={() => onRestore(category.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-input bg-background p-2 text-muted-foreground',
|
'rounded-md border border-input bg-[#0c0d10] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-accent-foreground'
|
'hover:bg-accent hover:text-accent-foreground'
|
||||||
)}
|
)}
|
||||||
title="Restore category"
|
title="Restore category"
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function CreateCategoryModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
|
<label htmlFor="name" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Category Name <span className="text-red-400">*</span>
|
Category Name <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -104,14 +104,14 @@ export function CreateCategoryModal({
|
|||||||
placeholder="e.g., Network Troubleshooting"
|
placeholder="e.g., Network Troubleshooting"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{name.length}/100 characters
|
{name.length}/100 characters
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
|
<label htmlFor="description" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Description <span className="text-muted-foreground">(optional)</span>
|
Description <span className="text-[#848b9b]">(optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
|
|||||||
@@ -50,16 +50,16 @@ export function DataTable<T>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto rounded-lg border border-border">
|
<div className="overflow-x-auto rounded-lg border border-[#1e2130]">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border bg-accent">
|
<tr className="border-b border-[#1e2130] bg-accent">
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<th
|
<th
|
||||||
key={col.key}
|
key={col.key}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-muted-foreground',
|
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-[#848b9b]',
|
||||||
col.sortable && 'cursor-pointer select-none hover:text-foreground',
|
col.sortable && 'cursor-pointer select-none hover:text-[#e2e5eb]',
|
||||||
col.className
|
col.className
|
||||||
)}
|
)}
|
||||||
onClick={col.sortable ? () => handleSort(col.key) : undefined}
|
onClick={col.sortable ? () => handleSort(col.key) : undefined}
|
||||||
@@ -87,7 +87,7 @@ export function DataTable<T>({
|
|||||||
<tbody>
|
<tbody>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
Array.from({ length: skeletonRows }).map((_, i) => (
|
Array.from({ length: skeletonRows }).map((_, i) => (
|
||||||
<tr key={i} className="border-b border-border last:border-0">
|
<tr key={i} className="border-b border-[#1e2130] last:border-0">
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<td key={col.key} className="px-4 py-3">
|
<td key={col.key} className="px-4 py-3">
|
||||||
<div className="h-4 w-3/4 animate-pulse rounded bg-accent" />
|
<div className="h-4 w-3/4 animate-pulse rounded bg-accent" />
|
||||||
@@ -99,7 +99,7 @@ export function DataTable<T>({
|
|||||||
<tr>
|
<tr>
|
||||||
<td colSpan={columns.length} className="px-4 py-12 text-center">
|
<td colSpan={columns.length} className="px-4 py-12 text-center">
|
||||||
{emptyState || (
|
{emptyState || (
|
||||||
<span className="text-muted-foreground">No data found</span>
|
<span className="text-[#848b9b]">No data found</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -107,7 +107,7 @@ export function DataTable<T>({
|
|||||||
data.map((item) => (
|
data.map((item) => (
|
||||||
<tr
|
<tr
|
||||||
key={keyExtractor(item)}
|
key={keyExtractor(item)}
|
||||||
className="border-b border-border last:border-0 hover:bg-accent transition-colors"
|
className="border-b border-[#1e2130] last:border-0 hover:bg-accent transition-colors"
|
||||||
>
|
>
|
||||||
{columns.map((col) => (
|
{columns.map((col) => (
|
||||||
<td key={col.key} className={cn('px-4 py-3', col.className)}>
|
<td key={col.key} className={cn('px-4 py-3', col.className)}>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export function EditCategoryModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
|
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Category Name <span className="text-red-400">*</span>
|
Category Name <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -116,14 +116,14 @@ export function EditCategoryModal({
|
|||||||
placeholder="e.g., Network Troubleshooting"
|
placeholder="e.g., Network Troubleshooting"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{name.length}/100 characters
|
{name.length}/100 characters
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
|
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Description <span className="text-muted-foreground">(optional)</span>
|
Description <span className="text-[#848b9b]">(optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="edit-description"
|
id="edit-description"
|
||||||
|
|||||||
@@ -36,20 +36,20 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-4 pt-4">
|
<div className="flex items-center justify-between gap-4 pt-4">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
Showing {start}-{end} of {total}
|
Showing {start}-{end} of {total}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => onPageChange(page - 1)}
|
onClick={() => onPageChange(page - 1)}
|
||||||
disabled={page <= 1}
|
disabled={page <= 1}
|
||||||
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
className={cn(btnBase, 'px-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]')}
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
{getPageNumbers().map((p, i) =>
|
{getPageNumbers().map((p, i) =>
|
||||||
p === 'ellipsis' ? (
|
p === 'ellipsis' ? (
|
||||||
<span key={`e${i}`} className="px-1 text-muted-foreground">...</span>
|
<span key={`e${i}`} className="px-1 text-[#848b9b]">...</span>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
key={p}
|
key={p}
|
||||||
@@ -58,8 +58,8 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
|||||||
btnBase,
|
btnBase,
|
||||||
'px-2',
|
'px-2',
|
||||||
p === page
|
p === page
|
||||||
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
|
? 'bg-[#22d3ee] text-white'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{p}
|
{p}
|
||||||
@@ -69,7 +69,7 @@ export function Pagination({ page, totalPages, total, pageSize, onPageChange }:
|
|||||||
<button
|
<button
|
||||||
onClick={() => onPageChange(page + 1)}
|
onClick={() => onPageChange(page + 1)}
|
||||||
disabled={page >= totalPages}
|
disabled={page >= totalPages}
|
||||||
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
className={cn(btnBase, 'px-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]')}
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-4 w-4" />
|
<ChevronRight className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -40,21 +40,21 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#848b9b]" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={localValue}
|
value={localValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
|
'h-9 w-full rounded-md border border-[#1e2130] bg-[#14161d] pl-9 pr-8 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{localValue && (
|
{localValue && (
|
||||||
<button
|
<button
|
||||||
onClick={handleClear}
|
onClick={handleClear}
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground"
|
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const variantClasses: Record<BadgeVariant, string> = {
|
|||||||
success: 'bg-emerald-400/10 text-emerald-400',
|
success: 'bg-emerald-400/10 text-emerald-400',
|
||||||
destructive: 'bg-red-400/10 text-red-400',
|
destructive: 'bg-red-400/10 text-red-400',
|
||||||
warning: 'bg-yellow-400/10 text-yellow-400',
|
warning: 'bg-yellow-400/10 text-yellow-400',
|
||||||
default: 'bg-accent text-muted-foreground',
|
default: 'bg-accent text-[#848b9b]',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {
|
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ function getFlowCountStyle(count: number) {
|
|||||||
export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
||||||
if (data.domains.length === 0) {
|
if (data.domains.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-6">
|
<div className="card-flat p-6">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<MapPin size={16} className="text-foreground" />
|
<MapPin size={16} className="text-[#e2e5eb]" />
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Domain Coverage</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
No session data for this period. Start using FlowPilot to see coverage metrics.
|
No session data for this period. Start using FlowPilot to see coverage metrics.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -52,13 +52,13 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<MapPin size={16} className="text-foreground" />
|
<MapPin size={16} className="text-[#e2e5eb]" />
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Domain Coverage</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
Resolution coverage and knowledge gaps by problem domain
|
Resolution coverage and knowledge gaps by problem domain
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,26 +66,26 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="px-3 py-2 text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Domain
|
Domain
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Flows
|
Flows
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Sessions
|
Sessions
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Resolution %
|
Resolution %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Escalation %
|
Escalation %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Guided %
|
Guided %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]"
|
||||||
title="Average time to resolve sessions in this domain. Green: <10 min, Amber: 10–20 min, Red: >20 min. Lower is better.">
|
title="Average time to resolve sessions in this domain. Green: <10 min, Amber: 10–20 min, Red: >20 min. Lower is better.">
|
||||||
Avg Resolution
|
Avg Resolution
|
||||||
</th>
|
</th>
|
||||||
@@ -93,15 +93,15 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.domains.map((row) => (
|
{data.domains.map((row) => (
|
||||||
<tr key={row.domain} className="border-b border-border">
|
<tr key={row.domain} className="border-b border-[#1e2130]">
|
||||||
<td className="px-3 py-2 text-sm text-foreground font-medium">
|
<td className="px-3 py-2 text-sm text-[#e2e5eb] font-medium">
|
||||||
{row.domain}
|
{row.domain}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm text-right">
|
<td className="px-3 py-2 text-sm text-right">
|
||||||
{row.flow_count === 0 ? (
|
{row.flow_count === 0 ? (
|
||||||
<Link
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
|
className="inline-flex items-center gap-1 text-xs text-[#22d3ee] hover:underline"
|
||||||
>
|
>
|
||||||
<Plus size={10} />
|
<Plus size={10} />
|
||||||
Create Flow
|
Create Flow
|
||||||
@@ -115,7 +115,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm text-right text-muted-foreground">
|
<td className="px-3 py-2 text-sm text-right text-[#848b9b]">
|
||||||
{row.session_count}
|
{row.session_count}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm text-right">
|
<td className="px-3 py-2 text-sm text-right">
|
||||||
@@ -144,7 +144,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm text-right">
|
<td className="px-3 py-2 text-sm text-right">
|
||||||
{row.avg_resolution_minutes == null ? (
|
{row.avg_resolution_minutes == null ? (
|
||||||
<span className="text-muted-foreground">—</span>
|
<span className="text-[#848b9b]">—</span>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -167,7 +167,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
{data.unmapped_session_count > 0 && (
|
{data.unmapped_session_count > 0 && (
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="px-3 py-2 text-xs text-muted-foreground">
|
<td colSpan={7} className="px-3 py-2 text-xs text-[#848b9b]">
|
||||||
{data.unmapped_session_count} sessions had no domain classification
|
{data.unmapped_session_count} sessions had no domain classification
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -176,7 +176,7 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 mt-3 text-[0.625rem] font-label text-muted-foreground">
|
<div className="flex flex-wrap gap-4 mt-3 text-[0.625rem] font-sans text-xs text-[#848b9b]">
|
||||||
<span><span className="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1" />Good</span>
|
<span><span className="inline-block w-2 h-2 rounded-full bg-emerald-400 mr-1" />Good</span>
|
||||||
<span><span className="inline-block w-2 h-2 rounded-full bg-amber-400 mr-1" />Needs Improvement</span>
|
<span><span className="inline-block w-2 h-2 rounded-full bg-amber-400 mr-1" />Needs Improvement</span>
|
||||||
<span><span className="inline-block w-2 h-2 rounded-full bg-rose-500 mr-1" />Critical</span>
|
<span><span className="inline-block w-2 h-2 rounded-full bg-rose-500 mr-1" />Critical</span>
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-[#848b9b]" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12 text-muted-foreground">
|
<div className="text-center py-12 text-[#848b9b]">
|
||||||
No analytics data available for this flow.
|
No analytics data available for this flow.
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -73,11 +73,11 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Period selector */}
|
{/* Period selector */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-lg font-semibold text-foreground">Flow Analytics</h3>
|
<h3 className="text-lg font-semibold text-[#e2e5eb]">Flow Analytics</h3>
|
||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-ring"
|
className="rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:outline-hidden focus:ring-1 focus:ring-ring"
|
||||||
>
|
>
|
||||||
{PERIOD_OPTIONS.map((opt) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
@@ -107,8 +107,8 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
|
|
||||||
{/* Area chart - Sessions over time */}
|
{/* Area chart - Sessions over time */}
|
||||||
{time_series.length > 0 && (
|
{time_series.length > 0 && (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-foreground mb-3">Sessions Over Time</p>
|
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Sessions Over Time</p>
|
||||||
<ResponsiveContainer width="100%" height={180}>
|
<ResponsiveContainer width="100%" height={180}>
|
||||||
<AreaChart data={time_series}>
|
<AreaChart data={time_series}>
|
||||||
<CartesianGrid
|
<CartesianGrid
|
||||||
@@ -192,7 +192,7 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
className="h-2 w-2 rounded-full"
|
className="h-2 w-2 rounded-full"
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-muted-foreground capitalize">
|
<span className="text-xs text-[#848b9b] capitalize">
|
||||||
{key}
|
{key}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,16 +203,16 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
|
|
||||||
{/* Step Feedback Table with Dropoff Metrics */}
|
{/* Step Feedback Table with Dropoff Metrics */}
|
||||||
{step_feedback.length > 0 && (
|
{step_feedback.length > 0 && (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-foreground mb-3">Step Performance</p>
|
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Step Performance</p>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="text-left py-2 pr-4 text-foreground font-medium">Step</th>
|
<th className="text-left py-2 pr-4 text-[#e2e5eb] font-medium">Step</th>
|
||||||
<th className="text-right py-2 pr-4 text-foreground font-medium">Visits</th>
|
<th className="text-right py-2 pr-4 text-[#e2e5eb] font-medium">Visits</th>
|
||||||
<th className="text-right py-2 pr-4 text-foreground font-medium">Dropoffs</th>
|
<th className="text-right py-2 pr-4 text-[#e2e5eb] font-medium">Dropoffs</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">Dropoff Rate</th>
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">Dropoff Rate</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -220,23 +220,23 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
<tr
|
<tr
|
||||||
key={step.node_id}
|
key={step.node_id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-b border-border last:border-0',
|
'border-b border-[#1e2130] last:border-0',
|
||||||
step.dropoff_rate > 0.2 && 'bg-red-400/5'
|
step.dropoff_rate > 0.2 && 'bg-red-400/5'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<td className="py-2 pr-4 text-muted-foreground truncate max-w-[200px]">
|
<td className="py-2 pr-4 text-[#848b9b] truncate max-w-[200px]">
|
||||||
{step.node_title}
|
{step.node_title}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-4 text-right text-muted-foreground">
|
<td className="py-2 pr-4 text-right text-[#848b9b]">
|
||||||
{step.visit_count}
|
{step.visit_count}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-4 text-right text-muted-foreground">
|
<td className="py-2 pr-4 text-right text-[#848b9b]">
|
||||||
{step.dropoff_count}
|
{step.dropoff_count}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className={cn(
|
className={cn(
|
||||||
'py-2 text-right font-medium',
|
'py-2 text-right font-medium',
|
||||||
step.dropoff_rate > 0.2 ? 'text-red-400' : 'text-muted-foreground'
|
step.dropoff_rate > 0.2 ? 'text-red-400' : 'text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{(step.dropoff_rate * 100).toFixed(1)}%
|
{(step.dropoff_rate * 100).toFixed(1)}%
|
||||||
@@ -251,13 +251,13 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
|
|
||||||
{/* Recent Comments (Anonymous) */}
|
{/* Recent Comments (Anonymous) */}
|
||||||
{recent_comments.length > 0 && (
|
{recent_comments.length > 0 && (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-foreground mb-3">Recent Feedback</p>
|
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Recent Feedback</p>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recent_comments.map((item, i) => (
|
{recent_comments.map((item, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex items-start gap-3 border-b border-border/50 pb-3 last:border-0 last:pb-0"
|
className="flex items-start gap-3 border-b border-[#1e2130]/50 pb-3 last:border-0 last:pb-0"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-0.5 shrink-0 pt-0.5">
|
<div className="flex items-center gap-0.5 shrink-0 pt-0.5">
|
||||||
{[1, 2, 3, 4, 5].map((v) => (
|
{[1, 2, 3, 4, 5].map((v) => (
|
||||||
@@ -267,16 +267,16 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
v <= item.rating
|
v <= item.rating
|
||||||
? 'fill-yellow-400 text-yellow-400'
|
? 'fill-yellow-400 text-yellow-400'
|
||||||
: 'fill-none text-muted-foreground'
|
: 'fill-none text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
{item.comment && (
|
{item.comment && (
|
||||||
<p className="text-sm text-foreground">{item.comment}</p>
|
<p className="text-sm text-[#e2e5eb]">{item.comment}</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">
|
<p className="text-xs text-[#848b9b] mt-0.5">
|
||||||
{new Date(item.created_at).toLocaleDateString()}
|
{new Date(item.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,11 +299,11 @@ function StatCard({
|
|||||||
subtitle?: string
|
subtitle?: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<p className="text-xs text-muted-foreground">{label}</p>
|
<p className="text-xs text-[#848b9b]">{label}</p>
|
||||||
<p className="text-xl font-bold text-foreground mt-1">{value}</p>
|
<p className="text-xl font-bold text-[#e2e5eb] mt-1">{value}</p>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{subtitle}</p>
|
<p className="text-xs text-[#848b9b] mt-0.5">{subtitle}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type SortColumn = 'name' | 'usage_count' | 'success_rate' | 'last_matched_at' |
|
|||||||
type SortDir = 'asc' | 'desc'
|
type SortDir = 'asc' | 'desc'
|
||||||
|
|
||||||
const TYPE_LABELS: Record<string, { label: string; color: string }> = {
|
const TYPE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
troubleshooting: { label: 'Troubleshooting', color: 'text-primary' },
|
troubleshooting: { label: 'Troubleshooting', color: 'text-[#22d3ee]' },
|
||||||
procedural: { label: 'Project', color: 'text-amber-400' },
|
procedural: { label: 'Project', color: 'text-amber-400' },
|
||||||
maintenance: { label: 'Maintenance', color: 'text-blue-400' },
|
maintenance: { label: 'Maintenance', color: 'text-blue-400' },
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ function formatRelativeTime(dateStr: string | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rateColor(value: number | null): string {
|
function rateColor(value: number | null): string {
|
||||||
if (value === null) return 'text-muted-foreground'
|
if (value === null) return 'text-[#848b9b]'
|
||||||
if (value > 75) return 'text-emerald-400'
|
if (value > 75) return 'text-emerald-400'
|
||||||
if (value >= 50) return 'text-amber-400'
|
if (value >= 50) return 'text-amber-400'
|
||||||
return 'text-rose-500'
|
return 'text-rose-500'
|
||||||
@@ -110,9 +110,9 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
|
|
||||||
if (data.flows.length === 0) {
|
if (data.flows.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<div className="flex items-center justify-center min-h-[200px]">
|
<div className="flex items-center justify-center min-h-[200px]">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
No flows found for this account. Create your first flow to start tracking quality.
|
No flows found for this account. Create your first flow to start tracking quality.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,20 +130,20 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">
|
||||||
Flow Quality Scores
|
Flow Quality Scores
|
||||||
</h3>
|
</h3>
|
||||||
<span className="group relative">
|
<span className="group relative">
|
||||||
<Info size={14} className="text-muted-foreground cursor-help" />
|
<Info size={14} className="text-[#848b9b] cursor-help" />
|
||||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-64 p-2 rounded-lg bg-card border border-border text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
|
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 w-64 p-2 rounded-lg bg-[#14161d] border border-[#1e2130] text-xs text-[#848b9b] opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-10">
|
||||||
Quality score combines success rate (50%), AI confidence level (30%), and recent usage (20%). Higher is better.
|
Quality score combines success rate (50%), AI confidence level (30%), and recent usage (20%). Higher is better.
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-[#848b9b] mt-1">
|
||||||
Performance and usage metrics for your troubleshooting flows
|
Performance and usage metrics for your troubleshooting flows
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,7 +151,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
{columns.map((col) => {
|
{columns.map((col) => {
|
||||||
const isActive = sortCol === col.key
|
const isActive = sortCol === col.key
|
||||||
const SortIcon = isActive
|
const SortIcon = isActive
|
||||||
@@ -160,7 +160,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
key={col.key}
|
key={col.key}
|
||||||
className="text-left py-2 px-2 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
|
className="text-left py-2 px-2 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] cursor-pointer select-none hover:text-[#e2e5eb] transition-colors"
|
||||||
onClick={() => handleSort(col.key)}
|
onClick={() => handleSort(col.key)}
|
||||||
title={col.title}
|
title={col.title}
|
||||||
>
|
>
|
||||||
@@ -207,7 +207,7 @@ function FlowRow({
|
|||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-b border-border/50 hover:bg-card/30 transition-colors',
|
'border-b border-[#1e2130]/50 hover:bg-[#14161d]/30 transition-colors',
|
||||||
isTopPerformer && 'border-l-2 border-l-emerald-400',
|
isTopPerformer && 'border-l-2 border-l-emerald-400',
|
||||||
needsAttention && 'border-l-2 border-l-rose-500',
|
needsAttention && 'border-l-2 border-l-rose-500',
|
||||||
)}
|
)}
|
||||||
@@ -217,15 +217,15 @@ function FlowRow({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
to={getTreeEditorPath(flow.flow_id, flow.tree_type)}
|
to={getTreeEditorPath(flow.flow_id, flow.tree_type)}
|
||||||
className="text-foreground hover:text-primary transition-colors font-medium truncate max-w-[200px]"
|
className="text-[#e2e5eb] hover:text-[#22d3ee] transition-colors font-medium truncate max-w-[200px]"
|
||||||
>
|
>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</Link>
|
</Link>
|
||||||
<span className={cn('font-label text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-muted-foreground')}>
|
<span className={cn('font-sans text-xs text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-[#848b9b]')}>
|
||||||
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
|
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
{needsAttention && (
|
{needsAttention && (
|
||||||
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-label text-[0.625rem] px-1.5 py-0.5 rounded">
|
<span className="shrink-0 bg-amber-400/10 text-amber-400 font-sans text-xs text-[0.625rem] px-1.5 py-0.5 rounded">
|
||||||
Needs attention
|
Needs attention
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -233,7 +233,7 @@ function FlowRow({
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Usage */}
|
{/* Usage */}
|
||||||
<td className="py-2.5 px-2 text-foreground">{flow.usage_count}</td>
|
<td className="py-2.5 px-2 text-[#e2e5eb]">{flow.usage_count}</td>
|
||||||
|
|
||||||
{/* Success Rate */}
|
{/* Success Rate */}
|
||||||
<td className={cn('py-2.5 px-2', rateColor(flow.success_rate))}>
|
<td className={cn('py-2.5 px-2', rateColor(flow.success_rate))}>
|
||||||
@@ -241,7 +241,7 @@ function FlowRow({
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Last Used */}
|
{/* Last Used */}
|
||||||
<td className="py-2.5 px-2 text-muted-foreground">
|
<td className="py-2.5 px-2 text-[#848b9b]">
|
||||||
{formatRelativeTime(flow.last_matched_at)}
|
{formatRelativeTime(flow.last_matched_at)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ function FlowRow({
|
|||||||
style={{ width: `${flow.quality_score * 100}%` }}
|
style={{ width: `${flow.quality_score * 100}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-foreground">
|
<span className="text-xs text-[#e2e5eb]">
|
||||||
{(flow.quality_score * 100).toFixed(0)}
|
{(flow.quality_score * 100).toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
|
|||||||
|
|
||||||
if (!hasActivity) {
|
if (!hasActivity) {
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<div className="flex items-center justify-center min-h-[200px]">
|
<div className="flex items-center justify-center min-h-[200px]">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
No PSA activity data for this period. Link sessions to PSA tickets to see metrics.
|
No PSA activity data for this period. Link sessions to PSA tickets to see metrics.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,20 +33,20 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Row 1 — Metric cards */}
|
{/* Row 1 — Metric cards */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
<div className="glass-card-static p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Time Entries</p>
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Time Entries</p>
|
||||||
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_time_entries}</p>
|
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.total_time_entries}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">logged to PSA</p>
|
<p className="text-xs text-[#848b9b] mt-1">logged to PSA</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card-static p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Hours Logged</p>
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Hours Logged</p>
|
||||||
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
|
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">total hours tracked</p>
|
<p className="text-xs text-[#848b9b] mt-1">total hours tracked</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card-static p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Avg Hours/Session</p>
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170]">Avg Hours/Session</p>
|
||||||
<p className="text-gradient-brand font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
|
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">per resolved session</p>
|
<p className="text-xs text-[#848b9b] mt-1">per resolved session</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
|
|||||||
|
|
||||||
{/* Row 3 — Daily Trend Chart */}
|
{/* Row 3 — Daily Trend Chart */}
|
||||||
{data.daily_trend.length > 0 && (
|
{data.daily_trend.length > 0 && (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
PSA Activity Trend
|
PSA Activity Trend
|
||||||
</h3>
|
</h3>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -129,8 +129,8 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Documentation Push Funnel
|
Documentation Push Funnel
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -139,15 +139,15 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
|
|||||||
{steps.map((step, i) => (
|
{steps.map((step, i) => (
|
||||||
<div key={step.label} className="flex items-center gap-2 flex-1">
|
<div key={step.label} className="flex items-center gap-2 flex-1">
|
||||||
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center flex-1">
|
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center flex-1">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
{step.label}
|
{step.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg font-heading text-foreground">{step.count}</p>
|
<p className="text-lg font-heading text-[#e2e5eb]">{step.count}</p>
|
||||||
</div>
|
</div>
|
||||||
{i < steps.length - 1 && (
|
{i < steps.length - 1 && (
|
||||||
<div className="flex flex-col items-center shrink-0 px-1">
|
<div className="flex flex-col items-center shrink-0 px-1">
|
||||||
<ArrowRight size={14} className="text-muted-foreground" />
|
<ArrowRight size={14} className="text-[#848b9b]" />
|
||||||
<span className="text-[0.625rem] text-muted-foreground font-label">
|
<span className="text-[0.625rem] text-[#848b9b] font-sans text-xs">
|
||||||
{funnelPct(steps[i].count, steps[i + 1].count)}
|
{funnelPct(steps[i].count, steps[i + 1].count)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,15 +161,15 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
|
|||||||
{steps.map((step, i) => (
|
{steps.map((step, i) => (
|
||||||
<div key={step.label} className="flex flex-col items-center gap-2 w-full">
|
<div key={step.label} className="flex flex-col items-center gap-2 w-full">
|
||||||
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center w-full">
|
<div className="bg-primary/5 border border-primary/20 rounded-lg p-3 text-center w-full">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
{step.label}
|
{step.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg font-heading text-foreground">{step.count}</p>
|
<p className="text-lg font-heading text-[#e2e5eb]">{step.count}</p>
|
||||||
</div>
|
</div>
|
||||||
{i < steps.length - 1 && (
|
{i < steps.length - 1 && (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<ArrowDown size={14} className="text-muted-foreground" />
|
<ArrowDown size={14} className="text-[#848b9b]" />
|
||||||
<span className="text-[0.625rem] text-muted-foreground font-label">
|
<span className="text-[0.625rem] text-[#848b9b] font-sans text-xs">
|
||||||
{funnelPct(steps[i].count, steps[i + 1].count)}
|
{funnelPct(steps[i].count, steps[i + 1].count)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
|
|||||||
<div
|
<div
|
||||||
className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
|
className={`shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
|
||||||
role === 'assistant'
|
role === 'assistant'
|
||||||
? 'bg-primary/15 text-primary'
|
? 'bg-primary/15 text-[#22d3ee]'
|
||||||
: 'bg-white/[0.08] text-muted-foreground'
|
: 'bg-white/[0.08] text-[#848b9b]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{role === 'assistant' ? <Sparkles size={14} /> : <User size={14} />}
|
{role === 'assistant' ? <Sparkles size={14} /> : <User size={14} />}
|
||||||
@@ -28,8 +28,8 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
|
|||||||
<div
|
<div
|
||||||
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
|
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
|
||||||
role === 'user'
|
role === 'user'
|
||||||
? 'bg-primary/15 text-foreground'
|
? 'bg-primary/15 text-[#e2e5eb]'
|
||||||
: 'bg-white/[0.04] text-foreground border border-brand-border'
|
: 'bg-white/[0.04] text-[#e2e5eb] border border-brand-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
|
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
|
||||||
@@ -38,7 +38,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
|
|||||||
{/* Suggested flows (assistant only) */}
|
{/* Suggested flows (assistant only) */}
|
||||||
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
|
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||||
Related Flows
|
Related Flows
|
||||||
</span>
|
</span>
|
||||||
{suggestedFlows.map(flow => (
|
{suggestedFlows.map(flow => (
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function ChatSidebar({
|
|||||||
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<button
|
<button
|
||||||
onClick={onNewChat}
|
onClick={onNewChat}
|
||||||
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="w-full flex items-center justify-center gap-2 bg-[#22d3ee] text-brand-dark font-semibold text-sm rounded-lg px-4 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
New Chat
|
New Chat
|
||||||
@@ -42,7 +42,7 @@ export function ChatSidebar({
|
|||||||
<div className="flex-1 overflow-y-auto py-2">
|
<div className="flex-1 overflow-y-auto py-2">
|
||||||
{pinnedChats.length > 0 && (
|
{pinnedChats.length > 0 && (
|
||||||
<div className="px-3 mb-1">
|
<div className="px-3 mb-1">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||||
Pinned
|
Pinned
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ export function ChatSidebar({
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{chats.length === 0 && (
|
{chats.length === 0 && (
|
||||||
<div className="px-4 py-8 text-center text-muted-foreground text-sm">
|
<div className="px-4 py-8 text-center text-[#848b9b] text-sm">
|
||||||
No conversations yet
|
No conversations yet
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -102,14 +102,14 @@ function ChatItem({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
|
'group flex items-center gap-2 px-3 py-2.5 mx-1.5 rounded-lg cursor-pointer transition-colors',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary/10 text-foreground'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:bg-white/[0.04] hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-white/[0.04] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MessageSquare size={14} className="shrink-0" />
|
<MessageSquare size={14} className="shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-[0.8125rem] font-medium truncate">{chat.title}</div>
|
<div className="text-[0.8125rem] font-medium truncate">{chat.title}</div>
|
||||||
<div className="text-[0.6875rem] text-muted-foreground">
|
<div className="text-[0.6875rem] text-[#848b9b]">
|
||||||
{chat.message_count} messages
|
{chat.message_count} messages
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,11 +119,11 @@ function ChatItem({
|
|||||||
className="p-1 rounded hover:bg-white/[0.08]"
|
className="p-1 rounded hover:bg-white/[0.08]"
|
||||||
title={chat.pinned ? 'Unpin' : 'Pin'}
|
title={chat.pinned ? 'Unpin' : 'Pin'}
|
||||||
>
|
>
|
||||||
<Pin size={12} className={chat.pinned ? 'text-primary' : ''} />
|
<Pin size={12} className={chat.pinned ? 'text-[#22d3ee]' : ''} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); onDelete() }}
|
onClick={e => { e.stopPropagation(); onDelete() }}
|
||||||
className="p-1 rounded hover:bg-white/[0.08] text-muted-foreground hover:text-rose-400"
|
className="p-1 rounded hover:bg-white/[0.08] text-[#848b9b] hover:text-rose-400"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 size={12} />
|
<Trash2 size={12} />
|
||||||
|
|||||||
@@ -136,13 +136,13 @@ export function ConcludeSessionModal({
|
|||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
|
className="absolute inset-0 bg-black/60"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div
|
<div
|
||||||
className="relative w-full max-w-2xl mx-4 glass-card-static overflow-hidden animate-in fade-in zoom-in-95 duration-200"
|
className="relative w-full max-w-2xl mx-4 card-flat overflow-hidden animate-in fade-in zoom-in-95 duration-200"
|
||||||
style={{
|
style={{
|
||||||
maxHeight: 'calc(100vh - 4rem)',
|
maxHeight: 'calc(100vh - 4rem)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -155,21 +155,21 @@ export function ConcludeSessionModal({
|
|||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-9 h-9 rounded-xl bg-primary/10 flex items-center justify-center">
|
<div className="w-9 h-9 rounded-xl bg-[rgba(34,211,238,0.10)] flex items-center justify-center">
|
||||||
<ClipboardList size={18} className="text-primary" />
|
<ClipboardList size={18} className="text-[#22d3ee]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-heading font-semibold text-foreground">
|
<h2 className="text-base font-heading font-semibold text-[#e2e5eb]">
|
||||||
Conclude Session
|
Conclude Session
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-muted-foreground truncate max-w-[300px]">
|
<p className="text-xs text-[#848b9b] truncate max-w-[300px]">
|
||||||
{chatTitle}
|
{chatTitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-2 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
className="p-2 rounded-lg hover:bg-brand-border text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -194,20 +194,20 @@ export function ConcludeSessionModal({
|
|||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-label font-medium transition-colors',
|
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-sans text-xs font-medium transition-colors',
|
||||||
step === s
|
step === s
|
||||||
? 'bg-gradient-brand text-brand-dark'
|
? 'bg-[#22d3ee] text-brand-dark'
|
||||||
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
||||||
? 'bg-primary/20 text-primary'
|
? 'bg-primary/20 text-[#22d3ee]'
|
||||||
: 'bg-brand-border text-muted-foreground'
|
: 'bg-brand-border text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{i + 1}
|
{i + 1}
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs font-label',
|
'text-xs font-sans text-xs',
|
||||||
step === s ? 'text-foreground' : 'text-muted-foreground'
|
step === s ? 'text-[#e2e5eb]' : 'text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{s === 'select-outcome' ? 'Outcome' : s === 'add-notes' ? 'Notes' : 'Summary'}
|
{s === 'select-outcome' ? 'Outcome' : s === 'add-notes' ? 'Notes' : 'Summary'}
|
||||||
@@ -221,7 +221,7 @@ export function ConcludeSessionModal({
|
|||||||
{/* Step 1: Select Outcome */}
|
{/* Step 1: Select Outcome */}
|
||||||
{step === 'select-outcome' && (
|
{step === 'select-outcome' && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-[#848b9b] mb-4">
|
||||||
How did this session end?
|
How did this session end?
|
||||||
</p>
|
</p>
|
||||||
{OUTCOMES.map(o => {
|
{OUTCOMES.map(o => {
|
||||||
@@ -241,8 +241,8 @@ export function ConcludeSessionModal({
|
|||||||
<Icon size={20} className={o.color} />
|
<Icon size={20} className={o.color} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-semibold text-foreground block">{o.label}</span>
|
<span className="text-sm font-semibold text-[#e2e5eb] block">{o.label}</span>
|
||||||
<span className="text-xs text-muted-foreground">{o.description}</span>
|
<span className="text-xs text-[#848b9b]">{o.description}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -255,20 +255,20 @@ export function ConcludeSessionModal({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Selected outcome badge */}
|
{/* Selected outcome badge */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className={cn('px-3 py-1.5 rounded-lg flex items-center gap-2 text-xs font-label', selectedOutcome.bg, selectedOutcome.border, 'border')}>
|
<div className={cn('px-3 py-1.5 rounded-lg flex items-center gap-2 text-xs font-sans text-xs', selectedOutcome.bg, selectedOutcome.border, 'border')}>
|
||||||
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
|
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
|
||||||
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
|
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setStep('select-outcome')}
|
onClick={() => setStep('select-outcome')}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-2">
|
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] block mb-2">
|
||||||
Additional Notes (optional)
|
Additional Notes (optional)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -282,7 +282,7 @@ export function ConcludeSessionModal({
|
|||||||
: 'What still needs to be done, where you left off...'
|
: 'What still needs to be done, where you left off...'
|
||||||
}
|
}
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-primary/30"
|
className="w-full resize-none rounded-xl border bg-[#14161d] text-[#e2e5eb] text-sm placeholder:text-[#848b9b] px-4 py-3 focus:outline-hidden focus:border-primary/30"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -300,7 +300,7 @@ export function ConcludeSessionModal({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Outcome badge */}
|
{/* Outcome badge */}
|
||||||
{selectedOutcome && (
|
{selectedOutcome && (
|
||||||
<div className={cn('px-3 py-1.5 rounded-lg inline-flex items-center gap-2 text-xs font-label', selectedOutcome.bg, selectedOutcome.border, 'border')}>
|
<div className={cn('px-3 py-1.5 rounded-lg inline-flex items-center gap-2 text-xs font-sans text-xs', selectedOutcome.bg, selectedOutcome.border, 'border')}>
|
||||||
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
|
<selectedOutcome.icon size={14} className={selectedOutcome.color} />
|
||||||
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
|
<span className={selectedOutcome.color}>{selectedOutcome.label}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,12 +312,12 @@ export function ConcludeSessionModal({
|
|||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] flex items-center gap-1.5">
|
||||||
<Sparkles size={10} className="text-primary" />
|
<Sparkles size={10} className="text-[#22d3ee]" />
|
||||||
Generated Ticket Notes
|
Generated Ticket Notes
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="prose-sm text-foreground">
|
<div className="prose-sm text-[#e2e5eb]">
|
||||||
<MarkdownContent content={summary} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={summary} className="text-[0.8125rem] leading-relaxed" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -335,7 +335,7 @@ export function ConcludeSessionModal({
|
|||||||
<div />
|
<div />
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
className="px-4 py-2 rounded-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -346,14 +346,14 @@ export function ConcludeSessionModal({
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setStep('select-outcome')}
|
onClick={() => setStep('select-outcome')}
|
||||||
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
className="px-4 py-2 rounded-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={generating}
|
disabled={generating}
|
||||||
className="flex items-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
|
className="flex items-center gap-2 bg-[#22d3ee] text-brand-dark font-semibold text-sm rounded-lg px-5 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{generating ? (
|
{generating ? (
|
||||||
<>
|
<>
|
||||||
@@ -376,7 +376,7 @@ export function ConcludeSessionModal({
|
|||||||
{outcome === 'paused' && (
|
{outcome === 'paused' && (
|
||||||
<button
|
<button
|
||||||
onClick={handleResumeNew}
|
onClick={handleResumeNew}
|
||||||
className="flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-medium text-blue-400 bg-blue-400/10 border border-blue-400/20 hover:bg-blue-400/15 transition-all"
|
className="flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium text-blue-400 bg-blue-400/10 border border-blue-400/20 hover:bg-blue-400/15 transition-all"
|
||||||
>
|
>
|
||||||
<RefreshCw size={14} />
|
<RefreshCw size={14} />
|
||||||
Resume in New Chat
|
Resume in New Chat
|
||||||
@@ -387,10 +387,10 @@ export function ConcludeSessionModal({
|
|||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
|
'flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-semibold transition-all',
|
||||||
copied
|
copied
|
||||||
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
|
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
|
||||||
: 'bg-gradient-brand text-brand-dark hover:opacity-90 active:scale-[0.97]'
|
: 'bg-[#22d3ee] text-brand-dark hover:brightness-110 active:scale-[0.98]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -407,7 +407,7 @@ export function ConcludeSessionModal({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
className="px-4 py-2.5 rounded-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-white/[0.04] border border-brand-border hover:border-white/[0.12] transition-all"
|
||||||
>
|
>
|
||||||
Done
|
Done
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@ export function SuggestedFlowCard({ flow }: SuggestedFlowCardProps) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className="w-full text-left glass-card-static p-3 rounded-xl hover:border-white/[0.12] transition-colors group"
|
className="w-full text-left card-flat p-3 rounded-xl hover:border-white/[0.12] transition-colors group"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Box size={14} className="text-primary mt-0.5 shrink-0" />
|
<Box size={14} className="text-[#22d3ee] mt-0.5 shrink-0" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-[0.8125rem] font-medium text-foreground truncate">
|
<span className="text-[0.8125rem] font-medium text-[#e2e5eb] truncate">
|
||||||
{flow.tree_name}
|
{flow.tree_name}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-wider text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b]">
|
||||||
{flow.tree_type}
|
{flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[0.75rem] text-muted-foreground mt-0.5 line-clamp-2">
|
<p className="text-[0.75rem] text-[#848b9b] mt-0.5 line-clamp-2">
|
||||||
{flow.relevance_snippet}
|
{flow.relevance_snippet}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight size={14} className="text-muted-foreground group-hover:text-primary transition-colors shrink-0 mt-0.5" />
|
<ArrowRight size={14} className="text-[#848b9b] group-hover:text-[#22d3ee] transition-colors shrink-0 mt-0.5" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
aria-label="Actions"
|
aria-label="Actions"
|
||||||
>
|
>
|
||||||
@@ -64,7 +64,7 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute z-50 mt-1 min-w-[180px] bg-card border border-border rounded-lg p-1',
|
'absolute z-50 mt-1 min-w-[180px] bg-[#14161d] border border-[#1e2130] rounded-lg p-1',
|
||||||
align === 'right' ? 'right-0' : 'left-0'
|
align === 'right' ? 'right-0' : 'left-0'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -81,7 +81,7 @@ export function ActionMenu({ actions, align = 'right' }: ActionMenuProps) {
|
|||||||
? 'cursor-not-allowed opacity-40'
|
? 'cursor-not-allowed opacity-40'
|
||||||
: action.variant === 'destructive'
|
: action.variant === 'destructive'
|
||||||
? 'text-red-400 hover:bg-accent hover:text-red-300'
|
? 'text-red-400 hover:bg-accent hover:text-red-300'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Icon && <Icon className="h-4 w-4" />}
|
{Icon && <Icon className="h-4 w-4" />}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ export function ConfirmDialog({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl border border-border px-4 py-2 text-sm font-medium',
|
'rounded-xl border border-[#1e2130] px-4 py-2 text-sm font-medium',
|
||||||
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -49,7 +49,7 @@ export function ConfirmDialog({
|
|||||||
'disabled:opacity-50 disabled:cursor-not-allowed',
|
'disabled:opacity-50 disabled:cursor-not-allowed',
|
||||||
confirmVariant === 'destructive'
|
confirmVariant === 'destructive'
|
||||||
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20'
|
? 'bg-red-400/10 text-red-400 hover:bg-red-400/20 border border-red-400/20'
|
||||||
: 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
|
: 'bg-[#22d3ee] text-white hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? 'Processing...' : confirmLabel}
|
{isLoading ? 'Processing...' : confirmLabel}
|
||||||
@@ -57,7 +57,7 @@ export function ConfirmDialog({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<p className="text-sm text-muted-foreground">{message}</p>
|
<p className="text-sm text-[#848b9b]">{message}</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
|
|||||||
left: position.x,
|
left: position.x,
|
||||||
top: position.y,
|
top: position.y,
|
||||||
}}
|
}}
|
||||||
className="min-w-[200px] rounded-xl border border-border bg-card p-1 shadow-lg backdrop-blur-md"
|
className="min-w-[200px] rounded-xl border border-[#1e2130] bg-[#14161d] p-1 shadow-lg"
|
||||||
>
|
>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
{item.separator && (
|
{item.separator && (
|
||||||
<div className="my-1 border-t border-border" />
|
<div className="my-1 border-t border-[#1e2130]" />
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -87,11 +87,11 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
|
|||||||
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
|
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
|
||||||
item.variant === 'danger'
|
item.variant === 'danger'
|
||||||
? 'text-rose-400 hover:bg-rose-500/10'
|
? 'text-rose-400 hover:bg-rose-500/10'
|
||||||
: 'text-foreground hover:bg-brand-border'
|
: 'text-[#e2e5eb] hover:bg-brand-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<span className="flex h-4 w-4 items-center justify-center text-muted-foreground">
|
<span className="flex h-4 w-4 items-center justify-center text-[#848b9b]">
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function CreateFlowDropdown({
|
|||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowMenu(!showMenu)}
|
onClick={() => setShowMenu(!showMenu)}
|
||||||
className="flex items-center gap-2 rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-primary/20 hover:opacity-90 transition-opacity"
|
className="flex items-center gap-2 rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-white hover:brightness-110 transition-opacity"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
{label}
|
{label}
|
||||||
@@ -74,17 +74,17 @@ export function CreateFlowDropdown({
|
|||||||
{showMenu && (
|
{showMenu && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
|
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
|
||||||
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
|
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-[#1e2130] bg-[#14161d] p-1 shadow-xl">
|
||||||
{/* Troubleshooting */}
|
{/* Troubleshooting */}
|
||||||
<Link
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
onClick={() => setShowMenu(false)}
|
onClick={() => setShowMenu(false)}
|
||||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<FolderTree className="h-4 w-4 text-muted-foreground" />
|
<FolderTree className="h-4 w-4 text-[#848b9b]" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">Troubleshooting Tree</div>
|
<div className="font-medium">Troubleshooting Tree</div>
|
||||||
<div className="text-xs text-muted-foreground">Branching decision flow</div>
|
<div className="text-xs text-[#848b9b]">Branching decision flow</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{aiEnabled && (
|
{aiEnabled && (
|
||||||
@@ -95,27 +95,27 @@ export function CreateFlowDropdown({
|
|||||||
setAiPromptFlowType('troubleshooting')
|
setAiPromptFlowType('troubleshooting')
|
||||||
setAiPromptOpen(true)
|
setAiPromptOpen(true)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="my-1 border-t border-border" />
|
<div className="my-1 border-t border-[#1e2130]" />
|
||||||
|
|
||||||
{/* Procedural */}
|
{/* Procedural */}
|
||||||
<Link
|
<Link
|
||||||
to="/flows/new"
|
to="/flows/new"
|
||||||
onClick={() => setShowMenu(false)}
|
onClick={() => setShowMenu(false)}
|
||||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<ListOrdered className="h-4 w-4 text-muted-foreground" />
|
<ListOrdered className="h-4 w-4 text-[#848b9b]" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">Procedural Flow</div>
|
<div className="font-medium">Procedural Flow</div>
|
||||||
<div className="text-xs text-muted-foreground">Step-by-step procedure</div>
|
<div className="text-xs text-[#848b9b]">Step-by-step procedure</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{aiEnabled && (
|
{aiEnabled && (
|
||||||
@@ -126,27 +126,27 @@ export function CreateFlowDropdown({
|
|||||||
setAiPromptFlowType('procedural')
|
setAiPromptFlowType('procedural')
|
||||||
setAiPromptOpen(true)
|
setAiPromptOpen(true)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="my-1 border-t border-border" />
|
<div className="my-1 border-t border-[#1e2130]" />
|
||||||
|
|
||||||
{/* Maintenance */}
|
{/* Maintenance */}
|
||||||
<Link
|
<Link
|
||||||
to="/flows/new?type=maintenance"
|
to="/flows/new?type=maintenance"
|
||||||
onClick={() => setShowMenu(false)}
|
onClick={() => setShowMenu(false)}
|
||||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Wrench className="h-4 w-4 text-amber-400" />
|
<Wrench className="h-4 w-4 text-amber-400" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">Maintenance Flow</div>
|
<div className="font-medium">Maintenance Flow</div>
|
||||||
<div className="text-xs text-muted-foreground">Scheduled multi-target tasks</div>
|
<div className="text-xs text-[#848b9b]">Scheduled multi-target tasks</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{aiEnabled && (
|
{aiEnabled && (
|
||||||
@@ -157,11 +157,11 @@ export function CreateFlowDropdown({
|
|||||||
setAiPromptFlowType('maintenance')
|
setAiPromptFlowType('maintenance')
|
||||||
setAiPromptOpen(true)
|
setAiPromptOpen(true)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ export function EmptyState({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!illustration && icon && (
|
{!illustration && icon && (
|
||||||
<div className="mb-4 text-muted-foreground">{icon}</div>
|
<div className="mb-4 text-[#848b9b]">{icon}</div>
|
||||||
)}
|
)}
|
||||||
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
<h3 className="text-lg font-semibold text-[#e2e5eb]">{title}</h3>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="mt-2 max-w-sm text-sm text-muted-foreground">{description}</p>
|
<p className="mt-2 max-w-sm text-sm text-[#848b9b]">{description}</p>
|
||||||
)}
|
)}
|
||||||
{action && <div className="mt-4">{action}</div>}
|
{action && <div className="mt-4">{action}</div>}
|
||||||
{learnMoreLink && (
|
{learnMoreLink && (
|
||||||
<Link
|
<Link
|
||||||
to={learnMoreLink}
|
to={learnMoreLink}
|
||||||
className="mt-3 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
className="mt-3 text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
{learnMoreText} →
|
{learnMoreText} →
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -39,10 +39,10 @@ function DefaultFallback({ error, resetError }: FallbackProps) {
|
|||||||
<h2 className="mb-2 text-xl font-semibold text-red-400">
|
<h2 className="mb-2 text-xl font-semibold text-red-400">
|
||||||
Something went wrong
|
Something went wrong
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-muted-foreground">
|
<p className="mb-4 text-[#848b9b]">
|
||||||
An unexpected error occurred. Please try refreshing the page.
|
An unexpected error occurred. Please try refreshing the page.
|
||||||
</p>
|
</p>
|
||||||
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-border p-3 text-left text-xs text-red-400">
|
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-[#1e2130] p-3 text-left text-xs text-red-400">
|
||||||
{error.message}
|
{error.message}
|
||||||
</pre>
|
</pre>
|
||||||
<div className="flex justify-center gap-3">
|
<div className="flex justify-center gap-3">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ interface InfoTipProps {
|
|||||||
export function InfoTip({ text }: InfoTipProps) {
|
export function InfoTip({ text }: InfoTipProps) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="inline-flex items-center justify-center h-3.5 w-3.5 rounded-full border border-muted-foreground/40 text-[9px] text-muted-foreground cursor-help shrink-0"
|
className="inline-flex items-center justify-center h-3.5 w-3.5 rounded-full border border-muted-foreground/40 text-[9px] text-[#848b9b] cursor-help shrink-0"
|
||||||
title={text}
|
title={text}
|
||||||
>
|
>
|
||||||
i
|
i
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
>
|
>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
|
className="absolute inset-0 bg-black/80"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -134,7 +134,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
<div
|
<div
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex w-full flex-col border border-border bg-card shadow-lg',
|
'relative flex w-full flex-col border border-[#1e2130] bg-[#14161d] shadow-lg',
|
||||||
'animate-scale-in transition-all duration-200',
|
'animate-scale-in transition-all duration-200',
|
||||||
isFullScreen
|
isFullScreen
|
||||||
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
|
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
|
||||||
@@ -145,8 +145,8 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Header - Fixed at top */}
|
{/* Header - Fixed at top */}
|
||||||
<div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
|
<div className="flex shrink-0 items-center justify-between border-b border-[#1e2130] px-4 py-3 sm:px-6 sm:py-4">
|
||||||
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
<h2 id="modal-title" className="text-lg font-semibold text-[#e2e5eb]">
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -154,7 +154,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
title={isFullScreen ? 'Exit full screen' : 'Full screen'}
|
title={isFullScreen ? 'Exit full screen' : 'Full screen'}
|
||||||
>
|
>
|
||||||
{isFullScreen
|
{isFullScreen
|
||||||
@@ -166,8 +166,8 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
'rounded-md p-1.5 text-[#848b9b] transition-colors sm:p-1',
|
||||||
'hover:bg-accent hover:text-foreground',
|
'hover:bg-accent hover:text-[#e2e5eb]',
|
||||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
|
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
aria-label="Close modal"
|
aria-label="Close modal"
|
||||||
@@ -184,7 +184,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
|
|||||||
|
|
||||||
{/* Footer - Fixed at bottom */}
|
{/* Footer - Fixed at bottom */}
|
||||||
{footer && (
|
{footer && (
|
||||||
<div className="shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
|
<div className="shrink-0 border-t border-[#1e2130] px-4 py-3 sm:px-6 sm:py-4">
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ export function PageHeader({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
{icon && <div className="shrink-0">{icon}</div>}
|
{icon && <div className="shrink-0">{icon}</div>}
|
||||||
<div>
|
<div>
|
||||||
<h1 className={cn('text-2xl font-bold font-heading text-foreground', titleClassName)}>
|
<h1 className={cn('text-2xl font-bold font-heading text-[#e2e5eb]', titleClassName)}>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
{description && (
|
{description && (
|
||||||
<p className={cn('mt-1 text-sm text-muted-foreground', descriptionClassName)}>
|
<p className={cn('mt-1 text-sm text-[#848b9b]', descriptionClassName)}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export function PageLoader() {
|
|||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
<p className="text-sm text-[#848b9b]">Loading…</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function PasswordInput({ className, ...props }: PasswordInputProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setVisible((v) => !v)}
|
onClick={() => setVisible((v) => !v)}
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
title={visible ? 'Hide password' : 'Show password'}
|
title={visible ? 'Hide password' : 'Show password'}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export function RichTextInput({
|
|||||||
rows={rows}
|
rows={rows}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full bg-card border border-border rounded-xl p-3 text-sm text-foreground placeholder:text-muted-foreground',
|
'w-full bg-[#14161d] border border-[#1e2130] rounded-xl p-3 text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none transition-colors',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none transition-colors',
|
||||||
isDragOver && 'border-primary/50 bg-primary/5',
|
isDragOver && 'border-primary/50 bg-primary/5',
|
||||||
disabled && 'opacity-50 cursor-not-allowed'
|
disabled && 'opacity-50 cursor-not-allowed'
|
||||||
@@ -243,7 +243,7 @@ export function RichTextInput({
|
|||||||
{/* Drag overlay hint */}
|
{/* Drag overlay hint */}
|
||||||
{isDragOver && (
|
{isDragOver && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center rounded-xl border-2 border-dashed border-primary/50 bg-primary/5 pointer-events-none">
|
<div className="absolute inset-0 flex items-center justify-center rounded-xl border-2 border-dashed border-primary/50 bg-primary/5 pointer-events-none">
|
||||||
<div className="flex items-center gap-2 text-sm text-primary">
|
<div className="flex items-center gap-2 text-sm text-[#22d3ee]">
|
||||||
<ImagePlus size={16} />
|
<ImagePlus size={16} />
|
||||||
Drop image to attach
|
Drop image to attach
|
||||||
</div>
|
</div>
|
||||||
@@ -254,19 +254,19 @@ export function RichTextInput({
|
|||||||
{pendingUploads.length > 0 && (
|
{pendingUploads.length > 0 && (
|
||||||
<div className="flex gap-2 flex-wrap mt-2">
|
<div className="flex gap-2 flex-wrap mt-2">
|
||||||
{pendingUploads.map((upload) => (
|
{pendingUploads.map((upload) => (
|
||||||
<div key={upload.id} className="relative w-16 h-16 rounded-lg overflow-hidden border border-border">
|
<div key={upload.id} className="relative w-16 h-16 rounded-lg overflow-hidden border border-[#1e2130]">
|
||||||
<img src={upload.preview} alt="" className="w-full h-full object-cover" />
|
<img src={upload.preview} alt="" className="w-full h-full object-cover" />
|
||||||
{upload.status === 'uploading' && (
|
{upload.status === 'uploading' && (
|
||||||
<div className="absolute inset-0 bg-background/50 flex items-center justify-center">
|
<div className="absolute inset-0 bg-[#0c0d10]/50 flex items-center justify-center">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-[#22d3ee]" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{upload.status === 'done' && (
|
{upload.status === 'done' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemove(upload.id)}
|
onClick={() => handleRemove(upload.id)}
|
||||||
className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-background/80 border border-border flex items-center justify-center hover:bg-rose-500/20 transition-colors"
|
className="absolute -top-1 -right-1 w-4 h-4 rounded-full bg-[#0c0d10]/80 border border-[#1e2130] flex items-center justify-center hover:bg-rose-500/20 transition-colors"
|
||||||
>
|
>
|
||||||
<X size={10} className="text-muted-foreground" />
|
<X size={10} className="text-[#848b9b]" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{upload.status === 'error' && (
|
{upload.status === 'error' && (
|
||||||
@@ -284,7 +284,7 @@ export function RichTextInput({
|
|||||||
|
|
||||||
{/* Paste hint */}
|
{/* Paste hint */}
|
||||||
{isFocused && !value && pendingUploads.length === 0 && (
|
{isFocused && !value && pendingUploads.length === 0 && (
|
||||||
<p className="text-[0.625rem] text-muted-foreground/50 mt-1">Paste screenshots with Ctrl+V</p>
|
<p className="text-[0.625rem] text-[#848b9b]/50 mt-1">Paste screenshots with Ctrl+V</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ export function RouteError() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen flex-col items-center justify-center bg-background p-8">
|
<div className="flex min-h-screen flex-col items-center justify-center bg-[#0c0d10] p-8">
|
||||||
<div className="max-w-md text-center">
|
<div className="max-w-md text-center">
|
||||||
<h1 className="mb-2 text-4xl font-bold text-foreground">Oops!</h1>
|
<h1 className="mb-2 text-4xl font-bold text-[#e2e5eb]">Oops!</h1>
|
||||||
<h2 className="mb-2 text-xl font-semibold text-red-400">{errorMessage}</h2>
|
<h2 className="mb-2 text-xl font-semibold text-red-400">{errorMessage}</h2>
|
||||||
{errorDetails && (
|
{errorDetails && (
|
||||||
<p className="mb-4 text-muted-foreground">{errorDetails}</p>
|
<p className="mb-4 text-[#848b9b]">{errorDetails}</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center gap-4">
|
<div className="flex justify-center gap-4">
|
||||||
<Button variant="secondary" onClick={() => navigate(-1)}>
|
<Button variant="secondary" onClick={() => navigate(-1)}>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function Spinner({ size = 'md', className }: SpinnerProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'animate-spin rounded-full border-border border-t-primary',
|
'animate-spin rounded-full border-[#1e2130] border-t-primary',
|
||||||
SIZES[size],
|
SIZES[size],
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export function StarRating({
|
|||||||
sizeClasses[size],
|
sizeClasses[size],
|
||||||
star <= value
|
star <= value
|
||||||
? 'fill-yellow-400 text-yellow-400'
|
? 'fill-yellow-400 text-yellow-400'
|
||||||
: 'fill-none text-muted-foreground',
|
: 'fill-none text-[#848b9b]',
|
||||||
!readonly && 'hover:text-yellow-300'
|
!readonly && 'hover:text-yellow-300'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{showCount && (
|
{showCount && (
|
||||||
<span className="ml-1 text-sm text-muted-foreground">
|
<span className="ml-1 text-sm text-[#848b9b]">
|
||||||
({value}/5)
|
({value}/5)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export function TagBadges({
|
|||||||
}}
|
}}
|
||||||
disabled={!onTagClick}
|
disabled={!onTagClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-full font-label transition-colors',
|
'rounded-full font-sans text-xs transition-colors',
|
||||||
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
|
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
|
||||||
variant === 'default'
|
variant === 'default'
|
||||||
? 'bg-accent text-muted-foreground hover:bg-accent'
|
? 'bg-accent text-[#848b9b] hover:bg-accent'
|
||||||
: 'bg-accent/50 text-muted-foreground hover:bg-accent',
|
: 'bg-accent/50 text-[#848b9b] hover:bg-accent',
|
||||||
!onTagClick && 'cursor-default'
|
!onTagClick && 'cursor-default'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -48,9 +48,9 @@ export function TagBadges({
|
|||||||
{hiddenCount > 0 && (
|
{hiddenCount > 0 && (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-full font-label',
|
'rounded-full font-sans text-xs',
|
||||||
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
|
size === 'sm' ? 'px-2 py-0.5 text-xs' : 'px-2.5 py-1 text-sm',
|
||||||
'bg-accent/50 text-muted-foreground'
|
'bg-accent/50 text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
title={tags.slice(maxVisible).join(', ')}
|
title={tags.slice(maxVisible).join(', ')}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -123,10 +123,10 @@ export function TagInput({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-wrap gap-1.5 rounded-xl border px-2 py-1.5',
|
'flex flex-wrap gap-1.5 rounded-xl border px-2 py-1.5',
|
||||||
'bg-card text-foreground',
|
'bg-[#14161d] text-[#e2e5eb]',
|
||||||
'focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/20',
|
'focus-within:border-primary focus-within:ring-1 focus-within:ring-primary/20',
|
||||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||||
'border-border'
|
'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
onClick={() => inputRef.current?.focus()}
|
onClick={() => inputRef.current?.focus()}
|
||||||
>
|
>
|
||||||
@@ -136,7 +136,7 @@ export function TagInput({
|
|||||||
key={tag}
|
key={tag}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs',
|
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs',
|
||||||
'bg-accent text-muted-foreground'
|
'bg-accent text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
@@ -184,8 +184,8 @@ export function TagInput({
|
|||||||
placeholder={tags.length === 0 ? placeholder : ''}
|
placeholder={tags.length === 0 ? placeholder : ''}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
|
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground',
|
'placeholder:text-[#848b9b]',
|
||||||
'focus:outline-hidden focus:ring-0'
|
'focus:outline-hidden focus:ring-0'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -196,8 +196,8 @@ export function TagInput({
|
|||||||
{showSuggestions && suggestions.length > 0 && (
|
{showSuggestions && suggestions.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute z-10 mt-1 w-full rounded-xl border border-border',
|
'absolute z-10 mt-1 w-full rounded-xl border border-[#1e2130]',
|
||||||
'bg-card shadow-lg'
|
'bg-[#14161d] shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{suggestions.map((suggestion, index) => (
|
{suggestions.map((suggestion, index) => (
|
||||||
@@ -206,13 +206,13 @@ export function TagInput({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => addTag(suggestion.name)}
|
onClick={() => addTag(suggestion.name)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-between px-3 py-2 text-sm text-muted-foreground',
|
'flex w-full items-center justify-between px-3 py-2 text-sm text-[#848b9b]',
|
||||||
'hover:bg-accent',
|
'hover:bg-accent',
|
||||||
index === selectedIndex && 'bg-accent'
|
index === selectedIndex && 'bg-accent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{suggestion.name}</span>
|
<span>{suggestion.name}</span>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-[#848b9b]">
|
||||||
{suggestion.usage_count} trees
|
{suggestion.usage_count} trees
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -225,8 +225,8 @@ export function TagInput({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => addTag(inputValue)}
|
onClick={() => addTag(inputValue)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 border-t border-border px-3 py-2 text-sm',
|
'flex w-full items-center gap-2 border-t border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'hover:bg-accent text-foreground'
|
'hover:bg-accent text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -237,7 +237,7 @@ export function TagInput({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Helper text */}
|
{/* Helper text */}
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{tags.length}/{maxTags} tags. Press Enter, Tab, comma, or semicolon to add.
|
{tags.length}/{maxTags} tags. Press Enter, Tab, comma, or semicolon to add.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles size={16} className="text-primary" />
|
<Sparkles size={16} className="text-[#22d3ee]" />
|
||||||
<span className="text-sm font-semibold text-foreground">AI Copilot</span>
|
<span className="text-sm font-semibold text-[#e2e5eb]">AI Copilot</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
className="p-1.5 rounded-lg hover:bg-brand-border text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -123,8 +123,8 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
<div
|
<div
|
||||||
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
|
||||||
msg.role === 'user'
|
msg.role === 'user'
|
||||||
? 'bg-primary/15 text-foreground'
|
? 'bg-primary/15 text-[#e2e5eb]'
|
||||||
: 'bg-white/[0.04] text-foreground border border-brand-border'
|
: 'bg-white/[0.04] text-[#e2e5eb] border border-brand-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
||||||
@@ -134,7 +134,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="bg-white/[0.04] border border-brand-border rounded-xl px-3.5 py-2.5">
|
<div className="bg-white/[0.04] border border-brand-border rounded-xl px-3.5 py-2.5">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-[#22d3ee]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -142,7 +142,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
{/* Suggested flows */}
|
{/* Suggested flows */}
|
||||||
{suggestedFlows.length > 0 && (
|
{suggestedFlows.length > 0 && (
|
||||||
<div className="space-y-2 pt-2">
|
<div className="space-y-2 pt-2">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||||
Related Flows
|
Related Flows
|
||||||
</span>
|
</span>
|
||||||
{suggestedFlows.map(flow => (
|
{suggestedFlows.map(flow => (
|
||||||
@@ -164,19 +164,19 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Ask about this step..."
|
placeholder="Ask about this step..."
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-hidden focus:border-primary/30"
|
className="flex-1 resize-none rounded-xl border bg-[#14161d] text-[#e2e5eb] text-[0.8125rem] placeholder:text-[#848b9b] px-3.5 py-2.5 focus:outline-hidden focus:border-primary/30"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
disabled={loading || initializing}
|
disabled={loading || initializing}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || loading || initializing}
|
disabled={!input.trim() || loading || initializing}
|
||||||
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
className="bg-[#22d3ee] text-brand-dark p-2.5 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
|
||||||
>
|
>
|
||||||
<Send size={16} />
|
<Send size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[0.625rem] text-muted-foreground mt-1.5 px-1">Shift + Enter for a new line</p>
|
<p className="text-[0.625rem] text-[#848b9b] mt-1.5 px-1">Shift + Enter for a new line</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function CopilotToggle({ isOpen, onToggle }: CopilotToggleProps) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-brand-dark p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="fixed bottom-6 right-6 z-40 bg-[#22d3ee] text-brand-dark p-3.5 rounded-full shadow-lg shadow-primary/30 hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
title="Open AI Copilot"
|
title="Open AI Copilot"
|
||||||
>
|
>
|
||||||
<MessageCircle size={22} />
|
<MessageCircle size={22} />
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'glass-card-static border-l-4 p-4 transition-all',
|
'card-flat border-l-4 p-4 transition-all',
|
||||||
confidenceColor(node.confidence_score),
|
confidenceColor(node.confidence_score),
|
||||||
node.user_approved && 'opacity-75',
|
node.user_approved && 'opacity-75',
|
||||||
)}
|
)}
|
||||||
@@ -66,17 +66,17 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
{node.node_type}
|
{node.node_type}
|
||||||
</span>
|
</span>
|
||||||
<span className={cn('font-label text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
|
<span className={cn('font-sans text-xs text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
|
||||||
{confidenceLabel(node.confidence_score)} ({Math.round(node.confidence_score * 100)}%)
|
{confidenceLabel(node.confidence_score)} ({Math.round(node.confidence_score * 100)}%)
|
||||||
</span>
|
</span>
|
||||||
{node.user_approved && (
|
{node.user_approved && (
|
||||||
<span className="font-label text-[0.625rem] text-emerald-400">Approved</span>
|
<span className="font-sans text-xs text-[0.625rem] text-emerald-400">Approved</span>
|
||||||
)}
|
)}
|
||||||
{node.user_edited && (
|
{node.user_edited && (
|
||||||
<span className="font-label text-[0.625rem] text-blue-400">Edited</span>
|
<span className="font-sans text-xs text-[0.625rem] text-blue-400">Edited</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,27 +85,27 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<textarea
|
<textarea
|
||||||
value={editContent}
|
value={editContent}
|
||||||
onChange={e => setEditContent(e.target.value)}
|
onChange={e => setEditContent(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary/30 focus:outline-hidden"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-primary/30 focus:outline-hidden"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleAction('edit', { content: { ...node.content, question: editContent, content: editContent } })}
|
onClick={() => handleAction('edit', { content: { ...node.content, question: editContent, content: editContent } })}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="px-3 py-1.5 text-xs font-medium rounded-md bg-gradient-brand text-[#101114] hover:opacity-90"
|
className="px-3 py-1.5 text-xs font-medium rounded-md bg-[#22d3ee] text-white hover:brightness-110"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditMode(false)}
|
onClick={() => setEditMode(false)}
|
||||||
className="px-3 py-1.5 text-xs font-medium rounded-md bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground"
|
className="px-3 py-1.5 text-xs font-medium rounded-md bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-foreground">{stepContent || question}</p>
|
<p className="text-sm text-[#e2e5eb]">{stepContent || question}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('approve')}
|
onClick={() => handleAction('approve')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-emerald-400 hover:bg-emerald-400/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-emerald-400 hover:bg-emerald-400/10 transition-colors"
|
||||||
title="Approve"
|
title="Approve"
|
||||||
>
|
>
|
||||||
<Check size={14} />
|
<Check size={14} />
|
||||||
@@ -126,7 +126,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('reject')}
|
onClick={() => handleAction('reject')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-amber-400 hover:bg-amber-400/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-amber-400 hover:bg-amber-400/10 transition-colors"
|
||||||
title="Unapprove"
|
title="Unapprove"
|
||||||
>
|
>
|
||||||
<X size={14} />
|
<X size={14} />
|
||||||
@@ -135,7 +135,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={startEdit}
|
onClick={startEdit}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-blue-400 hover:bg-blue-400/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-blue-400 hover:bg-blue-400/10 transition-colors"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
@@ -143,7 +143,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('regenerate')}
|
onClick={() => handleAction('regenerate')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-[#22d3ee] hover:bg-[rgba(34,211,238,0.10)] transition-colors"
|
||||||
title="Regenerate"
|
title="Regenerate"
|
||||||
>
|
>
|
||||||
<RotateCcw size={14} />
|
<RotateCcw size={14} />
|
||||||
@@ -151,7 +151,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('insert_after', { content: { question: 'New node', type: node.node_type } })}
|
onClick={() => handleAction('insert_after', { content: { question: 'New node', type: node.node_type } })}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-primary/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-[#22d3ee] hover:bg-[rgba(34,211,238,0.10)] transition-colors"
|
||||||
title="Insert after"
|
title="Insert after"
|
||||||
>
|
>
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
@@ -159,7 +159,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('delete')}
|
onClick={() => handleAction('delete')}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-rose-500 hover:bg-rose-500/10 transition-colors"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
@@ -173,15 +173,15 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
className="flex items-center gap-1 text-xs text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
{expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
|
||||||
{options.length} option{options.length !== 1 ? 's' : ''}
|
{options.length} option{options.length !== 1 ? 's' : ''}
|
||||||
</button>
|
</button>
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="mt-2 space-y-1 pl-3 border-l border-border">
|
<div className="mt-2 space-y-1 pl-3 border-l border-[#1e2130]">
|
||||||
{options.map((opt, i) => (
|
{options.map((opt, i) => (
|
||||||
<p key={i} className="text-xs text-muted-foreground">
|
<p key={i} className="text-xs text-[#848b9b]">
|
||||||
{opt.label} {opt.next_node_id && <span className="text-[#5a6170]">→ {opt.next_node_id}</span>}
|
{opt.label} {opt.next_node_id && <span className="text-[#5a6170]">→ {opt.next_node_id}</span>}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -30,23 +30,23 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full gap-4">
|
<div className="flex flex-col h-full gap-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="glass-card-static p-4 flex flex-wrap items-center justify-between gap-3">
|
<div className="card-flat p-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-heading font-semibold text-foreground">{flowTitle}</h2>
|
<h2 className="text-lg font-heading font-semibold text-[#e2e5eb]">{flowTitle}</h2>
|
||||||
{flowDescription && (
|
{flowDescription && (
|
||||||
<p className="text-sm text-muted-foreground mt-0.5">{flowDescription}</p>
|
<p className="text-sm text-[#848b9b] mt-0.5">{flowDescription}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<BarChart3 size={14} className="text-muted-foreground" />
|
<BarChart3 size={14} className="text-[#848b9b]" />
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
Avg confidence: <span className="text-foreground font-medium">{Math.round(avgConfidence * 100)}%</span>
|
Avg confidence: <span className="text-[#e2e5eb] font-medium">{Math.round(avgConfidence * 100)}%</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<CheckCircle2 size={14} className="text-emerald-400" />
|
<CheckCircle2 size={14} className="text-emerald-400" />
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
{approvedCount}/{nodes.length} approved
|
{approvedCount}/{nodes.length} approved
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,10 +88,10 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nodes panel */}
|
{/* Nodes panel */}
|
||||||
<div className="flex flex-col glass-card-static overflow-hidden">
|
<div className="flex flex-col card-flat overflow-hidden">
|
||||||
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<span className="text-sm font-medium text-foreground">Generated Flow</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">Generated Flow</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] ml-auto">
|
||||||
{kbImport.target_type === 'troubleshooting' ? 'Troubleshooting' : 'Project'}
|
{kbImport.target_type === 'troubleshooting' ? 'Troubleshooting' : 'Project'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +105,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{nodes.length === 0 && (
|
{nodes.length === 0 && (
|
||||||
<p className="text-sm text-muted-foreground text-center py-8">
|
<p className="text-sm text-[#848b9b] text-center py-8">
|
||||||
No nodes generated. Try converting again.
|
No nodes generated. Try converting again.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -119,9 +119,9 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
onClick={onDiscard}
|
onClick={onDiscard}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-2.5 rounded-[10px] text-sm font-medium transition-colors',
|
'px-4 py-2.5 rounded-lg text-sm font-medium transition-colors',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#848b9b]',
|
||||||
'hover:text-foreground hover:border-[rgba(255,255,255,0.12)]',
|
'hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -131,9 +131,9 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
onClick={() => onCommit()}
|
onClick={() => onCommit()}
|
||||||
disabled={loading || nodes.length === 0}
|
disabled={loading || nodes.length === 0}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
|
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -17,23 +17,23 @@ export function SourcePanel({ sourceText, sourceFormat, highlightExcerpt }: Sour
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>{sourceText.slice(0, idx)}</span>
|
<span>{sourceText.slice(0, idx)}</span>
|
||||||
<mark className="bg-primary/20 text-foreground rounded px-0.5">{highlightExcerpt}</mark>
|
<mark className="bg-primary/20 text-[#e2e5eb] rounded px-0.5">{highlightExcerpt}</mark>
|
||||||
<span>{sourceText.slice(idx + highlightExcerpt.length)}</span>
|
<span>{sourceText.slice(idx + highlightExcerpt.length)}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [sourceText, highlightExcerpt])
|
}, [sourceText, highlightExcerpt])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="glass-card-static flex flex-col h-full">
|
<div className="card-flat flex flex-col h-full">
|
||||||
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="flex items-center gap-2 px-4 py-3 border-b" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<FileText size={16} className="text-muted-foreground" />
|
<FileText size={16} className="text-[#848b9b]" />
|
||||||
<span className="text-sm font-medium text-foreground">Source Document</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">Source Document</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground ml-auto">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] ml-auto">
|
||||||
{sourceFormat}
|
{sourceFormat}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<pre className="text-sm text-muted-foreground whitespace-pre-wrap font-sans leading-relaxed">
|
<pre className="text-sm text-[#848b9b] whitespace-pre-wrap font-sans leading-relaxed">
|
||||||
{renderedText}
|
{renderedText}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-heading font-semibold text-foreground">
|
<h2 className="text-xl font-heading font-semibold text-[#e2e5eb]">
|
||||||
Flow Created Successfully
|
Flow Created Successfully
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-muted-foreground mt-2">
|
<p className="text-sm text-[#848b9b] mt-2">
|
||||||
Your KB article has been converted into a {result.tree_type === 'troubleshooting' ? 'troubleshooting' : 'project'} flow
|
Your KB article has been converted into a {result.tree_type === 'troubleshooting' ? 'troubleshooting' : 'project'} flow
|
||||||
and added to your library.
|
and added to your library.
|
||||||
</p>
|
</p>
|
||||||
@@ -31,9 +31,9 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
|
|||||||
<button
|
<button
|
||||||
onClick={onViewFlow}
|
onClick={onViewFlow}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
|
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-semibold transition-all',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97]'
|
'hover:brightness-110 active:scale-[0.98]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
View Flow
|
View Flow
|
||||||
@@ -42,8 +42,8 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
|
|||||||
<button
|
<button
|
||||||
onClick={onConvertAnother}
|
onClick={onConvertAnother}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-6 py-2.5 rounded-[10px] text-sm font-medium transition-colors',
|
'flex items-center gap-2 px-6 py-2.5 rounded-lg text-sm font-medium transition-colors',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
||||||
'hover:border-[rgba(255,255,255,0.12)]'
|
'hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -82,16 +82,16 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
<div className="max-w-3xl mx-auto space-y-6">
|
<div className="max-w-3xl mx-auto space-y-6">
|
||||||
{/* Quota info */}
|
{/* Quota info */}
|
||||||
{quota && (
|
{quota && (
|
||||||
<div className="glass-card-static p-4 flex items-center justify-between">
|
<div className="card-flat p-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Sparkles size={18} className="text-primary" />
|
<Sparkles size={18} className="text-[#22d3ee]" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">
|
<p className="text-sm font-medium text-[#e2e5eb]">
|
||||||
{quota.lifetime_conversions_limit
|
{quota.lifetime_conversions_limit
|
||||||
? `${quota.lifetime_conversions_limit - quota.lifetime_conversions_used} conversions remaining`
|
? `${quota.lifetime_conversions_limit - quota.lifetime_conversions_used} conversions remaining`
|
||||||
: 'Unlimited conversions'}
|
: 'Unlimited conversions'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
{quota.plan.charAt(0).toUpperCase() + quota.plan.slice(1)} plan
|
{quota.plan.charAt(0).toUpperCase() + quota.plan.slice(1)} plan
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,10 +110,10 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
<button
|
<button
|
||||||
onClick={() => setMode('paste')}
|
onClick={() => setMode('paste')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-4 py-2 rounded-[10px] text-sm font-medium transition-colors',
|
'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||||
mode === 'paste'
|
mode === 'paste'
|
||||||
? 'bg-primary/10 text-foreground border border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border border-primary/30'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ClipboardPaste size={16} />
|
<ClipboardPaste size={16} />
|
||||||
@@ -123,10 +123,10 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
<button
|
<button
|
||||||
onClick={() => setMode('file')}
|
onClick={() => setMode('file')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-4 py-2 rounded-[10px] text-sm font-medium transition-colors',
|
'flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||||
mode === 'file'
|
mode === 'file'
|
||||||
? 'bg-primary/10 text-foreground border border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border border-primary/30'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
: 'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FileUp size={16} />
|
<FileUp size={16} />
|
||||||
@@ -136,11 +136,11 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content area */}
|
{/* Content area */}
|
||||||
<div className="glass-card-static p-5 space-y-4">
|
<div className="card-flat p-5 space-y-4">
|
||||||
{mode === 'paste' ? (
|
{mode === 'paste' ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="kb-title" className="block font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
|
<label htmlFor="kb-title" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-1.5">
|
||||||
Title (optional)
|
Title (optional)
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -152,7 +152,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="kb-content" className="block font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1.5">
|
<label htmlFor="kb-content" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-1.5">
|
||||||
KB Article Content
|
KB Article Content
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -163,7 +163,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
rows={12}
|
rows={12}
|
||||||
maxLength={500000}
|
maxLength={500000}
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{content.length.toLocaleString()} / 500,000 characters
|
{content.length.toLocaleString()} / 500,000 characters
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,15 +183,15 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
>
|
>
|
||||||
{file ? (
|
{file ? (
|
||||||
<>
|
<>
|
||||||
<FileText size={32} className="text-primary" />
|
<FileText size={32} className="text-[#22d3ee]" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm font-medium text-foreground">{file.name}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{file.name}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-[#848b9b] mt-1">
|
||||||
{(file.size / 1024).toFixed(1)} KB
|
{(file.size / 1024).toFixed(1)} KB
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); setFile(null) }}
|
onClick={e => { e.stopPropagation(); setFile(null) }}
|
||||||
className="mt-2 text-xs text-primary hover:underline"
|
className="mt-2 text-xs text-[#22d3ee] hover:underline"
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
@@ -199,12 +199,12 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Upload size={32} className="text-muted-foreground" />
|
<Upload size={32} className="text-[#848b9b]" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm text-foreground">
|
<p className="text-sm text-[#e2e5eb]">
|
||||||
Drop a file here or <span className="text-primary">browse</span>
|
Drop a file here or <span className="text-[#22d3ee]">browse</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-[#848b9b] mt-1">
|
||||||
Supported: {fileFormats.map(f => FORMAT_LABELS[f] || f.toUpperCase()).join(', ')}
|
Supported: {fileFormats.map(f => FORMAT_LABELS[f] || f.toUpperCase()).join(', ')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,7 +223,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
|
|
||||||
{/* Target type selector */}
|
{/* Target type selector */}
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
||||||
Target Flow Type
|
Target Flow Type
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
@@ -232,14 +232,14 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
key={t.value}
|
key={t.value}
|
||||||
onClick={() => setTargetType(t.value)}
|
onClick={() => setTargetType(t.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'glass-card-static p-4 text-left transition-all',
|
'card-flat p-4 text-left transition-all',
|
||||||
targetType === t.value
|
targetType === t.value
|
||||||
? 'border-primary/30 bg-primary/5'
|
? 'border-primary/30 bg-primary/5'
|
||||||
: 'hover:border-[rgba(255,255,255,0.12)]'
|
: 'hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="text-sm font-medium text-foreground">{t.label}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{t.label}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">{t.description}</p>
|
<p className="text-xs text-[#848b9b] mt-1">{t.description}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -250,9 +250,9 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!canSubmit || loading || (quota != null && !quota.can_convert)}
|
disabled={!canSubmit || loading || (quota != null && !quota.can_convert)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-[10px] text-sm font-semibold transition-all',
|
'w-full flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold transition-all',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
setIsOpen(!isOpen)
|
setIsOpen(!isOpen)
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Add to folder"
|
title="Add to folder"
|
||||||
aria-label="Add to folder"
|
aria-label="Add to folder"
|
||||||
@@ -101,14 +101,14 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
|
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-[#1e2130]',
|
||||||
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
'bg-[#14161d] py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
|
<div className="px-3 py-2 text-sm text-[#848b9b]">Loading...</div>
|
||||||
) : folders.length === 0 ? (
|
) : folders.length === 0 ? (
|
||||||
<div className="px-3 py-2 text-sm text-muted-foreground">No folders yet</div>
|
<div className="px-3 py-2 text-sm text-[#848b9b]">No folders yet</div>
|
||||||
) : (
|
) : (
|
||||||
folders.map((folder) => (
|
folders.map((folder) => (
|
||||||
<button
|
<button
|
||||||
@@ -117,7 +117,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
toggleFolder(folder.id)
|
toggleFolder(folder.id)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="h-3 w-3 rounded-sm"
|
className="h-3 w-3 rounded-sm"
|
||||||
@@ -125,13 +125,13 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
/>
|
/>
|
||||||
<span className="flex-1 truncate text-left">{folder.name}</span>
|
<span className="flex-1 truncate text-left">{folder.name}</span>
|
||||||
{treeFolderIds.has(folder.id) && (
|
{treeFolderIds.has(folder.id) && (
|
||||||
<Check className="h-4 w-4 text-foreground" />
|
<Check className="h-4 w-4 text-[#e2e5eb]" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="border-t border-border my-1" />
|
<div className="border-t border-[#1e2130] my-1" />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -139,7 +139,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
onFolderCreated?.()
|
onFolderCreated?.()
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Create new folder
|
Create new folder
|
||||||
|
|||||||
@@ -53,19 +53,19 @@ export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalPr
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="w-full max-w-sm rounded-xl border border-border bg-card shadow-xl"
|
className="w-full max-w-sm rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Download className="h-4 w-4 text-muted-foreground" />
|
<Download className="h-4 w-4 text-[#848b9b]" />
|
||||||
<h2 className="text-sm font-semibold text-foreground">Export Flow</h2>
|
<h2 className="text-sm font-semibold text-[#e2e5eb]">Export Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -73,13 +73,13 @@ export function ExportFlowModal({ treeId, treeName, onClose }: ExportFlowModalPr
|
|||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="px-5 py-4">
|
<div className="px-5 py-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Export <span className="font-medium text-foreground">{treeName}</span> as a <code className="text-xs font-label">.rfflow</code> file (JSON format).
|
Export <span className="font-medium text-[#e2e5eb]">{treeName}</span> as a <code className="text-xs font-sans text-xs">.rfflow</code> file (JSON format).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-end gap-2 border-t border-border px-5 py-3">
|
<div className="flex justify-end gap-2 border-t border-[#1e2130] px-5 py-3">
|
||||||
<Button variant="secondary" onClick={onClose}>
|
<Button variant="secondary" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -175,15 +175,15 @@ export function FolderEditModal({
|
|||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-xs" onClick={onClose} />
|
<div className="absolute inset-0 bg-black/80" onClick={onClose} />
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">
|
<div className="relative z-10 w-full max-w-md bg-[#14161d] border border-[#1e2130] rounded-2xl p-6 shadow-lg">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||||
{isEditMode ? 'Edit Folder' : initialParentId ? 'Create Subfolder' : 'Create Folder'}
|
{isEditMode ? 'Edit Folder' : initialParentId ? 'Create Subfolder' : 'Create Folder'}
|
||||||
</h2>
|
</h2>
|
||||||
<button onClick={onClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground">
|
<button onClick={onClose} className="rounded-md p-1 text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]">
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +191,7 @@ export function FolderEditModal({
|
|||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{/* Name input */}
|
{/* Name input */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor="folder-name" className="block text-sm font-medium text-foreground">
|
<label htmlFor="folder-name" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -202,9 +202,9 @@ export function FolderEditModal({
|
|||||||
placeholder="e.g., Citrix Issues"
|
placeholder="e.g., Citrix Issues"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'border-border'
|
'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -212,7 +212,7 @@ export function FolderEditModal({
|
|||||||
|
|
||||||
{/* Parent folder dropdown */}
|
{/* Parent folder dropdown */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor="folder-parent" className="block text-sm font-medium text-foreground">
|
<label htmlFor="folder-parent" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Parent Folder
|
Parent Folder
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -221,9 +221,9 @@ export function FolderEditModal({
|
|||||||
onChange={(e) => setParentId(e.target.value || null)}
|
onChange={(e) => setParentId(e.target.value || null)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-[#14161d] text-[#e2e5eb]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'border-border'
|
'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">None (root level)</option>
|
<option value="">None (root level)</option>
|
||||||
@@ -233,14 +233,14 @@ export function FolderEditModal({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
Folders can be nested up to 3 levels deep.
|
Folders can be nested up to 3 levels deep.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Color picker */}
|
{/* Color picker */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium text-foreground">Color</label>
|
<label className="block text-sm font-medium text-[#e2e5eb]">Color</label>
|
||||||
<div className="mt-2 flex flex-wrap gap-2">
|
<div className="mt-2 flex flex-wrap gap-2">
|
||||||
{FOLDER_COLORS.map((c) => (
|
{FOLDER_COLORS.map((c) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ function FolderItem({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-1 rounded-md py-1.5 text-sm',
|
'flex w-full items-center gap-1 rounded-md py-1.5 text-sm',
|
||||||
'transition-colors hover:bg-accent',
|
'transition-colors hover:bg-accent',
|
||||||
selectedFolderId === folder.id && 'bg-accent text-foreground font-medium'
|
selectedFolderId === folder.id && 'bg-accent text-[#e2e5eb] font-medium'
|
||||||
)}
|
)}
|
||||||
style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }}
|
style={{ paddingLeft: `${8 + depth * 16}px`, paddingRight: '8px' }}
|
||||||
>
|
>
|
||||||
@@ -139,7 +139,7 @@ function FolderItem({
|
|||||||
)}
|
)}
|
||||||
<Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} />
|
<Folder className="h-4 w-4 shrink-0" style={{ color: folder.color }} />
|
||||||
<span className="flex-1 truncate text-left">{folder.name}</span>
|
<span className="flex-1 truncate text-left">{folder.name}</span>
|
||||||
<span className="text-xs text-muted-foreground group-hover:hidden">{folder.tree_count}</span>
|
<span className="text-xs text-[#848b9b] group-hover:hidden">{folder.tree_count}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Folder menu button - replaces tree count on hover */}
|
{/* Folder menu button - replaces tree count on hover */}
|
||||||
@@ -161,8 +161,8 @@ function FolderItem({
|
|||||||
{menuOpenId === folder.id && (
|
{menuOpenId === folder.id && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
|
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-[#1e2130]',
|
||||||
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
'bg-[#14161d] py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -171,7 +171,7 @@ function FolderItem({
|
|||||||
onEditFolder(folder)
|
onEditFolder(folder)
|
||||||
onMenuToggle(null)
|
onMenuToggle(null)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3" />
|
<Pencil className="h-3 w-3" />
|
||||||
Edit
|
Edit
|
||||||
@@ -183,7 +183,7 @@ function FolderItem({
|
|||||||
onAddSubfolder(folder.id)
|
onAddSubfolder(folder.id)
|
||||||
onMenuToggle(null)
|
onMenuToggle(null)
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<FolderPlus className="h-3 w-3" />
|
<FolderPlus className="h-3 w-3" />
|
||||||
Add Subfolder
|
Add Subfolder
|
||||||
@@ -362,13 +362,13 @@ export function FolderSidebar({
|
|||||||
{/* Mobile backdrop */}
|
{/* Mobile backdrop */}
|
||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-xs md:hidden"
|
className="fixed inset-0 z-40 bg-black/80 md:hidden"
|
||||||
onClick={onMobileClose}
|
onClick={onMobileClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'w-56 shrink-0 border-r border-border bg-transparent',
|
'w-56 shrink-0 border-r border-[#1e2130] bg-transparent',
|
||||||
'hidden md:block',
|
'hidden md:block',
|
||||||
mobileOpen && 'fixed inset-y-0 left-0 z-50 block animate-slide-in-left md:relative md:animate-none'
|
mobileOpen && 'fixed inset-y-0 left-0 z-50 block animate-slide-in-left md:relative md:animate-none'
|
||||||
)}>
|
)}>
|
||||||
@@ -376,10 +376,10 @@ export function FolderSidebar({
|
|||||||
{/* Mobile close button */}
|
{/* Mobile close button */}
|
||||||
{mobileOpen && (
|
{mobileOpen && (
|
||||||
<div className="mb-3 flex items-center justify-between md:hidden">
|
<div className="mb-3 flex items-center justify-between md:hidden">
|
||||||
<span className="text-sm font-medium text-foreground">Folders</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">Folders</span>
|
||||||
<button
|
<button
|
||||||
onClick={onMobileClose}
|
onClick={onMobileClose}
|
||||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent"
|
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent"
|
||||||
aria-label="Close folders"
|
aria-label="Close folders"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
@@ -388,7 +388,7 @@ export function FolderSidebar({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="flex w-full items-center gap-2 text-sm font-medium text-foreground"
|
className="flex w-full items-center gap-2 text-sm font-medium text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
@@ -406,7 +406,7 @@ export function FolderSidebar({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
|
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
|
||||||
'transition-colors hover:bg-accent',
|
'transition-colors hover:bg-accent',
|
||||||
selectedFolderId === null && 'bg-accent text-foreground font-medium'
|
selectedFolderId === null && 'bg-accent text-[#e2e5eb] font-medium'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Folder className="h-4 w-4" />
|
<Folder className="h-4 w-4" />
|
||||||
@@ -415,7 +415,7 @@ export function FolderSidebar({
|
|||||||
|
|
||||||
{/* Loading state */}
|
{/* Loading state */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">Loading...</div>
|
<div className="px-2 py-1.5 text-sm text-[#848b9b]">Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* User folders (hierarchical) */}
|
{/* User folders (hierarchical) */}
|
||||||
@@ -445,7 +445,7 @@ export function FolderSidebar({
|
|||||||
onClick={() => onCreateFolder(null)}
|
onClick={() => onCreateFolder(null)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
|
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm',
|
||||||
'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground'
|
'text-[#848b9b] transition-colors hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -460,8 +460,8 @@ export function FolderSidebar({
|
|||||||
{contextMenu && (
|
{contextMenu && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'fixed z-50 w-44 rounded-md border border-border',
|
'fixed z-50 w-44 rounded-md border border-[#1e2130]',
|
||||||
'bg-card backdrop-blur-xs py-1 shadow-lg'
|
'bg-[#14161d] py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
style={{ left: contextMenu.x, top: contextMenu.y }}
|
style={{ left: contextMenu.x, top: contextMenu.y }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
@@ -471,7 +471,7 @@ export function FolderSidebar({
|
|||||||
onEditFolder(contextMenu.folder)
|
onEditFolder(contextMenu.folder)
|
||||||
closeContextMenu()
|
closeContextMenu()
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3" />
|
<Pencil className="h-3 w-3" />
|
||||||
Edit
|
Edit
|
||||||
@@ -482,7 +482,7 @@ export function FolderSidebar({
|
|||||||
handleAddSubfolder(contextMenu.folder.id)
|
handleAddSubfolder(contextMenu.folder.id)
|
||||||
closeContextMenu()
|
closeContextMenu()
|
||||||
}}
|
}}
|
||||||
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<FolderPlus className="h-3 w-3" />
|
<FolderPlus className="h-3 w-3" />
|
||||||
Add Subfolder
|
Add Subfolder
|
||||||
|
|||||||
@@ -54,19 +54,19 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl"
|
className="w-full max-w-md rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
<GitBranch className="h-4 w-4 text-[#848b9b]" />
|
||||||
<h2 className="text-sm font-semibold text-foreground">Fork Flow</h2>
|
<h2 className="text-sm font-semibold text-[#e2e5eb]">Fork Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -75,7 +75,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
{/* Body */}
|
{/* Body */}
|
||||||
<form onSubmit={handleSubmit} className="space-y-4 px-5 py-4">
|
<form onSubmit={handleSubmit} className="space-y-4 px-5 py-4">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="fork-name" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
<label htmlFor="fork-name" className="mb-1.5 block text-xs font-medium text-[#848b9b]">
|
||||||
Name <span className="text-red-400">*</span>
|
Name <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -87,16 +87,16 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
autoFocus
|
autoFocus
|
||||||
maxLength={255}
|
maxLength={255}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="fork-reason" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
<label htmlFor="fork-reason" className="mb-1.5 block text-xs font-medium text-[#848b9b]">
|
||||||
Reason for Forking{' '}
|
Reason for Forking{' '}
|
||||||
<span className="text-muted-foreground/60">(optional)</span>
|
<span className="text-[#848b9b]/60">(optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="fork-reason"
|
id="fork-reason"
|
||||||
@@ -105,8 +105,8 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
|
|||||||
rows={3}
|
rows={3}
|
||||||
placeholder="e.g. customizing for a specific client…"
|
placeholder="e.g. customizing for a specific client…"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full resize-none rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,19 +95,19 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl"
|
className="w-full max-w-md rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileUp className="h-4 w-4 text-muted-foreground" />
|
<FileUp className="h-4 w-4 text-[#848b9b]" />
|
||||||
<h2 className="text-sm font-semibold text-foreground">Import Flow</h2>
|
<h2 className="text-sm font-semibold text-[#e2e5eb]">Import Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -122,18 +122,18 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
'flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors cursor-pointer',
|
'flex flex-col items-center justify-center rounded-lg border-2 border-dashed px-4 py-8 text-center transition-colors cursor-pointer',
|
||||||
isDragging
|
isDragging
|
||||||
? 'border-primary/50 bg-primary/5'
|
? 'border-primary/50 bg-primary/5'
|
||||||
: 'border-border hover:border-white/[0.12]'
|
: 'border-[#1e2130] hover:border-white/[0.12]'
|
||||||
)}
|
)}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
|
onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
|
||||||
onDragLeave={() => setIsDragging(false)}
|
onDragLeave={() => setIsDragging(false)}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
<FileUp className="mb-2 h-8 w-8 text-muted-foreground" />
|
<FileUp className="mb-2 h-8 w-8 text-[#848b9b]" />
|
||||||
<p className="text-sm text-foreground">
|
<p className="text-sm text-[#e2e5eb]">
|
||||||
Drop .rfflow file here or <span className="text-primary cursor-pointer">browse</span>
|
Drop .rfflow file here or <span className="text-[#22d3ee] cursor-pointer">browse</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">JSON format</p>
|
<p className="mt-1 text-xs text-[#848b9b]">JSON format</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
@@ -155,7 +155,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Editable name */}
|
{/* Editable name */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="import-name" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
<label htmlFor="import-name" className="mb-1.5 block text-xs font-medium text-[#848b9b]">
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -165,8 +165,8 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
onChange={(e) => setNameOverride(e.target.value)}
|
onChange={(e) => setNameOverride(e.target.value)}
|
||||||
maxLength={255}
|
maxLength={255}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,31 +174,31 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
{/* Flow info */}
|
{/* Flow info */}
|
||||||
<div className="space-y-2 text-xs">
|
<div className="space-y-2 text-xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">Type:</span>
|
<span className="text-[#848b9b]">Type:</span>
|
||||||
<span className="rounded bg-primary/10 px-2 py-0.5 font-label text-primary">
|
<span className="rounded bg-[rgba(34,211,238,0.10)] px-2 py-0.5 font-sans text-xs text-[#22d3ee]">
|
||||||
{TYPE_LABELS[parsed.flow.tree_type] || parsed.flow.tree_type}
|
{TYPE_LABELS[parsed.flow.tree_type] || parsed.flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{parsed.flow.description && (
|
{parsed.flow.description && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Description:</span>
|
<span className="text-[#848b9b]">Description:</span>
|
||||||
<p className="mt-0.5 text-foreground line-clamp-2">{parsed.flow.description}</p>
|
<p className="mt-0.5 text-[#e2e5eb] line-clamp-2">{parsed.flow.description}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsed.flow.category && (
|
{parsed.flow.category && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">Category:</span>
|
<span className="text-[#848b9b]">Category:</span>
|
||||||
<span className="text-foreground">{parsed.flow.category.name}</span>
|
<span className="text-[#e2e5eb]">{parsed.flow.category.name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{parsed.flow.tags.length > 0 && (
|
{parsed.flow.tags.length > 0 && (
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
<span className="text-muted-foreground">Tags:</span>
|
<span className="text-[#848b9b]">Tags:</span>
|
||||||
{parsed.flow.tags.map((tag) => (
|
{parsed.flow.tags.map((tag) => (
|
||||||
<span key={tag} className="rounded bg-card border border-border px-2 py-0.5 font-label text-foreground">
|
<span key={tag} className="rounded bg-[#14161d] border border-[#1e2130] px-2 py-0.5 font-sans text-xs text-[#e2e5eb]">
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -207,26 +207,26 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
|
|||||||
|
|
||||||
{parsed.flow.author_name && (
|
{parsed.flow.author_name && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">Original author:</span>
|
<span className="text-[#848b9b]">Original author:</span>
|
||||||
<span className="text-foreground">{parsed.flow.author_name}</span>
|
<span className="text-[#e2e5eb]">{parsed.flow.author_name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">Version:</span>
|
<span className="text-[#848b9b]">Version:</span>
|
||||||
<span className="text-foreground">v{parsed.flow.version}</span>
|
<span className="text-[#e2e5eb]">v{parsed.flow.version}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
Flow will be imported as a <span className="font-medium text-foreground">draft</span>.
|
Flow will be imported as a <span className="font-medium text-[#e2e5eb]">draft</span>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-end gap-2 border-t border-border px-5 py-3">
|
<div className="flex justify-end gap-2 border-t border-[#1e2130] px-5 py-3">
|
||||||
{step === 'preview' && (
|
{step === 'preview' && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -115,18 +115,18 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
|
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
|
className="absolute inset-0 bg-black/80"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative w-full max-w-full rounded-t-2xl bg-card border border-border shadow-lg sm:max-w-lg sm:rounded-2xl">
|
<div className="relative w-full max-w-full rounded-t-2xl bg-[#14161d] border border-[#1e2130] shadow-lg sm:max-w-lg sm:rounded-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-6 py-4">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Share Tree</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Share Tree</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -136,9 +136,9 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
<div className="px-6 py-4 space-y-6">
|
<div className="px-6 py-4 space-y-6">
|
||||||
{/* Tree Info */}
|
{/* Tree Info */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-foreground">{tree.name}</h3>
|
<h3 className="font-medium text-[#e2e5eb]">{tree.name}</h3>
|
||||||
{tree.description && (
|
{tree.description && (
|
||||||
<p className="mt-1 text-sm text-muted-foreground line-clamp-2">
|
<p className="mt-1 text-sm text-[#848b9b] line-clamp-2">
|
||||||
{tree.description}
|
{tree.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -146,7 +146,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
|
|
||||||
{/* Visibility Settings */}
|
{/* Visibility Settings */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-2 block text-sm font-medium text-foreground">
|
<label className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Visibility
|
Visibility
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -157,14 +157,14 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
|
'flex w-full items-center gap-3 rounded-md border px-4 py-3 text-left transition-colors',
|
||||||
visibility === level
|
visibility === level
|
||||||
? 'border-border bg-accent text-foreground'
|
? 'border-[#1e2130] bg-accent text-[#e2e5eb]'
|
||||||
: 'border-border bg-transparent text-muted-foreground hover:border-primary/30 hover:bg-accent/50'
|
: 'border-[#1e2130] bg-transparent text-[#848b9b] hover:border-primary/30 hover:bg-accent/50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{getVisibilityIcon(level)}
|
{getVisibilityIcon(level)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="text-sm font-medium capitalize">{level}</div>
|
<div className="text-sm font-medium capitalize">{level}</div>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-[#848b9b]">
|
||||||
{getVisibilityDescription(level)}
|
{getVisibilityDescription(level)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,7 +179,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
{/* Share Link Generation */}
|
{/* Share Link Generation */}
|
||||||
{visibility !== 'private' && (
|
{visibility !== 'private' && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-2 block text-sm font-medium text-foreground">
|
<label className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Share Link
|
Share Link
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -190,11 +190,11 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
id="allow-forking"
|
id="allow-forking"
|
||||||
checked={allowForking}
|
checked={allowForking}
|
||||||
onChange={(e) => setAllowForking(e.target.checked)}
|
onChange={(e) => setAllowForking(e.target.checked)}
|
||||||
className="h-4 w-4 rounded border-border bg-card text-foreground focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-black"
|
className="h-4 w-4 rounded border-[#1e2130] bg-[#14161d] text-[#e2e5eb] focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-black"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="allow-forking"
|
htmlFor="allow-forking"
|
||||||
className="text-sm text-muted-foreground cursor-pointer"
|
className="text-sm text-[#848b9b] cursor-pointer"
|
||||||
>
|
>
|
||||||
Allow recipients to fork this tree
|
Allow recipients to fork this tree
|
||||||
</label>
|
</label>
|
||||||
@@ -214,20 +214,20 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
{/* Active Share Link */}
|
{/* Active Share Link */}
|
||||||
{activeShare && (
|
{activeShare && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 rounded-md border border-border bg-card p-3">
|
<div className="flex items-center gap-2 rounded-md border border-[#1e2130] bg-[#14161d] p-3">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={activeShare.share_url}
|
value={activeShare.share_url}
|
||||||
readOnly
|
readOnly
|
||||||
className="flex-1 bg-transparent text-sm text-foreground outline-hidden"
|
className="flex-1 bg-transparent text-sm text-[#e2e5eb] outline-hidden"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-border px-3 py-1.5 text-sm font-medium transition-colors',
|
'flex items-center gap-2 rounded-md border border-[#1e2130] px-3 py-1.5 text-sm font-medium transition-colors',
|
||||||
copied
|
copied
|
||||||
? 'border-green-500 bg-green-500/10 text-green-400'
|
? 'border-green-500 bg-green-500/10 text-green-400'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -243,13 +243,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
{activeShare.allow_forking
|
{activeShare.allow_forking
|
||||||
? 'Recipients can fork this tree'
|
? 'Recipients can fork this tree'
|
||||||
: 'Forking disabled for this share'}
|
: 'Forking disabled for this share'}
|
||||||
</p>
|
</p>
|
||||||
{shares.length > 1 && (
|
{shares.length > 1 && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
{shares.length} active share links
|
{shares.length} active share links
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -260,7 +260,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-end gap-3 border-t border-border px-6 py-4">
|
<div className="flex justify-end gap-3 border-t border-[#1e2130] px-6 py-4">
|
||||||
<Button variant="secondary" onClick={onClose}>
|
<Button variant="secondary" onClick={onClose}>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const sortOptions: { value: SortBy; label: string }[] = [
|
|||||||
export function SortDropdown({ value, onChange, className }: SortDropdownProps) {
|
export function SortDropdown({ value, onChange, className }: SortDropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative inline-flex items-center', className)}>
|
<div className={cn('relative inline-flex items-center', className)}>
|
||||||
<span className="mr-2 flex items-center gap-1.5 text-sm text-muted-foreground">
|
<span className="mr-2 flex items-center gap-1.5 text-sm text-[#848b9b]">
|
||||||
<ArrowUpDown className="h-4 w-4" />
|
<ArrowUpDown className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">Sort:</span>
|
<span className="hidden sm:inline">Sort:</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -29,8 +29,8 @@ export function SortDropdown({ value, onChange, className }: SortDropdownProps)
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value as SortBy)}
|
onChange={(e) => onChange(e.target.value as SortBy)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
|
'rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm',
|
||||||
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{sortOptions.map((option) => (
|
{sortOptions.map((option) => (
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ export function TreeGridView({
|
|||||||
{trees.map((tree) => (
|
{trees.map((tree) => (
|
||||||
<div
|
<div
|
||||||
key={tree.id}
|
key={tree.id}
|
||||||
className="relative bg-card border border-border rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
|
className="relative bg-[#14161d] border border-[#1e2130] rounded-2xl p-4 transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-md sm:p-6"
|
||||||
>
|
>
|
||||||
<div className="mb-2 flex items-start justify-between gap-2">
|
<div className="mb-2 flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-semibold text-foreground">{tree.name}</h3>
|
<h3 className="font-semibold text-[#e2e5eb]">{tree.name}</h3>
|
||||||
{tree.status === 'draft' && (
|
{tree.status === 'draft' && (
|
||||||
<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">
|
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
|
||||||
<FileText className="h-3 w-3" />
|
<FileText className="h-3 w-3" />
|
||||||
@@ -46,7 +46,7 @@ export function TreeGridView({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.tree_type === 'maintenance' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400">
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
Maintenance
|
Maintenance
|
||||||
</span>
|
</span>
|
||||||
@@ -60,21 +60,21 @@ export function TreeGridView({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{tree.is_public ? (
|
{tree.is_public ? (
|
||||||
<span title="Public tree">
|
<span title="Public tree">
|
||||||
<Globe className="h-4 w-4 text-muted-foreground" />
|
<Globe className="h-4 w-4 text-[#848b9b]" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-4 w-4 text-muted-foreground" />
|
<Lock className="h-4 w-4 text-[#848b9b]" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.category_info && (
|
{tree.category_info && (
|
||||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]">
|
||||||
{tree.category_info.name}
|
{tree.category_info.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
|
<p className="mb-3 text-sm text-[#848b9b] line-clamp-2">
|
||||||
{tree.description || 'No description available'}
|
{tree.description || 'No description available'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ export function TreeGridView({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-[#848b9b]">
|
||||||
v{tree.version} · {tree.usage_count} uses
|
v{tree.version} · {tree.usage_count} uses
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -95,8 +95,8 @@ export function TreeGridView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onExportTree(tree.id)}
|
onClick={() => onExportTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Export flow"
|
title="Export flow"
|
||||||
aria-label="Export flow"
|
aria-label="Export flow"
|
||||||
@@ -109,8 +109,8 @@ export function TreeGridView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onForkTree(tree.id)}
|
onClick={() => onForkTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Fork tree"
|
title="Fork tree"
|
||||||
aria-label="Fork tree"
|
aria-label="Fork tree"
|
||||||
@@ -122,8 +122,8 @@ export function TreeGridView({
|
|||||||
<Link
|
<Link
|
||||||
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Edit tree"
|
title="Edit tree"
|
||||||
aria-label="Edit tree"
|
aria-label="Edit tree"
|
||||||
@@ -136,7 +136,7 @@ export function TreeGridView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteTree(tree)}
|
onClick={() => onDeleteTree(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-red-400/10 hover:text-red-400'
|
'hover:bg-red-400/10 hover:text-red-400'
|
||||||
)}
|
)}
|
||||||
title="Delete tree"
|
title="Delete tree"
|
||||||
@@ -150,7 +150,7 @@ export function TreeGridView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onPrepareSession(tree)}
|
onClick={() => onPrepareSession(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
||||||
)}
|
)}
|
||||||
title="Prepare session for engineer"
|
title="Prepare session for engineer"
|
||||||
@@ -163,8 +163,8 @@ export function TreeGridView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
'rounded-md bg-[#22d3ee] px-3 py-2 text-sm font-medium text-white',
|
||||||
'hover:opacity-90'
|
'hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Start Session
|
Start Session
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ export function TreeListView({
|
|||||||
{trees.map((tree) => (
|
{trees.map((tree) => (
|
||||||
<div
|
<div
|
||||||
key={tree.id}
|
key={tree.id}
|
||||||
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-xs"
|
className="flex items-center gap-4 bg-[#14161d] border border-[#1e2130] rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-xs"
|
||||||
>
|
>
|
||||||
{/* Left: Name and Description */}
|
{/* Left: Name and Description */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h3 className="font-semibold text-foreground truncate">{tree.name}</h3>
|
<h3 className="font-semibold text-[#e2e5eb] truncate">{tree.name}</h3>
|
||||||
{tree.status === 'draft' && (
|
{tree.status === 'draft' && (
|
||||||
<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 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 shrink-0">
|
||||||
<FileText className="h-3 w-3" />
|
<FileText className="h-3 w-3" />
|
||||||
@@ -46,7 +46,7 @@ export function TreeListView({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.tree_type === 'maintenance' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
Maintenance
|
Maintenance
|
||||||
</span>
|
</span>
|
||||||
@@ -58,15 +58,15 @@ export function TreeListView({
|
|||||||
)}
|
)}
|
||||||
{tree.is_public ? (
|
{tree.is_public ? (
|
||||||
<span title="Public tree">
|
<span title="Public tree">
|
||||||
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<Globe className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<Lock className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground truncate">
|
<p className="text-sm text-[#848b9b] truncate">
|
||||||
{tree.description || 'No description available'}
|
{tree.description || 'No description available'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ export function TreeListView({
|
|||||||
{/* Center: Category and Tags */}
|
{/* Center: Category and Tags */}
|
||||||
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
|
<div className="hidden lg:flex items-center gap-2 min-w-0" style={{ maxWidth: '300px' }}>
|
||||||
{tree.category_info && (
|
{tree.category_info && (
|
||||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground whitespace-nowrap">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b] whitespace-nowrap">
|
||||||
{tree.category_info.name}
|
{tree.category_info.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -87,7 +87,7 @@ export function TreeListView({
|
|||||||
|
|
||||||
{/* Right: Metadata and Actions */}
|
{/* Right: Metadata and Actions */}
|
||||||
<div className="flex items-center gap-3 shrink-0">
|
<div className="flex items-center gap-3 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-[#848b9b]">
|
||||||
<span>v{tree.version}</span>
|
<span>v{tree.version}</span>
|
||||||
<span>{tree.usage_count} uses</span>
|
<span>{tree.usage_count} uses</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,8 +98,8 @@ export function TreeListView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onExportTree(tree.id)}
|
onClick={() => onExportTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Export flow"
|
title="Export flow"
|
||||||
aria-label="Export flow"
|
aria-label="Export flow"
|
||||||
@@ -112,8 +112,8 @@ export function TreeListView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onForkTree(tree.id)}
|
onClick={() => onForkTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Fork tree"
|
title="Fork tree"
|
||||||
aria-label="Fork tree"
|
aria-label="Fork tree"
|
||||||
@@ -126,8 +126,8 @@ export function TreeListView({
|
|||||||
<Link
|
<Link
|
||||||
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Edit tree"
|
title="Edit tree"
|
||||||
aria-label="Edit tree"
|
aria-label="Edit tree"
|
||||||
@@ -138,7 +138,7 @@ export function TreeListView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteTree(tree)}
|
onClick={() => onDeleteTree(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-red-500/20 hover:text-red-400'
|
'hover:bg-red-500/20 hover:text-red-400'
|
||||||
)}
|
)}
|
||||||
title="Delete tree"
|
title="Delete tree"
|
||||||
@@ -153,7 +153,7 @@ export function TreeListView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onPrepareSession(tree)}
|
onClick={() => onPrepareSession(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
||||||
)}
|
)}
|
||||||
title="Prepare session for engineer"
|
title="Prepare session for engineer"
|
||||||
@@ -166,8 +166,8 @@ export function TreeListView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md bg-gradient-brand px-3 py-1.5 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
'rounded-md bg-[#22d3ee] px-3 py-1.5 text-sm font-medium text-white',
|
||||||
'hover:opacity-90 whitespace-nowrap'
|
'hover:brightness-110 whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Start
|
Start
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ export function TreeTableView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto rounded-2xl border border-border">
|
<div className="overflow-x-auto rounded-2xl border border-[#1e2130]">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-accent/50 sticky top-0 z-10">
|
<thead className="bg-accent/50 sticky top-0 z-10">
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th
|
<th
|
||||||
className="px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
|
className="px-4 py-3 text-left text-sm font-medium text-[#848b9b] cursor-pointer hover:text-[#e2e5eb]"
|
||||||
onClick={() => handleSort('name')}
|
onClick={() => handleSort('name')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -86,11 +86,11 @@ export function TreeTableView({
|
|||||||
{getSortIcon('name')}
|
{getSortIcon('name')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
<th className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-[#848b9b]">
|
||||||
Description
|
Description
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
|
className="hidden lg:table-cell px-4 py-3 text-left text-sm font-medium text-[#848b9b] cursor-pointer hover:text-[#e2e5eb]"
|
||||||
onClick={() => handleSort('category')}
|
onClick={() => handleSort('category')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -98,11 +98,11 @@ export function TreeTableView({
|
|||||||
{getSortIcon('category')}
|
{getSortIcon('category')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground">
|
<th className="hidden xl:table-cell px-4 py-3 text-left text-sm font-medium text-[#848b9b]">
|
||||||
Tags
|
Tags
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
|
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-[#848b9b] cursor-pointer hover:text-[#e2e5eb]"
|
||||||
onClick={() => handleSort('version')}
|
onClick={() => handleSort('version')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
@@ -111,7 +111,7 @@ export function TreeTableView({
|
|||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
|
className="hidden sm:table-cell px-4 py-3 text-center text-sm font-medium text-[#848b9b] cursor-pointer hover:text-[#e2e5eb]"
|
||||||
onClick={() => handleSort('usage')}
|
onClick={() => handleSort('usage')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
@@ -120,7 +120,7 @@ export function TreeTableView({
|
|||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-muted-foreground cursor-pointer hover:text-foreground"
|
className="hidden md:table-cell px-4 py-3 text-left text-sm font-medium text-[#848b9b] cursor-pointer hover:text-[#e2e5eb]"
|
||||||
onClick={() => handleSort('updated')}
|
onClick={() => handleSort('updated')}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -128,17 +128,17 @@ export function TreeTableView({
|
|||||||
{getSortIcon('updated')}
|
{getSortIcon('updated')}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-medium text-muted-foreground">
|
<th className="px-4 py-3 text-right text-sm font-medium text-[#848b9b]">
|
||||||
Actions
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-transparent">
|
<tbody className="bg-transparent">
|
||||||
{trees.map((tree) => (
|
{trees.map((tree) => (
|
||||||
<tr key={tree.id} className="border-b border-border last:border-0 hover:bg-accent/50">
|
<tr key={tree.id} className="border-b border-[#1e2130] last:border-0 hover:bg-accent/50">
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium text-foreground truncate max-w-[200px]">
|
<span className="font-medium text-[#e2e5eb] truncate max-w-[200px]">
|
||||||
{tree.name}
|
{tree.name}
|
||||||
</span>
|
</span>
|
||||||
{tree.status === 'draft' && (
|
{tree.status === 'draft' && (
|
||||||
@@ -148,7 +148,7 @@ export function TreeTableView({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.tree_type === 'maintenance' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
|
||||||
<Wrench className="h-3 w-3" />
|
<Wrench className="h-3 w-3" />
|
||||||
Maintenance
|
Maintenance
|
||||||
</span>
|
</span>
|
||||||
@@ -160,23 +160,23 @@ export function TreeTableView({
|
|||||||
)}
|
)}
|
||||||
{tree.is_public ? (
|
{tree.is_public ? (
|
||||||
<span title="Public tree">
|
<span title="Public tree">
|
||||||
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<Globe className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<Lock className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
|
<td className="hidden md:table-cell px-4 py-3 text-sm text-[#848b9b]">
|
||||||
<span className="truncate block max-w-[250px]">
|
<span className="truncate block max-w-[250px]">
|
||||||
{tree.description || 'No description'}
|
{tree.description || 'No description'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden lg:table-cell px-4 py-3">
|
<td className="hidden lg:table-cell px-4 py-3">
|
||||||
{tree.category_info && (
|
{tree.category_info && (
|
||||||
<span className="inline-block rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
|
<span className="inline-block rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]">
|
||||||
{tree.category_info.name}
|
{tree.category_info.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -186,13 +186,13 @@ export function TreeTableView({
|
|||||||
<TagBadges tags={tree.tags} maxVisible={2} onTagClick={onTagClick} />
|
<TagBadges tags={tree.tags} maxVisible={2} onTagClick={onTagClick} />
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
|
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-[#848b9b]">
|
||||||
v{tree.version}
|
v{tree.version}
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-muted-foreground">
|
<td className="hidden sm:table-cell px-4 py-3 text-center text-sm text-[#848b9b]">
|
||||||
{tree.usage_count}
|
{tree.usage_count}
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden md:table-cell px-4 py-3 text-sm text-muted-foreground">
|
<td className="hidden md:table-cell px-4 py-3 text-sm text-[#848b9b]">
|
||||||
{formatDate(tree.updated_at)}
|
{formatDate(tree.updated_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
@@ -202,8 +202,8 @@ export function TreeTableView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onExportTree(tree.id)}
|
onClick={() => onExportTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Export flow"
|
title="Export flow"
|
||||||
aria-label="Export flow"
|
aria-label="Export flow"
|
||||||
@@ -216,8 +216,8 @@ export function TreeTableView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onForkTree(tree.id)}
|
onClick={() => onForkTree(tree.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Fork tree"
|
title="Fork tree"
|
||||||
aria-label="Fork tree"
|
aria-label="Fork tree"
|
||||||
@@ -230,8 +230,8 @@ export function TreeTableView({
|
|||||||
<Link
|
<Link
|
||||||
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
to={getTreeEditorPath(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Edit tree"
|
title="Edit tree"
|
||||||
aria-label="Edit tree"
|
aria-label="Edit tree"
|
||||||
@@ -242,7 +242,7 @@ export function TreeTableView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteTree(tree)}
|
onClick={() => onDeleteTree(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-red-500/20 hover:text-red-400'
|
'hover:bg-red-500/20 hover:text-red-400'
|
||||||
)}
|
)}
|
||||||
title="Delete tree"
|
title="Delete tree"
|
||||||
@@ -257,7 +257,7 @@ export function TreeTableView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onPrepareSession(tree)}
|
onClick={() => onPrepareSession(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-1.5 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
||||||
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
'hover:bg-cyan-500/10 hover:text-cyan-400 hover:border-cyan-500/30'
|
||||||
)}
|
)}
|
||||||
title="Prepare session for engineer"
|
title="Prepare session for engineer"
|
||||||
@@ -270,8 +270,8 @@ export function TreeTableView({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
onClick={() => onStartSession(tree.id, tree.tree_type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-lg shadow-primary/20',
|
'rounded-md bg-[#22d3ee] px-3 py-1.5 text-xs font-medium text-white',
|
||||||
'hover:opacity-90 whitespace-nowrap'
|
'hover:brightness-110 whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Start
|
Start
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ interface ViewToggleProps {
|
|||||||
|
|
||||||
export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
|
export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center gap-1 rounded-md border border-border p-1', className)}>
|
<div className={cn('flex items-center gap-1 rounded-md border border-[#1e2130] p-1', className)}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onChange('grid')}
|
onClick={() => onChange('grid')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded p-1.5 transition-colors',
|
'rounded p-1.5 transition-colors',
|
||||||
view === 'grid'
|
view === 'grid'
|
||||||
? 'bg-accent text-foreground border-border'
|
? 'bg-accent text-[#e2e5eb] border-[#1e2130]'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Grid view"
|
title="Grid view"
|
||||||
aria-label="Grid view"
|
aria-label="Grid view"
|
||||||
@@ -32,8 +32,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded p-1.5 transition-colors',
|
'rounded p-1.5 transition-colors',
|
||||||
view === 'list'
|
view === 'list'
|
||||||
? 'bg-accent text-foreground border-border'
|
? 'bg-accent text-[#e2e5eb] border-[#1e2130]'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="List view"
|
title="List view"
|
||||||
aria-label="List view"
|
aria-label="List view"
|
||||||
@@ -46,8 +46,8 @@ export function ViewToggle({ view, onChange, className }: ViewToggleProps) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded p-1.5 transition-colors',
|
'rounded p-1.5 transition-colors',
|
||||||
view === 'table'
|
view === 'table'
|
||||||
? 'bg-accent text-foreground border-border'
|
? 'bg-accent text-[#e2e5eb] border-[#1e2130]'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Table view"
|
title="Table view"
|
||||||
aria-label="Table view"
|
aria-label="Table view"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function CollapsibleEditorSection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-border">
|
<div className="border-b border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
@@ -45,14 +45,14 @@ export function CollapsibleEditorSection({
|
|||||||
>
|
>
|
||||||
<ChevronRight
|
<ChevronRight
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200',
|
'h-4 w-4 shrink-0 text-[#848b9b] transition-transform duration-200',
|
||||||
isExpanded && 'rotate-90'
|
isExpanded && 'rotate-90'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="shrink-0 text-muted-foreground">{icon}</span>
|
<span className="shrink-0 text-[#848b9b]">{icon}</span>
|
||||||
<span className="text-sm font-medium text-foreground">{title}</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">{title}</span>
|
||||||
{!isExpanded && (
|
{!isExpanded && (
|
||||||
<span className="min-w-0 truncate text-sm text-muted-foreground">
|
<span className="min-w-0 truncate text-sm text-[#848b9b]">
|
||||||
— {summary}
|
— {summary}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -26,49 +26,49 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
const needsOptions = field.field_type === 'select' || field.field_type === 'multi_select'
|
const needsOptions = field.field_type === 'select' || field.field_type === 'multi_select'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-xl p-3">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-3">
|
||||||
{/* Header row */}
|
{/* Header row */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-muted-foreground" />
|
<GripVertical className="h-4 w-4 shrink-0 cursor-grab text-[#848b9b]" />
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.label}
|
value={field.label}
|
||||||
onChange={(e) => onUpdate({ label: e.target.value })}
|
onChange={(e) => onUpdate({ label: e.target.value })}
|
||||||
placeholder="Field label"
|
placeholder="Field label"
|
||||||
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="min-w-0 flex-1 rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<select
|
<select
|
||||||
value={field.field_type}
|
value={field.field_type}
|
||||||
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
|
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
|
||||||
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{FIELD_TYPE_OPTIONS.map((opt) => (
|
{FIELD_TYPE_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label className="flex items-center gap-1 text-xs text-muted-foreground">
|
<label className="flex items-center gap-1 text-xs text-[#848b9b]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={field.required}
|
checked={field.required}
|
||||||
onChange={(e) => onUpdate({ required: e.target.checked })}
|
onChange={(e) => onUpdate({ required: e.target.checked })}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Req
|
Req
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpanded(!expanded)}
|
onClick={() => setExpanded(!expanded)}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{expanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
|
{expanded ? <ChevronUp className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-red-500/20 hover:text-red-400"
|
className="rounded p-1 text-[#848b9b] hover:bg-red-500/20 hover:text-red-400"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -76,66 +76,66 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
|
|
||||||
{/* Expanded details */}
|
{/* Expanded details */}
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-border pt-3">
|
<div className="mt-3 grid grid-cols-2 gap-3 border-t border-[#1e2130] pt-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Variable Name</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Variable Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.variable_name}
|
value={field.variable_name}
|
||||||
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
|
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
|
||||||
placeholder="e.g. server_name"
|
placeholder="e.g. server_name"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm font-mono text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
|
<p className="mt-0.5 text-[10px] text-[#848b9b]">Used as [VAR:{field.variable_name}]</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Placeholder</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Placeholder</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.placeholder || ''}
|
value={field.placeholder || ''}
|
||||||
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
|
||||||
placeholder="Hint text"
|
placeholder="Hint text"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Help Text</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Help Text</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.help_text || ''}
|
value={field.help_text || ''}
|
||||||
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
|
||||||
placeholder="Description or instructions"
|
placeholder="Description or instructions"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Default Value</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Default Value</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.default_value || ''}
|
value={field.default_value || ''}
|
||||||
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
|
||||||
placeholder="Pre-filled value"
|
placeholder="Pre-filled value"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Group Name</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Group Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={field.group_name || ''}
|
value={field.group_name || ''}
|
||||||
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
|
||||||
placeholder="e.g. Network Settings"
|
placeholder="e.g. Network Settings"
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{needsOptions && (
|
{needsOptions && (
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Options (one per line)</label>
|
<label className="mb-1 block text-xs text-[#848b9b]">Options (one per line)</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={(field.options || []).join('\n')}
|
value={(field.options || []).join('\n')}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -144,7 +144,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
|
|||||||
}}
|
}}
|
||||||
placeholder="Option 1 Option 2 Option 3"
|
placeholder="Option 1 Option 2 Option 3"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export function IntakeFormBuilder() {
|
|||||||
<div className="mb-3 flex items-center justify-end">
|
<div className="mb-3 flex items-center justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={addField}
|
onClick={addField}
|
||||||
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1.5 rounded-md border border-[#1e2130] px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
Add Field
|
Add Field
|
||||||
@@ -18,10 +18,10 @@ export function IntakeFormBuilder() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{intakeForm.length === 0 ? (
|
{intakeForm.length === 0 ? (
|
||||||
<div className="rounded-lg border border-dashed border-border bg-white/2 py-8 text-center">
|
<div className="rounded-lg border border-dashed border-[#1e2130] bg-white/2 py-8 text-center">
|
||||||
<FileText className="mx-auto mb-2 h-8 w-8 text-muted-foreground" />
|
<FileText className="mx-auto mb-2 h-8 w-8 text-[#848b9b]" />
|
||||||
<p className="text-sm text-muted-foreground">No intake form fields yet</p>
|
<p className="text-sm text-[#848b9b]">No intake form fields yet</p>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
Add fields to collect project data before the procedure starts
|
Add fields to collect project data before the procedure starts
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="py-3 text-sm text-muted-foreground">Loading schedule...</div>
|
<div className="py-3 text-sm text-[#848b9b]">Loading schedule...</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +150,11 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Frequency */}
|
{/* Frequency */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Frequency</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Frequency</label>
|
||||||
<select
|
<select
|
||||||
value={frequency}
|
value={frequency}
|
||||||
onChange={(e) => setFrequency(e.target.value)}
|
onChange={(e) => setFrequency(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{FREQUENCY_OPTIONS.map(opt => (
|
{FREQUENCY_OPTIONS.map(opt => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
@@ -165,36 +165,36 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
{/* Time */}
|
{/* Time */}
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Hour</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Hour</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
max={23}
|
max={23}
|
||||||
value={hour}
|
value={hour}
|
||||||
onChange={(e) => setHour(Number(e.target.value))}
|
onChange={(e) => setHour(Number(e.target.value))}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Minute</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Minute</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
max={59}
|
max={59}
|
||||||
value={minute}
|
value={minute}
|
||||||
onChange={(e) => setMinute(Number(e.target.value))}
|
onChange={(e) => setMinute(Number(e.target.value))}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Timezone */}
|
{/* Timezone */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Timezone</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Timezone</label>
|
||||||
<select
|
<select
|
||||||
value={timezone}
|
value={timezone}
|
||||||
onChange={(e) => setTimezone(e.target.value)}
|
onChange={(e) => setTimezone(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
{TIMEZONE_OPTIONS.map(tz => (
|
{TIMEZONE_OPTIONS.map(tz => (
|
||||||
<option key={tz} value={tz}>{tz}</option>
|
<option key={tz} value={tz}>{tz}</option>
|
||||||
@@ -205,11 +205,11 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
{/* Target List */}
|
{/* Target List */}
|
||||||
{targetLists.length > 0 && (
|
{targetLists.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Target List (optional)</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Target List (optional)</label>
|
||||||
<select
|
<select
|
||||||
value={selectedTargetListId}
|
value={selectedTargetListId}
|
||||||
onChange={(e) => setSelectedTargetListId(e.target.value)}
|
onChange={(e) => setSelectedTargetListId(e.target.value)}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None — manual targets only</option>
|
<option value="">None — manual targets only</option>
|
||||||
{targetLists.map(tl => (
|
{targetLists.map(tl => (
|
||||||
@@ -226,15 +226,15 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
'flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
treeId
|
treeId
|
||||||
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90'
|
? 'bg-[#22d3ee] text-white hover:brightness-110'
|
||||||
: 'cursor-not-allowed border border-border bg-card text-muted-foreground opacity-50'
|
: 'cursor-not-allowed border border-[#1e2130] bg-[#14161d] text-[#848b9b] opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
{isSaving ? 'Saving...' : schedule ? 'Update Schedule' : 'Create Schedule'}
|
{isSaving ? 'Saving...' : schedule ? 'Update Schedule' : 'Create Schedule'}
|
||||||
</button>
|
</button>
|
||||||
{!treeId && (
|
{!treeId && (
|
||||||
<p className="text-xs text-muted-foreground">Save the flow first to configure a schedule.</p>
|
<p className="text-xs text-[#848b9b]">Save the flow first to configure a schedule.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { FallbackSteps } from '@/components/procedural/FallbackSteps'
|
|||||||
|
|
||||||
const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [
|
const CONTENT_TYPE_OPTIONS: { value: StepContentType; label: string; color: string }[] = [
|
||||||
{ value: 'action', label: 'Action', color: 'text-blue-400' },
|
{ value: 'action', label: 'Action', color: 'text-blue-400' },
|
||||||
{ value: 'informational', label: 'Info', color: 'text-muted-foreground' },
|
{ value: 'informational', label: 'Info', color: 'text-[#848b9b]' },
|
||||||
{ value: 'verification', label: 'Verify', color: 'text-emerald-400' },
|
{ value: 'verification', label: 'Verify', color: 'text-emerald-400' },
|
||||||
{ value: 'warning', label: 'Warning', color: 'text-yellow-400' },
|
{ value: 'warning', label: 'Warning', color: 'text-yellow-400' },
|
||||||
]
|
]
|
||||||
@@ -25,24 +25,24 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
// Section header steps get a minimal editor
|
// Section header steps get a minimal editor
|
||||||
if (step.type === 'section_header') {
|
if (step.type === 'section_header') {
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<span className="text-sm font-medium text-muted-foreground">Edit Section Header</span>
|
<span className="text-sm font-medium text-[#848b9b]">Edit Section Header</span>
|
||||||
<button
|
<button
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<ChevronUp className="h-4 w-4" />
|
<ChevronUp className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Title</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Title</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => onUpdate({ title: e.target.value })}
|
onChange={(e) => onUpdate({ title: e.target.value })}
|
||||||
placeholder="Section title"
|
placeholder="Section title"
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,18 +50,18 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-accent text-xs font-medium text-foreground">
|
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-accent text-xs font-medium text-[#e2e5eb]">
|
||||||
{stepNumber}
|
{stepNumber}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-medium text-foreground">Edit Step</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">Edit Step</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<ChevronUp className="h-4 w-4" />
|
<ChevronUp className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -70,18 +70,18 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Title</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Title</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => onUpdate({ title: e.target.value })}
|
onChange={(e) => onUpdate({ title: e.target.value })}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Est. Minutes */}
|
{/* Est. Minutes */}
|
||||||
<div className="w-40">
|
<div className="w-40">
|
||||||
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-[#848b9b]">
|
||||||
<Clock className="h-3 w-3" />
|
<Clock className="h-3 w-3" />
|
||||||
Est. Minutes
|
Est. Minutes
|
||||||
</label>
|
</label>
|
||||||
@@ -91,28 +91,28 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
|
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
|
||||||
placeholder="—"
|
placeholder="—"
|
||||||
min={1}
|
min={1}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Description / Instructions</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Description / Instructions</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={step.description || ''}
|
value={step.description || ''}
|
||||||
onChange={(e) => onUpdate({ description: e.target.value })}
|
onChange={(e) => onUpdate({ description: e.target.value })}
|
||||||
placeholder="Step instructions. Use [VAR:name] for variables."
|
placeholder="Step instructions. Use [VAR:name] for variables."
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
{availableVariables.length > 0 && (
|
{availableVariables.length > 0 && (
|
||||||
<div className="mt-1 flex flex-wrap gap-1">
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
<span className="text-[10px] text-muted-foreground">Variables:</span>
|
<span className="text-[10px] text-[#848b9b]">Variables:</span>
|
||||||
{availableVariables.map((v) => (
|
{availableVariables.map((v) => (
|
||||||
<button
|
<button
|
||||||
key={v.variable_name}
|
key={v.variable_name}
|
||||||
onClick={() => onUpdate({ description: (step.description || '') + `[VAR:${v.variable_name}]` })}
|
onClick={() => onUpdate({ description: (step.description || '') + `[VAR:${v.variable_name}]` })}
|
||||||
className="rounded bg-accent/50 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground hover:bg-accent hover:text-muted-foreground"
|
className="rounded bg-accent/50 px-1.5 py-0.5 font-mono text-[10px] text-[#848b9b] hover:bg-accent hover:text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{v.variable_name}
|
{v.variable_name}
|
||||||
</button>
|
</button>
|
||||||
@@ -123,7 +123,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
|
|
||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-[#848b9b]">
|
||||||
<Terminal className="h-3 w-3" />
|
<Terminal className="h-3 w-3" />
|
||||||
Commands (optional)
|
Commands (optional)
|
||||||
</label>
|
</label>
|
||||||
@@ -132,7 +132,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
|
||||||
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
|
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 font-mono text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowMore(!showMore)}
|
onClick={() => setShowMore(!showMore)}
|
||||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-muted-foreground"
|
className="flex items-center gap-1.5 text-xs text-[#848b9b] hover:text-[#848b9b]"
|
||||||
>
|
>
|
||||||
<Settings2 className="h-3 w-3" />
|
<Settings2 className="h-3 w-3" />
|
||||||
More Options
|
More Options
|
||||||
@@ -148,10 +148,10 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showMore && (
|
{showMore && (
|
||||||
<div className="space-y-4 border-t border-border pt-4">
|
<div className="space-y-4 border-t border-[#1e2130] pt-4">
|
||||||
{/* Content Type */}
|
{/* Content Type */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Content Type</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Content Type</label>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{CONTENT_TYPE_OPTIONS.map((opt) => (
|
{CONTENT_TYPE_OPTIONS.map((opt) => (
|
||||||
<button
|
<button
|
||||||
@@ -161,7 +161,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
'rounded px-2 py-1 text-xs font-medium transition-colors',
|
'rounded px-2 py-1 text-xs font-medium transition-colors',
|
||||||
step.content_type === opt.value
|
step.content_type === opt.value
|
||||||
? 'bg-white/15 ' + opt.color
|
? 'bg-white/15 ' + opt.color
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-muted-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
@@ -182,27 +182,27 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
|
||||||
placeholder="Caution: This will restart the service..."
|
placeholder="Caution: This will restart the service..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-hidden focus:ring-1 focus:ring-yellow-400/20"
|
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-yellow-400/30 focus:outline-hidden focus:ring-1 focus:ring-yellow-400/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Expected Outcome */}
|
{/* Expected Outcome */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Expected Outcome (optional)</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Expected Outcome (optional)</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={step.expected_outcome || ''}
|
value={step.expected_outcome || ''}
|
||||||
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
|
||||||
placeholder="Server should respond with..."
|
placeholder="Server should respond with..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Verification */}
|
{/* Verification */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-[#848b9b]">
|
||||||
<CheckSquare className="h-3 w-3" />
|
<CheckSquare className="h-3 w-3" />
|
||||||
Verification Prompt (optional)
|
Verification Prompt (optional)
|
||||||
</label>
|
</label>
|
||||||
@@ -211,15 +211,15 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.verification_prompt || ''}
|
value={step.verification_prompt || ''}
|
||||||
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
|
||||||
placeholder="Confirm the role was installed"
|
placeholder="Confirm the role was installed"
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Verification Type</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Verification Type</label>
|
||||||
<select
|
<select
|
||||||
value={step.verification_type || ''}
|
value={step.verification_type || ''}
|
||||||
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
|
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
<option value="checkbox">Checkbox (confirm done)</option>
|
<option value="checkbox">Checkbox (confirm done)</option>
|
||||||
@@ -231,7 +231,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
{/* Reference URL + Notes toggle */}
|
{/* Reference URL + Notes toggle */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1 text-xs font-medium text-[#848b9b]">
|
||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
Reference URL (optional)
|
Reference URL (optional)
|
||||||
</label>
|
</label>
|
||||||
@@ -240,16 +240,16 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
value={step.reference_url || ''}
|
value={step.reference_url || ''}
|
||||||
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
|
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
|
||||||
placeholder="https://learn.microsoft.com/..."
|
placeholder="https://learn.microsoft.com/..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#848b9b]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={step.notes_enabled !== false}
|
checked={step.notes_enabled !== false}
|
||||||
onChange={(e) => onUpdate({ notes_enabled: e.target.checked })}
|
onChange={(e) => onUpdate({ notes_enabled: e.target.checked })}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Allow tech notes
|
Allow tech notes
|
||||||
</label>
|
</label>
|
||||||
@@ -258,7 +258,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
|
|
||||||
{/* Library Visibility — procedure_step nodes only */}
|
{/* Library Visibility — procedure_step nodes only */}
|
||||||
{step.type === 'procedure_step' && <div>
|
{step.type === 'procedure_step' && <div>
|
||||||
<label htmlFor="library-visibility" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
<label htmlFor="library-visibility" className="mb-1.5 block text-xs font-medium text-[#848b9b]">
|
||||||
Library Visibility
|
Library Visibility
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -267,13 +267,13 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
|
|||||||
onChange={(e) => onUpdate({
|
onChange={(e) => onUpdate({
|
||||||
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
|
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
|
||||||
})}
|
})}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">Inherit from flow</option>
|
<option value="">Inherit from flow</option>
|
||||||
<option value="team">Team only</option>
|
<option value="team">Team only</option>
|
||||||
<option value="public">Public</option>
|
<option value="public">Public</option>
|
||||||
</select>
|
</select>
|
||||||
<p className="mt-1 text-[10px] text-muted-foreground">
|
<p className="mt-1 text-[10px] text-[#848b9b]">
|
||||||
Controls visibility in the step library. Defaults to the flow's own visibility setting.
|
Controls visibility in the step library. Defaults to the flow's own visibility setting.
|
||||||
</p>
|
</p>
|
||||||
</div>}
|
</div>}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { cn } from '@/lib/utils'
|
|||||||
|
|
||||||
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; label: string }> = {
|
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; label: string }> = {
|
||||||
action: { icon: Zap, color: 'text-blue-400', label: 'Action' },
|
action: { icon: Zap, color: 'text-blue-400', label: 'Action' },
|
||||||
informational: { icon: Info, color: 'text-muted-foreground', label: 'Info' },
|
informational: { icon: Info, color: 'text-[#848b9b]', label: 'Info' },
|
||||||
verification: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Verify' },
|
verification: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Verify' },
|
||||||
warning: { icon: AlertTriangle, color: 'text-yellow-400', label: 'Warning' },
|
warning: { icon: AlertTriangle, color: 'text-yellow-400', label: 'Warning' },
|
||||||
}
|
}
|
||||||
@@ -104,9 +104,9 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Shield className="h-5 w-5 text-muted-foreground" />
|
<Shield className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Steps</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Steps</h2>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''}
|
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''}
|
||||||
{totalMinutes > 0 ? ` \u00b7 ~${totalMinutes} min` : ''})
|
{totalMinutes > 0 ? ` \u00b7 ~${totalMinutes} min` : ''})
|
||||||
</span>
|
</span>
|
||||||
@@ -114,14 +114,14 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => addSectionHeader()}
|
onClick={() => addSectionHeader()}
|
||||||
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1.5 rounded-md border border-[#1e2130] px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<SeparatorHorizontal className="h-3.5 w-3.5" />
|
<SeparatorHorizontal className="h-3.5 w-3.5" />
|
||||||
Add Section
|
Add Section
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => addStep()}
|
onClick={() => addStep()}
|
||||||
className="flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1.5 rounded-md border border-[#1e2130] px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
Add Step
|
Add Step
|
||||||
@@ -138,17 +138,17 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={step.id}
|
key={step.id}
|
||||||
className="flex items-center gap-2 rounded-lg border border-dashed border-border bg-accent/50 px-3 py-2"
|
className="flex items-center gap-2 rounded-lg border border-dashed border-[#1e2130] bg-accent/50 px-3 py-2"
|
||||||
>
|
>
|
||||||
<CheckCircle2 className="h-4 w-4 text-emerald-400/50" />
|
<CheckCircle2 className="h-4 w-4 text-emerald-400/50" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={step.title}
|
value={step.title}
|
||||||
onChange={(e) => updateStep(step.id, { title: e.target.value })}
|
onChange={(e) => updateStep(step.id, { title: e.target.value })}
|
||||||
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-hidden"
|
className="flex-1 bg-transparent text-sm text-[#848b9b] focus:outline-hidden"
|
||||||
placeholder="Procedure Complete"
|
placeholder="Procedure Complete"
|
||||||
/>
|
/>
|
||||||
<span className="text-[10px] text-muted-foreground">END</span>
|
<span className="text-[10px] text-[#848b9b]">END</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -178,24 +178,24 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
return (
|
return (
|
||||||
<SortableStepWrapper key={step.id} id={step.id}>
|
<SortableStepWrapper key={step.id} id={step.id}>
|
||||||
{({ dragHandleProps }) => (
|
{({ dragHandleProps }) => (
|
||||||
<div className="group flex items-center gap-2 border-b border-border pb-1 pt-3" data-step-id={step.id}>
|
<div className="group flex items-center gap-2 border-b border-[#1e2130] pb-1 pt-3" data-step-id={step.id}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="shrink-0 cursor-grab touch-none text-muted-foreground active:cursor-grabbing"
|
className="shrink-0 cursor-grab touch-none text-[#848b9b] active:cursor-grabbing"
|
||||||
aria-label={`Drag to reorder section: ${step.title || 'Untitled Section'}`}
|
aria-label={`Drag to reorder section: ${step.title || 'Untitled Section'}`}
|
||||||
{...dragHandleProps}
|
{...dragHandleProps}
|
||||||
>
|
>
|
||||||
<GripVertical className="h-4 w-4" />
|
<GripVertical className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-muted-foreground hover:text-muted-foreground"
|
className="min-w-0 flex-1 cursor-pointer text-xs font-semibold uppercase tracking-wider text-[#848b9b] hover:text-[#848b9b]"
|
||||||
onClick={() => setExpandedStepId(step.id)}
|
onClick={() => setExpandedStepId(step.id)}
|
||||||
>
|
>
|
||||||
{step.title || 'Untitled Section'}
|
{step.title || 'Untitled Section'}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeStep(step.id)}
|
onClick={() => removeStep(step.id)}
|
||||||
className="shrink-0 rounded p-1 text-muted-foreground opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
className="shrink-0 rounded p-1 text-[#848b9b] opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -237,7 +237,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
{({ dragHandleProps }) => (
|
{({ dragHandleProps }) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'group flex flex-col rounded-xl border border-border px-3 py-2.5 transition-colors',
|
'group flex flex-col rounded-xl border border-[#1e2130] px-3 py-2.5 transition-colors',
|
||||||
'hover:border-primary/30 hover:bg-accent/50',
|
'hover:border-primary/30 hover:bg-accent/50',
|
||||||
isGhost && 'border-l-2 border-dashed border-l-primary/40! opacity-60'
|
isGhost && 'border-l-2 border-dashed border-l-primary/40! opacity-60'
|
||||||
)}
|
)}
|
||||||
@@ -247,14 +247,14 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="shrink-0 cursor-grab touch-none text-muted-foreground active:cursor-grabbing"
|
className="shrink-0 cursor-grab touch-none text-[#848b9b] active:cursor-grabbing"
|
||||||
aria-label={`Drag to reorder step ${stepNumber}: ${step.title || 'Untitled step'}`}
|
aria-label={`Drag to reorder step ${stepNumber}: ${step.title || 'Untitled step'}`}
|
||||||
{...dragHandleProps}
|
{...dragHandleProps}
|
||||||
>
|
>
|
||||||
<GripVertical className="h-4 w-4" />
|
<GripVertical className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent text-xs font-medium text-muted-foreground">
|
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent text-xs font-medium text-[#848b9b]">
|
||||||
{stepNumber}
|
{stepNumber}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -263,35 +263,35 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className="min-w-0 flex-1 cursor-pointer truncate text-sm text-foreground"
|
className="min-w-0 flex-1 cursor-pointer truncate text-sm text-[#e2e5eb]"
|
||||||
onClick={() => setExpandedStepId(step.id)}
|
onClick={() => setExpandedStepId(step.id)}
|
||||||
>
|
>
|
||||||
{step.title || 'Untitled step'}
|
{step.title || 'Untitled step'}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{step.estimated_minutes && (
|
{step.estimated_minutes && (
|
||||||
<span className="shrink-0 text-[10px] text-muted-foreground">
|
<span className="shrink-0 text-[10px] text-[#848b9b]">
|
||||||
~{step.estimated_minutes}m
|
~{step.estimated_minutes}m
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpandedStepId(step.id)}
|
onClick={() => setExpandedStepId(step.id)}
|
||||||
className="shrink-0 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="shrink-0 rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<ChevronDown className="h-3.5 w-3.5" />
|
<ChevronDown className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => removeStep(step.id)}
|
onClick={() => removeStep(step.id)}
|
||||||
className="shrink-0 rounded p-1 text-muted-foreground opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
className="shrink-0 rounded p-1 text-[#848b9b] opacity-0 hover:bg-red-500/20 hover:text-red-400 group-hover:opacity-100"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isGhost && (
|
{isGhost && (
|
||||||
<div className="mt-2 flex gap-2 border-t border-border/50 pt-2">
|
<div className="mt-2 flex gap-2 border-t border-[#1e2130]/50 pt-2">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -324,7 +324,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
|
|||||||
{/* Add step button at bottom */}
|
{/* Add step button at bottom */}
|
||||||
<button
|
<button
|
||||||
onClick={() => addStep()}
|
onClick={() => addStep()}
|
||||||
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-border py-2 text-sm text-muted-foreground transition-colors hover:border-primary/30 hover:text-muted-foreground"
|
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-[#1e2130] py-2 text-sm text-[#848b9b] transition-colors hover:border-primary/30 hover:text-[#848b9b]"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
Add Step
|
Add Step
|
||||||
|
|||||||
@@ -59,38 +59,38 @@ export function CompletionSummary({
|
|||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-emerald-400/10">
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-emerald-400/10">
|
||||||
<CheckCircle2 className="h-8 w-8 text-emerald-400" />
|
<CheckCircle2 className="h-8 w-8 text-emerald-400" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-foreground">Procedure Complete</h1>
|
<h1 className="text-2xl font-bold text-[#e2e5eb]">Procedure Complete</h1>
|
||||||
<p className="mt-1 text-muted-foreground">{treeName}</p>
|
<p className="mt-1 text-[#848b9b]">{treeName}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Summary stats */}
|
{/* Summary stats */}
|
||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div className="bg-card border border-border rounded-xl p-3 text-center">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-3 text-center">
|
||||||
<CheckCircle2 className="mx-auto mb-1 h-5 w-5 text-emerald-400" />
|
<CheckCircle2 className="mx-auto mb-1 h-5 w-5 text-emerald-400" />
|
||||||
<div className="text-lg font-semibold text-foreground">{procedureSteps.length}</div>
|
<div className="text-lg font-semibold text-[#e2e5eb]">{procedureSteps.length}</div>
|
||||||
<div className="text-xs text-muted-foreground">Steps Completed</div>
|
<div className="text-xs text-[#848b9b]">Steps Completed</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-card border border-border rounded-xl p-3 text-center">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-3 text-center">
|
||||||
<Clock className="mx-auto mb-1 h-5 w-5 text-muted-foreground" />
|
<Clock className="mx-auto mb-1 h-5 w-5 text-[#848b9b]" />
|
||||||
<div className="text-lg font-semibold text-foreground">{formatTime(totalMinutes)}</div>
|
<div className="text-lg font-semibold text-[#e2e5eb]">{formatTime(totalMinutes)}</div>
|
||||||
<div className="text-xs text-muted-foreground">Total Time</div>
|
<div className="text-xs text-[#848b9b]">Total Time</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-card border border-border rounded-xl p-3 text-center">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-3 text-center">
|
||||||
<FileText className="mx-auto mb-1 h-5 w-5 text-muted-foreground" />
|
<FileText className="mx-auto mb-1 h-5 w-5 text-[#848b9b]" />
|
||||||
<div className="text-lg font-semibold text-foreground">{Object.keys(variables).length}</div>
|
<div className="text-lg font-semibold text-[#e2e5eb]">{Object.keys(variables).length}</div>
|
||||||
<div className="text-xs text-muted-foreground">Parameters</div>
|
<div className="text-xs text-[#848b9b]">Parameters</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Project parameters */}
|
{/* Project parameters */}
|
||||||
{Object.keys(variables).length > 0 && (
|
{Object.keys(variables).length > 0 && (
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<h3 className="mb-3 text-sm font-semibold text-muted-foreground">Project Parameters</h3>
|
<h3 className="mb-3 text-sm font-semibold text-[#848b9b]">Project Parameters</h3>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{Object.entries(variables).map(([key, value]) => (
|
{Object.entries(variables).map(([key, value]) => (
|
||||||
<div key={key} className="flex items-baseline justify-between gap-4 text-sm">
|
<div key={key} className="flex items-baseline justify-between gap-4 text-sm">
|
||||||
<span className="font-mono text-muted-foreground">{key}</span>
|
<span className="font-mono text-[#848b9b]">{key}</span>
|
||||||
<span className="text-right text-muted-foreground">{value}</span>
|
<span className="text-right text-[#848b9b]">{value}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -98,8 +98,8 @@ export function CompletionSummary({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step details */}
|
{/* Step details */}
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<h3 className="mb-3 text-sm font-semibold text-muted-foreground">Step Summary</h3>
|
<h3 className="mb-3 text-sm font-semibold text-[#848b9b]">Step Summary</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{procedureSteps.map((step, index) => {
|
{procedureSteps.map((step, index) => {
|
||||||
const completion = completions.get(step.id)
|
const completion = completions.get(step.id)
|
||||||
@@ -108,15 +108,15 @@ export function CompletionSummary({
|
|||||||
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-emerald-400" />
|
<CheckCircle2 className="mt-0.5 h-4 w-4 shrink-0 text-emerald-400" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
{index + 1}. {step.title}
|
{index + 1}. {step.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{completion?.notes && (
|
{completion?.notes && (
|
||||||
<p className="mt-0.5 text-xs text-muted-foreground">Note: {completion.notes}</p>
|
<p className="mt-0.5 text-xs text-[#848b9b]">Note: {completion.notes}</p>
|
||||||
)}
|
)}
|
||||||
{completion?.verificationValue && (
|
{completion?.verificationValue && (
|
||||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
<p className="mt-0.5 text-xs text-[#848b9b]">
|
||||||
Verified: {completion.verificationValue}
|
Verified: {completion.verificationValue}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function FallbackSteps({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setExpanded((v) => !v)}
|
onClick={() => setExpanded((v) => !v)}
|
||||||
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-amber-400/80 transition-colors"
|
className="flex items-center gap-2 text-sm text-[#848b9b] hover:text-amber-400/80 transition-colors"
|
||||||
>
|
>
|
||||||
<AlertCircle className="h-4 w-4 text-amber-400/80 shrink-0" />
|
<AlertCircle className="h-4 w-4 text-amber-400/80 shrink-0" />
|
||||||
<span>{toggleLabel}</span>
|
<span>{toggleLabel}</span>
|
||||||
@@ -64,7 +64,7 @@ export function FallbackSteps({
|
|||||||
key={fbStep.id}
|
key={fbStep.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg border p-3 transition-colors',
|
'rounded-lg border p-3 transition-colors',
|
||||||
'bg-white/[0.02] border-border/50',
|
'bg-white/[0.02] border-[#1e2130]/50',
|
||||||
isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
|
isCompleted && 'border-emerald-500/30 bg-emerald-500/5'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -76,12 +76,12 @@ export function FallbackSteps({
|
|||||||
value={fbStep.title}
|
value={fbStep.title}
|
||||||
onChange={(e) => onUpdate?.(index, { title: e.target.value })}
|
onChange={(e) => onUpdate?.(index, { title: e.target.value })}
|
||||||
placeholder="Fallback step title"
|
placeholder="Fallback step title"
|
||||||
className="flex-1 rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="flex-1 rounded border border-[#1e2130] bg-[#14161d] px-2.5 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onRemove?.(index)}
|
onClick={() => onRemove?.(index)}
|
||||||
className="shrink-0 rounded p-1.5 text-muted-foreground hover:bg-rose-500/10 hover:text-rose-400 transition-colors"
|
className="shrink-0 rounded p-1.5 text-[#848b9b] hover:bg-rose-500/10 hover:text-rose-400 transition-colors"
|
||||||
title="Remove fallback step"
|
title="Remove fallback step"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
@@ -94,17 +94,17 @@ export function FallbackSteps({
|
|||||||
}
|
}
|
||||||
placeholder="Describe this alternative approach..."
|
placeholder="Describe this alternative approach..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full rounded border border-border bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-2.5 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// Execute mode
|
// Execute mode
|
||||||
<div>
|
<div>
|
||||||
<p className={cn('text-sm font-medium', isCompleted ? 'text-emerald-400' : 'text-foreground')}>
|
<p className={cn('text-sm font-medium', isCompleted ? 'text-emerald-400' : 'text-[#e2e5eb]')}>
|
||||||
{fbStep.title}
|
{fbStep.title}
|
||||||
</p>
|
</p>
|
||||||
{fbStep.description && (
|
{fbStep.description && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground">{fbStep.description}</p>
|
<p className="mt-1 text-xs text-[#848b9b]">{fbStep.description}</p>
|
||||||
)}
|
)}
|
||||||
{!isCompleted && (
|
{!isCompleted && (
|
||||||
<div className="mt-3 flex gap-2">
|
<div className="mt-3 flex gap-2">
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function InlineVariablePrompt({
|
|||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (!value) setIsEditing(false)
|
if (!value) setIsEditing(false)
|
||||||
}}
|
}}
|
||||||
className="rounded-md border border-cyan-500/40 bg-cyan-500/5 px-2.5 py-1 text-sm text-foreground shadow-[0_0_12px_rgba(6,182,212,0.15)] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
className="rounded-md border border-cyan-500/40 bg-cyan-500/5 px-2.5 py-1 text-sm text-[#e2e5eb] shadow-[0_0_12px_rgba(6,182,212,0.15)] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||||
>
|
>
|
||||||
<option value="">{placeholder}</option>
|
<option value="">{placeholder}</option>
|
||||||
{options.map((opt) => (
|
{options.map((opt) => (
|
||||||
@@ -117,10 +117,10 @@ export function InlineVariablePrompt({
|
|||||||
onBlur={handleSubmit}
|
onBlur={handleSubmit}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="w-48 rounded-md border border-cyan-500/40 bg-cyan-500/5 px-2.5 py-1 text-sm text-foreground shadow-[0_0_12px_rgba(6,182,212,0.15)] placeholder:text-muted-foreground focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
className="w-48 rounded-md border border-cyan-500/40 bg-cyan-500/5 px-2.5 py-1 text-sm text-[#e2e5eb] shadow-[0_0_12px_rgba(6,182,212,0.15)] placeholder:text-[#848b9b] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||||
/>
|
/>
|
||||||
{helpText && (
|
{helpText && (
|
||||||
<span className="absolute -bottom-5 left-0 text-[0.625rem] text-muted-foreground whitespace-nowrap">
|
<span className="absolute -bottom-5 left-0 text-[0.625rem] text-[#848b9b] whitespace-nowrap">
|
||||||
{helpText}
|
{helpText}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
const error = errors[field.variable_name]
|
const error = errors[field.variable_name]
|
||||||
|
|
||||||
const baseInputClass = cn(
|
const baseInputClass = cn(
|
||||||
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:ring-1',
|
'w-full rounded-lg border bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:outline-hidden focus:ring-1',
|
||||||
error
|
error
|
||||||
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
|
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
|
||||||
: 'border-border focus:border-primary focus:ring-primary/20'
|
: 'border-[#1e2130] focus:border-primary focus:ring-primary/20'
|
||||||
)
|
)
|
||||||
|
|
||||||
let input: React.ReactNode
|
let input: React.ReactNode
|
||||||
@@ -112,9 +112,9 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={value === 'true'}
|
checked={value === 'true'}
|
||||||
onChange={(e) => setValue(field.variable_name, e.target.checked ? 'true' : 'false')}
|
onChange={(e) => setValue(field.variable_name, e.target.checked ? 'true' : 'false')}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-muted-foreground">{field.placeholder || field.label}</span>
|
<span className="text-sm text-[#848b9b]">{field.placeholder || field.label}</span>
|
||||||
</label>
|
</label>
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
@@ -162,9 +162,9 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
: [...selected, opt]
|
: [...selected, opt]
|
||||||
setValue(field.variable_name, next.join(','))
|
setValue(field.variable_name, next.join(','))
|
||||||
}}
|
}}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-muted-foreground">{opt}</span>
|
<span className="text-sm text-[#848b9b]">{opt}</span>
|
||||||
</label>
|
</label>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -198,12 +198,12 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={field.variable_name}>
|
<div key={field.variable_name}>
|
||||||
<label className="mb-1 flex items-center gap-1 text-sm font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1 text-sm font-medium text-[#848b9b]">
|
||||||
{field.label}
|
{field.label}
|
||||||
{field.required && <span className="text-red-400">*</span>}
|
{field.required && <span className="text-red-400">*</span>}
|
||||||
</label>
|
</label>
|
||||||
{field.help_text && (
|
{field.help_text && (
|
||||||
<p className="mb-1.5 text-xs text-muted-foreground">{field.help_text}</p>
|
<p className="mb-1.5 text-xs text-[#848b9b]">{field.help_text}</p>
|
||||||
)}
|
)}
|
||||||
{input}
|
{input}
|
||||||
{error && <p className="mt-1 text-xs text-red-400">{error}</p>}
|
{error && <p className="mt-1 text-xs text-red-400">{error}</p>}
|
||||||
@@ -212,13 +212,13 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4 bg-black/60 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4 bg-black/60">
|
||||||
<div className="w-full max-w-full rounded-t-2xl border border-border bg-background shadow-xl sm:max-w-lg sm:rounded-2xl">
|
<div className="w-full max-w-full rounded-t-2xl border border-[#1e2130] bg-[#0c0d10] shadow-xl sm:max-w-lg sm:rounded-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b border-border px-6 py-4">
|
<div className="border-b border-[#1e2130] px-6 py-4">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Project Information</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Project Information</h2>
|
||||||
<p className="mt-0.5 text-sm text-muted-foreground">
|
<p className="mt-0.5 text-sm text-[#848b9b]">
|
||||||
Fill in the details for <span className="text-muted-foreground">{treeName}</span>
|
Fill in the details for <span className="text-[#848b9b]">{treeName}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
{Array.from(groups.entries()).map(([groupName, groupFields]) => (
|
{Array.from(groups.entries()).map(([groupName, groupFields]) => (
|
||||||
<div key={groupName}>
|
<div key={groupName}>
|
||||||
{groupName && (
|
{groupName && (
|
||||||
<h3 className="mb-3 border-b border-border pb-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
<h3 className="mb-3 border-b border-[#1e2130] pb-1 text-xs font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||||
{groupName}
|
{groupName}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
@@ -240,7 +240,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-end gap-2 border-t border-border px-6 py-4">
|
<div className="flex items-center justify-end gap-2 border-t border-[#1e2130] px-6 py-4">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -89,17 +89,17 @@ export function PrepareSessionModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
|
<div className="fixed inset-0 z-50 flex items-end justify-center p-0 sm:items-center sm:p-4">
|
||||||
<div className="absolute inset-0 bg-background/60 backdrop-blur-xs" onClick={onClose} />
|
<div className="absolute inset-0 bg-[#0c0d10]/60" onClick={onClose} />
|
||||||
<div className="relative w-full max-w-full rounded-t-2xl border border-border bg-card shadow-2xl sm:max-w-lg sm:rounded-2xl">
|
<div className="relative w-full max-w-full rounded-t-2xl border border-[#1e2130] bg-[#14161d] shadow-2xl sm:max-w-lg sm:rounded-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileText className="h-4 w-4 text-cyan-400" />
|
<FileText className="h-4 w-4 text-cyan-400" />
|
||||||
<h3 className="text-sm font-semibold text-foreground">Prepare Session</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">Prepare Session</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-lg p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-lg p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -109,30 +109,30 @@ export function PrepareSessionModal({
|
|||||||
<div className="max-h-[70vh] overflow-y-auto p-4 space-y-5 sm:max-h-[60vh] sm:p-5">
|
<div className="max-h-[70vh] overflow-y-auto p-4 space-y-5 sm:max-h-[60vh] sm:p-5">
|
||||||
{/* Flow name */}
|
{/* Flow name */}
|
||||||
<div className="rounded-lg bg-accent px-3 py-2">
|
<div className="rounded-lg bg-accent px-3 py-2">
|
||||||
<p className="text-xs text-muted-foreground">Flow</p>
|
<p className="text-xs text-[#848b9b]">Flow</p>
|
||||||
<p className="text-sm font-medium text-foreground">{treeName}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{treeName}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Context fields */}
|
{/* Context fields */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Ticket Number</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Ticket Number</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={ticketNumber}
|
value={ticketNumber}
|
||||||
onChange={(e) => setTicketNumber(e.target.value)}
|
onChange={(e) => setTicketNumber(e.target.value)}
|
||||||
placeholder="e.g. TKT-12345"
|
placeholder="e.g. TKT-12345"
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Client Name</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Client Name</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={clientName}
|
value={clientName}
|
||||||
onChange={(e) => setClientName(e.target.value)}
|
onChange={(e) => setClientName(e.target.value)}
|
||||||
placeholder="e.g. Acme Corp"
|
placeholder="e.g. Acme Corp"
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,14 +140,14 @@ export function PrepareSessionModal({
|
|||||||
{/* Assignee */}
|
{/* Assignee */}
|
||||||
{teamMembers.length > 0 && (
|
{teamMembers.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
|
<label className="mb-1 flex items-center gap-1.5 text-xs font-medium text-[#848b9b]">
|
||||||
<UserPlus className="h-3.5 w-3.5" />
|
<UserPlus className="h-3.5 w-3.5" />
|
||||||
Assign to Engineer
|
Assign to Engineer
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={assignedToId}
|
value={assignedToId}
|
||||||
onChange={(e) => setAssignedToId(e.target.value)}
|
onChange={(e) => setAssignedToId(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">Unassigned (visible to all)</option>
|
<option value="">Unassigned (visible to all)</option>
|
||||||
{teamMembers.map(m => (
|
{teamMembers.map(m => (
|
||||||
@@ -160,17 +160,17 @@ export function PrepareSessionModal({
|
|||||||
{/* Intake form fields */}
|
{/* Intake form fields */}
|
||||||
{intakeFields.length > 0 && (
|
{intakeFields.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Variables (optional — can be filled later)
|
Variables (optional — can be filled later)
|
||||||
</h4>
|
</h4>
|
||||||
{Array.from(grouped.entries()).map(([groupName, fields]) => (
|
{Array.from(grouped.entries()).map(([groupName, fields]) => (
|
||||||
<div key={groupName} className="space-y-3">
|
<div key={groupName} className="space-y-3">
|
||||||
{grouped.size > 1 && (
|
{grouped.size > 1 && (
|
||||||
<p className="text-xs font-medium text-muted-foreground">{groupName}</p>
|
<p className="text-xs font-medium text-[#848b9b]">{groupName}</p>
|
||||||
)}
|
)}
|
||||||
{fields.map(field => (
|
{fields.map(field => (
|
||||||
<div key={field.variable_name}>
|
<div key={field.variable_name}>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">
|
||||||
{field.label}
|
{field.label}
|
||||||
{field.required && <span className="ml-1 text-amber-400">*</span>}
|
{field.required && <span className="ml-1 text-amber-400">*</span>}
|
||||||
</label>
|
</label>
|
||||||
@@ -178,7 +178,7 @@ export function PrepareSessionModal({
|
|||||||
<select
|
<select
|
||||||
value={values[field.variable_name] || ''}
|
value={values[field.variable_name] || ''}
|
||||||
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">{field.placeholder || 'Select...'}</option>
|
<option value="">{field.placeholder || 'Select...'}</option>
|
||||||
{field.options.map(opt => (
|
{field.options.map(opt => (
|
||||||
@@ -191,7 +191,7 @@ export function PrepareSessionModal({
|
|||||||
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<input
|
<input
|
||||||
@@ -199,11 +199,11 @@ export function PrepareSessionModal({
|
|||||||
value={values[field.variable_name] || ''}
|
value={values[field.variable_name] || ''}
|
||||||
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
onChange={(e) => handleFieldChange(field.variable_name, e.target.value)}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{field.help_text && (
|
{field.help_text && (
|
||||||
<p className="mt-1 text-[0.625rem] text-muted-foreground">{field.help_text}</p>
|
<p className="mt-1 text-[0.625rem] text-[#848b9b]">{field.help_text}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -214,10 +214,10 @@ export function PrepareSessionModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-end gap-3 border-t border-border px-5 py-4">
|
<div className="flex items-center justify-end gap-3 border-t border-[#1e2130] px-5 py-4">
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-[10px] px-4 py-2 text-sm text-muted-foreground hover:text-foreground"
|
className="rounded-lg px-4 py-2 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -225,8 +225,8 @@ export function PrepareSessionModal({
|
|||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114]',
|
'rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-white',
|
||||||
'shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-40'
|
'disabled:opacity-40'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,24 +20,24 @@ export function ProgressBar({ currentStep, totalSteps, elapsedMinutes, estimated
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between text-xs">
|
<div className="flex items-center justify-between text-xs">
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
Step {currentStep} of {totalSteps}
|
Step {currentStep} of {totalSteps}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{elapsedMinutes !== undefined && (
|
{elapsedMinutes !== undefined && (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
{formatTime(elapsed)}
|
{formatTime(elapsed)}
|
||||||
{estimatedTotalMinutes ? (
|
{estimatedTotalMinutes ? (
|
||||||
<span className="text-muted-foreground"> / est. {formatTime(estimatedTotalMinutes)}</span>
|
<span className="text-[#848b9b]"> / est. {formatTime(estimatedTotalMinutes)}</span>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="font-medium text-muted-foreground">{percentage}%</span>
|
<span className="font-medium text-[#848b9b]">{percentage}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-1.5 overflow-hidden rounded-full bg-accent">
|
<div className="h-1.5 overflow-hidden rounded-full bg-accent">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full bg-gradient-brand transition-all duration-300"
|
className="h-full rounded-full bg-[#22d3ee] transition-all duration-300"
|
||||||
style={{ width: `${percentage}%` }}
|
style={{ width: `${percentage}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
|
|||||||
return (
|
return (
|
||||||
<div key={step.id}>
|
<div key={step.id}>
|
||||||
{showSection && (
|
{showSection && (
|
||||||
<div className="mb-1 mt-3 border-b border-border pb-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground first:mt-0">
|
<div className="mb-1 mt-3 border-b border-[#1e2130] pb-1 text-[10px] font-semibold uppercase tracking-wider text-[#848b9b] first:mt-0">
|
||||||
{'section_header' in step ? step.section_header : undefined}
|
{'section_header' in step ? step.section_header : undefined}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -40,17 +40,17 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
|
|||||||
onClick={() => onStepClick(index)}
|
onClick={() => onStepClick(index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors',
|
'flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-sm transition-colors',
|
||||||
isCurrent && 'bg-accent text-foreground',
|
isCurrent && 'bg-accent text-[#e2e5eb]',
|
||||||
!isCurrent && isCompleted && 'text-muted-foreground',
|
!isCurrent && isCompleted && 'text-[#848b9b]',
|
||||||
!isCurrent && !isCompleted && 'text-muted-foreground hover:bg-accent/50'
|
!isCurrent && !isCompleted && 'text-[#848b9b] hover:bg-accent/50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isCompleted ? (
|
{isCompleted ? (
|
||||||
<CheckCircle2 className="h-4 w-4 shrink-0 text-emerald-400" />
|
<CheckCircle2 className="h-4 w-4 shrink-0 text-emerald-400" />
|
||||||
) : isCurrent ? (
|
) : isCurrent ? (
|
||||||
<ArrowRight className="h-4 w-4 shrink-0 text-foreground" />
|
<ArrowRight className="h-4 w-4 shrink-0 text-[#e2e5eb]" />
|
||||||
) : (
|
) : (
|
||||||
<Circle className="h-4 w-4 shrink-0 text-muted-foreground" />
|
<Circle className="h-4 w-4 shrink-0 text-[#848b9b]" />
|
||||||
)}
|
)}
|
||||||
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent text-[10px] font-medium">
|
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent text-[10px] font-medium">
|
||||||
{index + 1}
|
{index + 1}
|
||||||
@@ -64,7 +64,7 @@ export function StepChecklist({ steps, currentStepIndex, completedStepIds, onSte
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{'estimated_minutes' in step && step.estimated_minutes && (
|
{'estimated_minutes' in step && step.estimated_minutes && (
|
||||||
<span className="shrink-0 text-[10px] text-muted-foreground">~{step.estimated_minutes}m</span>
|
<span className="shrink-0 text-[10px] text-[#848b9b]">~{step.estimated_minutes}m</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { cn } from '@/lib/utils'
|
|||||||
|
|
||||||
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; bg: string; label: string }> = {
|
const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: string; bg: string; label: string }> = {
|
||||||
action: { icon: Zap, color: 'text-blue-400', bg: 'bg-blue-400/10', label: 'Action' },
|
action: { icon: Zap, color: 'text-blue-400', bg: 'bg-blue-400/10', label: 'Action' },
|
||||||
informational: { icon: Info, color: 'text-muted-foreground', bg: 'bg-accent', label: 'Info' },
|
informational: { icon: Info, color: 'text-[#848b9b]', bg: 'bg-accent', label: 'Info' },
|
||||||
verification: { icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-400/10', label: 'Verification' },
|
verification: { icon: CheckCircle2, color: 'text-emerald-400', bg: 'bg-emerald-400/10', label: 'Verification' },
|
||||||
warning: { icon: AlertTriangle, color: 'text-yellow-400', bg: 'bg-yellow-400/10', label: 'Warning' },
|
warning: { icon: AlertTriangle, color: 'text-yellow-400', bg: 'bg-yellow-400/10', label: 'Warning' },
|
||||||
}
|
}
|
||||||
@@ -91,11 +91,11 @@ export function StepDetail({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Step header */}
|
{/* Step header */}
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent text-sm font-semibold text-foreground">
|
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent text-sm font-semibold text-[#e2e5eb]">
|
||||||
{stepNumber}
|
{stepNumber}
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h2 className="text-lg font-semibold text-foreground">{step.title}</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">{step.title}</h2>
|
||||||
<div className="mt-1 flex items-center gap-2">
|
<div className="mt-1 flex items-center gap-2">
|
||||||
{isCustom ? (
|
{isCustom ? (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-amber-400/15 px-2 py-0.5 text-xs text-amber-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-amber-400/15 px-2 py-0.5 text-xs text-amber-400">
|
||||||
@@ -107,11 +107,11 @@ export function StepDetail({
|
|||||||
{config.label}
|
{config.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-[#848b9b]">
|
||||||
Step {stepNumber} of {totalSteps}
|
Step {stepNumber} of {totalSteps}
|
||||||
</span>
|
</span>
|
||||||
{'estimated_minutes' in step && step.estimated_minutes && (
|
{'estimated_minutes' in step && step.estimated_minutes && (
|
||||||
<span className="text-xs text-muted-foreground">~{step.estimated_minutes} min</span>
|
<span className="text-xs text-[#848b9b]">~{step.estimated_minutes} min</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +134,7 @@ export function StepDetail({
|
|||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
{step.description && (
|
{step.description && (
|
||||||
<div className="prose prose-invert prose-sm max-w-none text-muted-foreground">
|
<div className="prose prose-invert prose-sm max-w-none text-[#848b9b]">
|
||||||
<p className="whitespace-pre-wrap">
|
<p className="whitespace-pre-wrap">
|
||||||
<ResolvedText
|
<ResolvedText
|
||||||
text={step.description}
|
text={step.description}
|
||||||
@@ -150,14 +150,14 @@ export function StepDetail({
|
|||||||
{commandBlocks.length > 0 && (
|
{commandBlocks.length > 0 && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{commandBlocks.map((cmd, i) => (
|
{commandBlocks.map((cmd, i) => (
|
||||||
<div key={i} className="rounded-lg border border-border bg-card">
|
<div key={i} className="rounded-lg border border-[#1e2130] bg-[#14161d]">
|
||||||
<div className="flex items-center justify-between border-b border-border px-3 py-1.5">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-3 py-1.5">
|
||||||
<span className="text-xs font-medium text-muted-foreground">
|
<span className="text-xs font-medium text-[#848b9b]">
|
||||||
{cmd.label || (cmd.language ? cmd.language : 'Command')}
|
{cmd.label || (cmd.language ? cmd.language : 'Command')}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopyCommand(cmd.code, i)}
|
onClick={() => handleCopyCommand(cmd.code, i)}
|
||||||
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{copiedIndex === i ? <Check className="h-3 w-3 text-emerald-400" /> : <Copy className="h-3 w-3" />}
|
{copiedIndex === i ? <Check className="h-3 w-3 text-emerald-400" /> : <Copy className="h-3 w-3" />}
|
||||||
{copiedIndex === i ? 'Copied' : 'Copy'}
|
{copiedIndex === i ? 'Copied' : 'Copy'}
|
||||||
@@ -178,37 +178,37 @@ export function StepDetail({
|
|||||||
|
|
||||||
{/* Expected outcome */}
|
{/* Expected outcome */}
|
||||||
{'expected_outcome' in step && step.expected_outcome && (
|
{'expected_outcome' in step && step.expected_outcome && (
|
||||||
<div className="rounded-lg border border-border bg-white/2 p-3">
|
<div className="rounded-lg border border-[#1e2130] bg-white/2 p-3">
|
||||||
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
|
<h4 className="mb-1 text-xs font-medium text-[#848b9b]">Expected Outcome</h4>
|
||||||
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
|
<p className="text-sm text-[#848b9b]">{resolve(step.expected_outcome)}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Verification */}
|
{/* Verification */}
|
||||||
{verificationPrompt && (
|
{verificationPrompt && (
|
||||||
<div className="rounded-lg border border-border bg-white/2 p-3">
|
<div className="rounded-lg border border-[#1e2130] bg-white/2 p-3">
|
||||||
<h4 className="mb-2 text-xs font-medium text-muted-foreground">Verification</h4>
|
<h4 className="mb-2 text-xs font-medium text-[#848b9b]">Verification</h4>
|
||||||
{verificationType === 'checkbox' ? (
|
{verificationType === 'checkbox' ? (
|
||||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#848b9b]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={!!verificationValue}
|
checked={!!verificationValue}
|
||||||
onChange={(e) => onVerificationChange(e.target.checked ? 'confirmed' : '')}
|
onChange={(e) => onVerificationChange(e.target.checked ? 'confirmed' : '')}
|
||||||
disabled={isCompleted}
|
disabled={isCompleted}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
{resolve(verificationPrompt)}
|
{resolve(verificationPrompt)}
|
||||||
</label>
|
</label>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-sm text-muted-foreground">{resolve(verificationPrompt)}</p>
|
<p className="mb-2 text-sm text-[#848b9b]">{resolve(verificationPrompt)}</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={verificationValue}
|
value={verificationValue}
|
||||||
onChange={(e) => onVerificationChange(e.target.value)}
|
onChange={(e) => onVerificationChange(e.target.value)}
|
||||||
disabled={isCompleted}
|
disabled={isCompleted}
|
||||||
placeholder="Enter observed value..."
|
placeholder="Enter observed value..."
|
||||||
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
|
className="w-full rounded border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -218,13 +218,13 @@ export function StepDetail({
|
|||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
{(!('notes_enabled' in step) || step.notes_enabled !== false) && (
|
{(!('notes_enabled' in step) || step.notes_enabled !== false) && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-xs font-medium text-muted-foreground">Notes</label>
|
<label className="mb-1 block text-xs font-medium text-[#848b9b]">Notes</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={notes}
|
value={notes}
|
||||||
onChange={(e) => onNotesChange(e.target.value)}
|
onChange={(e) => onNotesChange(e.target.value)}
|
||||||
placeholder="Add notes for this step..."
|
placeholder="Add notes for this step..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -235,7 +235,7 @@ export function StepDetail({
|
|||||||
href={resolve(step.reference_url)}
|
href={resolve(step.reference_url)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
|
className="inline-flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-3.5 w-3.5" />
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
Reference Documentation
|
Reference Documentation
|
||||||
@@ -251,7 +251,7 @@ export function StepDetail({
|
|||||||
'flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'flex w-full items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
isCompleted
|
isCompleted
|
||||||
? 'bg-emerald-400/10 text-emerald-400'
|
? 'bg-emerald-400/10 text-emerald-400'
|
||||||
: 'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-40 disabled:hover:opacity-100'
|
: 'bg-[#22d3ee] text-white hover:brightness-110 disabled:opacity-40 disabled:hover:opacity-100'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isCompleted ? (
|
{isCompleted ? (
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ export function FlowTemplateCard({ template, onClick }: FlowTemplateCardProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="glass-card text-left w-full flex flex-col gap-3 p-5"
|
className="card-interactive text-left w-full flex flex-col gap-3 p-5"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<h3 className="font-heading text-foreground text-base font-semibold leading-tight line-clamp-2">
|
<h3 className="font-heading text-[#e2e5eb] text-base font-semibold leading-tight line-clamp-2">
|
||||||
{template.name}
|
{template.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="shrink-0">
|
<span className="shrink-0">
|
||||||
<GitBranch className="w-4 h-4 text-muted-foreground" />
|
<GitBranch className="w-4 h-4 text-[#848b9b]" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{template.description && (
|
{template.description && (
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-2">
|
<p className="text-[#848b9b] text-sm leading-relaxed line-clamp-2">
|
||||||
{template.description}
|
{template.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -40,33 +40,33 @@ export function FlowTemplateCard({ template, onClick }: FlowTemplateCardProps) {
|
|||||||
{template.tags.slice(0, 3).map((tag) => (
|
{template.tags.slice(0, 3).map((tag) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground"
|
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-[#14161d] border border-[#1e2130] text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{template.tags.length > 3 && (
|
{template.tags.length > 3 && (
|
||||||
<span className="font-label text-[0.625rem] text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] text-[#848b9b]">
|
||||||
+{template.tags.length - 3}
|
+{template.tags.length - 3}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-4 mt-auto pt-2 border-t border-border">
|
<div className="flex items-center gap-4 mt-auto pt-2 border-t border-[#1e2130]">
|
||||||
{template.category && (
|
{template.category && (
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
{template.category}
|
{template.category}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground flex items-center gap-1">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] flex items-center gap-1">
|
||||||
<Layers className="w-3 h-3" />
|
<Layers className="w-3 h-3" />
|
||||||
{template.step_count} steps
|
{template.step_count} steps
|
||||||
</span>
|
</span>
|
||||||
{template.success_rate !== null && (
|
{template.success_rate !== null && (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-label text-[0.625rem] uppercase tracking-[0.1em] flex items-center gap-1 ml-auto',
|
'font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] flex items-center gap-1 ml-auto',
|
||||||
template.success_rate >= 80
|
template.success_rate >= 80
|
||||||
? 'text-emerald-400'
|
? 'text-emerald-400'
|
||||||
: template.success_rate >= 50
|
: template.success_rate >= 50
|
||||||
@@ -82,12 +82,12 @@ export function FlowTemplateCard({ template, onClick }: FlowTemplateCardProps) {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
'font-label text-[0.625rem] px-2 py-0.5 rounded-md border',
|
'font-sans text-xs text-[0.625rem] px-2 py-0.5 rounded-md border',
|
||||||
'bg-primary/5 border-primary/20 text-primary'
|
'bg-primary/5 border-primary/20 text-[#22d3ee]'
|
||||||
)}>
|
)}>
|
||||||
{typeLabels[template.tree_type] || template.tree_type}
|
{typeLabels[template.tree_type] || template.tree_type}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-label text-[0.625rem] text-[#5a6170]">
|
<span className="font-sans text-xs text-[0.625rem] text-[#5a6170]">
|
||||||
{template.usage_count.toLocaleString()} uses
|
{template.usage_count.toLocaleString()} uses
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,24 +18,24 @@ export function ScriptTemplateCard({ template, onClick }: ScriptTemplateCardProp
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="glass-card text-left w-full flex flex-col gap-3 p-5"
|
className="card-interactive text-left w-full flex flex-col gap-3 p-5"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<h3 className="font-heading text-foreground text-base font-semibold leading-tight line-clamp-2">
|
<h3 className="font-heading text-[#e2e5eb] text-base font-semibold leading-tight line-clamp-2">
|
||||||
{template.name}
|
{template.name}
|
||||||
</h3>
|
</h3>
|
||||||
{template.is_verified && (
|
{template.is_verified && (
|
||||||
<CheckCircle2 className="w-4 h-4 text-primary shrink-0" />
|
<CheckCircle2 className="w-4 h-4 text-[#22d3ee] shrink-0" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="shrink-0">
|
<span className="shrink-0">
|
||||||
<Terminal className="w-4 h-4 text-muted-foreground" />
|
<Terminal className="w-4 h-4 text-[#848b9b]" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{template.description && (
|
{template.description && (
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-2">
|
<p className="text-[#848b9b] text-sm leading-relaxed line-clamp-2">
|
||||||
{template.description}
|
{template.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -45,31 +45,31 @@ export function ScriptTemplateCard({ template, onClick }: ScriptTemplateCardProp
|
|||||||
{template.tags.slice(0, 3).map((tag) => (
|
{template.tags.slice(0, 3).map((tag) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground"
|
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-[#14161d] border border-[#1e2130] text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{template.tags.length > 3 && (
|
{template.tags.length > 3 && (
|
||||||
<span className="font-label text-[0.625rem] text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] text-[#848b9b]">
|
||||||
+{template.tags.length - 3}
|
+{template.tags.length - 3}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-3 mt-auto pt-2 border-t border-border">
|
<div className="flex items-center gap-3 mt-auto pt-2 border-t border-[#1e2130]">
|
||||||
{template.complexity && (
|
{template.complexity && (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-label text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md border',
|
'font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md border',
|
||||||
complexityColors[template.complexity] || 'text-muted-foreground border-border bg-card'
|
complexityColors[template.complexity] || 'text-[#848b9b] border-[#1e2130] bg-[#14161d]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{template.complexity}
|
{template.complexity}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
{template.parameter_count} param{template.parameter_count !== 1 ? 's' : ''}
|
{template.parameter_count} param{template.parameter_count !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
{template.requires_elevation && (
|
{template.requires_elevation && (
|
||||||
@@ -82,7 +82,7 @@ export function ScriptTemplateCard({ template, onClick }: ScriptTemplateCardProp
|
|||||||
{template.requires_modules.slice(0, 2).map((mod) => (
|
{template.requires_modules.slice(0, 2).map((mod) => (
|
||||||
<span
|
<span
|
||||||
key={mod}
|
key={mod}
|
||||||
className="font-label text-[0.625rem] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground flex items-center gap-1"
|
className="font-sans text-xs text-[0.625rem] px-2 py-0.5 rounded-md bg-[#14161d] border border-[#1e2130] text-[#848b9b] flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<Package className="w-3 h-3" />
|
<Package className="w-3 h-3" />
|
||||||
{mod}
|
{mod}
|
||||||
@@ -92,7 +92,7 @@ export function ScriptTemplateCard({ template, onClick }: ScriptTemplateCardProp
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
<span className="font-label text-[0.625rem] text-[#5a6170]">
|
<span className="font-sans text-xs text-[0.625rem] text-[#5a6170]">
|
||||||
{template.usage_count.toLocaleString()} uses
|
{template.usage_count.toLocaleString()} uses
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,29 +58,29 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
<div className="absolute inset-0 bg-black/60" />
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div
|
<div
|
||||||
className="glass-card-static relative w-full max-w-2xl max-h-[85vh] flex flex-col"
|
className="card-flat relative w-full max-w-2xl max-h-[85vh] flex flex-col"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between gap-4 p-6 border-b border-border">
|
<div className="flex items-start justify-between gap-4 p-6 border-b border-[#1e2130]">
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
{type === 'flow' ? (
|
{type === 'flow' ? (
|
||||||
<GitBranch className="w-5 h-5 text-primary shrink-0" />
|
<GitBranch className="w-5 h-5 text-[#22d3ee] shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<Terminal className="w-5 h-5 text-primary shrink-0" />
|
<Terminal className="w-5 h-5 text-[#22d3ee] shrink-0" />
|
||||||
)}
|
)}
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h2 className="font-heading text-foreground text-xl font-semibold leading-tight flex items-center gap-2">
|
<h2 className="font-heading text-[#e2e5eb] text-xl font-semibold leading-tight flex items-center gap-2">
|
||||||
{template.name}
|
{template.name}
|
||||||
{type === 'script' && (template as PublicScriptDetail).is_verified && (
|
{type === 'script' && (template as PublicScriptDetail).is_verified && (
|
||||||
<CheckCircle2 className="w-4 h-4 text-primary shrink-0" />
|
<CheckCircle2 className="w-4 h-4 text-[#22d3ee] shrink-0" />
|
||||||
)}
|
)}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mt-1">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mt-1">
|
||||||
{type === 'flow' ? 'Flow Template' : 'Script Template'}
|
{type === 'flow' ? 'Flow Template' : 'Script Template'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,7 +88,7 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)] transition-colors"
|
className="p-1.5 rounded-lg text-[#848b9b] hover:text-[#e2e5eb] hover:bg-[rgba(255,255,255,0.04)] transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-5 h-5" />
|
<X className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -97,7 +97,7 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
||||||
{template.description && (
|
{template.description && (
|
||||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
<p className="text-[#848b9b] text-sm leading-relaxed">
|
||||||
{template.description}
|
{template.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -139,7 +139,7 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
value={(template as PublicScriptDetail).complexity!}
|
value={(template as PublicScriptDetail).complexity!}
|
||||||
valueClassName={
|
valueClassName={
|
||||||
complexityColors[(template as PublicScriptDetail).complexity!] ||
|
complexityColors[(template as PublicScriptDetail).complexity!] ||
|
||||||
'text-foreground'
|
'text-[#e2e5eb]'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -161,14 +161,14 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
{template.tags.length > 0 && (
|
{template.tags.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
||||||
Tags
|
Tags
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{template.tags.map((tag) => (
|
{template.tags.map((tag) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground"
|
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-[#14161d] border border-[#1e2130] text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
@@ -180,10 +180,10 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
{/* Flow preview structure */}
|
{/* Flow preview structure */}
|
||||||
{type === 'flow' && (template as PublicFlowDetail).preview_structure && (
|
{type === 'flow' && (template as PublicFlowDetail).preview_structure && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
||||||
Flow Preview
|
Flow Preview
|
||||||
</h4>
|
</h4>
|
||||||
<div className="bg-card border border-border rounded-xl p-4">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
||||||
<PreviewTree structure={(template as PublicFlowDetail).preview_structure!} />
|
<PreviewTree structure={(template as PublicFlowDetail).preview_structure!} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,22 +192,22 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
{/* Script parameters */}
|
{/* Script parameters */}
|
||||||
{type === 'script' && (template as PublicScriptDetail).parameters.length > 0 && (
|
{type === 'script' && (template as PublicScriptDetail).parameters.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
||||||
Parameters
|
Parameters
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{(template as PublicScriptDetail).parameters.map((param) => (
|
{(template as PublicScriptDetail).parameters.map((param) => (
|
||||||
<div
|
<div
|
||||||
key={param.name}
|
key={param.name}
|
||||||
className="flex items-start gap-3 bg-card border border-border rounded-xl p-3"
|
className="flex items-start gap-3 bg-[#14161d] border border-[#1e2130] rounded-xl p-3"
|
||||||
>
|
>
|
||||||
<code className="font-label text-xs text-primary shrink-0">
|
<code className="font-sans text-xs text-xs text-[#22d3ee] shrink-0">
|
||||||
{param.name}
|
{param.name}
|
||||||
</code>
|
</code>
|
||||||
<span className="text-muted-foreground text-sm flex-1">
|
<span className="text-[#848b9b] text-sm flex-1">
|
||||||
{param.description}
|
{param.description}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170] shrink-0">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#5a6170] shrink-0">
|
||||||
{param.type}
|
{param.type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,14 +219,14 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
{/* Script modules */}
|
{/* Script modules */}
|
||||||
{type === 'script' && (template as PublicScriptDetail).requires_modules.length > 0 && (
|
{type === 'script' && (template as PublicScriptDetail).requires_modules.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
||||||
Required Modules
|
Required Modules
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{(template as PublicScriptDetail).requires_modules.map((mod) => (
|
{(template as PublicScriptDetail).requires_modules.map((mod) => (
|
||||||
<span
|
<span
|
||||||
key={mod}
|
key={mod}
|
||||||
className="font-label text-[0.625rem] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground flex items-center gap-1"
|
className="font-sans text-xs text-[0.625rem] px-2 py-0.5 rounded-md bg-[#14161d] border border-[#1e2130] text-[#848b9b] flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<Package className="w-3 h-3" />
|
<Package className="w-3 h-3" />
|
||||||
{mod}
|
{mod}
|
||||||
@@ -238,17 +238,17 @@ export function TemplateDetailModal(props: TemplateDetailModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between gap-4 p-6 border-t border-border">
|
<div className="flex items-center justify-between gap-4 p-6 border-t border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-4 py-2.5 rounded-[10px] text-sm font-medium bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
className="px-4 py-2.5 rounded-lg text-sm font-medium bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
to="/register"
|
to="/register"
|
||||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-[10px] text-sm font-semibold bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-semibold bg-[#22d3ee] text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
Sign Up to Use This
|
Sign Up to Use This
|
||||||
<ChevronRight className="w-4 h-4" />
|
<ChevronRight className="w-4 h-4" />
|
||||||
@@ -271,11 +271,11 @@ function MetaStat({
|
|||||||
valueClassName?: string
|
valueClassName?: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border border-border rounded-xl p-3">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-3">
|
||||||
<div className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-1">
|
<div className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-1">
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
<div className={cn('text-foreground text-sm font-semibold flex items-center gap-1.5', valueClassName)}>
|
<div className={cn('text-[#e2e5eb] text-sm font-semibold flex items-center gap-1.5', valueClassName)}>
|
||||||
{icon}
|
{icon}
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
@@ -293,17 +293,17 @@ function PreviewTree({ structure }: { structure: Record<string, unknown> }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<div className="flex items-center gap-2 text-foreground">
|
<div className="flex items-center gap-2 text-[#e2e5eb]">
|
||||||
<ChevronRight className="w-3.5 h-3.5 text-primary" />
|
<ChevronRight className="w-3.5 h-3.5 text-[#22d3ee]" />
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
</div>
|
</div>
|
||||||
{children.length > 0 && (
|
{children.length > 0 && (
|
||||||
<div className="ml-5 mt-1 space-y-1 border-l border-border pl-3">
|
<div className="ml-5 mt-1 space-y-1 border-l border-[#1e2130] pl-3">
|
||||||
{children.slice(0, 8).map((child, i) => (
|
{children.slice(0, 8).map((child, i) => (
|
||||||
<PreviewTree key={i} structure={child} />
|
<PreviewTree key={i} structure={child} />
|
||||||
))}
|
))}
|
||||||
{children.length > 8 && (
|
{children.length > 8 && (
|
||||||
<span className="text-muted-foreground text-xs">
|
<span className="text-[#848b9b] text-xs">
|
||||||
+{children.length - 8} more steps...
|
+{children.length - 8} more steps...
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -55,29 +55,29 @@ export function ParameterCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-border rounded-xl overflow-hidden">
|
<div className="border border-[#1e2130] rounded-xl overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setExpanded(v => !v)}
|
onClick={() => setExpanded(v => !v)}
|
||||||
className="w-full flex items-center gap-2 px-3 py-2.5 bg-white/[0.02] hover:bg-white/[0.04] transition-colors"
|
className="w-full flex items-center gap-2 px-3 py-2.5 bg-white/[0.02] hover:bg-white/[0.04] transition-colors"
|
||||||
>
|
>
|
||||||
<GripVertical size={14} className="text-muted-foreground/50 shrink-0" />
|
<GripVertical size={14} className="text-[#848b9b]/50 shrink-0" />
|
||||||
{expanded ? <ChevronDown size={14} className="text-muted-foreground" /> : <ChevronRight size={14} className="text-muted-foreground" />}
|
{expanded ? <ChevronDown size={14} className="text-[#848b9b]" /> : <ChevronRight size={14} className="text-[#848b9b]" />}
|
||||||
<span className="text-sm font-medium text-foreground flex-1 text-left">
|
<span className="text-sm font-medium text-[#e2e5eb] flex-1 text-left">
|
||||||
{param.label || param.key || `Parameter ${index + 1}`}
|
{param.label || param.key || `Parameter ${index + 1}`}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-label text-[0.625rem] text-muted-foreground uppercase">{param.type}</span>
|
<span className="font-sans text-xs text-[0.625rem] text-[#848b9b] uppercase">{param.type}</span>
|
||||||
{param.required && <span className="text-red-400 text-xs">*</span>}
|
{param.required && <span className="text-red-400 text-xs">*</span>}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<div className="px-3 py-3 space-y-3 border-t border-border">
|
<div className="px-3 py-3 space-y-3 border-t border-[#1e2130]">
|
||||||
{/* Row 1: key + label */}
|
{/* Row 1: key + label */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Key (used in {{key}})</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Key (used in {{key}})</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.key}
|
value={param.key}
|
||||||
onChange={e => update({ key: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
|
onChange={e => update({ key: e.target.value.replace(/[^a-zA-Z0-9_]/g, '') })}
|
||||||
@@ -86,7 +86,7 @@ export function ParameterCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Label</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Label</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.label}
|
value={param.label}
|
||||||
onChange={e => update({ label: e.target.value })}
|
onChange={e => update({ label: e.target.value })}
|
||||||
@@ -99,12 +99,12 @@ export function ParameterCard({
|
|||||||
{/* Row 2: type + group */}
|
{/* Row 2: type + group */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Type</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Type</label>
|
||||||
<select
|
<select
|
||||||
value={param.type}
|
value={param.type}
|
||||||
onChange={e => update({ type: e.target.value as ScriptParameter['type'] })}
|
onChange={e => update({ type: e.target.value as ScriptParameter['type'] })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="w-full rounded-[10px] border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)] disabled:cursor-not-allowed disabled:opacity-50"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] text-[#e2e5eb] px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{PARAM_TYPES.map(t => (
|
{PARAM_TYPES.map(t => (
|
||||||
<option key={t.value} value={t.value}>{t.label}</option>
|
<option key={t.value} value={t.value}>{t.label}</option>
|
||||||
@@ -112,7 +112,7 @@ export function ParameterCard({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Group (optional)</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Group (optional)</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.group ?? ''}
|
value={param.group ?? ''}
|
||||||
onChange={e => update({ group: e.target.value || null })}
|
onChange={e => update({ group: e.target.value || null })}
|
||||||
@@ -125,7 +125,7 @@ export function ParameterCard({
|
|||||||
{/* Row 3: placeholder + help text */}
|
{/* Row 3: placeholder + help text */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Placeholder</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Placeholder</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.placeholder ?? ''}
|
value={param.placeholder ?? ''}
|
||||||
onChange={e => update({ placeholder: e.target.value || null })}
|
onChange={e => update({ placeholder: e.target.value || null })}
|
||||||
@@ -134,7 +134,7 @@ export function ParameterCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Help text</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Help text</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.help_text ?? ''}
|
value={param.help_text ?? ''}
|
||||||
onChange={e => update({ help_text: e.target.value || null })}
|
onChange={e => update({ help_text: e.target.value || null })}
|
||||||
@@ -146,23 +146,23 @@ export function ParameterCard({
|
|||||||
|
|
||||||
{/* Row 4: toggles */}
|
{/* Row 4: toggles */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={param.required}
|
checked={param.required}
|
||||||
onChange={e => update({ required: e.target.checked })}
|
onChange={e => update({ required: e.target.checked })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Required
|
Required
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={param.sensitive}
|
checked={param.sensitive}
|
||||||
onChange={e => update({ sensitive: e.target.checked })}
|
onChange={e => update({ sensitive: e.target.checked })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Sensitive (redacted in logs)
|
Sensitive (redacted in logs)
|
||||||
</label>
|
</label>
|
||||||
@@ -170,7 +170,7 @@ export function ParameterCard({
|
|||||||
|
|
||||||
{/* Default value */}
|
{/* Default value */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Default value</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Default value</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.default !== null && param.default !== undefined ? String(param.default) : ''}
|
value={param.default !== null && param.default !== undefined ? String(param.default) : ''}
|
||||||
onChange={e => update({ default: e.target.value || null })}
|
onChange={e => update({ default: e.target.value || null })}
|
||||||
@@ -182,7 +182,7 @@ export function ParameterCard({
|
|||||||
{/* Select options (only for select type) */}
|
{/* Select options (only for select type) */}
|
||||||
{param.type === 'select' && (
|
{param.type === 'select' && (
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Options</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Options</label>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{(param.options ?? []).map((opt, i) => (
|
{(param.options ?? []).map((opt, i) => (
|
||||||
<div key={i} className="flex items-center gap-2">
|
<div key={i} className="flex items-center gap-2">
|
||||||
@@ -202,7 +202,7 @@ export function ParameterCard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeOption(i)}
|
onClick={() => removeOption(i)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="p-1 text-muted-foreground hover:text-rose-500 transition-colors"
|
className="p-1 text-[#848b9b] hover:text-rose-500 transition-colors"
|
||||||
>
|
>
|
||||||
<X size={14} />
|
<X size={14} />
|
||||||
</button>
|
</button>
|
||||||
@@ -212,7 +212,7 @@ export function ParameterCard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={addOption}
|
onClick={addOption}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="flex items-center gap-1 text-xs text-primary hover:underline"
|
className="flex items-center gap-1 text-xs text-[#22d3ee] hover:underline"
|
||||||
>
|
>
|
||||||
<Plus size={12} /> Add option
|
<Plus size={12} /> Add option
|
||||||
</button>
|
</button>
|
||||||
@@ -223,12 +223,12 @@ export function ParameterCard({
|
|||||||
{/* Validation (for text/number types) */}
|
{/* Validation (for text/number types) */}
|
||||||
{(param.type === 'text' || param.type === 'number' || param.type === 'textarea') && (
|
{(param.type === 'text' || param.type === 'number' || param.type === 'textarea') && (
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Validation (optional)</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Validation (optional)</label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
{param.type === 'number' ? (
|
{param.type === 'number' ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[0.625rem] text-muted-foreground">Min value</label>
|
<label className="text-[0.625rem] text-[#848b9b]">Min value</label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={param.validation?.min_value ?? ''}
|
value={param.validation?.min_value ?? ''}
|
||||||
@@ -237,7 +237,7 @@ export function ParameterCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[0.625rem] text-muted-foreground">Max value</label>
|
<label className="text-[0.625rem] text-[#848b9b]">Max value</label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={param.validation?.max_value ?? ''}
|
value={param.validation?.max_value ?? ''}
|
||||||
@@ -249,7 +249,7 @@ export function ParameterCard({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[0.625rem] text-muted-foreground">Min length</label>
|
<label className="text-[0.625rem] text-[#848b9b]">Min length</label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={param.validation?.min_length ?? ''}
|
value={param.validation?.min_length ?? ''}
|
||||||
@@ -258,7 +258,7 @@ export function ParameterCard({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[0.625rem] text-muted-foreground">Max length</label>
|
<label className="text-[0.625rem] text-[#848b9b]">Max length</label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={param.validation?.max_length ?? ''}
|
value={param.validation?.max_length ?? ''}
|
||||||
@@ -269,7 +269,7 @@ export function ParameterCard({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[0.625rem] text-muted-foreground">Pattern (regex)</label>
|
<label className="text-[0.625rem] text-[#848b9b]">Pattern (regex)</label>
|
||||||
<Input
|
<Input
|
||||||
value={param.validation?.pattern ?? ''}
|
value={param.validation?.pattern ?? ''}
|
||||||
onChange={e => updateValidation({ pattern: e.target.value || undefined })}
|
onChange={e => updateValidation({ pattern: e.target.value || undefined })}
|
||||||
@@ -282,13 +282,13 @@ export function ParameterCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Actions row */}
|
{/* Actions row */}
|
||||||
<div className="flex items-center justify-between pt-1 border-t border-border">
|
<div className="flex items-center justify-between pt-1 border-t border-[#1e2130]">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onMoveUp(index)}
|
onClick={() => onMoveUp(index)}
|
||||||
disabled={isFirst || disabled}
|
disabled={isFirst || disabled}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground disabled:opacity-30 px-1.5 py-0.5"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] disabled:opacity-30 px-1.5 py-0.5"
|
||||||
>
|
>
|
||||||
↑ Up
|
↑ Up
|
||||||
</button>
|
</button>
|
||||||
@@ -296,7 +296,7 @@ export function ParameterCard({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onMoveDown(index)}
|
onClick={() => onMoveDown(index)}
|
||||||
disabled={isLast || disabled}
|
disabled={isLast || disabled}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground disabled:opacity-30 px-1.5 py-0.5"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] disabled:opacity-30 px-1.5 py-0.5"
|
||||||
>
|
>
|
||||||
↓ Down
|
↓ Down
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function ParameterDetectorStepper({
|
|||||||
<div className="border border-primary/20 rounded-xl bg-primary/[0.03] p-4 space-y-3">
|
<div className="border border-primary/20 rounded-xl bg-primary/[0.03] p-4 space-y-3">
|
||||||
{/* Progress */}
|
{/* Progress */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-xs font-medium text-foreground">
|
<p className="text-xs font-medium text-[#e2e5eb]">
|
||||||
Candidate {currentIndex + 1} of {candidates.length}
|
Candidate {currentIndex + 1} of {candidates.length}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
@@ -127,10 +127,10 @@ export function ParameterDetectorStepper({
|
|||||||
|
|
||||||
{/* Matched line */}
|
{/* Matched line */}
|
||||||
<div className="rounded-lg bg-black/20 px-3 py-2">
|
<div className="rounded-lg bg-black/20 px-3 py-2">
|
||||||
<p className="font-label text-xs text-amber-400 break-all">
|
<p className="font-sans text-xs text-xs text-amber-400 break-all">
|
||||||
{current.matchedLine}
|
{current.matchedLine}
|
||||||
</p>
|
</p>
|
||||||
<p className="font-label text-[0.5rem] text-muted-foreground mt-1">
|
<p className="font-sans text-xs text-[0.5rem] text-[#848b9b] mt-1">
|
||||||
Line {current.lineNumber} · {current.source === 'param_block' ? 'param() block' : 'variable assignment'}
|
Line {current.lineNumber} · {current.source === 'param_block' ? 'param() block' : 'variable assignment'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +138,7 @@ export function ParameterDetectorStepper({
|
|||||||
{/* Editable fields */}
|
{/* Editable fields */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Key</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Key</label>
|
||||||
<Input
|
<Input
|
||||||
value={key}
|
value={key}
|
||||||
onChange={e => setKey(e.target.value.replace(/[^a-zA-Z0-9_]/g, ''))}
|
onChange={e => setKey(e.target.value.replace(/[^a-zA-Z0-9_]/g, ''))}
|
||||||
@@ -149,7 +149,7 @@ export function ParameterDetectorStepper({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Label</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Label</label>
|
||||||
<Input
|
<Input
|
||||||
value={label}
|
value={label}
|
||||||
onChange={e => setLabel(e.target.value)}
|
onChange={e => setLabel(e.target.value)}
|
||||||
@@ -160,12 +160,12 @@ export function ParameterDetectorStepper({
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 flex items-center gap-1.5">
|
<label className="text-xs text-[#848b9b] mb-1 flex items-center gap-1.5">
|
||||||
Type
|
Type
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowInferenceInfo(!showInferenceInfo)}
|
onClick={() => setShowInferenceInfo(!showInferenceInfo)}
|
||||||
className="text-muted-foreground hover:text-primary transition-colors"
|
className="text-[#848b9b] hover:text-[#22d3ee] transition-colors"
|
||||||
title={current.inferenceReason}
|
title={current.inferenceReason}
|
||||||
>
|
>
|
||||||
<Info size={11} />
|
<Info size={11} />
|
||||||
@@ -174,20 +174,20 @@ export function ParameterDetectorStepper({
|
|||||||
<select
|
<select
|
||||||
value={type}
|
value={type}
|
||||||
onChange={e => setType(e.target.value as ScriptParameter['type'])}
|
onChange={e => setType(e.target.value as ScriptParameter['type'])}
|
||||||
className="w-full rounded-[10px] border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] text-[#e2e5eb] px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
||||||
>
|
>
|
||||||
{PARAM_TYPES.map(t => (
|
{PARAM_TYPES.map(t => (
|
||||||
<option key={t.value} value={t.value}>{t.label}</option>
|
<option key={t.value} value={t.value}>{t.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
{showInferenceInfo && (
|
{showInferenceInfo && (
|
||||||
<p className="text-[0.625rem] text-primary/80 mt-1 italic">
|
<p className="text-[0.625rem] text-[#22d3ee]/80 mt-1 italic">
|
||||||
{current.inferenceReason}
|
{current.inferenceReason}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-muted-foreground mb-1 block">Default value</label>
|
<label className="text-xs text-[#848b9b] mb-1 block">Default value</label>
|
||||||
<Input
|
<Input
|
||||||
value={defaultValue}
|
value={defaultValue}
|
||||||
onChange={e => setDefaultValue(e.target.value)}
|
onChange={e => setDefaultValue(e.target.value)}
|
||||||
@@ -197,32 +197,32 @@ export function ParameterDetectorStepper({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={required}
|
checked={required}
|
||||||
onChange={e => setRequired(e.target.checked)}
|
onChange={e => setRequired(e.target.checked)}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Required
|
Required
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={sensitive}
|
checked={sensitive}
|
||||||
onChange={e => setSensitive(e.target.checked)}
|
onChange={e => setSensitive(e.target.checked)}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Sensitive
|
Sensitive
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex items-center justify-end gap-2 pt-1 border-t border-border">
|
<div className="flex items-center justify-end gap-2 pt-1 border-t border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSkip}
|
onClick={handleSkip}
|
||||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors px-3 py-1.5"
|
className="flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors px-3 py-1.5"
|
||||||
>
|
>
|
||||||
<SkipForward size={13} />
|
<SkipForward size={13} />
|
||||||
{isLast ? 'Skip & Finish' : 'Skip'}
|
{isLast ? 'Skip & Finish' : 'Skip'}
|
||||||
@@ -231,7 +231,7 @@ export function ParameterDetectorStepper({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleAccept}
|
onClick={handleAccept}
|
||||||
disabled={!key.trim() || !label.trim()}
|
disabled={!key.trim() || !label.trim()}
|
||||||
className="flex items-center gap-1.5 bg-gradient-brand text-[#101114] font-semibold text-sm px-4 py-1.5 rounded-[10px] hover:opacity-90 active:scale-[0.97] transition-all shadow-lg shadow-primary/20 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="flex items-center gap-1.5 bg-[#22d3ee] text-white font-semibold text-sm px-4 py-1.5 rounded-lg hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isLast ? (
|
{isLast ? (
|
||||||
<><Check size={13} /> Accept & Finish</>
|
<><Check size={13} /> Accept & Finish</>
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ export function ParameterSchemaBuilder({ schema, onChange, disabled }: Props) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => mode === 'json' ? switchToVisual() : undefined}
|
onClick={() => mode === 'json' ? switchToVisual() : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 font-label text-xs px-3 py-1.5 rounded-full border transition-all',
|
'flex items-center gap-1.5 font-sans text-xs text-xs px-3 py-1.5 rounded-full border transition-all',
|
||||||
mode === 'visual'
|
mode === 'visual'
|
||||||
? 'bg-primary/10 border-primary/30 text-foreground'
|
? 'bg-[rgba(34,211,238,0.10)] border-primary/30 text-[#e2e5eb]'
|
||||||
: 'border-border text-muted-foreground hover:text-foreground'
|
: 'border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<List size={12} /> Visual
|
<List size={12} /> Visual
|
||||||
@@ -109,10 +109,10 @@ export function ParameterSchemaBuilder({ schema, onChange, disabled }: Props) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => mode === 'visual' ? switchToJson() : undefined}
|
onClick={() => mode === 'visual' ? switchToJson() : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 font-label text-xs px-3 py-1.5 rounded-full border transition-all',
|
'flex items-center gap-1.5 font-sans text-xs text-xs px-3 py-1.5 rounded-full border transition-all',
|
||||||
mode === 'json'
|
mode === 'json'
|
||||||
? 'bg-primary/10 border-primary/30 text-foreground'
|
? 'bg-[rgba(34,211,238,0.10)] border-primary/30 text-[#e2e5eb]'
|
||||||
: 'border-border text-muted-foreground hover:text-foreground'
|
: 'border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Code size={12} /> JSON
|
<Code size={12} /> JSON
|
||||||
@@ -122,7 +122,7 @@ export function ParameterSchemaBuilder({ schema, onChange, disabled }: Props) {
|
|||||||
{mode === 'visual' ? (
|
{mode === 'visual' ? (
|
||||||
<>
|
<>
|
||||||
{parameters.length === 0 ? (
|
{parameters.length === 0 ? (
|
||||||
<p className="text-sm text-muted-foreground py-4 text-center">
|
<p className="text-sm text-[#848b9b] py-4 text-center">
|
||||||
No parameters defined. Add one to create dynamic form fields.
|
No parameters defined. Add one to create dynamic form fields.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -147,7 +147,7 @@ export function ParameterSchemaBuilder({ schema, onChange, disabled }: Props) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="flex items-center gap-1.5 text-sm text-primary hover:underline self-start"
|
className="flex items-center gap-1.5 text-sm text-[#22d3ee] hover:underline self-start"
|
||||||
>
|
>
|
||||||
<Plus size={14} /> Add Parameter
|
<Plus size={14} /> Add Parameter
|
||||||
</button>
|
</button>
|
||||||
@@ -159,7 +159,7 @@ export function ParameterSchemaBuilder({ schema, onChange, disabled }: Props) {
|
|||||||
onChange={e => { setJsonText(e.target.value); setJsonError(null) }}
|
onChange={e => { setJsonText(e.target.value); setJsonError(null) }}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
className="w-full min-h-[300px] resize-y font-label text-sm bg-card border border-border rounded-xl p-4 text-foreground focus:outline-none focus:border-[rgba(6,182,212,0.3)] disabled:cursor-not-allowed disabled:opacity-50"
|
className="w-full min-h-[300px] resize-y font-sans text-xs text-sm bg-[#14161d] border border-[#1e2130] rounded-xl p-4 text-[#e2e5eb] focus:outline-none focus:border-[rgba(6,182,212,0.3)] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
placeholder='{ "parameters": [...] }'
|
placeholder='{ "parameters": [...] }'
|
||||||
/>
|
/>
|
||||||
{jsonError && (
|
{jsonError && (
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function ScriptBodyEditor({ value, onChange, disabled }: Props) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-border overflow-hidden">
|
<div className="rounded-xl border border-[#1e2130] overflow-hidden">
|
||||||
<Editor
|
<Editor
|
||||||
height="300px"
|
height="300px"
|
||||||
language="powershell"
|
language="powershell"
|
||||||
@@ -25,7 +25,7 @@ export function ScriptBodyEditor({ value, onChange, disabled }: Props) {
|
|||||||
onChange={v => onChange(v ?? '')}
|
onChange={v => onChange(v ?? '')}
|
||||||
beforeMount={handleBeforeMount}
|
beforeMount={handleBeforeMount}
|
||||||
loading={
|
loading={
|
||||||
<div className="flex h-[300px] items-center justify-center bg-card">
|
<div className="flex h-[300px] items-center justify-center bg-[#14161d]">
|
||||||
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-20">
|
<div className="flex items-center justify-center py-20">
|
||||||
<Loader2 size={28} className="text-primary animate-spin" />
|
<Loader2 size={28} className="text-[#22d3ee] animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -302,22 +302,22 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit"
|
className="flex items-center gap-1.5 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors w-fit"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={12} />
|
<ArrowLeft size={12} />
|
||||||
Back to templates
|
Back to templates
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground">
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">
|
||||||
{templateId ? 'Edit Template' : 'New Template'}
|
{templateId ? 'Edit Template' : 'New Template'}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* ── Metadata ──────────────────────────────────────────────── */}
|
{/* ── Metadata ──────────────────────────────────────────────── */}
|
||||||
<section className="glass-card-static p-5 space-y-4">
|
<section className="card-flat p-5 space-y-4">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Metadata</p>
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Metadata</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">
|
||||||
Name <span className="text-red-400">*</span>
|
Name <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -329,7 +329,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Description</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Description</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={form.description}
|
value={form.description}
|
||||||
onChange={e => updateField('description', e.target.value)}
|
onChange={e => updateField('description', e.target.value)}
|
||||||
@@ -338,7 +338,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Use Case</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Use Case</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={form.use_case}
|
value={form.use_case}
|
||||||
onChange={e => updateField('use_case', e.target.value)}
|
onChange={e => updateField('use_case', e.target.value)}
|
||||||
@@ -350,13 +350,13 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
|
|
||||||
<div className="grid grid-cols-3 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">
|
||||||
Category <span className="text-red-400">*</span>
|
Category <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={form.category_id}
|
value={form.category_id}
|
||||||
onChange={e => updateField('category_id', e.target.value)}
|
onChange={e => updateField('category_id', e.target.value)}
|
||||||
className="w-full rounded-[10px] border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] text-[#e2e5eb] px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
||||||
>
|
>
|
||||||
<option value="">Select category…</option>
|
<option value="">Select category…</option>
|
||||||
{categories.map(c => (
|
{categories.map(c => (
|
||||||
@@ -365,11 +365,11 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Complexity</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Complexity</label>
|
||||||
<select
|
<select
|
||||||
value={form.complexity}
|
value={form.complexity}
|
||||||
onChange={e => updateField('complexity', e.target.value as FormState['complexity'])}
|
onChange={e => updateField('complexity', e.target.value as FormState['complexity'])}
|
||||||
className="w-full rounded-[10px] border border-border bg-card text-foreground px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] text-[#e2e5eb] px-3 py-2 text-sm focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
|
||||||
>
|
>
|
||||||
<option value="beginner">Beginner</option>
|
<option value="beginner">Beginner</option>
|
||||||
<option value="intermediate">Intermediate</option>
|
<option value="intermediate">Intermediate</option>
|
||||||
@@ -377,7 +377,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Estimated Runtime</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Estimated Runtime</label>
|
||||||
<Input
|
<Input
|
||||||
value={form.estimated_runtime}
|
value={form.estimated_runtime}
|
||||||
onChange={e => updateField('estimated_runtime', e.target.value)}
|
onChange={e => updateField('estimated_runtime', e.target.value)}
|
||||||
@@ -388,7 +388,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Tags (comma-separated)</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Tags (comma-separated)</label>
|
||||||
<Input
|
<Input
|
||||||
value={form.tags}
|
value={form.tags}
|
||||||
onChange={e => updateField('tags', e.target.value)}
|
onChange={e => updateField('tags', e.target.value)}
|
||||||
@@ -396,7 +396,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-foreground mb-1 block">Required Modules (comma-separated)</label>
|
<label className="text-sm font-medium text-[#e2e5eb] mb-1 block">Required Modules (comma-separated)</label>
|
||||||
<Input
|
<Input
|
||||||
value={form.requires_modules}
|
value={form.requires_modules}
|
||||||
onChange={e => updateField('requires_modules', e.target.value)}
|
onChange={e => updateField('requires_modules', e.target.value)}
|
||||||
@@ -406,41 +406,41 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={form.requires_elevation}
|
checked={form.requires_elevation}
|
||||||
onChange={e => updateField('requires_elevation', e.target.checked)}
|
onChange={e => updateField('requires_elevation', e.target.checked)}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Requires elevation (Run as Administrator)
|
Requires elevation (Run as Administrator)
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Share toggle — only for owners/admins editing an existing template */}
|
{/* Share toggle — only for owners/admins editing an existing template */}
|
||||||
{templateId && template && canShareScriptTemplate && (
|
{templateId && template && canShareScriptTemplate && (
|
||||||
<label className="flex items-center gap-2 text-sm text-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#e2e5eb]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={template.team_id !== null}
|
checked={template.team_id !== null}
|
||||||
onChange={e => handleShare(e.target.checked)}
|
onChange={e => handleShare(e.target.checked)}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Share with team
|
Share with team
|
||||||
<span className="text-xs text-muted-foreground">(visible to all team members)</span>
|
<span className="text-xs text-[#848b9b]">(visible to all team members)</span>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Script Body ───────────────────────────────────────────── */}
|
{/* ── Script Body ───────────────────────────────────────────── */}
|
||||||
<section className="glass-card-static p-5 space-y-3">
|
<section className="card-flat p-5 space-y-3">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||||
Script Body <span className="text-red-400">*</span>
|
Script Body <span className="text-red-400">*</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
Use <code className="font-label text-amber-400">{'{{param_key}}'}</code> for parameter placeholders.
|
Use <code className="font-sans text-xs text-amber-400">{'{{param_key}}'}</code> for parameter placeholders.
|
||||||
Supports <code className="font-label text-amber-400">{'{% if param %} ... {% endif %}'}</code> conditionals
|
Supports <code className="font-sans text-xs text-amber-400">{'{% if param %} ... {% endif %}'}</code> conditionals
|
||||||
and filters like <code className="font-label text-amber-400">{'{{ param | as_secure_string }}'}</code>.
|
and filters like <code className="font-sans text-xs text-amber-400">{'{{ param | as_secure_string }}'}</code>.
|
||||||
</p>
|
</p>
|
||||||
<ScriptBodyEditor
|
<ScriptBodyEditor
|
||||||
value={form.script_body}
|
value={form.script_body}
|
||||||
@@ -452,7 +452,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDetectParameters}
|
onClick={handleDetectParameters}
|
||||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] px-3 py-1.5 rounded-[10px] transition-all"
|
className="flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] px-3 py-1.5 rounded-lg transition-all"
|
||||||
>
|
>
|
||||||
<Scan size={14} />
|
<Scan size={14} />
|
||||||
Detect Parameters
|
Detect Parameters
|
||||||
@@ -460,7 +460,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{detectionSummary && (
|
{detectionSummary && (
|
||||||
<p className="text-xs text-muted-foreground italic">{detectionSummary}</p>
|
<p className="text-xs text-[#848b9b] italic">{detectionSummary}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showStepper && detectedCandidates.length > 0 && (
|
{showStepper && detectedCandidates.length > 0 && (
|
||||||
@@ -475,10 +475,10 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Parameters Schema ─────────────────────────────────────── */}
|
{/* ── Parameters Schema ─────────────────────────────────────── */}
|
||||||
<section className="glass-card-static p-5 space-y-3">
|
<section className="card-flat p-5 space-y-3">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Parameters</p>
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Parameters</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
Define form fields that users fill in when generating a script. Each parameter maps to a <code className="font-label text-amber-400">{'{{key}}'}</code> placeholder in the script body.
|
Define form fields that users fill in when generating a script. Each parameter maps to a <code className="font-sans text-xs text-amber-400">{'{{key}}'}</code> placeholder in the script body.
|
||||||
</p>
|
</p>
|
||||||
<ParameterSchemaBuilder
|
<ParameterSchemaBuilder
|
||||||
schema={form.parameters_schema}
|
schema={form.parameters_schema}
|
||||||
@@ -487,14 +487,14 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Fixed Action Bar ──────────────────────────────────────── */}
|
{/* ── Fixed Action Bar ──────────────────────────────────────── */}
|
||||||
<div className="fixed bottom-0 left-0 right-0 z-20 border-t border-border bg-background/80 backdrop-blur-sm px-6 py-3">
|
<div className="fixed bottom-0 left-0 right-0 z-20 border-t border-[#1e2130] bg-[#0c0d10]/80 px-6 py-3">
|
||||||
<div className="max-w-5xl mx-auto flex items-center justify-between">
|
<div className="max-w-5xl mx-auto flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="flex items-center gap-1.5 bg-gradient-brand text-[#101114] font-semibold text-sm px-5 py-2 rounded-[10px] hover:opacity-90 active:scale-[0.97] transition-all shadow-lg shadow-primary/20 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="flex items-center gap-1.5 bg-[#22d3ee] text-white font-semibold text-sm px-5 py-2 rounded-lg hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{isSaving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
{isSaving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
||||||
{templateId ? 'Save Changes' : 'Create Template'}
|
{templateId ? 'Save Changes' : 'Create Template'}
|
||||||
@@ -502,7 +502,7 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleBack}
|
onClick={handleBack}
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors px-4 py-2"
|
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors px-4 py-2"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -515,14 +515,14 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
className="text-xs font-label text-rose-500 hover:text-rose-400 px-2 py-1"
|
className="text-xs font-sans text-xs text-rose-500 hover:text-rose-400 px-2 py-1"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDeleteConfirm(false)}
|
onClick={() => setDeleteConfirm(false)}
|
||||||
className="text-xs font-label text-muted-foreground hover:text-foreground px-2 py-1"
|
className="text-xs font-sans text-xs text-[#848b9b] hover:text-[#e2e5eb] px-2 py-1"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
{/* Back link */}
|
{/* Back link */}
|
||||||
<Link
|
<Link
|
||||||
to="/scripts"
|
to="/scripts"
|
||||||
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors w-fit"
|
className="flex items-center gap-1.5 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors w-fit"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={12} />
|
<ArrowLeft size={12} />
|
||||||
Back to Script Library
|
Back to Script Library
|
||||||
@@ -80,8 +80,8 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
{/* Header row */}
|
{/* Header row */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground">Manage Templates</h1>
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">Manage Templates</h1>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-[#848b9b] mt-1">
|
||||||
Create and edit PowerShell script templates.
|
Create and edit PowerShell script templates.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +89,7 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCreate}
|
onClick={onCreate}
|
||||||
className="flex items-center gap-1.5 bg-gradient-brand text-[#101114] font-semibold text-sm px-4 py-2 rounded-[10px] hover:opacity-90 active:scale-[0.97] transition-all shadow-lg shadow-primary/20"
|
className="flex items-center gap-1.5 bg-[#22d3ee] text-white font-semibold text-sm px-4 py-2 rounded-lg hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
New Template
|
New Template
|
||||||
@@ -99,70 +99,70 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="relative w-64">
|
<div className="relative w-64">
|
||||||
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none" />
|
<Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[#848b9b] pointer-events-none" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={e => setSearchQuery(e.target.value)}
|
onChange={e => setSearchQuery(e.target.value)}
|
||||||
placeholder="Search templates…"
|
placeholder="Search templates…"
|
||||||
className="w-full pl-8 pr-3 py-1.5 text-sm rounded-md border border-border bg-card text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-[rgba(6,182,212,0.3)] focus:ring-1 focus:ring-[rgba(6,182,212,0.2)]"
|
className="w-full pl-8 pr-3 py-1.5 text-sm rounded-md border border-[#1e2130] bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b] focus:outline-none focus:border-[rgba(6,182,212,0.3)] focus:ring-1 focus:ring-[rgba(6,182,212,0.2)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Template list */}
|
{/* Template list */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 size={28} className="text-primary animate-spin" />
|
<Loader2 size={28} className="text-[#22d3ee] animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : templates.length === 0 ? (
|
) : templates.length === 0 ? (
|
||||||
<div className="glass-card-static flex flex-col items-center justify-center gap-3 py-12 text-center">
|
<div className="card-flat flex flex-col items-center justify-center gap-3 py-12 text-center">
|
||||||
<FileCode size={32} className="text-muted-foreground/40" />
|
<FileCode size={32} className="text-[#848b9b]/40" />
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
{searchQuery ? 'No templates match your search' : 'No templates yet. Create your first one!'}
|
{searchQuery ? 'No templates match your search' : 'No templates yet. Create your first one!'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="glass-card-static overflow-hidden">
|
<div className="card-flat overflow-hidden">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Name</th>
|
<th className="text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Name</th>
|
||||||
<th className="text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Category</th>
|
<th className="text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Category</th>
|
||||||
<th className="text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Complexity</th>
|
<th className="text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Complexity</th>
|
||||||
<th className="text-left font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Scope</th>
|
<th className="text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Scope</th>
|
||||||
<th className="text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Uses</th>
|
<th className="text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Uses</th>
|
||||||
<th className="text-right font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground px-4 py-3">Actions</th>
|
<th className="text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] px-4 py-3">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{templates.map(t => (
|
{templates.map(t => (
|
||||||
<tr
|
<tr
|
||||||
key={t.id}
|
key={t.id}
|
||||||
className="border-b border-border last:border-b-0 hover:bg-white/[0.02] transition-colors"
|
className="border-b border-[#1e2130] last:border-b-0 hover:bg-white/[0.02] transition-colors"
|
||||||
>
|
>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className="text-foreground font-medium">{t.name}</span>
|
<span className="text-[#e2e5eb] font-medium">{t.name}</span>
|
||||||
{t.description && (
|
{t.description && (
|
||||||
<p className="text-xs text-muted-foreground line-clamp-1 mt-0.5">{t.description}</p>
|
<p className="text-xs text-[#848b9b] line-clamp-1 mt-0.5">{t.description}</p>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-muted-foreground">{getCategoryName(t.category_id)}</td>
|
<td className="px-4 py-3 text-[#848b9b]">{getCategoryName(t.category_id)}</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className={cn('font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded', COMPLEXITY_CLASSES[t.complexity])}>
|
<span className={cn('font-sans text-xs text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded', COMPLEXITY_CLASSES[t.complexity])}>
|
||||||
{t.complexity}
|
{t.complexity}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
'inline-flex items-center gap-1 font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded border',
|
'inline-flex items-center gap-1 font-sans text-xs text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded border',
|
||||||
t.team_id
|
t.team_id
|
||||||
? 'text-primary bg-primary/10 border-primary/20'
|
? 'text-[#22d3ee] bg-[rgba(34,211,238,0.10)] border-primary/20'
|
||||||
: 'text-muted-foreground bg-white/5 border-border'
|
: 'text-[#848b9b] bg-white/5 border-[#1e2130]'
|
||||||
)}>
|
)}>
|
||||||
{t.team_id ? <><Users size={10} /> Team</> : <><UserIcon size={10} /> Personal</>}
|
{t.team_id ? <><Users size={10} /> Team</> : <><UserIcon size={10} /> Personal</>}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-muted-foreground">{t.usage_count}</td>
|
<td className="px-4 py-3 text-right text-[#848b9b]">{t.usage_count}</td>
|
||||||
<td className="px-4 py-3 text-right">
|
<td className="px-4 py-3 text-right">
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="flex items-center justify-end gap-1">
|
||||||
{canManageScriptTemplate(t) && (
|
{canManageScriptTemplate(t) && (
|
||||||
@@ -170,7 +170,7 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEdit(t.id)}
|
onClick={() => onEdit(t.id)}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-white/5 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-[#e2e5eb] hover:bg-white/5 transition-colors"
|
||||||
title="Edit template"
|
title="Edit template"
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
@@ -180,14 +180,14 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleDelete(t.id)}
|
onClick={() => handleDelete(t.id)}
|
||||||
className="text-[0.625rem] font-label text-rose-500 hover:text-rose-400 px-1.5 py-0.5"
|
className="text-[0.625rem] font-sans text-xs text-rose-500 hover:text-rose-400 px-1.5 py-0.5"
|
||||||
>
|
>
|
||||||
Confirm
|
Confirm
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDeleteConfirm(null)}
|
onClick={() => setDeleteConfirm(null)}
|
||||||
className="text-[0.625rem] font-label text-muted-foreground hover:text-foreground px-1.5 py-0.5"
|
className="text-[0.625rem] font-sans text-xs text-[#848b9b] hover:text-[#e2e5eb] px-1.5 py-0.5"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -196,7 +196,7 @@ export function ScriptTemplateListView({ onEdit, onCreate }: Props) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDeleteConfirm(t.id)}
|
onClick={() => setDeleteConfirm(t.id)}
|
||||||
className="p-1.5 rounded-md text-muted-foreground hover:text-rose-500 hover:bg-white/5 transition-colors"
|
className="p-1.5 rounded-md text-[#848b9b] hover:text-rose-500 hover:bg-white/5 transition-colors"
|
||||||
title="Delete template"
|
title="Delete template"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-xs sm:items-center sm:p-4">
|
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 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-2xl">
|
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-[#1e2130] bg-[#14161d] shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border p-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] p-4">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Add Custom Step</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Add Custom Step</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
@@ -80,15 +80,15 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex border-b border-border">
|
<div className="flex border-b border-[#1e2130]">
|
||||||
{canCreateSteps && (
|
{canCreateSteps && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('create')}
|
onClick={() => setActiveTab('create')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
||||||
activeTab === 'create'
|
activeTab === 'create'
|
||||||
? 'border-b-2 border-primary bg-primary/5 text-foreground'
|
? 'border-b-2 border-primary bg-primary/5 text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Type My Own
|
Type My Own
|
||||||
@@ -99,8 +99,8 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
'flex-1 px-4 py-3 text-sm font-medium transition-colors',
|
||||||
activeTab === 'browse'
|
activeTab === 'browse'
|
||||||
? 'border-b-2 border-primary bg-primary/5 text-primary'
|
? 'border-b-2 border-primary bg-primary/5 text-[#22d3ee]'
|
||||||
: 'text-muted-foreground hover:bg-muted/50 hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-muted/50 hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Browse Library
|
Browse Library
|
||||||
@@ -133,10 +133,10 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
|
|||||||
|
|
||||||
{/* Loading Overlay */}
|
{/* Loading Overlay */}
|
||||||
{isSubmitting && (
|
{isSubmitting && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/80">
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<Spinner className="border-t-foreground" />
|
<Spinner className="border-t-foreground" />
|
||||||
<p className="text-sm text-muted-foreground">Creating step...</p>
|
<p className="text-sm text-[#848b9b]">Creating step...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
const isOwn = currentUserId ? step.created_by === currentUserId : false
|
const isOwn = currentUserId ? step.created_by === currentUserId : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group rounded-lg border border-border bg-card p-4 transition-shadow hover:shadow-md">
|
<div className="group rounded-lg border border-[#1e2130] bg-[#14161d] p-4 transition-shadow hover:shadow-md">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-3 flex items-start justify-between gap-2">
|
<div className="mb-3 flex items-start justify-between gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -65,12 +65,12 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h3 className="font-semibold text-foreground line-clamp-2">{step.title}</h3>
|
<h3 className="font-semibold text-[#e2e5eb] line-clamp-2">{step.title}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Metadata */}
|
{/* Metadata */}
|
||||||
<div className="mb-3 space-y-1.5 text-sm text-muted-foreground">
|
<div className="mb-3 space-y-1.5 text-sm text-[#848b9b]">
|
||||||
{/* Category */}
|
{/* Category */}
|
||||||
{step.category_name && (
|
{step.category_name && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
@@ -116,13 +116,13 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
{visibleTags.map(tag => (
|
{visibleTags.map(tag => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground"
|
className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{remainingTags > 0 && (
|
{remainingTags > 0 && (
|
||||||
<span className="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">
|
<span className="rounded-full bg-muted px-2 py-0.5 text-xs text-[#848b9b]">
|
||||||
+{remainingTags} more
|
+{remainingTags} more
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -138,14 +138,14 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => onPreview(step)}
|
onClick={() => onPreview(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<span
|
<span
|
||||||
title="Managed by source flow — fork to customize"
|
title="Managed by source flow — fork to customize"
|
||||||
className="flex items-center justify-center rounded-md border border-border px-3 py-2 text-muted-foreground opacity-50 cursor-default"
|
className="flex items-center justify-center rounded-md border border-[#1e2130] px-3 py-2 text-[#848b9b] opacity-50 cursor-default"
|
||||||
>
|
>
|
||||||
<Lock className="h-4 w-4" />
|
<Lock className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
@@ -155,21 +155,21 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => onPreview(step)}
|
onClick={() => onPreview(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit?.(step)}
|
onClick={() => onEdit?.(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="h-4 w-4" />
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onDelete?.(step)}
|
onClick={() => onDelete?.(step)}
|
||||||
className="flex items-center justify-center rounded-md border border-border px-3 py-2 text-muted-foreground hover:bg-red-400/10 hover:text-red-400 hover:border-red-400/30 transition-colors"
|
className="flex items-center justify-center rounded-md border border-[#1e2130] px-3 py-2 text-[#848b9b] hover:bg-red-400/10 hover:text-red-400 hover:border-red-400/30 transition-colors"
|
||||||
aria-label="Delete step"
|
aria-label="Delete step"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@@ -181,14 +181,14 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => onPreview(step)}
|
onClick={() => onPreview(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onSave?.(step)}
|
onClick={() => onSave?.(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium hover:opacity-90 transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md bg-[#22d3ee] text-white px-3 py-2 text-sm font-medium hover:brightness-110 transition-colors"
|
||||||
>
|
>
|
||||||
<Bookmark className="h-4 w-4" />
|
<Bookmark className="h-4 w-4" />
|
||||||
Save
|
Save
|
||||||
@@ -200,14 +200,14 @@ export function StepCard({ step, onPreview, onInsert, onEdit, onDelete, onSave,
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => onPreview(step)}
|
onClick={() => onPreview(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
Preview
|
Preview
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onInsert?.(step)}
|
onClick={() => onInsert?.(step)}
|
||||||
className="flex flex-1 items-center justify-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium hover:opacity-90 transition-colors"
|
className="flex flex-1 items-center justify-center gap-2 rounded-md bg-[#22d3ee] text-white px-3 py-2 text-sm font-medium hover:brightness-110 transition-colors"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Insert
|
Insert
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
|
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||||
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-[#14161d] border border-[#1e2130] rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-start justify-between border-b border-border p-6 pb-4">
|
<div className="flex items-start justify-between border-b border-[#1e2130] p-6 pb-4">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="h-6 w-48 animate-pulse rounded bg-accent" />
|
<div className="h-6 w-48 animate-pulse rounded bg-accent" />
|
||||||
) : error ? (
|
) : error ? (
|
||||||
@@ -97,7 +97,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{step.step_type}
|
{step.step_type}
|
||||||
</span>
|
</span>
|
||||||
{step.category_name && (
|
{step.category_name && (
|
||||||
<span className="text-xs text-muted-foreground">{step.category_name}</span>
|
<span className="text-xs text-[#848b9b]">{step.category_name}</span>
|
||||||
)}
|
)}
|
||||||
{step.is_featured && (
|
{step.is_featured && (
|
||||||
<span className="rounded bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
|
<span className="rounded bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400">
|
||||||
@@ -110,12 +110,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-semibold text-foreground">{step.title}</h2>
|
<h2 className="text-xl font-semibold text-[#e2e5eb]">{step.title}</h2>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
@@ -131,13 +131,13 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
<div className="h-4 w-5/6 animate-pulse rounded bg-accent" />
|
<div className="h-4 w-5/6 animate-pulse rounded bg-accent" />
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<p className="text-sm text-muted-foreground">{error}</p>
|
<p className="text-sm text-[#848b9b]">{error}</p>
|
||||||
) : step ? (
|
) : step ? (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Rating */}
|
{/* Rating */}
|
||||||
{hasRating && (
|
{hasRating && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 text-sm font-semibold text-foreground">Rating</h3>
|
<h3 className="mb-2 text-sm font-semibold text-[#e2e5eb]">Rating</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{[1, 2, 3, 4, 5].map(i => (
|
{[1, 2, 3, 4, 5].map(i => (
|
||||||
@@ -147,12 +147,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
'h-4 w-4',
|
'h-4 w-4',
|
||||||
i <= Math.round(step.rating_average)
|
i <= Math.round(step.rating_average)
|
||||||
? 'fill-yellow-400 text-yellow-400'
|
? 'fill-yellow-400 text-yellow-400'
|
||||||
: 'text-muted-foreground'
|
: 'text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
{step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
|
{step.rating_average.toFixed(1)} ({step.rating_count} {step.rating_count === 1 ? 'rating' : 'ratings'})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,12 +162,12 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
{step.tags.length > 0 && (
|
{step.tags.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 text-sm font-semibold text-foreground">Tags</h3>
|
<h3 className="mb-2 text-sm font-semibold text-[#e2e5eb]">Tags</h3>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{step.tags.map(tag => (
|
{step.tags.map(tag => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="rounded-full bg-accent px-2 py-1 text-xs text-muted-foreground"
|
className="rounded-full bg-accent px-2 py-1 text-xs text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
@@ -179,7 +179,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 text-sm font-semibold">Instructions</h3>
|
<h3 className="mb-2 text-sm font-semibold">Instructions</h3>
|
||||||
<div className="rounded-lg border border-border bg-accent/50 p-4">
|
<div className="rounded-lg border border-[#1e2130] bg-accent/50 p-4">
|
||||||
<MarkdownContent content={step.content.instructions} />
|
<MarkdownContent content={step.content.instructions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,8 +187,8 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{/* Help Text */}
|
{/* Help Text */}
|
||||||
{step.content.help_text && (
|
{step.content.help_text && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 text-sm font-semibold text-foreground">Help Text</h3>
|
<h3 className="mb-2 text-sm font-semibold text-[#e2e5eb]">Help Text</h3>
|
||||||
<div className="rounded-lg border border-border bg-blue-400/5 p-4 text-sm">
|
<div className="rounded-lg border border-[#1e2130] bg-blue-400/5 p-4 text-sm">
|
||||||
<MarkdownContent content={step.content.help_text} />
|
<MarkdownContent content={step.content.help_text} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,19 +197,19 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
{step.content.commands && step.content.commands.length > 0 && (
|
{step.content.commands && step.content.commands.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="mb-2 text-sm font-semibold text-foreground">Commands</h3>
|
<h3 className="mb-2 text-sm font-semibold text-[#e2e5eb]">Commands</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{step.content.commands.map((cmd, index) => (
|
{step.content.commands.map((cmd, index) => (
|
||||||
<div key={index} className="group relative">
|
<div key={index} className="group relative">
|
||||||
<div className="mb-1 flex items-center justify-between">
|
<div className="mb-1 flex items-center justify-between">
|
||||||
<span className="text-xs font-medium text-muted-foreground">{cmd.label}</span>
|
<span className="text-xs font-medium text-[#848b9b]">{cmd.label}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopyCommand(cmd.command, index)}
|
onClick={() => handleCopyCommand(cmd.command, index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors',
|
'flex items-center gap-1 rounded px-2 py-1 text-xs transition-colors',
|
||||||
copiedCommandIndex === index
|
copiedCommandIndex === index
|
||||||
? 'bg-emerald-400/10 text-emerald-400'
|
? 'bg-emerald-400/10 text-emerald-400'
|
||||||
: 'bg-accent text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'bg-accent text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copiedCommandIndex === index ? (
|
{copiedCommandIndex === index ? (
|
||||||
@@ -225,7 +225,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="overflow-x-auto rounded bg-card p-3 text-xs text-foreground">
|
<pre className="overflow-x-auto rounded bg-[#14161d] p-3 text-xs text-[#e2e5eb]">
|
||||||
<code>{cmd.command}</code>
|
<code>{cmd.command}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,11 +238,11 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
{visibleReviews.length > 0 && (
|
{visibleReviews.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h3 className="text-sm font-semibold text-foreground">Reviews</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">Reviews</h3>
|
||||||
{allTextReviews.length > 3 && (
|
{allTextReviews.length > 3 && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAllReviews(v => !v)}
|
onClick={() => setShowAllReviews(v => !v)}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
{showAllReviews ? 'Show less' : `See all ${allTextReviews.length} reviews`}
|
{showAllReviews ? 'Show less' : `See all ${allTextReviews.length} reviews`}
|
||||||
</button>
|
</button>
|
||||||
@@ -250,11 +250,11 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{visibleReviews.map(review => (
|
{visibleReviews.map(review => (
|
||||||
<div key={review.id} className="rounded-lg border border-border bg-accent/50 p-3">
|
<div key={review.id} className="rounded-lg border border-[#1e2130] bg-accent/50 p-3">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<User className="h-3.5 w-3.5" />
|
<User className="h-3.5 w-3.5" />
|
||||||
<span className="font-medium text-foreground">{review.user_name || 'Anonymous'}</span>
|
<span className="font-medium text-[#e2e5eb]">{review.user_name || 'Anonymous'}</span>
|
||||||
{review.verified_use && (
|
{review.verified_use && (
|
||||||
<span className="rounded bg-emerald-400/10 px-1.5 py-0.5 text-xs text-emerald-400">
|
<span className="rounded bg-emerald-400/10 px-1.5 py-0.5 text-xs text-emerald-400">
|
||||||
Verified Use
|
Verified Use
|
||||||
@@ -269,14 +269,14 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
'h-3 w-3',
|
'h-3 w-3',
|
||||||
i <= review.rating
|
i <= review.rating
|
||||||
? 'fill-yellow-400 text-yellow-400'
|
? 'fill-yellow-400 text-yellow-400'
|
||||||
: 'text-muted-foreground'
|
: 'text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">{review.review_text}</p>
|
<p className="text-sm text-[#848b9b]">{review.review_text}</p>
|
||||||
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
|
<div className="mt-2 flex items-center gap-2 text-xs text-[#848b9b]">
|
||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
{new Date(review.created_at).toLocaleDateString()}
|
{new Date(review.created_at).toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
@@ -287,28 +287,28 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Metadata */}
|
{/* Metadata */}
|
||||||
<div className="rounded-lg border border-border bg-accent/50 p-4">
|
<div className="rounded-lg border border-[#1e2130] bg-accent/50 p-4">
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Author:</span>
|
<span className="text-[#848b9b]">Author:</span>
|
||||||
<span className="ml-2 font-medium text-foreground">{step.author_name || 'Unknown'}</span>
|
<span className="ml-2 font-medium text-[#e2e5eb]">{step.author_name || 'Unknown'}</span>
|
||||||
</div>
|
</div>
|
||||||
{step.is_flow_synced && step.source_tree_name && (
|
{step.is_flow_synced && step.source_tree_name && (
|
||||||
<div className="col-span-2 flex items-center gap-1.5 text-muted-foreground">
|
<div className="col-span-2 flex items-center gap-1.5 text-[#848b9b]">
|
||||||
<GitBranch className="h-3.5 w-3.5 shrink-0" />
|
<GitBranch className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span>Sourced from <span className="font-medium text-foreground">{step.source_tree_name}</span></span>
|
<span>Sourced from <span className="font-medium text-[#e2e5eb]">{step.source_tree_name}</span></span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Usage Count:</span>
|
<span className="text-[#848b9b]">Usage Count:</span>
|
||||||
<span className="ml-2 font-medium text-foreground">{step.usage_count}</span>
|
<span className="ml-2 font-medium text-[#e2e5eb]">{step.usage_count}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Created:</span>
|
<span className="text-[#848b9b]">Created:</span>
|
||||||
<span className="ml-2 font-medium text-foreground">{new Date(step.created_at).toLocaleDateString()}</span>
|
<span className="ml-2 font-medium text-[#e2e5eb]">{new Date(step.created_at).toLocaleDateString()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground">Visibility:</span>
|
<span className="text-[#848b9b]">Visibility:</span>
|
||||||
<span className="ml-2 font-medium capitalize">{step.visibility}</span>
|
<span className="ml-2 font-medium capitalize">{step.visibility}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -318,7 +318,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer - Actions */}
|
{/* Footer - Actions */}
|
||||||
<div className="flex gap-2 border-t border-border p-4">
|
<div className="flex gap-2 border-t border-[#1e2130] p-4">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
{/* Step Type */}
|
{/* Step Type */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-2 block text-sm font-medium text-foreground">
|
<label className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Step Type <span className="text-red-400">*</span>
|
Step Type <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
@@ -155,15 +155,15 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg border p-3 text-left transition-colors',
|
'rounded-lg border p-3 text-left transition-colors',
|
||||||
stepType === option.value
|
stepType === option.value
|
||||||
? 'border-border bg-accent ring-2 ring-primary/20'
|
? 'border-[#1e2130] bg-accent ring-2 ring-primary/20'
|
||||||
: 'border-border hover:border-border'
|
: 'border-[#1e2130] hover:border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="mb-1 flex items-center gap-2">
|
<div className="mb-1 flex items-center gap-2">
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
<span className="font-medium text-sm text-foreground">{option.label}</span>
|
<span className="font-medium text-sm text-[#e2e5eb]">{option.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">{option.description}</p>
|
<p className="text-xs text-[#848b9b]">{option.description}</p>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -172,7 +172,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="title" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="title" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Title <span className="text-red-400">*</span>
|
Title <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -187,9 +187,9 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="instructions" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="instructions" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Instructions <span className="text-red-400">*</span>
|
Instructions <span className="text-red-400">*</span>
|
||||||
<span className="ml-2 text-xs font-normal text-muted-foreground">(Markdown supported)</span>
|
<span className="ml-2 text-xs font-normal text-[#848b9b]">(Markdown supported)</span>
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="instructions"
|
id="instructions"
|
||||||
@@ -203,8 +203,8 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Help Text */}
|
{/* Help Text */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="helpText" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Help Text <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
|
Help Text <span className="text-xs font-normal text-[#848b9b]">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="helpText"
|
id="helpText"
|
||||||
@@ -218,13 +218,13 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<label className="text-sm font-medium text-foreground">
|
<label className="text-sm font-medium text-[#e2e5eb]">
|
||||||
Commands <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
|
Commands <span className="text-xs font-normal text-[#848b9b]">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addCommand}
|
onClick={addCommand}
|
||||||
className="flex items-center gap-1 rounded-md bg-accent px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex items-center gap-1 rounded-md bg-accent px-2 py-1 text-xs font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
Add Command
|
Add Command
|
||||||
@@ -233,13 +233,13 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
{commands.length > 0 && (
|
{commands.length > 0 && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{commands.map((cmd, index) => (
|
{commands.map((cmd, index) => (
|
||||||
<div key={index} className="rounded-lg border border-border bg-accent/50 p-3">
|
<div key={index} className="rounded-lg border border-[#1e2130] bg-accent/50 p-3">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<span className="text-xs font-medium text-muted-foreground">Command {index + 1}</span>
|
<span className="text-xs font-medium text-[#848b9b]">Command {index + 1}</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeCommand(index)}
|
onClick={() => removeCommand(index)}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-red-400/10 hover:text-red-400"
|
className="rounded p-1 text-[#848b9b] hover:bg-red-400/10 hover:text-red-400"
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -270,14 +270,14 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Category */}
|
{/* Category */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="category" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="category" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Category <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
|
Category <span className="text-xs font-normal text-[#848b9b]">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="category"
|
id="category"
|
||||||
value={categoryId}
|
value={categoryId}
|
||||||
onChange={(e) => setCategoryId(e.target.value)}
|
onChange={(e) => setCategoryId(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">None</option>
|
<option value="">None</option>
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
@@ -288,8 +288,8 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="tagInput" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Tags <span className="text-xs font-normal text-muted-foreground">(Optional)</span>
|
Tags <span className="text-xs font-normal text-[#848b9b]">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
@@ -304,7 +304,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addTag}
|
onClick={addTag}
|
||||||
className="rounded-md bg-accent px-4 py-2 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md bg-accent px-4 py-2 text-sm font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
@@ -314,7 +314,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
{tags.map(tag => (
|
{tags.map(tag => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="flex items-center gap-1 rounded-full bg-accent px-2.5 py-1 text-xs text-muted-foreground"
|
className="flex items-center gap-1 rounded-full bg-accent px-2.5 py-1 text-xs text-[#848b9b]"
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
<button
|
<button
|
||||||
@@ -333,14 +333,14 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
|
|||||||
|
|
||||||
{/* Visibility */}
|
{/* Visibility */}
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="visibility" className="mb-2 block text-sm font-medium text-foreground">
|
<label htmlFor="visibility" className="mb-2 block text-sm font-medium text-[#e2e5eb]">
|
||||||
Visibility
|
Visibility
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
id="visibility"
|
id="visibility"
|
||||||
value={visibility}
|
value={visibility}
|
||||||
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
|
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
|
||||||
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="private">Private (only me)</option>
|
<option value="private">Private (only me)</option>
|
||||||
<option value="team">Team (my team members)</option>
|
<option value="team">Team (my team members)</option>
|
||||||
|
|||||||
@@ -49,17 +49,17 @@ export function StepFormModal({ isOpen, onClose, onSuccess, editingStep }: StepF
|
|||||||
} : undefined
|
} : undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
||||||
<div className="relative flex h-[90vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[90vh] w-full max-w-2xl flex-col bg-[#14161d] border border-[#1e2130] rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border p-6 pb-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] p-6 pb-4">
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||||
{isEditMode ? 'Edit Step' : 'Create Step'}
|
{isEditMode ? 'Edit Step' : 'Create Step'}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
|
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
|
|||||||
@@ -145,16 +145,16 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Header - Filters */}
|
{/* Header - Filters */}
|
||||||
<div className="space-y-4 border-b border-border p-4">
|
<div className="space-y-4 border-b border-[#1e2130] p-4">
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#848b9b]" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search steps..."
|
placeholder="Search steps..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="w-full rounded-md border border-[#1e2130] bg-[#14161d] py-2 pl-10 pr-4 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by category"
|
aria-label="Filter by category"
|
||||||
value={selectedCategoryId || ''}
|
value={selectedCategoryId || ''}
|
||||||
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
|
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">All Categories</option>
|
<option value="">All Categories</option>
|
||||||
{categories.map(cat => (
|
{categories.map(cat => (
|
||||||
@@ -178,7 +178,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by step type"
|
aria-label="Filter by step type"
|
||||||
value={selectedStepType || ''}
|
value={selectedStepType || ''}
|
||||||
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
|
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
<option value="decision">Decision</option>
|
<option value="decision">Decision</option>
|
||||||
@@ -191,7 +191,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Filter by minimum rating"
|
aria-label="Filter by minimum rating"
|
||||||
value={minRating?.toString() || ''}
|
value={minRating?.toString() || ''}
|
||||||
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
|
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="">Any Rating</option>
|
<option value="">Any Rating</option>
|
||||||
<option value="4">4+ Stars</option>
|
<option value="4">4+ Stars</option>
|
||||||
@@ -204,7 +204,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
aria-label="Sort steps by"
|
aria-label="Sort steps by"
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
|
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
|
||||||
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
|
||||||
>
|
>
|
||||||
<option value="recent">Most Recent</option>
|
<option value="recent">Most Recent</option>
|
||||||
<option value="popular">Most Popular</option>
|
<option value="popular">Most Popular</option>
|
||||||
@@ -216,7 +216,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
{/* Popular Tags */}
|
{/* Popular Tags */}
|
||||||
{popularTags.length > 0 && (
|
{popularTags.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2 text-xs font-medium text-muted-foreground">Popular Tags:</div>
|
<div className="mb-2 text-xs font-medium text-[#848b9b]">Popular Tags:</div>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{popularTags.map(tag => (
|
{popularTags.map(tag => (
|
||||||
<button
|
<button
|
||||||
@@ -225,8 +225,8 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-full px-2.5 py-1 text-xs transition-colors',
|
'rounded-full px-2.5 py-1 text-xs transition-colors',
|
||||||
selectedTag === tag.tag
|
selectedTag === tag.tag
|
||||||
? 'bg-gradient-brand text-white shadow-lg shadow-primary/20'
|
? 'bg-[#22d3ee] text-white'
|
||||||
: 'bg-accent text-muted-foreground hover:bg-accent'
|
: 'bg-accent text-[#848b9b] hover:bg-accent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tag.tag} ({tag.count})
|
{tag.tag} ({tag.count})
|
||||||
@@ -240,7 +240,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<button
|
<button
|
||||||
onClick={clearFilters}
|
onClick={clearFilters}
|
||||||
className="text-sm text-muted-foreground hover:text-foreground hover:underline"
|
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
Clear all filters
|
Clear all filters
|
||||||
</button>
|
</button>
|
||||||
@@ -251,7 +251,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
<Loader2 className="h-8 w-8 animate-spin text-[#848b9b]" />
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center">
|
<div className="rounded-lg border border-red-400/20 bg-red-400/10 p-4 text-center">
|
||||||
@@ -262,9 +262,9 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
</div>
|
</div>
|
||||||
) : steps.length === 0 ? (
|
) : steps.length === 0 ? (
|
||||||
hasActiveFilters ? (
|
hasActiveFilters ? (
|
||||||
<div className="rounded-lg border border-border bg-accent/50 p-12 text-center">
|
<div className="rounded-lg border border-[#1e2130] bg-accent/50 p-12 text-center">
|
||||||
<p className="mb-2 text-lg font-medium text-foreground">No steps found</p>
|
<p className="mb-2 text-lg font-medium text-[#e2e5eb]">No steps found</p>
|
||||||
<p className="text-sm text-muted-foreground">Try adjusting your filters</p>
|
<p className="text-sm text-[#848b9b]">Try adjusting your filters</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -288,7 +288,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
onClick={() => toggleSection('private')}
|
onClick={() => toggleSection('private')}
|
||||||
className="mb-3 flex w-full items-center justify-between"
|
className="mb-3 flex w-full items-center justify-between"
|
||||||
>
|
>
|
||||||
<h3 className="text-sm font-semibold text-foreground">My Steps ({groupedSteps.private.length})</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">My Steps ({groupedSteps.private.length})</h3>
|
||||||
{collapsedSections.private ? (
|
{collapsedSections.private ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
@@ -321,7 +321,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
onClick={() => toggleSection('team')}
|
onClick={() => toggleSection('team')}
|
||||||
className="mb-3 flex w-full items-center justify-between"
|
className="mb-3 flex w-full items-center justify-between"
|
||||||
>
|
>
|
||||||
<h3 className="text-sm font-semibold text-foreground">Team Steps ({groupedSteps.team.length})</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">Team Steps ({groupedSteps.team.length})</h3>
|
||||||
{collapsedSections.team ? (
|
{collapsedSections.team ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
@@ -354,7 +354,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
onClick={() => toggleSection('public')}
|
onClick={() => toggleSection('public')}
|
||||||
className="mb-3 flex w-full items-center justify-between"
|
className="mb-3 flex w-full items-center justify-between"
|
||||||
>
|
>
|
||||||
<h3 className="text-sm font-semibold text-foreground">Community ({groupedSteps.public.length})</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">Community ({groupedSteps.public.length})</h3>
|
||||||
{collapsedSections.public ? (
|
{collapsedSections.public ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
@@ -385,7 +385,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
|
|||||||
|
|
||||||
{/* Footer - Optional Create Button */}
|
{/* Footer - Optional Create Button */}
|
||||||
{showCreateButton && onCreateNew && (
|
{showCreateButton && onCreateNew && (
|
||||||
<div className="border-t border-border p-4">
|
<div className="border-t border-[#1e2130] p-4">
|
||||||
<Button onClick={onCreateNew} className="w-full">
|
<Button onClick={onCreateNew} className="w-full">
|
||||||
+ Create New Step
|
+ Create New Step
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -40,19 +40,19 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
const allHandled = pendingFixes.length === 0
|
const allHandled = pendingFixes.length === 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
||||||
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
|
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-[#14161d] border border-[#1e2130] rounded-2xl shadow-lg">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-6 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Sparkles className="h-5 w-5 text-primary" />
|
<Sparkles className="h-5 w-5 text-[#22d3ee]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||||
AI Fix Proposals ({fixes.length})
|
AI Fix Proposals ({fixes.length})
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -73,16 +73,16 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
isApplied
|
isApplied
|
||||||
? 'border-emerald-400/30 bg-emerald-400/5'
|
? 'border-emerald-400/30 bg-emerald-400/5'
|
||||||
: isSkipped
|
: isSkipped
|
||||||
? 'border-border bg-accent/30 opacity-60'
|
? 'border-[#1e2130] bg-accent/30 opacity-60'
|
||||||
: 'border-border bg-card'
|
: 'border-[#1e2130] bg-[#14161d]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Fix header */}
|
{/* Fix header */}
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm text-red-400 mb-1">{fix.error_message}</p>
|
<p className="text-sm text-red-400 mb-1">{fix.error_message}</p>
|
||||||
<p className="text-sm text-foreground">{fix.description}</p>
|
<p className="text-sm text-[#e2e5eb]">{fix.description}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-[#848b9b] mt-1">
|
||||||
Node: {fix.target_node_id}
|
Node: {fix.target_node_id}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,7 +92,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isSkipped && (
|
{isSkipped && (
|
||||||
<span className="text-xs text-muted-foreground">Skipped</span>
|
<span className="text-xs text-[#848b9b]">Skipped</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleExpanded(fix.target_node_id)}
|
onClick={() => toggleExpanded(fix.target_node_id)}
|
||||||
className="mt-2 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
className="mt-2 flex items-center gap-1 text-xs text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{isExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
{isExpanded ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
|
||||||
{isExpanded ? 'Hide' : 'Show'} details
|
{isExpanded ? 'Hide' : 'Show'} details
|
||||||
@@ -110,14 +110,14 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="mt-3 grid grid-cols-2 gap-3">
|
<div className="mt-3 grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-muted-foreground mb-1">Before</p>
|
<p className="text-xs font-medium text-[#848b9b] mb-1">Before</p>
|
||||||
<pre className="overflow-x-auto rounded bg-accent/50 p-2 text-xs text-muted-foreground max-h-48 overflow-y-auto">
|
<pre className="overflow-x-auto rounded bg-accent/50 p-2 text-xs text-[#848b9b] max-h-48 overflow-y-auto">
|
||||||
{JSON.stringify(fix.original_node, null, 2)}
|
{JSON.stringify(fix.original_node, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs font-medium text-emerald-400 mb-1">After</p>
|
<p className="text-xs font-medium text-emerald-400 mb-1">After</p>
|
||||||
<pre className="overflow-x-auto rounded bg-emerald-400/5 p-2 text-xs text-foreground max-h-48 overflow-y-auto">
|
<pre className="overflow-x-auto rounded bg-emerald-400/5 p-2 text-xs text-[#e2e5eb] max-h-48 overflow-y-auto">
|
||||||
{JSON.stringify(fix.fixed_node, null, 2)}
|
{JSON.stringify(fix.fixed_node, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +150,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between border-t border-border px-6 py-4">
|
<div className="flex items-center justify-between border-t border-[#1e2130] px-6 py-4">
|
||||||
<Button variant="secondary" onClick={onClose}>
|
<Button variant="secondary" onClick={onClose}>
|
||||||
{allHandled ? 'Done' : 'Cancel'}
|
{allHandled ? 'Done' : 'Cancel'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
<div
|
<div
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50',
|
'relative min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-[#1e2130] bg-[#14161d]/50',
|
||||||
'transition-all duration-150',
|
'transition-all duration-150',
|
||||||
!picking && !confirming && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30'
|
!picking && !confirming && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30'
|
||||||
)}
|
)}
|
||||||
@@ -43,7 +43,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); setConfirming(true) }}
|
onClick={(e) => { e.stopPropagation(); setConfirming(true) }}
|
||||||
className="absolute top-1.5 right-1.5 rounded p-0.5 text-muted-foreground/40 hover:bg-red-500/10 hover:text-red-400 transition-colors"
|
className="absolute top-1.5 right-1.5 rounded p-0.5 text-[#848b9b]/40 hover:bg-red-500/10 hover:text-red-400 transition-colors"
|
||||||
title="Delete stub"
|
title="Delete stub"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
@@ -51,33 +51,33 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Label */}
|
{/* Label */}
|
||||||
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-foreground text-center">
|
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-[#e2e5eb] text-center">
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Confirm delete */}
|
{/* Confirm delete */}
|
||||||
{confirming ? (
|
{confirming ? (
|
||||||
<div className="px-2 pb-2.5 text-center space-y-1.5">
|
<div className="px-2 pb-2.5 text-center space-y-1.5">
|
||||||
<p className="text-[10px] text-muted-foreground">Delete this stub?</p>
|
<p className="text-[10px] text-[#848b9b]">Delete this stub?</p>
|
||||||
<div className="flex items-center justify-center gap-1.5">
|
<div className="flex items-center justify-center gap-1.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onDelete(node.id) }}
|
onClick={(e) => { e.stopPropagation(); onDelete(node.id) }}
|
||||||
className="rounded-md px-2 py-1 text-[10px] font-label border border-red-500/30 bg-red-500/10 text-red-400 hover:bg-red-500/20"
|
className="rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-red-500/30 bg-red-500/10 text-red-400 hover:bg-red-500/20"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); setConfirming(false) }}
|
onClick={(e) => { e.stopPropagation(); setConfirming(false) }}
|
||||||
className="rounded-md px-2 py-1 text-[10px] font-label border border-border text-muted-foreground hover:bg-accent"
|
className="rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-[#1e2130] text-[#848b9b] hover:bg-accent"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : !picking ? (
|
) : !picking ? (
|
||||||
<div className="pb-2.5 text-center text-[10px] text-muted-foreground font-label">
|
<div className="pb-2.5 text-center text-[10px] text-[#848b9b] font-sans text-xs">
|
||||||
+ Choose Type
|
+ Choose Type
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -86,7 +86,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
||||||
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -96,7 +96,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'action') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'action') }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
||||||
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -106,7 +106,7 @@ export function AnswerStubCard({ node, fromOption, onSelectType, onDelete }: Ans
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'solution') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'solution') }}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs',
|
||||||
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function DynamicArrayField<T>({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleMoveUp(index)}
|
onClick={() => handleMoveUp(index)}
|
||||||
disabled={index === 0}
|
disabled={index === 0}
|
||||||
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
|
className="rounded p-0.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-30"
|
||||||
title="Move up"
|
title="Move up"
|
||||||
aria-label="Move up"
|
aria-label="Move up"
|
||||||
>
|
>
|
||||||
@@ -61,7 +61,7 @@ export function DynamicArrayField<T>({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleMoveDown(index)}
|
onClick={() => handleMoveDown(index)}
|
||||||
disabled={index === items.length - 1}
|
disabled={index === items.length - 1}
|
||||||
className="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-30"
|
className="rounded p-0.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-30"
|
||||||
title="Move down"
|
title="Move down"
|
||||||
aria-label="Move down"
|
aria-label="Move down"
|
||||||
>
|
>
|
||||||
@@ -78,7 +78,7 @@ export function DynamicArrayField<T>({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onRemove(index)}
|
onClick={() => onRemove(index)}
|
||||||
className="mt-1 rounded p-1 text-muted-foreground hover:bg-red-400/20 hover:text-red-400"
|
className="mt-1 rounded p-1 text-[#848b9b] hover:bg-red-400/20 hover:text-red-400"
|
||||||
title="Remove"
|
title="Remove"
|
||||||
aria-label="Remove"
|
aria-label="Remove"
|
||||||
>
|
>
|
||||||
@@ -94,9 +94,9 @@ export function DynamicArrayField<T>({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onAdd}
|
onClick={onAdd}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-border',
|
'flex w-full items-center justify-center gap-1 rounded-md border border-dashed border-[#1e2130]',
|
||||||
'px-3 py-2 text-sm text-muted-foreground',
|
'px-3 py-2 text-sm text-[#848b9b]',
|
||||||
'hover:border-border hover:text-foreground'
|
'hover:border-[#1e2130] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -106,7 +106,7 @@ export function DynamicArrayField<T>({
|
|||||||
|
|
||||||
{/* Empty state */}
|
{/* Empty state */}
|
||||||
{items.length === 0 && !canAdd && (
|
{items.length === 0 && !canAdd && (
|
||||||
<p className="text-center text-sm text-muted-foreground">No items</p>
|
<p className="text-center text-sm text-[#848b9b]">No items</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,13 +147,13 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
|||||||
>
|
>
|
||||||
<GlowEdgeDefs />
|
<GlowEdgeDefs />
|
||||||
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
|
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
|
||||||
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
|
<Controls showInteractive={false} className="bg-[#14161d]! border-[#1e2130]! shadow-lg!" />
|
||||||
{minimapVisible && (
|
{minimapVisible && (
|
||||||
<MiniMap
|
<MiniMap
|
||||||
pannable
|
pannable
|
||||||
zoomable
|
zoomable
|
||||||
nodeColor={minimapNodeColor}
|
nodeColor={minimapNodeColor}
|
||||||
className="bg-card! border-border!"
|
className="bg-[#14161d]! border-[#1e2130]!"
|
||||||
nodeStrokeWidth={2}
|
nodeStrokeWidth={2}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -163,7 +163,7 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
|
|||||||
<button
|
<button
|
||||||
onClick={() => setMinimapVisible(v => !v)}
|
onClick={() => setMinimapVisible(v => !v)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'absolute bottom-2 right-2 z-10 rounded-lg border border-border bg-card p-2 text-muted-foreground shadow-lg hover:bg-accent hover:text-foreground transition-colors',
|
'absolute bottom-2 right-2 z-10 rounded-lg border border-[#1e2130] bg-[#14161d] p-2 text-[#848b9b] shadow-lg hover:bg-accent hover:text-[#e2e5eb] transition-colors',
|
||||||
minimapVisible && 'bottom-[170px]'
|
minimapVisible && 'bottom-[170px]'
|
||||||
)}
|
)}
|
||||||
title={minimapVisible ? 'Hide minimap' : 'Show minimap'}
|
title={minimapVisible ? 'Hide minimap' : 'Show minimap'}
|
||||||
|
|||||||
@@ -20,18 +20,18 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50 transition-all',
|
'w-[280px] rounded-xl border-2 border-dashed border-[#1e2130] bg-[#14161d]/50 transition-all',
|
||||||
!picking && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30',
|
!picking && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30',
|
||||||
selected && 'ring-1 ring-primary'
|
selected && 'ring-1 ring-primary'
|
||||||
)}
|
)}
|
||||||
onClick={() => !picking && setPicking(true)}
|
onClick={() => !picking && setPicking(true)}
|
||||||
>
|
>
|
||||||
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-foreground text-center">
|
<div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-[#e2e5eb] text-center">
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!picking ? (
|
{!picking ? (
|
||||||
<div className="pb-2.5 text-center text-[10px] text-muted-foreground font-label">
|
<div className="pb-2.5 text-center text-[10px] text-[#848b9b] font-sans text-xs">
|
||||||
+ Choose Type
|
+ Choose Type
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -39,21 +39,21 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'decision') }}
|
||||||
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20"
|
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20"
|
||||||
>
|
>
|
||||||
<HelpCircle className="h-2.5 w-2.5" /> Decision
|
<HelpCircle className="h-2.5 w-2.5" /> Decision
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'action') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'action') }}
|
||||||
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
|
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20"
|
||||||
>
|
>
|
||||||
<Zap className="h-2.5 w-2.5" /> Action
|
<Zap className="h-2.5 w-2.5" /> Action
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'solution') }}
|
onClick={(e) => { e.stopPropagation(); onSelectType(node.id, 'solution') }}
|
||||||
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20"
|
className="flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-sans text-xs border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20"
|
||||||
>
|
>
|
||||||
<CheckCircle className="h-2.5 w-2.5" /> Solution
|
<CheckCircle className="h-2.5 w-2.5" /> Solution
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
<div
|
<div
|
||||||
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
onContextMenu={(e) => onContextMenu?.(e, node.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group w-[280px] rounded-xl border border-border bg-card shadow-xs cursor-pointer transition-all',
|
'group w-[280px] rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xs cursor-pointer transition-all',
|
||||||
config.borderClass,
|
config.borderClass,
|
||||||
selected && 'ring-1 ring-primary shadow-md',
|
selected && 'ring-1 ring-primary shadow-md',
|
||||||
isGhost && 'border-dashed border-primary/40! opacity-60'
|
isGhost && 'border-dashed border-primary/40! opacity-60'
|
||||||
@@ -83,13 +83,13 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">
|
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Badges */}
|
{/* Badges */}
|
||||||
{isNew && (
|
{isNew && (
|
||||||
<span className="rounded-full bg-yellow-500/20 px-1.5 py-0.5 text-[10px] font-label text-yellow-400">
|
<span className="rounded-full bg-yellow-500/20 px-1.5 py-0.5 text-[10px] font-sans text-xs text-yellow-400">
|
||||||
New
|
New
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -105,7 +105,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onDelete(node.id)
|
onDelete(node.id)
|
||||||
}}
|
}}
|
||||||
className="shrink-0 rounded-md p-1 text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-red-500/20 hover:text-red-400 transition-all"
|
className="shrink-0 rounded-md p-1 text-[#848b9b] opacity-0 group-hover:opacity-100 hover:bg-red-500/20 hover:text-red-400 transition-all"
|
||||||
title="Delete node"
|
title="Delete node"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
@@ -115,19 +115,19 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
{/* Decision options preview */}
|
{/* Decision options preview */}
|
||||||
{node.type === 'decision' && optionCount > 0 && (
|
{node.type === 'decision' && optionCount > 0 && (
|
||||||
<div className="border-t border-border px-3 py-1.5">
|
<div className="border-t border-[#1e2130] px-3 py-1.5">
|
||||||
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
<div className="flex items-center gap-1.5 text-xs text-[#848b9b]">
|
||||||
<span className="font-label">{optionCount} option{optionCount !== 1 ? 's' : ''}</span>
|
<span className="font-sans text-xs">{optionCount} option{optionCount !== 1 ? 's' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 space-y-0.5">
|
<div className="mt-1 space-y-0.5">
|
||||||
{node.options!.slice(0, 3).map((opt, i) => (
|
{node.options!.slice(0, 3).map((opt, i) => (
|
||||||
<div key={opt.id} className="truncate text-xs text-muted-foreground">
|
<div key={opt.id} className="truncate text-xs text-[#848b9b]">
|
||||||
<span className="font-label text-foreground/60">{String.fromCharCode(65 + i)}</span>{' '}
|
<span className="font-sans text-xs text-[#e2e5eb]/60">{String.fromCharCode(65 + i)}</span>{' '}
|
||||||
{opt.label || '(empty)'}
|
{opt.label || '(empty)'}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{optionCount > 3 && (
|
{optionCount > 3 && (
|
||||||
<div className="text-xs text-muted-foreground">+{optionCount - 3} more</div>
|
<div className="text-xs text-[#848b9b]">+{optionCount - 3} more</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,31 +135,31 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
{/* Description preview for action/solution */}
|
{/* Description preview for action/solution */}
|
||||||
{(node.type === 'action' || node.type === 'solution') && node.description && (
|
{(node.type === 'action' || node.type === 'solution') && node.description && (
|
||||||
<div className="border-t border-border px-3 py-1.5">
|
<div className="border-t border-[#1e2130] px-3 py-1.5">
|
||||||
<div className="line-clamp-2 text-xs text-muted-foreground">{node.description}</div>
|
<div className="line-clamp-2 text-xs text-[#848b9b]">{node.description}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Collapse chevron */}
|
{/* Collapse chevron */}
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<div className="flex justify-center border-t border-border py-1">
|
<div className="flex justify-center border-t border-[#1e2130] py-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onToggleCollapse(node.id)
|
onToggleCollapse(node.id)
|
||||||
}}
|
}}
|
||||||
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
className="flex items-center gap-1 rounded px-2 py-0.5 text-xs text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<>
|
<>
|
||||||
<ChevronRight className="h-3 w-3" />
|
<ChevronRight className="h-3 w-3" />
|
||||||
<span className="font-label">Expand</span>
|
<span className="font-sans text-xs">Expand</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
<span className="font-label">Collapse</span>
|
<span className="font-sans text-xs">Collapse</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -168,7 +168,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
|
|||||||
|
|
||||||
{/* Ghost node accept/dismiss overlay */}
|
{/* Ghost node accept/dismiss overlay */}
|
||||||
{isGhost && (
|
{isGhost && (
|
||||||
<div className="mt-2 flex gap-2 border-t border-border/50 pt-2 px-3 pb-2">
|
<div className="mt-2 flex gap-2 border-t border-[#1e2130]/50 pt-2 px-3 pb-2">
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|||||||
@@ -28,26 +28,26 @@ export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
|
|||||||
<>
|
<>
|
||||||
{/* Backdrop — click to close */}
|
{/* Backdrop — click to close */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-xs"
|
className="fixed inset-0 z-40 bg-[#0c0d10]/40"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Side panel — slides in from right */}
|
{/* Side panel — slides in from right */}
|
||||||
<div
|
<div
|
||||||
className="fixed right-0 top-0 z-50 flex h-full w-80 flex-col border-l border-border bg-card shadow-xl"
|
className="fixed right-0 top-0 z-50 flex h-full w-80 flex-col border-l border-[#1e2130] bg-[#14161d] shadow-xl"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label="Flow metadata"
|
aria-label="Flow metadata"
|
||||||
>
|
>
|
||||||
{/* Panel header */}
|
{/* Panel header */}
|
||||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-4 py-3">
|
||||||
<h2 className="text-sm font-semibold text-foreground font-heading">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] font-heading">
|
||||||
Flow Details
|
Flow Details
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
aria-label="Close metadata panel"
|
aria-label="Close metadata panel"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function NodeEditorModal({ node, onClose, isNewNode = false }: NodeEditor
|
|||||||
return (
|
return (
|
||||||
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent} allowFullScreen={true}>
|
<Modal isOpen={true} onClose={onClose} title={getTitle()} size="lg" footer={footerContent} allowFullScreen={true}>
|
||||||
{/* Node ID display */}
|
{/* Node ID display */}
|
||||||
<div className="mb-4 text-xs text-muted-foreground">
|
<div className="mb-4 text-xs text-[#848b9b]">
|
||||||
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
|
Node ID: <code className="rounded bg-accent px-1 py-0.5">{node.id}</code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -135,17 +135,17 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
// Answer stub: show type picker instead of form
|
// Answer stub: show type picker instead of form
|
||||||
if (node.type === 'answer') {
|
if (node.type === 'answer') {
|
||||||
return (
|
return (
|
||||||
<div ref={panelRef} className="flex h-full w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
<div ref={panelRef} className="flex h-full w-[400px] shrink-0 flex-col border-l border-[#1e2130] bg-[#14161d]">
|
||||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-4 py-3">
|
||||||
<span className="text-sm font-heading font-medium text-foreground">
|
<span className="text-sm font-heading font-medium text-[#e2e5eb]">
|
||||||
{node.title || 'Answer Placeholder'}
|
{node.title || 'Answer Placeholder'}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={handleClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground">
|
<button onClick={handleClose} className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-4">
|
<div className="flex flex-1 flex-col items-center justify-center gap-4 px-4">
|
||||||
<p className="text-sm text-muted-foreground text-center">Choose a type for this node:</p>
|
<p className="text-sm text-[#848b9b] text-center">Choose a type for this node:</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{(['decision', 'action', 'solution'] as const).map(type => {
|
{(['decision', 'action', 'solution'] as const).map(type => {
|
||||||
const cfg = TYPE_CONFIG[type]
|
const cfg = TYPE_CONFIG[type]
|
||||||
@@ -156,7 +156,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelectType?.(nodeId, type)}
|
onClick={() => onSelectType?.(nodeId, type)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-label border transition-colors',
|
'flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-sans text-xs border transition-colors',
|
||||||
type === 'decision' && 'border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20',
|
type === 'decision' && 'border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20',
|
||||||
type === 'action' && 'border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20',
|
type === 'action' && 'border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20',
|
||||||
type === 'solution' && 'border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20',
|
type === 'solution' && 'border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20',
|
||||||
@@ -178,14 +178,14 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
const isRoot = treeStructure?.id === nodeId
|
const isRoot = treeStructure?.id === nodeId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={panelRef} className="flex h-full min-h-0 w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
<div ref={panelRef} className="flex h-full min-h-0 w-[400px] shrink-0 flex-col border-l border-[#1e2130] bg-[#14161d]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-2 border-b border-border px-4 py-3 shrink-0">
|
<div className="flex items-center gap-2 border-b border-[#1e2130] px-4 py-3 shrink-0">
|
||||||
<span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded', config.badgeClass)}>
|
<span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded', config.badgeClass)}>
|
||||||
<TypeIcon className="h-3 w-3" />
|
<TypeIcon className="h-3 w-3" />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">{title}</span>
|
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">{title}</span>
|
||||||
<button onClick={handleClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground">
|
<button onClick={handleClose} className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,20 +198,20 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="sticky bottom-0 flex items-center gap-2 border-t border-border bg-card px-4 py-3 shrink-0">
|
<div className="sticky bottom-0 flex items-center gap-2 border-t border-[#1e2130] bg-[#14161d] px-4 py-3 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20 transition-opacity',
|
'flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity',
|
||||||
isDirty ? 'bg-gradient-brand hover:opacity-90' : 'bg-gradient-brand opacity-50 cursor-not-allowed'
|
isDirty ? 'bg-[#22d3ee] hover:brightness-110' : 'bg-[#22d3ee] opacity-50 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Save className="h-3.5 w-3.5" /> Save
|
<Save className="h-3.5 w-3.5" /> Save
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="rounded-lg border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-lg border border-[#1e2130] px-4 py-2 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -220,7 +220,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleDuplicate}
|
onClick={handleDuplicate}
|
||||||
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
title="Duplicate"
|
title="Duplicate"
|
||||||
>
|
>
|
||||||
<Copy className="h-4 w-4" />
|
<Copy className="h-4 w-4" />
|
||||||
@@ -235,7 +235,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDeleteConfirm(false)}
|
onClick={() => setShowDeleteConfirm(false)}
|
||||||
className="rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent"
|
className="rounded-md px-2 py-1 text-xs text-[#848b9b] hover:bg-accent"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -243,7 +243,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDeleteConfirm(true)}
|
onClick={() => setShowDeleteConfirm(true)}
|
||||||
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-red-400"
|
className="rounded-md p-2 text-[#848b9b] hover:bg-accent hover:text-red-400"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Title <span className="text-red-400">*</span>
|
Title <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -61,9 +61,9 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
placeholder="e.g., Restart the Service"
|
placeholder="e.g., Restart the Service"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
titleError ? 'border-red-400' : 'border-border'
|
titleError ? 'border-red-400' : 'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{titleError && (
|
{titleError && (
|
||||||
@@ -74,7 +74,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
Description
|
Description
|
||||||
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
||||||
</label>
|
</label>
|
||||||
@@ -82,14 +82,14 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPreview(!showPreview)}
|
onClick={() => setShowPreview(!showPreview)}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
{showPreview ? 'Edit' : 'Preview'}
|
{showPreview ? 'Edit' : 'Preview'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showPreview && node.description ? (
|
{showPreview && node.description ? (
|
||||||
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
|
<div className="mt-1 rounded-md border border-[#1e2130] bg-accent/50 p-3 text-sm">
|
||||||
<MarkdownContent content={node.description} />
|
<MarkdownContent content={node.description} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -105,8 +105,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
**Note:** Important information here"
|
**Note:** Important information here"
|
||||||
rows={5}
|
rows={5}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -115,7 +115,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
|
|
||||||
{/* Commands */}
|
{/* Commands */}
|
||||||
<div>
|
<div>
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
Commands
|
Commands
|
||||||
<InfoTip text="PowerShell or CLI commands to execute" />
|
<InfoTip text="PowerShell or CLI commands to execute" />
|
||||||
</label>
|
</label>
|
||||||
@@ -132,8 +132,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
onChange={(e) => handleUpdateCommand(index, e.target.value)}
|
onChange={(e) => handleUpdateCommand(index, e.target.value)}
|
||||||
placeholder="e.g., Get-Service BrokerAgent"
|
placeholder="e.g., Get-Service BrokerAgent"
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
|
'block w-full rounded-md border border-[#1e2130] px-3 py-2 font-mono text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -143,7 +143,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
|
|
||||||
{/* Expected Outcome */}
|
{/* Expected Outcome */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Expected Outcome
|
Expected Outcome
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -152,8 +152,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
|
onChange={(e) => onUpdate({ expected_outcome: e.target.value })}
|
||||||
placeholder="e.g., Service should show as Running"
|
placeholder="e.g., Service should show as Running"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -161,13 +161,13 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
|
|
||||||
{/* Link to existing node */}
|
{/* Link to existing node */}
|
||||||
<div>
|
<div>
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
<Link2 className="h-3.5 w-3.5" />
|
<Link2 className="h-3.5 w-3.5" />
|
||||||
Next Step
|
Next Step
|
||||||
</label>
|
</label>
|
||||||
{hasNextNode ? (
|
{hasNextNode ? (
|
||||||
<div className="mt-1 flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2">
|
<div className="mt-1 flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2">
|
||||||
<span className="flex-1 truncate text-sm text-foreground">
|
<span className="flex-1 truncate text-sm text-[#e2e5eb]">
|
||||||
Linked to: {(() => {
|
Linked to: {(() => {
|
||||||
const treeStructure = useTreeEditorStore.getState().treeStructure
|
const treeStructure = useTreeEditorStore.getState().treeStructure
|
||||||
const allNodes = collectAllNodesFlat(treeStructure)
|
const allNodes = collectAllNodesFlat(treeStructure)
|
||||||
@@ -178,7 +178,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onUpdate({ next_node_id: undefined })}
|
onClick={() => onUpdate({ next_node_id: undefined })}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
title="Remove link"
|
title="Remove link"
|
||||||
>
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
@@ -193,8 +193,8 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-[#14161d] text-[#e2e5eb]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -212,7 +212,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
|
|||||||
})()}
|
})()}
|
||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
{hasNextNode
|
{hasNextNode
|
||||||
? 'This action will navigate to the linked node.'
|
? 'This action will navigate to the linked node.'
|
||||||
: 'Select a node to navigate to after this action, or save to create a new placeholder.'}
|
: 'Select a node to navigate to after this action, or save to create a new placeholder.'}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
|
|
||||||
{/* Question */}
|
{/* Question */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
|
{isRootNode ? 'Starting Question' : 'Question'} <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -103,9 +103,9 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
: "e.g., Can you ping the server?"}
|
: "e.g., Can you ping the server?"}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
questionError ? 'border-red-400' : 'border-border'
|
questionError ? 'border-red-400' : 'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{questionError && (
|
{questionError && (
|
||||||
@@ -115,7 +115,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
|
|
||||||
{/* Help Text */}
|
{/* Help Text */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Help Text
|
Help Text
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -124,8 +124,8 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
placeholder="Additional context or instructions for this decision..."
|
placeholder="Additional context or instructions for this decision..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -133,13 +133,13 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
|
|
||||||
{/* Options */}
|
{/* Options */}
|
||||||
<div>
|
<div>
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
|
{isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
|
||||||
<InfoTip text={isRootNode
|
<InfoTip text={isRootNode
|
||||||
? "Add as many options as needed (A, B, C, D...). Each option leads to a different troubleshooting path."
|
? "Add as many options as needed (A, B, C, D...). Each option leads to a different troubleshooting path."
|
||||||
: "Each option can branch to a different next step."} />
|
: "Each option can branch to a different next step."} />
|
||||||
</label>
|
</label>
|
||||||
<p className="text-xs text-muted-foreground mt-1">Options become answer placeholders you can fill in later.</p>
|
<p className="text-xs text-[#848b9b] mt-1">Options become answer placeholders you can fill in later.</p>
|
||||||
{optionsError && (
|
{optionsError && (
|
||||||
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
|
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
|
||||||
)}
|
)}
|
||||||
@@ -162,7 +162,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold',
|
'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold',
|
||||||
isRootNode ? 'bg-blue-500/20 text-blue-400' : 'bg-accent text-muted-foreground'
|
isRootNode ? 'bg-blue-500/20 text-blue-400' : 'bg-accent text-[#848b9b]'
|
||||||
)}>
|
)}>
|
||||||
{letter}
|
{letter}
|
||||||
</span>
|
</span>
|
||||||
@@ -186,9 +186,9 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
|
||||||
optionLabelError ? 'border-red-400' : 'border-border'
|
optionLabelError ? 'border-red-400' : 'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{optionLabelError && (
|
{optionLabelError && (
|
||||||
@@ -205,7 +205,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
const target = allNodes.find(n => n.id === option.next_node_id)
|
const target = allNodes.find(n => n.id === option.next_node_id)
|
||||||
if (!target) return null
|
if (!target) return null
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 text-xs text-primary" title={`Links to: ${target.label}`}>
|
<div className="flex items-center gap-1 text-xs text-[#22d3ee]" title={`Links to: ${target.label}`}>
|
||||||
<Link2 className="h-3 w-3" />
|
<Link2 className="h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -218,20 +218,20 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
|
|
||||||
{/* Quick-link: assign an option to an existing node */}
|
{/* Quick-link: assign an option to an existing node */}
|
||||||
<details className="mt-2">
|
<details className="mt-2">
|
||||||
<summary className="cursor-pointer text-xs text-muted-foreground hover:text-foreground">
|
<summary className="cursor-pointer text-xs text-[#848b9b] hover:text-[#e2e5eb]">
|
||||||
<Link2 className="inline h-3 w-3 mr-1" />
|
<Link2 className="inline h-3 w-3 mr-1" />
|
||||||
Link an option to an existing node (cross-reference)
|
Link an option to an existing node (cross-reference)
|
||||||
</summary>
|
</summary>
|
||||||
<div className="mt-2 space-y-2 rounded-md border border-border bg-accent/30 p-3">
|
<div className="mt-2 space-y-2 rounded-md border border-[#1e2130] bg-accent/30 p-3">
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
Select an option, then pick a target node. This creates a loop-back or cross-reference.
|
Select an option, then pick a target node. This creates a loop-back or cross-reference.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<select
|
<select
|
||||||
id="xref-option-select"
|
id="xref-option-select"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border px-2 py-1.5 text-xs',
|
'flex-1 rounded-md border border-[#1e2130] px-2 py-1.5 text-xs',
|
||||||
'bg-card text-foreground'
|
'bg-[#14161d] text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
>
|
>
|
||||||
@@ -245,8 +245,8 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
<select
|
<select
|
||||||
id="xref-target-select"
|
id="xref-target-select"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border px-2 py-1.5 text-xs',
|
'flex-1 rounded-md border border-[#1e2130] px-2 py-1.5 text-xs',
|
||||||
'bg-card text-foreground'
|
'bg-[#14161d] text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -280,7 +280,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
|
|
||||||
{/* Example hint for root node */}
|
{/* Example hint for root node */}
|
||||||
{isRootNode && (node.options?.length || 0) < 2 && (
|
{isRootNode && (node.options?.length || 0) < 2 && (
|
||||||
<div className="mt-3 rounded-md border border-dashed border-border bg-accent/50 p-3 text-xs text-muted-foreground">
|
<div className="mt-3 rounded-md border border-dashed border-[#1e2130] bg-accent/50 p-3 text-xs text-[#848b9b]">
|
||||||
<strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
|
<strong>Tip:</strong> Most troubleshooting trees start with 2-5 main branches.
|
||||||
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
|
For example: "Connection Issues", "Performance Problems", "Error Messages", "Other".
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Title <span className="text-red-400">*</span>
|
Title <span className="text-red-400">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -58,9 +58,9 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
|||||||
placeholder="e.g., VDA Successfully Registered"
|
placeholder="e.g., VDA Successfully Registered"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
titleError ? 'border-red-400' : 'border-border'
|
titleError ? 'border-red-400' : 'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{titleError && (
|
{titleError && (
|
||||||
@@ -71,7 +71,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
|||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
Description
|
Description
|
||||||
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
<InfoTip text="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`" />
|
||||||
</label>
|
</label>
|
||||||
@@ -79,14 +79,14 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPreview(!showPreview)}
|
onClick={() => setShowPreview(!showPreview)}
|
||||||
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
|
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
{showPreview ? 'Edit' : 'Preview'}
|
{showPreview ? 'Edit' : 'Preview'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showPreview && node.description ? (
|
{showPreview && node.description ? (
|
||||||
<div className="mt-1 rounded-md border border-border bg-accent/50 p-3 text-sm">
|
<div className="mt-1 rounded-md border border-[#1e2130] bg-accent/50 p-3 text-sm">
|
||||||
<MarkdownContent content={node.description} />
|
<MarkdownContent content={node.description} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -101,8 +101,8 @@ Document what was done and the outcome.
|
|||||||
**Close ticket as:** Resolved"
|
**Close ticket as:** Resolved"
|
||||||
rows={5}
|
rows={5}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'mt-1 block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -111,7 +111,7 @@ Document what was done and the outcome.
|
|||||||
|
|
||||||
{/* Resolution Steps */}
|
{/* Resolution Steps */}
|
||||||
<div>
|
<div>
|
||||||
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
<label className="flex items-center gap-1.5 text-sm font-medium text-[#e2e5eb]">
|
||||||
Resolution Steps
|
Resolution Steps
|
||||||
<InfoTip text="Step-by-step instructions for resolving the issue" />
|
<InfoTip text="Step-by-step instructions for resolving the issue" />
|
||||||
</label>
|
</label>
|
||||||
@@ -132,8 +132,8 @@ Document what was done and the outcome.
|
|||||||
onChange={(e) => handleUpdateStep(index, e.target.value)}
|
onChange={(e) => handleUpdateStep(index, e.target.value)}
|
||||||
placeholder={`Step ${index + 1}`}
|
placeholder={`Step ${index + 1}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border px-3 py-2 text-sm',
|
'block w-full rounded-md border border-[#1e2130] px-3 py-2 text-sm',
|
||||||
'bg-background text-foreground placeholder:text-muted-foreground',
|
'bg-[#0c0d10] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ function NodeListItem({
|
|||||||
decision: 'bg-blue-500/20 text-blue-400',
|
decision: 'bg-blue-500/20 text-blue-400',
|
||||||
action: 'bg-yellow-500/20 text-yellow-400',
|
action: 'bg-yellow-500/20 text-yellow-400',
|
||||||
solution: 'bg-green-500/20 text-green-400',
|
solution: 'bg-green-500/20 text-green-400',
|
||||||
answer: 'bg-muted text-muted-foreground border border-dashed border-border'
|
answer: 'bg-muted text-[#848b9b] border border-dashed border-[#1e2130]'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNodeLabel = () => {
|
const getNodeLabel = () => {
|
||||||
@@ -132,7 +132,7 @@ function NodeListItem({
|
|||||||
<span
|
<span
|
||||||
key={i}
|
key={i}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-block w-5 text-center text-muted-foreground/50',
|
'inline-block w-5 text-center text-[#848b9b]/50',
|
||||||
showLine ? 'border-l border-muted-foreground/30' : ''
|
showLine ? 'border-l border-muted-foreground/30' : ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -140,10 +140,10 @@ function NodeListItem({
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{/* Render current level connector */}
|
{/* Render current level connector */}
|
||||||
<span className="inline-block w-5 text-center text-muted-foreground/50 font-mono text-xs">
|
<span className="inline-block w-5 text-center text-[#848b9b]/50 font-mono text-xs">
|
||||||
{isLast ? '└' : '├'}
|
{isLast ? '└' : '├'}
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-block w-3 text-muted-foreground/50 font-mono text-xs">──</span>
|
<span className="inline-block w-3 text-[#848b9b]/50 font-mono text-xs">──</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ function NodeListItem({
|
|||||||
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
|
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
|
||||||
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
|
||||||
: isSelected
|
: isSelected
|
||||||
? 'bg-primary/10 ring-1 ring-primary'
|
? 'bg-[rgba(34,211,238,0.10)] ring-1 ring-primary'
|
||||||
: 'hover:bg-accent',
|
: 'hover:bg-accent',
|
||||||
hasError && 'ring-1 ring-destructive',
|
hasError && 'ring-1 ring-destructive',
|
||||||
hasWarning && !hasError && 'ring-1 ring-yellow-500'
|
hasWarning && !hasError && 'ring-1 ring-yellow-500'
|
||||||
@@ -204,9 +204,9 @@ function NodeListItem({
|
|||||||
className="rounded p-0.5 hover:bg-muted"
|
className="rounded p-0.5 hover:bg-muted"
|
||||||
>
|
>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
|
<ChevronRight className="h-3.5 w-3.5 text-[#848b9b]" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
|
<ChevronDown className="h-3.5 w-3.5 text-[#848b9b]" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
@@ -216,7 +216,7 @@ function NodeListItem({
|
|||||||
{/* Drag handle */}
|
{/* Drag handle */}
|
||||||
{node.id !== 'root' && (
|
{node.id !== 'root' && (
|
||||||
<GripVertical
|
<GripVertical
|
||||||
className="h-4 w-4 cursor-grab text-muted-foreground opacity-0 group-hover:opacity-100"
|
className="h-4 w-4 cursor-grab text-[#848b9b] opacity-0 group-hover:opacity-100"
|
||||||
onMouseDown={() => { gripInitiated.current = true }}
|
onMouseDown={() => { gripInitiated.current = true }}
|
||||||
onMouseUp={() => { gripInitiated.current = false }}
|
onMouseUp={() => { gripInitiated.current = false }}
|
||||||
/>
|
/>
|
||||||
@@ -238,13 +238,13 @@ function NodeListItem({
|
|||||||
|
|
||||||
{/* From option label */}
|
{/* From option label */}
|
||||||
{fromOption && (
|
{fromOption && (
|
||||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
|
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-[#848b9b]">
|
||||||
{fromOption}
|
{fromOption}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Node label */}
|
{/* Node label */}
|
||||||
<span className="flex-1 truncate text-foreground">
|
<span className="flex-1 truncate text-[#e2e5eb]">
|
||||||
{getNodeLabel()}
|
{getNodeLabel()}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ function NodeListItem({
|
|||||||
|
|
||||||
{/* Node ID */}
|
{/* Node ID */}
|
||||||
<span
|
<span
|
||||||
className="hidden text-xs text-muted-foreground sm:inline cursor-help"
|
className="hidden text-xs text-[#848b9b] sm:inline cursor-help"
|
||||||
title={`Full ID: ${node.id}`}
|
title={`Full ID: ${node.id}`}
|
||||||
>
|
>
|
||||||
{node.id === 'root' ? 'root' : node.id.slice(0, 8) + '...'}
|
{node.id === 'root' ? 'root' : node.id.slice(0, 8) + '...'}
|
||||||
@@ -289,7 +289,7 @@ function NodeListItem({
|
|||||||
}}
|
}}
|
||||||
title="Add child node"
|
title="Add child node"
|
||||||
aria-label="Add child node"
|
aria-label="Add child node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -302,7 +302,7 @@ function NodeListItem({
|
|||||||
}}
|
}}
|
||||||
title="Edit node"
|
title="Edit node"
|
||||||
aria-label="Edit node"
|
aria-label="Edit node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3" />
|
<Pencil className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -316,7 +316,7 @@ function NodeListItem({
|
|||||||
}}
|
}}
|
||||||
title="Duplicate node"
|
title="Duplicate node"
|
||||||
aria-label="Duplicate node"
|
aria-label="Duplicate node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
<Copy className="h-3 w-3" />
|
<Copy className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -328,7 +328,7 @@ function NodeListItem({
|
|||||||
}}
|
}}
|
||||||
title="Delete node"
|
title="Delete node"
|
||||||
aria-label="Delete node"
|
aria-label="Delete node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
|
className="rounded p-1 text-[#848b9b] hover:bg-destructive/20 hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -340,7 +340,7 @@ function NodeListItem({
|
|||||||
{/* Collapsed indicator */}
|
{/* Collapsed indicator */}
|
||||||
{hasChildren && isCollapsed && (
|
{hasChildren && isCollapsed && (
|
||||||
<div
|
<div
|
||||||
className="text-xs text-muted-foreground py-1"
|
className="text-xs text-[#848b9b] py-1"
|
||||||
style={{ marginLeft: `${(depth + 1) * 20 + 32}px` }}
|
style={{ marginLeft: `${(depth + 1) * 20 + 32}px` }}
|
||||||
>
|
>
|
||||||
<span className="rounded bg-muted px-2 py-0.5">
|
<span className="rounded bg-muted px-2 py-0.5">
|
||||||
@@ -536,22 +536,22 @@ export function NodeList() {
|
|||||||
|
|
||||||
if (!treeStructure) {
|
if (!treeStructure) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-border bg-card p-4 text-center text-sm text-muted-foreground">
|
<div className="rounded-lg border border-[#1e2130] bg-[#14161d] p-4 text-center text-sm text-[#848b9b]">
|
||||||
No tree structure. Add a root node to get started.
|
No tree structure. Add a root node to get started.
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-border bg-card">
|
<div className="rounded-lg border border-[#1e2130] bg-[#14161d]">
|
||||||
<div className="flex items-center justify-between border-b border-border p-3">
|
<div className="flex items-center justify-between border-b border-[#1e2130] p-3">
|
||||||
<h2 className="text-sm font-semibold text-card-foreground">Nodes</h2>
|
<h2 className="text-sm font-semibold text-card-foreground">Nodes</h2>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAddingToParent(treeStructure.id)}
|
onClick={() => setAddingToParent(treeStructure.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium',
|
||||||
'bg-primary text-primary-foreground hover:bg-primary/90'
|
'bg-primary text-[#22d3ee]-foreground hover:bg-primary/90'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
@@ -581,8 +581,8 @@ export function NodeList() {
|
|||||||
|
|
||||||
{/* Add Node Type Selector */}
|
{/* Add Node Type Selector */}
|
||||||
{addingToParent && (
|
{addingToParent && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#0c0d10]/80">
|
||||||
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
|
<div className="w-full max-w-xs rounded-lg border border-[#1e2130] bg-[#14161d] p-4 shadow-lg">
|
||||||
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<button
|
<button
|
||||||
@@ -596,7 +596,7 @@ export function NodeList() {
|
|||||||
<HelpCircle className="h-4 w-4 text-blue-500" />
|
<HelpCircle className="h-4 w-4 text-blue-500" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Decision</div>
|
<div className="font-medium">Decision</div>
|
||||||
<div className="text-xs text-muted-foreground">Question with options</div>
|
<div className="text-xs text-[#848b9b]">Question with options</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -610,7 +610,7 @@ export function NodeList() {
|
|||||||
<Zap className="h-4 w-4 text-yellow-500" />
|
<Zap className="h-4 w-4 text-yellow-500" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Action</div>
|
<div className="font-medium">Action</div>
|
||||||
<div className="text-xs text-muted-foreground">Task to perform</div>
|
<div className="text-xs text-[#848b9b]">Task to perform</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -624,7 +624,7 @@ export function NodeList() {
|
|||||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Solution</div>
|
<div className="font-medium">Solution</div>
|
||||||
<div className="text-xs text-muted-foreground">Resolution endpoint</div>
|
<div className="text-xs text-[#848b9b]">Resolution endpoint</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export function NodePicker({
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{label && (
|
{label && (
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
@@ -153,8 +153,8 @@ export function NodePicker({
|
|||||||
{/* Inline node creation UI */}
|
{/* Inline node creation UI */}
|
||||||
{creatingNodeType ? (
|
{creatingNodeType ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 rounded-md border border-border bg-accent/50 p-2">
|
<div className="flex items-center gap-2 rounded-md border border-[#1e2130] bg-accent/50 p-2">
|
||||||
<span className="text-xs font-medium text-foreground">
|
<span className="text-xs font-medium text-[#e2e5eb]">
|
||||||
New {NODE_TYPE_LABELS[creatingNodeType]}:
|
New {NODE_TYPE_LABELS[creatingNodeType]}:
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@@ -165,8 +165,8 @@ export function NodePicker({
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'}
|
placeholder={creatingNodeType === 'decision' ? 'Enter question...' : 'Enter title...'}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
|
'flex-1 rounded-md border border-[#1e2130] px-2 py-1 text-sm',
|
||||||
'bg-card text-foreground placeholder:text-muted-foreground',
|
'bg-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -175,7 +175,7 @@ export function NodePicker({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCancelCreate}
|
onClick={handleCancelCreate}
|
||||||
className="flex-1 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="flex-1 rounded-md border border-[#1e2130] px-3 py-1.5 text-xs font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -185,7 +185,7 @@ export function NodePicker({
|
|||||||
disabled={!newNodeTitle.trim()}
|
disabled={!newNodeTitle.trim()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md px-3 py-1.5 text-xs font-medium',
|
'flex-1 rounded-md px-3 py-1.5 text-xs font-medium',
|
||||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -200,9 +200,9 @@ export function NodePicker({
|
|||||||
onChange={(e) => handleChange(e.target.value)}
|
onChange={(e) => handleChange(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border px-3 py-2 text-sm',
|
'block w-full rounded-md border px-3 py-2 text-sm',
|
||||||
'bg-card text-foreground',
|
'bg-[#14161d] text-[#e2e5eb]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
error ? 'border-red-400' : 'border-border'
|
error ? 'border-red-400' : 'border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">{placeholder}</option>
|
<option value="">{placeholder}</option>
|
||||||
@@ -250,7 +250,7 @@ export function NodePicker({
|
|||||||
|
|
||||||
{/* Show what's selected */}
|
{/* Show what's selected */}
|
||||||
{value && selectedNode && (
|
{value && selectedNode && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
→ {selectedNode.label}
|
→ {selectedNode.label}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ interface AddNodePickerProps {
|
|||||||
|
|
||||||
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-xs">
|
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-[#14161d] px-3 py-2 shadow-xs">
|
||||||
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
|
<span className="text-xs text-[#848b9b] shrink-0">Add:</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelect('decision')}
|
onClick={() => onSelect('decision')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||||
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -83,7 +83,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelect('action')}
|
onClick={() => onSelect('action')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||||
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -95,7 +95,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onSelect('solution')}
|
onClick={() => onSelect('solution')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-label',
|
'flex items-center gap-1 rounded-md px-2 py-1 text-xs font-sans text-xs',
|
||||||
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -106,7 +106,7 @@ function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="ml-1 rounded p-0.5 text-muted-foreground hover:bg-accent"
|
className="ml-1 rounded p-0.5 text-[#848b9b] hover:bg-accent"
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -127,9 +127,9 @@ function AddNodeButton({ label = 'Add node', onClick }: AddNodeButtonProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded-lg px-3 py-1.5 text-xs font-label',
|
'flex items-center gap-1 rounded-lg px-3 py-1.5 text-xs font-sans text-xs',
|
||||||
'border border-dashed border-border text-muted-foreground',
|
'border border-dashed border-[#1e2130] text-[#848b9b]',
|
||||||
'hover:border-primary/40 hover:text-foreground hover:bg-accent/50',
|
'hover:border-primary/40 hover:text-[#e2e5eb] hover:bg-accent/50',
|
||||||
'transition-all duration-150'
|
'transition-all duration-150'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -505,7 +505,7 @@ export function TreeCanvas() {
|
|||||||
|
|
||||||
{/* Option label tag (above card, shown when this is a branch from a decision) */}
|
{/* Option label tag (above card, shown when this is a branch from a decision) */}
|
||||||
{optionLabel && (
|
{optionLabel && (
|
||||||
<div className="mb-1 rounded bg-muted px-2 py-0.5 text-[10px] text-muted-foreground font-label">
|
<div className="mb-1 rounded bg-muted px-2 py-0.5 text-[10px] text-[#848b9b] font-sans text-xs">
|
||||||
{optionLabel}
|
{optionLabel}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -547,7 +547,7 @@ export function TreeCanvas() {
|
|||||||
return (
|
return (
|
||||||
<div key={opt.id} className="flex flex-col items-center gap-1">
|
<div key={opt.id} className="flex flex-col items-center gap-1">
|
||||||
<div className="h-4 w-px bg-border" />
|
<div className="h-4 w-px bg-border" />
|
||||||
<span className="text-[10px] text-muted-foreground font-label">
|
<span className="text-[10px] text-[#848b9b] font-sans text-xs">
|
||||||
{opt.label || '(unlabeled option)'}
|
{opt.label || '(unlabeled option)'}
|
||||||
</span>
|
</span>
|
||||||
{pendingAddKey === key ? (
|
{pendingAddKey === key ? (
|
||||||
@@ -594,7 +594,7 @@ export function TreeCanvas() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleToggleSubtreeCollapse(node.id)}
|
onClick={() => handleToggleSubtreeCollapse(node.id)}
|
||||||
className="rounded-full border border-dashed border-border bg-card px-3 py-1 text-[10px] text-muted-foreground font-label hover:border-primary/40 hover:text-foreground transition-colors"
|
className="rounded-full border border-dashed border-[#1e2130] bg-[#14161d] px-3 py-1 text-[10px] text-[#848b9b] font-sans text-xs hover:border-primary/40 hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
{orderedChildren.length} node{orderedChildren.length !== 1 ? 's' : ''} hidden — click to expand
|
{orderedChildren.length} node{orderedChildren.length !== 1 ? 's' : ''} hidden — click to expand
|
||||||
</button>
|
</button>
|
||||||
@@ -676,7 +676,7 @@ export function TreeCanvas() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mb-2 text-muted-foreground text-sm">
|
<div className="mb-2 text-[#848b9b] text-sm">
|
||||||
No tree structure. Start by saving a tree name.
|
No tree structure. Start by saving a tree name.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -698,7 +698,7 @@ export function TreeCanvas() {
|
|||||||
<div className="flex min-h-full min-w-full items-start justify-center p-8 pb-24">
|
<div className="flex min-h-full min-w-full items-start justify-center p-8 pb-24">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
{/* START badge above root */}
|
{/* START badge above root */}
|
||||||
<div className="mb-2 rounded-full border border-border bg-card px-3 py-1 text-xs font-label text-muted-foreground">
|
<div className="mb-2 rounded-full border border-[#1e2130] bg-[#14161d] px-3 py-1 text-xs font-sans text-xs text-[#848b9b]">
|
||||||
START
|
START
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-1 h-4 w-px bg-border" />
|
<div className="mb-1 h-4 w-px bg-border" />
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export function TreeCanvasNode({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative rounded-xl border border-border bg-card shadow-xs transition-all duration-150',
|
'relative rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xs transition-all duration-150',
|
||||||
config.borderClass,
|
config.borderClass,
|
||||||
isExpanded && 'ring-1 ring-primary shadow-md',
|
isExpanded && 'ring-1 ring-primary shadow-md',
|
||||||
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
isSelected && !isExpanded && 'ring-1 ring-primary/50',
|
||||||
@@ -186,7 +186,7 @@ export function TreeCanvasNode({
|
|||||||
'flex items-center gap-2 px-3 py-2.5',
|
'flex items-center gap-2 px-3 py-2.5',
|
||||||
!isExpanded && 'cursor-pointer hover:bg-accent/50 rounded-t-xl',
|
!isExpanded && 'cursor-pointer hover:bg-accent/50 rounded-t-xl',
|
||||||
!isExpanded && 'rounded-xl',
|
!isExpanded && 'rounded-xl',
|
||||||
isExpanded && 'sticky top-0 z-10 bg-card rounded-t-xl'
|
isExpanded && 'sticky top-0 z-10 bg-[#14161d] rounded-t-xl'
|
||||||
)}
|
)}
|
||||||
onClick={!isExpanded ? handleCardClick : undefined}
|
onClick={!isExpanded ? handleCardClick : undefined}
|
||||||
>
|
>
|
||||||
@@ -200,20 +200,20 @@ export function TreeCanvasNode({
|
|||||||
onDragStart(e, node.id)
|
onDragStart(e, node.id)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<GripVertical className="h-4 w-4 text-muted-foreground/50" />
|
<GripVertical className="h-4 w-4 text-[#848b9b]/50" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Node type badge */}
|
{/* Node type badge */}
|
||||||
{isRoot ? (
|
{isRoot ? (
|
||||||
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold bg-blue-500/30 text-blue-400 font-label shrink-0">
|
<span className="flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold bg-blue-500/30 text-blue-400 font-sans text-xs shrink-0">
|
||||||
<Play className="h-3 w-3" />
|
<Play className="h-3 w-3" />
|
||||||
START
|
START
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-label shrink-0',
|
'flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-sans text-xs shrink-0',
|
||||||
config.badgeClass
|
config.badgeClass
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -224,21 +224,21 @@ export function TreeCanvasNode({
|
|||||||
|
|
||||||
{/* From-option label */}
|
{/* From-option label */}
|
||||||
{fromOption && (
|
{fromOption && (
|
||||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground truncate max-w-[80px]">
|
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-[#848b9b] truncate max-w-[80px]">
|
||||||
{fromOption}
|
{fromOption}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Title text (compact mode) */}
|
{/* Title text (compact mode) */}
|
||||||
{!isExpanded && (
|
{!isExpanded && (
|
||||||
<span className="flex-1 truncate text-sm font-heading font-medium text-foreground">
|
<span className="flex-1 truncate text-sm font-heading font-medium text-[#e2e5eb]">
|
||||||
{getTitle()}
|
{getTitle()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Options count badge */}
|
{/* Options count badge */}
|
||||||
{!isExpanded && getOptionsSummary() && (
|
{!isExpanded && getOptionsSummary() && (
|
||||||
<span className="text-[10px] text-muted-foreground shrink-0 font-label">
|
<span className="text-[10px] text-[#848b9b] shrink-0 font-sans text-xs">
|
||||||
{getOptionsSummary()}
|
{getOptionsSummary()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -265,7 +265,7 @@ export function TreeCanvasNode({
|
|||||||
|
|
||||||
{/* Unsaved badge */}
|
{/* Unsaved badge */}
|
||||||
{!isExpanded && isNew && (
|
{!isExpanded && isNew && (
|
||||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-label shrink-0">
|
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-sans text-xs shrink-0">
|
||||||
Unsaved
|
Unsaved
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -276,7 +276,7 @@ export function TreeCanvasNode({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => { e.stopPropagation(); onToggleSubtreeCollapse() }}
|
onClick={(e) => { e.stopPropagation(); onToggleSubtreeCollapse() }}
|
||||||
title={isSubtreeCollapsed ? 'Expand subtree' : 'Collapse subtree'}
|
title={isSubtreeCollapsed ? 'Expand subtree' : 'Collapse subtree'}
|
||||||
className="rounded p-0.5 text-muted-foreground/50 hover:bg-accent hover:text-foreground shrink-0"
|
className="rounded p-0.5 text-[#848b9b]/50 hover:bg-accent hover:text-[#e2e5eb] shrink-0"
|
||||||
>
|
>
|
||||||
{isSubtreeCollapsed
|
{isSubtreeCollapsed
|
||||||
? <ChevronsUpDown className="h-3.5 w-3.5" />
|
? <ChevronsUpDown className="h-3.5 w-3.5" />
|
||||||
@@ -287,9 +287,9 @@ export function TreeCanvasNode({
|
|||||||
|
|
||||||
{/* Expand/collapse chevron */}
|
{/* Expand/collapse chevron */}
|
||||||
{!isExpanded ? (
|
{!isExpanded ? (
|
||||||
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<ChevronRight className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
<ChevronDown className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Editing action buttons (expanded state) */}
|
{/* Editing action buttons (expanded state) */}
|
||||||
@@ -297,7 +297,7 @@ export function TreeCanvasNode({
|
|||||||
<div className="ml-auto flex items-center gap-1 shrink-0">
|
<div className="ml-auto flex items-center gap-1 shrink-0">
|
||||||
{/* New badge */}
|
{/* New badge */}
|
||||||
{isNew && (
|
{isNew && (
|
||||||
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-label">
|
<span className="rounded bg-yellow-500/20 px-1.5 py-0.5 text-[10px] text-yellow-500 font-sans text-xs">
|
||||||
Unsaved
|
Unsaved
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -311,7 +311,7 @@ export function TreeCanvasNode({
|
|||||||
onDuplicate(node.id)
|
onDuplicate(node.id)
|
||||||
}}
|
}}
|
||||||
title="Duplicate node"
|
title="Duplicate node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<Copy className="h-3.5 w-3.5" />
|
<Copy className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -326,7 +326,7 @@ export function TreeCanvasNode({
|
|||||||
onDelete(node.id)
|
onDelete(node.id)
|
||||||
}}
|
}}
|
||||||
title="Delete node"
|
title="Delete node"
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-destructive/20 hover:text-destructive"
|
className="rounded p-1 text-[#848b9b] hover:bg-destructive/20 hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3.5 w-3.5" />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -337,7 +337,7 @@ export function TreeCanvasNode({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
title={isNew ? 'Cancel (deletes this node)' : 'Cancel changes'}
|
title={isNew ? 'Cancel (deletes this node)' : 'Cancel changes'}
|
||||||
className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
<X className="h-3.5 w-3.5" />
|
<X className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -347,7 +347,7 @@ export function TreeCanvasNode({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
title="Save changes"
|
title="Save changes"
|
||||||
className="rounded p-1 bg-gradient-brand text-white hover:opacity-90"
|
className="rounded p-1 bg-[#22d3ee] text-white hover:brightness-110"
|
||||||
>
|
>
|
||||||
<Check className="h-3.5 w-3.5" />
|
<Check className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -357,7 +357,7 @@ export function TreeCanvasNode({
|
|||||||
|
|
||||||
{/* Expanded editing area */}
|
{/* Expanded editing area */}
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="border-t border-border px-3 pb-3 pt-3 max-h-[70vh] overflow-y-auto">
|
<div className="border-t border-[#1e2130] px-3 pb-3 pt-3 max-h-[70vh] overflow-y-auto">
|
||||||
{/* Validation errors */}
|
{/* Validation errors */}
|
||||||
{(hasError || hasWarning) && (
|
{(hasError || hasWarning) && (
|
||||||
<div className="mb-3 space-y-1">
|
<div className="mb-3 space-y-1">
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ export function TreeEditorLayout({
|
|||||||
<>
|
<>
|
||||||
{/* Code Mode: Monaco editor (60%) + Preview (40%) — unchanged */}
|
{/* Code Mode: Monaco editor (60%) + Preview (40%) — unchanged */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'flex flex-col overflow-hidden border-border',
|
'flex flex-col overflow-hidden border-[#1e2130]',
|
||||||
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
isMobile ? 'h-full w-full border-b' : 'w-3/5 border-r'
|
||||||
)}>
|
)}>
|
||||||
<Suspense fallback={
|
<Suspense fallback={
|
||||||
<div className="flex h-full items-center justify-center bg-card">
|
<div className="flex h-full items-center justify-center bg-[#14161d]">
|
||||||
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
<Spinner size="sm" className="h-6 w-6 border-t-foreground" />
|
||||||
</div>
|
</div>
|
||||||
}>
|
}>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user