refactor: replace hardcoded hex values with Tailwind semantic tokens
3,200+ hardcoded color values replaced with CSS variable-backed Tailwind classes (bg-card, text-foreground, border-border, etc.). Enables light mode via CSS variable swap. Only syntax highlighting colors and intentional one-offs remain hardcoded (~15 values). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,24 +39,24 @@ export function DeleteAccountModal({ onClose }: Props) {
|
|||||||
<div className="card-flat w-full max-w-md p-6">
|
<div className="card-flat w-full max-w-md p-6">
|
||||||
<div className="flex items-center gap-2 text-rose-500 mb-4">
|
<div className="flex items-center gap-2 text-rose-500 mb-4">
|
||||||
<AlertTriangle className="h-5 w-5" />
|
<AlertTriangle className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold font-heading text-[#e2e5eb]">Delete Account</h2>
|
<h2 className="text-lg font-semibold font-heading text-foreground">Delete Account</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#848b9b] mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
This action is <strong className="text-rose-400">permanent</strong>. Your account, data,
|
This action is <strong className="text-rose-400">permanent</strong>. Your account, data,
|
||||||
and all associated flows will be permanently deleted.
|
and all associated flows will be permanently deleted.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form onSubmit={handleDelete} className="space-y-4">
|
<form onSubmit={handleDelete} className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[#e2e5eb]">Confirm Password</label>
|
<label className="block text-sm font-medium text-foreground">Confirm Password</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
|
||||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +69,7 @@ export function DeleteAccountModal({ onClose }: Props) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg px-4 py-2 text-sm font-medium',
|
'rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[#191c25] border border-[#1e2130] text-[#e2e5eb]'
|
'bg-input border border-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
|
|||||||
<div className="card-flat w-full max-w-md p-6">
|
<div className="card-flat w-full max-w-md p-6">
|
||||||
<div className="flex items-center gap-2 text-amber-400 mb-4">
|
<div className="flex items-center gap-2 text-amber-400 mb-4">
|
||||||
<AlertTriangle className="h-5 w-5" />
|
<AlertTriangle className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold font-heading text-[#e2e5eb]">Leave Account</h2>
|
<h2 className="text-lg font-semibold font-heading text-foreground">Leave Account</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#848b9b] mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Are you sure you want to leave <strong className="text-[#e2e5eb]">{accountName}</strong>?
|
Are you sure you want to leave <strong className="text-foreground">{accountName}</strong>?
|
||||||
A new personal account will be created for you.
|
A new personal account will be created for you.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-end gap-3">
|
<div className="flex justify-end gap-3">
|
||||||
@@ -45,7 +45,7 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg px-4 py-2 text-sm font-medium',
|
'rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[#191c25] border border-[#1e2130] text-[#e2e5eb]'
|
'bg-input border border-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ export function NotificationSettings() {
|
|||||||
{/* Section header */}
|
{/* Section header */}
|
||||||
<div className="mb-6 flex items-center justify-between">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Bell className="h-6 w-6 text-[#848b9b]" />
|
<Bell className="h-6 w-6 text-muted-foreground" />
|
||||||
<h2 className="text-xl font-semibold font-heading text-[#e2e5eb]">Notifications</h2>
|
<h2 className="text-xl font-semibold font-heading text-foreground">Notifications</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative" ref={dropdownRef}>
|
<div className="relative" ref={dropdownRef}>
|
||||||
@@ -182,7 +182,7 @@ export function NotificationSettings() {
|
|||||||
onClick={() => setShowDropdown(!showDropdown)}
|
onClick={() => setShowDropdown(!showDropdown)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
'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-all'
|
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -192,16 +192,16 @@ export function NotificationSettings() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showDropdown && (
|
{showDropdown && (
|
||||||
<div className="absolute right-0 mt-1 z-20 w-48 rounded-xl border border-[#1e2130] bg-[#14161d] shadow-xl">
|
<div className="absolute right-0 mt-1 z-20 w-48 rounded-xl border border-border bg-card shadow-xl">
|
||||||
{(Object.entries(CHANNEL_LABELS) as [ChannelType, string][]).map(([key, label]) => {
|
{(Object.entries(CHANNEL_LABELS) as [ChannelType, string][]).map(([key, label]) => {
|
||||||
const Icon = CHANNEL_ICONS[key]
|
const Icon = CHANNEL_ICONS[key]
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => handleAddChannel(key)}
|
onClick={() => handleAddChannel(key)}
|
||||||
className="flex w-full items-center gap-2.5 px-4 py-2.5 text-sm text-[#e2e5eb] hover:bg-[rgba(255,255,255,0.04)] first:rounded-t-xl last:rounded-b-xl transition-colors"
|
className="flex w-full items-center gap-2.5 px-4 py-2.5 text-sm text-foreground hover:bg-[rgba(255,255,255,0.04)] first:rounded-t-xl last:rounded-b-xl transition-colors"
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4 text-[#848b9b]" />
|
<Icon className="h-4 w-4 text-muted-foreground" />
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -214,15 +214,15 @@ export function NotificationSettings() {
|
|||||||
{/* Loading */}
|
{/* Loading */}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex justify-center py-8">
|
<div className="flex justify-center py-8">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-[#848b9b]" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Empty state */}
|
{/* Empty state */}
|
||||||
{!loading && configs.length === 0 && !addingChannel && (
|
{!loading && configs.length === 0 && !addingChannel && (
|
||||||
<div className="card-flat p-6 text-center">
|
<div className="card-flat p-6 text-center">
|
||||||
<Bell className="mx-auto h-8 w-8 text-[#848b9b]/50 mb-3" />
|
<Bell className="mx-auto h-8 w-8 text-muted-foreground/50 mb-3" />
|
||||||
<p className="text-sm text-[#848b9b]">
|
<p className="text-sm text-muted-foreground">
|
||||||
No notification channels configured. Add a channel to receive alerts for session events.
|
No notification channels configured. Add a channel to receive alerts for session events.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,8 +237,8 @@ export function NotificationSettings() {
|
|||||||
<div key={config.id} className="card-flat p-5">
|
<div key={config.id} className="card-flat p-5">
|
||||||
{/* Header row */}
|
{/* Header row */}
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Icon className="h-5 w-5 text-[#848b9b]" />
|
<Icon className="h-5 w-5 text-muted-foreground" />
|
||||||
<span className="text-sm font-medium text-[#e2e5eb]">
|
<span className="text-sm font-medium text-foreground">
|
||||||
{CHANNEL_LABELS[config.channel]}
|
{CHANNEL_LABELS[config.channel]}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
@@ -249,7 +249,7 @@ export function NotificationSettings() {
|
|||||||
/>
|
/>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
'text-xs font-sans text-xs',
|
'text-xs font-sans text-xs',
|
||||||
config.is_active ? 'text-emerald-400' : 'text-[#848b9b]'
|
config.is_active ? 'text-emerald-400' : 'text-muted-foreground'
|
||||||
)}>
|
)}>
|
||||||
{config.is_active ? 'Active' : 'Inactive'}
|
{config.is_active ? 'Active' : 'Inactive'}
|
||||||
</span>
|
</span>
|
||||||
@@ -259,20 +259,20 @@ export function NotificationSettings() {
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{config.webhook_url && (
|
{config.webhook_url && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Webhook URL
|
Webhook URL
|
||||||
</span>
|
</span>
|
||||||
<p className="mt-0.5 text-sm text-[#e2e5eb] font-mono">
|
<p className="mt-0.5 text-sm text-foreground font-mono">
|
||||||
{maskWebhookUrl(config.webhook_url)}
|
{maskWebhookUrl(config.webhook_url)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{config.email_addresses && config.email_addresses.length > 0 && (
|
{config.email_addresses && config.email_addresses.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Email Addresses
|
Email Addresses
|
||||||
</span>
|
</span>
|
||||||
<p className="mt-0.5 text-sm text-[#e2e5eb]">
|
<p className="mt-0.5 text-sm text-foreground">
|
||||||
{config.email_addresses.join(', ')}
|
{config.email_addresses.join(', ')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,7 +281,7 @@ export function NotificationSettings() {
|
|||||||
|
|
||||||
{/* Event toggles */}
|
{/* Event toggles */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Events
|
Events
|
||||||
</span>
|
</span>
|
||||||
<div className="mt-2 grid gap-2 sm:grid-cols-2">
|
<div className="mt-2 grid gap-2 sm:grid-cols-2">
|
||||||
@@ -294,28 +294,28 @@ export function NotificationSettings() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={config.events_enabled[eventKey] ?? false}
|
checked={config.events_enabled[eventKey] ?? false}
|
||||||
onChange={() => handleToggleEvent(config, eventKey)}
|
onChange={() => handleToggleEvent(config, eventKey)}
|
||||||
className="h-3.5 w-3.5 rounded border-[#1e2130] bg-[#14161d] text-primary focus:ring-primary/30 focus:ring-offset-0 cursor-pointer accent-[#06b6d4]"
|
className="h-3.5 w-3.5 rounded border-border bg-card text-primary focus:ring-primary/30 focus:ring-offset-0 cursor-pointer accent-[#06b6d4]"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-[#e2e5eb]">{eventLabel}</span>
|
<span className="text-sm text-foreground">{eventLabel}</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
<div className="flex items-center gap-3 pt-2 border-t border-[#1e2130]">
|
<div className="flex items-center gap-3 pt-2 border-t border-border">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleActive(config)}
|
onClick={() => handleToggleActive(config)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
|
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
'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-all'
|
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{config.is_active ? (
|
{config.is_active ? (
|
||||||
<ToggleRight className="h-4 w-4 text-emerald-400" />
|
<ToggleRight className="h-4 w-4 text-emerald-400" />
|
||||||
) : (
|
) : (
|
||||||
<ToggleLeft className="h-4 w-4 text-[#848b9b]" />
|
<ToggleLeft className="h-4 w-4 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
{config.is_active ? 'Disable' : 'Enable'}
|
{config.is_active ? 'Disable' : 'Enable'}
|
||||||
</button>
|
</button>
|
||||||
@@ -325,7 +325,7 @@ export function NotificationSettings() {
|
|||||||
disabled={testingId === config.id}
|
disabled={testingId === config.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
|
'inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
'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-all',
|
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
@@ -349,7 +349,7 @@ export function NotificationSettings() {
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => setConfirmDeleteId(config.id)}
|
onClick={() => setConfirmDeleteId(config.id)}
|
||||||
className="ml-auto inline-flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-red-400 transition-colors"
|
className="ml-auto inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-red-400 transition-colors"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
Remove
|
Remove
|
||||||
@@ -366,16 +366,16 @@ export function NotificationSettings() {
|
|||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
{(() => {
|
{(() => {
|
||||||
const Icon = CHANNEL_ICONS[addingChannel]
|
const Icon = CHANNEL_ICONS[addingChannel]
|
||||||
return <Icon className="h-5 w-5 text-[#848b9b]" />
|
return <Icon className="h-5 w-5 text-muted-foreground" />
|
||||||
})()}
|
})()}
|
||||||
<span className="text-sm font-medium text-[#e2e5eb]">
|
<span className="text-sm font-medium text-foreground">
|
||||||
Add {CHANNEL_LABELS[addingChannel]}
|
Add {CHANNEL_LABELS[addingChannel]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{addingChannel === 'email' ? (
|
{addingChannel === 'email' ? (
|
||||||
<div>
|
<div>
|
||||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Email Addresses
|
Email Addresses
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -385,13 +385,13 @@ export function NotificationSettings() {
|
|||||||
placeholder="user@example.com, team@example.com"
|
placeholder="user@example.com, team@example.com"
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
Separate multiple addresses with commas
|
Separate multiple addresses with commas
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Webhook URL
|
Webhook URL
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -414,7 +414,7 @@ export function NotificationSettings() {
|
|||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||||
'bg-[#22d3ee] text-white',
|
'bg-primary text-white',
|
||||||
'hover:brightness-110 active:scale-[0.98] transition-all',
|
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
@@ -424,7 +424,7 @@ export function NotificationSettings() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setAddingChannel(null)}
|
onClick={() => setAddingChannel(null)}
|
||||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -43,24 +43,24 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
<div className="card-flat w-full max-w-md p-6">
|
<div className="card-flat w-full max-w-md p-6">
|
||||||
<div className="flex items-center gap-2 text-amber-400 mb-4">
|
<div className="flex items-center gap-2 text-amber-400 mb-4">
|
||||||
<AlertTriangle className="h-5 w-5" />
|
<AlertTriangle className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold font-heading text-[#e2e5eb]">Transfer Ownership</h2>
|
<h2 className="text-lg font-semibold font-heading text-foreground">Transfer Ownership</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#848b9b] mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
This will make the selected member the new account owner. You will become an engineer.
|
This will make the selected member the new account owner. You will become an engineer.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{nonOwnerMembers.length === 0 ? (
|
{nonOwnerMembers.length === 0 ? (
|
||||||
<p className="text-sm text-[#848b9b]">No other members to transfer to.</p>
|
<p className="text-sm text-muted-foreground">No other members to transfer to.</p>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[#e2e5eb]">New Owner</label>
|
<label className="block text-sm font-medium text-foreground">New Owner</label>
|
||||||
<select
|
<select
|
||||||
value={targetUserId}
|
value={targetUserId}
|
||||||
onChange={(e) => setTargetUserId(e.target.value)}
|
onChange={(e) => setTargetUserId(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
|
||||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{nonOwnerMembers.map((m) => (
|
{nonOwnerMembers.map((m) => (
|
||||||
@@ -69,15 +69,15 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[#e2e5eb]">Your Password</label>
|
<label className="block text-sm font-medium text-foreground">Your Password</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
'mt-1 block w-full rounded-lg border border-border bg-card px-3 py-2',
|
||||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden'
|
'text-foreground focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg px-4 py-2 text-sm font-medium',
|
'rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[#191c25] border border-[#1e2130] text-[#e2e5eb]'
|
'bg-input border border-border text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -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-[#848b9b] transition-colors',
|
'rounded-md p-1.5 text-muted-foreground transition-colors',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#1e2130]',
|
'fixed z-50 min-w-[160px] rounded-md border border-border',
|
||||||
'bg-[#14161d] py-1 shadow-lg animate-scale-in'
|
'bg-card 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-[#848b9b] hover:bg-accent'
|
: 'text-muted-foreground 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-[#1e2130] bg-[#14161d] md:block">
|
<div className="hidden w-60 shrink-0 border-r border-border bg-card 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-[#14161d]/80"
|
className="absolute inset-0 bg-card/80"
|
||||||
onClick={() => setMobileOpen(false)}
|
onClick={() => setMobileOpen(false)}
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-y-0 left-0 w-60 border-r border-[#1e2130] bg-[#14161d] shadow-xl">
|
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card 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-[#848b9b] hover:bg-accent"
|
className="rounded-md p-1.5 text-muted-foreground 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-[#848b9b] hover:bg-accent md:hidden"
|
className="mb-4 rounded-md p-2 text-muted-foreground 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-[#e2e5eb]">Admin Panel</h2>
|
<h2 className="text-lg font-bold text-foreground">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-[#e2e5eb]'
|
? 'bg-accent text-foreground'
|
||||||
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#1e2130] p-3">
|
<div className="border-t border-border 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#14161d] border border-[#1e2130] rounded-xl p-4',
|
'flex items-center gap-3 bg-card border border-border 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-[#848b9b] hover:text-[#e2e5eb] active:cursor-grabbing"
|
className="cursor-grab touch-none text-muted-foreground hover:text-foreground 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-[#e2e5eb]">{category.name}</h3>
|
<h3 className="font-medium text-foreground">{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-[#848b9b]">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs font-medium text-muted-foreground">
|
||||||
Archived
|
Archived
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{category.description && (
|
{category.description && (
|
||||||
<p className="mt-1 text-sm text-[#848b9b]">{category.description}</p>
|
<p className="mt-1 text-sm text-muted-foreground">{category.description}</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{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-[#1e2130] bg-[#14161d] p-2 text-[#848b9b]',
|
'rounded-md border border-border bg-card p-2 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#0c0d10] p-2 text-[#848b9b]',
|
'rounded-md border border-input bg-background p-2 text-muted-foreground',
|
||||||
'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-[#0c0d10] p-2 text-[#848b9b]',
|
'rounded-md border border-input bg-background p-2 text-muted-foreground',
|
||||||
'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-[#e2e5eb]">
|
<label htmlFor="name" className="mb-1 block text-sm font-medium text-foreground">
|
||||||
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-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{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-[#e2e5eb]">
|
<label htmlFor="description" className="mb-1 block text-sm font-medium text-foreground">
|
||||||
Description <span className="text-[#848b9b]">(optional)</span>
|
Description <span className="text-muted-foreground">(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-[#1e2130]">
|
<div className="overflow-x-auto rounded-lg border border-border">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-[#1e2130] bg-accent">
|
<tr className="border-b border-border 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-[#848b9b]',
|
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-muted-foreground',
|
||||||
col.sortable && 'cursor-pointer select-none hover:text-[#e2e5eb]',
|
col.sortable && 'cursor-pointer select-none hover:text-foreground',
|
||||||
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-[#1e2130] last:border-0">
|
<tr key={i} className="border-b border-border 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-[#848b9b]">No data found</span>
|
<span className="text-muted-foreground">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-[#1e2130] last:border-0 hover:bg-accent transition-colors"
|
className="border-b border-border 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-[#e2e5eb]">
|
<label htmlFor="edit-name" className="mb-1 block text-sm font-medium text-foreground">
|
||||||
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-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{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-[#e2e5eb]">
|
<label htmlFor="edit-description" className="mb-1 block text-sm font-medium text-foreground">
|
||||||
Description <span className="text-[#848b9b]">(optional)</span>
|
Description <span className="text-muted-foreground">(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-[#848b9b]">
|
<span className="text-sm text-muted-foreground">
|
||||||
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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]')}
|
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">...</span>
|
<span key={`e${i}`} className="px-1 text-muted-foreground">...</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-[#22d3ee] text-white'
|
? 'bg-primary text-white'
|
||||||
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]')}
|
className={cn(btnBase, 'px-2 text-muted-foreground hover:bg-accent hover:text-foreground')}
|
||||||
>
|
>
|
||||||
<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-[#848b9b]" />
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||||
<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-[#1e2130] bg-[#14161d] pl-9 pr-8 text-sm text-[#e2e5eb]',
|
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
|
||||||
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-muted-foreground 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-[#848b9b] hover:text-[#e2e5eb]"
|
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#848b9b]',
|
default: 'bg-accent text-muted-foreground',
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {
|
export function StatusBadge({ variant = 'default', children, className }: StatusBadgeProps) {
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat 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-[#e2e5eb]" />
|
<MapPin size={16} className="text-foreground" />
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Domain Coverage</h3>
|
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#848b9b]">
|
<p className="text-sm text-muted-foreground">
|
||||||
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>
|
||||||
@@ -55,10 +55,10 @@ export default function CoverageHeatmap({ data }: CoverageHeatmapProps) {
|
|||||||
<div className="card-flat 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-[#e2e5eb]" />
|
<MapPin size={16} className="text-foreground" />
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Domain Coverage</h3>
|
<h3 className="font-heading text-sm font-semibold text-foreground">Domain Coverage</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-[#848b9b]">
|
<p className="text-xs text-muted-foreground">
|
||||||
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-[#1e2130]">
|
<tr className="border-b border-border">
|
||||||
<th className="px-3 py-2 text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-left font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Domain
|
Domain
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Flows
|
Flows
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Sessions
|
Sessions
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Resolution %
|
Resolution %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Escalation %
|
Escalation %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Guided %
|
Guided %
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]"
|
<th className="px-3 py-2 text-right font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
||||||
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-[#1e2130]">
|
<tr key={row.domain} className="border-b border-border">
|
||||||
<td className="px-3 py-2 text-sm text-[#e2e5eb] font-medium">
|
<td className="px-3 py-2 text-sm text-foreground 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-[#22d3ee] hover:underline"
|
className="inline-flex items-center gap-1 text-xs text-primary 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-[#848b9b]">
|
<td className="px-3 py-2 text-sm text-right text-muted-foreground">
|
||||||
{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-[#848b9b]">—</span>
|
<span className="text-muted-foreground">—</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-[#848b9b]">
|
<td colSpan={7} className="px-3 py-2 text-xs text-muted-foreground">
|
||||||
{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-sans text-xs text-[#848b9b]">
|
<div className="flex flex-wrap gap-4 mt-3 text-[0.625rem] font-sans text-xs text-muted-foreground">
|
||||||
<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-[#848b9b]" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
return (
|
return (
|
||||||
<div className="text-center py-12 text-[#848b9b]">
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
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-[#e2e5eb]">Flow Analytics</h3>
|
<h3 className="text-lg font-semibold text-foreground">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-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:outline-hidden focus:ring-1 focus:ring-ring"
|
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"
|
||||||
>
|
>
|
||||||
{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-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
<div className="bg-card border border-border rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Sessions Over Time</p>
|
<p className="text-sm font-semibold text-foreground 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-[#848b9b] capitalize">
|
<span className="text-xs text-muted-foreground 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-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
<div className="bg-card border border-border rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Step Performance</p>
|
<p className="text-sm font-semibold text-foreground 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-[#1e2130]">
|
<tr className="border-b border-border">
|
||||||
<th className="text-left py-2 pr-4 text-[#e2e5eb] font-medium">Step</th>
|
<th className="text-left py-2 pr-4 text-foreground font-medium">Step</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">Visits</th>
|
||||||
<th className="text-right py-2 pr-4 text-[#e2e5eb] font-medium">Dropoffs</th>
|
<th className="text-right py-2 pr-4 text-foreground font-medium">Dropoffs</th>
|
||||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">Dropoff Rate</th>
|
<th className="text-right py-2 text-foreground 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-[#1e2130] last:border-0',
|
'border-b border-border 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-[#848b9b] truncate max-w-[200px]">
|
<td className="py-2 pr-4 text-muted-foreground truncate max-w-[200px]">
|
||||||
{step.node_title}
|
{step.node_title}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-4 text-right text-[#848b9b]">
|
<td className="py-2 pr-4 text-right text-muted-foreground">
|
||||||
{step.visit_count}
|
{step.visit_count}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 pr-4 text-right text-[#848b9b]">
|
<td className="py-2 pr-4 text-right text-muted-foreground">
|
||||||
{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-[#848b9b]'
|
step.dropoff_rate > 0.2 ? 'text-red-400' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{(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-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
<div className="bg-card border border-border rounded-xl p-4">
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb] mb-3">Recent Feedback</p>
|
<p className="text-sm font-semibold text-foreground 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-[#1e2130]/50 pb-3 last:border-0 last:pb-0"
|
className="flex items-start gap-3 border-b border-border/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-[#848b9b]'
|
: 'fill-none text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
{item.comment && (
|
{item.comment && (
|
||||||
<p className="text-sm text-[#e2e5eb]">{item.comment}</p>
|
<p className="text-sm text-foreground">{item.comment}</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-[#848b9b] mt-0.5">
|
<p className="text-xs text-muted-foreground 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-[#14161d] border border-[#1e2130] rounded-xl p-4">
|
<div className="bg-card border border-border rounded-xl p-4">
|
||||||
<p className="text-xs text-[#848b9b]">{label}</p>
|
<p className="text-xs text-muted-foreground">{label}</p>
|
||||||
<p className="text-xl font-bold text-[#e2e5eb] mt-1">{value}</p>
|
<p className="text-xl font-bold text-foreground mt-1">{value}</p>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<p className="text-xs text-[#848b9b] mt-0.5">{subtitle}</p>
|
<p className="text-xs text-muted-foreground 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-[#22d3ee]' },
|
troubleshooting: { label: 'Troubleshooting', color: 'text-primary' },
|
||||||
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-[#848b9b]'
|
if (value === null) return 'text-muted-foreground'
|
||||||
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'
|
||||||
@@ -112,7 +112,7 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat 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-[#848b9b]">
|
<p className="text-sm text-muted-foreground">
|
||||||
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>
|
||||||
@@ -133,17 +133,17 @@ export default function FlowQualityTable({ data }: FlowQualityTableProps) {
|
|||||||
<div className="card-flat 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-[#e2e5eb]">
|
<h3 className="font-heading text-sm font-semibold text-foreground">
|
||||||
Flow Quality Scores
|
Flow Quality Scores
|
||||||
</h3>
|
</h3>
|
||||||
<span className="group relative">
|
<span className="group relative">
|
||||||
<Info size={14} className="text-[#848b9b] cursor-help" />
|
<Info size={14} className="text-muted-foreground cursor-help" />
|
||||||
<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">
|
<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">
|
||||||
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-[#848b9b] mt-1">
|
<p className="text-xs text-muted-foreground 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-[#1e2130]">
|
<tr className="border-b border-border">
|
||||||
{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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] cursor-pointer select-none hover:text-[#e2e5eb] transition-colors"
|
className="text-left py-2 px-2 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground cursor-pointer select-none hover:text-foreground 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-[#1e2130]/50 hover:bg-[#14161d]/30 transition-colors',
|
'border-b border-border/50 hover:bg-card/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,11 +217,11 @@ 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-[#e2e5eb] hover:text-[#22d3ee] transition-colors font-medium truncate max-w-[200px]"
|
className="text-foreground hover:text-primary transition-colors font-medium truncate max-w-[200px]"
|
||||||
>
|
>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</Link>
|
</Link>
|
||||||
<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]')}>
|
<span className={cn('font-sans text-xs text-[0.5rem] uppercase tracking-wider ml-2 shrink-0', TYPE_LABELS[flow.tree_type]?.color || 'text-muted-foreground')}>
|
||||||
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
|
{TYPE_LABELS[flow.tree_type]?.label || flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
{needsAttention && (
|
{needsAttention && (
|
||||||
@@ -233,7 +233,7 @@ function FlowRow({
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
{/* Usage */}
|
{/* Usage */}
|
||||||
<td className="py-2.5 px-2 text-[#e2e5eb]">{flow.usage_count}</td>
|
<td className="py-2.5 px-2 text-foreground">{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-[#848b9b]">
|
<td className="py-2.5 px-2 text-muted-foreground">
|
||||||
{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-[#e2e5eb]">
|
<span className="text-xs text-foreground">
|
||||||
{(flow.quality_score * 100).toFixed(0)}
|
{(flow.quality_score * 100).toFixed(0)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat 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-[#848b9b]">
|
<p className="text-sm text-muted-foreground">
|
||||||
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>
|
||||||
@@ -34,19 +34,19 @@ export default function PsaMetricsPanel({ data }: PsaMetricsPanelProps) {
|
|||||||
{/* 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="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-sans text-xs 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-text-muted">Time Entries</p>
|
||||||
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.total_time_entries}</p>
|
<p className="text-accent-text font-heading text-2xl mt-1">{data.total_time_entries}</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">logged to PSA</p>
|
<p className="text-xs text-muted-foreground mt-1">logged to PSA</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-sans text-xs 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-text-muted">Hours Logged</p>
|
||||||
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
|
<p className="text-accent-text font-heading text-2xl mt-1">{data.total_hours_logged.toFixed(1)}</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">total hours tracked</p>
|
<p className="text-xs text-muted-foreground mt-1">total hours tracked</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<p className="font-sans text-xs 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-text-muted">Avg Hours/Session</p>
|
||||||
<p className="text-[#67e8f9] font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
|
<p className="text-accent-text font-heading text-2xl mt-1">{data.avg_hours_per_session.toFixed(2)}</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">per resolved session</p>
|
<p className="text-xs text-muted-foreground mt-1">per resolved session</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ 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="card-flat p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
||||||
PSA Activity Trend
|
PSA Activity Trend
|
||||||
</h3>
|
</h3>
|
||||||
<ResponsiveContainer width="100%" height={250}>
|
<ResponsiveContainer width="100%" height={250}>
|
||||||
@@ -130,7 +130,7 @@ function FunnelCard({ funnel }: { funnel: PsaFunnel }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card-flat p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
<h3 className="font-heading text-sm font-semibold text-foreground 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{step.label}
|
{step.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg font-heading text-[#e2e5eb]">{step.count}</p>
|
<p className="text-lg font-heading text-foreground">{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-[#848b9b]" />
|
<ArrowRight size={14} className="text-muted-foreground" />
|
||||||
<span className="text-[0.625rem] text-[#848b9b] font-sans text-xs">
|
<span className="text-[0.625rem] text-muted-foreground 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{step.label}
|
{step.label}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg font-heading text-[#e2e5eb]">{step.count}</p>
|
<p className="text-lg font-heading text-foreground">{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-[#848b9b]" />
|
<ArrowDown size={14} className="text-muted-foreground" />
|
||||||
<span className="text-[0.625rem] text-[#848b9b] font-sans text-xs">
|
<span className="text-[0.625rem] text-muted-foreground 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-[#22d3ee]'
|
? 'bg-primary/15 text-primary'
|
||||||
: 'bg-white/[0.08] text-[#848b9b]'
|
: 'bg-white/[0.08] text-muted-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{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-[#e2e5eb]'
|
? 'bg-primary/15 text-foreground'
|
||||||
: 'bg-[#191c25] text-[#e2e5eb] border border-[#1e2130]'
|
: 'bg-input text-foreground border border-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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
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-[#22d3ee] text-white font-semibold text-sm rounded-lg px-4 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all"
|
className="w-full flex items-center justify-center gap-2 bg-primary text-white 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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
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-[#848b9b] text-sm">
|
<div className="px-4 py-8 text-center text-muted-foreground 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-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
? 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">
|
<div className="text-[0.6875rem] text-muted-foreground">
|
||||||
{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-[#22d3ee]' : ''} />
|
<Pin size={12} className={chat.pinned ? 'text-primary' : ''} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); onDelete() }}
|
onClick={e => { e.stopPropagation(); onDelete() }}
|
||||||
className="p-1 rounded hover:bg-white/[0.08] text-[#848b9b] hover:text-rose-400"
|
className="p-1 rounded hover:bg-white/[0.08] text-muted-foreground hover:text-rose-400"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 size={12} />
|
<Trash2 size={12} />
|
||||||
|
|||||||
@@ -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-[rgba(34,211,238,0.10)] flex items-center justify-center">
|
<div className="w-9 h-9 rounded-xl bg-accent-dim flex items-center justify-center">
|
||||||
<ClipboardList size={18} className="text-[#22d3ee]" />
|
<ClipboardList size={18} className="text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-heading font-semibold text-[#e2e5eb]">
|
<h2 className="text-base font-heading font-semibold text-foreground">
|
||||||
Conclude Session
|
Conclude Session
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xs text-[#848b9b] truncate max-w-[300px]">
|
<p className="text-xs text-muted-foreground 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-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="p-2 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
</button>
|
</button>
|
||||||
@@ -196,10 +196,10 @@ export function ConcludeSessionModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-sans text-xs 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-[#22d3ee] text-white'
|
? 'bg-primary text-white'
|
||||||
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
|
||||||
? 'bg-primary/20 text-[#22d3ee]'
|
? 'bg-primary/20 text-primary'
|
||||||
: 'bg-brand-border text-[#848b9b]'
|
: 'bg-brand-border text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{i + 1}
|
{i + 1}
|
||||||
@@ -207,7 +207,7 @@ export function ConcludeSessionModal({
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs font-sans text-xs',
|
'text-xs font-sans text-xs',
|
||||||
step === s ? 'text-[#e2e5eb]' : 'text-[#848b9b]'
|
step === s ? 'text-foreground' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{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-[#848b9b] mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
How did this session end?
|
How did this session end?
|
||||||
</p>
|
</p>
|
||||||
{OUTCOMES.map(o => {
|
{OUTCOMES.map(o => {
|
||||||
@@ -233,16 +233,16 @@ export function ConcludeSessionModal({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
|
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
|
||||||
'hover:scale-[1.01] active:scale-[0.99]',
|
'hover:scale-[1.01] active:scale-[0.99]',
|
||||||
'bg-[#14161d] border-[#1e2130]',
|
'bg-card border-border',
|
||||||
'hover:border-[#2a2f3d] hover:bg-[#191c25]'
|
'hover:border-border-hover hover:bg-input'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', o.bg)}>
|
<div className={cn('w-10 h-10 rounded-xl flex items-center justify-center', o.bg)}>
|
||||||
<Icon size={20} className={o.color} />
|
<Icon size={20} className={o.color} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-semibold text-[#e2e5eb] block">{o.label}</span>
|
<span className="text-sm font-semibold text-foreground block">{o.label}</span>
|
||||||
<span className="text-xs text-[#848b9b]">{o.description}</span>
|
<span className="text-xs text-muted-foreground">{o.description}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -261,14 +261,14 @@ export function ConcludeSessionModal({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setStep('select-outcome')}
|
onClick={() => setStep('select-outcome')}
|
||||||
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] block mb-2">
|
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground 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-[#14161d] text-[#e2e5eb] text-sm placeholder:text-[#848b9b] px-4 py-3 focus:outline-hidden focus:border-primary/30"
|
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"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,16 +308,16 @@ export function ConcludeSessionModal({
|
|||||||
|
|
||||||
{/* Generated summary */}
|
{/* Generated summary */}
|
||||||
<div
|
<div
|
||||||
className="rounded-xl border p-5 bg-[#14161d]"
|
className="rounded-xl border p-5 bg-card"
|
||||||
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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] flex items-center gap-1.5">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
|
||||||
<Sparkles size={10} className="text-[#22d3ee]" />
|
<Sparkles size={10} className="text-primary" />
|
||||||
Generated Ticket Notes
|
Generated Ticket Notes
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="prose-sm text-[#e2e5eb]">
|
<div className="prose-sm text-foreground">
|
||||||
<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-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-[#191c25] border border-[#1e2130] hover:border-[#2a2f3d] transition-all"
|
className="px-4 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover 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-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-[#191c25] border border-[#1e2130] hover:border-[#2a2f3d] transition-all"
|
className="px-4 py-2 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover transition-all"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={generating}
|
disabled={generating}
|
||||||
className="flex items-center gap-2 bg-[#22d3ee] text-white font-semibold text-sm rounded-lg px-5 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
className="flex items-center gap-2 bg-primary text-white 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 ? (
|
||||||
<>
|
<>
|
||||||
@@ -390,7 +390,7 @@ export function ConcludeSessionModal({
|
|||||||
'flex items-center gap-2 px-4 py-2.5 rounded-lg 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-[#22d3ee] text-white hover:brightness-110 active:scale-[0.98]'
|
: 'bg-primary text-white 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-lg text-sm text-[#848b9b] hover:text-[#e2e5eb] bg-[#191c25] border border-[#1e2130] hover:border-[#2a2f3d] transition-all"
|
className="px-4 py-2.5 rounded-lg text-sm text-muted-foreground hover:text-foreground bg-input border border-border hover:border-border-hover 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 card-flat p-3 rounded-xl hover:border-[#2a2f3d] transition-colors group"
|
className="w-full text-left card-flat p-3 rounded-xl hover:border-border-hover transition-colors group"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Box size={14} className="text-[#22d3ee] mt-0.5 shrink-0" />
|
<Box size={14} className="text-primary 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-[#e2e5eb] truncate">
|
<span className="text-[0.8125rem] font-medium text-foreground truncate">
|
||||||
{flow.tree_name}
|
{flow.tree_name}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
|
||||||
{flow.tree_type}
|
{flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[0.75rem] text-[#848b9b] mt-0.5 line-clamp-2">
|
<p className="text-[0.75rem] text-muted-foreground mt-0.5 line-clamp-2">
|
||||||
{flow.relevance_snippet}
|
{flow.relevance_snippet}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight size={14} className="text-[#848b9b] group-hover:text-[#22d3ee] transition-colors shrink-0 mt-0.5" />
|
<ArrowRight size={14} className="text-muted-foreground group-hover:text-primary 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-[#1e2130] p-2 text-[#848b9b]',
|
'rounded-md border border-border p-2 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#14161d] border border-[#1e2130] rounded-lg p-1',
|
'absolute z-50 mt-1 min-w-[180px] bg-card border border-border 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Icon && <Icon className="h-4 w-4" />}
|
{Icon && <Icon className="h-4 w-4" />}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function BrandWordmark({ size = 'sm', className }: BrandWordmarkProps) {
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-heading font-bold tracking-tight text-[#f0f2f5]',
|
'font-heading font-bold tracking-tight text-text-heading',
|
||||||
size === 'sm' ? 'text-xl' : 'text-3xl',
|
size === 'sm' ? 'text-xl' : 'text-3xl',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ export function ConfirmDialog({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl border border-[#1e2130] px-4 py-2 text-sm font-medium',
|
'rounded-xl border border-border px-4 py-2 text-sm font-medium',
|
||||||
'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]',
|
'text-muted-foreground hover:bg-accent hover:text-foreground',
|
||||||
'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-[#22d3ee] text-white hover:brightness-110'
|
: 'bg-primary 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-[#848b9b]">{message}</p>
|
<p className="text-sm text-muted-foreground">{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-[#1e2130] bg-[#14161d] p-1 shadow-lg"
|
className="min-w-[200px] rounded-xl border border-border bg-card 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-[#1e2130]" />
|
<div className="my-1 border-t border-border" />
|
||||||
)}
|
)}
|
||||||
<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-[#e2e5eb] hover:bg-[#1e2130]'
|
: 'text-foreground hover:bg-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.icon && (
|
{item.icon && (
|
||||||
<span className="flex h-4 w-4 items-center justify-center text-[#848b9b]">
|
<span className="flex h-4 w-4 items-center justify-center text-muted-foreground">
|
||||||
{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-[#22d3ee] px-4 py-2 text-sm font-semibold text-white hover:brightness-110 transition-opacity"
|
className="flex items-center gap-2 rounded-lg bg-primary 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-[#1e2130] bg-[#14161d] p-1 shadow-xl">
|
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card 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-[#e2e5eb] hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
<FolderTree className="h-4 w-4 text-[#848b9b]" />
|
<FolderTree className="h-4 w-4 text-muted-foreground" />
|
||||||
<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-[#848b9b]">Branching decision flow</div>
|
<div className="text-xs text-muted-foreground">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-[#e2e5eb] hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="my-1 border-t border-[#1e2130]" />
|
<div className="my-1 border-t border-border" />
|
||||||
|
|
||||||
{/* 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-[#e2e5eb] hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
<ListOrdered className="h-4 w-4 text-[#848b9b]" />
|
<ListOrdered className="h-4 w-4 text-muted-foreground" />
|
||||||
<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-[#848b9b]">Step-by-step procedure</div>
|
<div className="text-xs text-muted-foreground">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-[#e2e5eb] hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-primary font-medium">Build with Flow Assist</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="my-1 border-t border-[#1e2130]" />
|
<div className="my-1 border-t border-border" />
|
||||||
|
|
||||||
{/* 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-[#e2e5eb] hover:bg-accent"
|
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground 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-[#848b9b]">Scheduled multi-target tasks</div>
|
<div className="text-xs text-muted-foreground">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-[#e2e5eb] hover:bg-accent"
|
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-foreground hover:bg-accent"
|
||||||
>
|
>
|
||||||
<Sparkles className="h-3.5 w-3.5 text-[#22d3ee] ml-0.5" />
|
<Sparkles className="h-3.5 w-3.5 text-primary ml-0.5" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xs text-[#22d3ee] font-medium">Build with Flow Assist</div>
|
<div className="text-xs text-primary 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-[#848b9b]">{icon}</div>
|
<div className="mb-4 text-muted-foreground">{icon}</div>
|
||||||
)}
|
)}
|
||||||
<h3 className="text-lg font-semibold text-[#e2e5eb]">{title}</h3>
|
<h3 className="text-lg font-semibold text-foreground">{title}</h3>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="mt-2 max-w-sm text-sm text-[#848b9b]">{description}</p>
|
<p className="mt-2 max-w-sm text-sm text-muted-foreground">{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-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="mt-3 text-sm text-muted-foreground hover:text-foreground 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-[#848b9b]">
|
<p className="mb-4 text-muted-foreground">
|
||||||
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-[#1e2130] p-3 text-left text-xs text-red-400">
|
<pre className="mb-4 overflow-auto rounded-xl bg-white/5 border border-border 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-[#848b9b] 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-muted-foreground cursor-help shrink-0"
|
||||||
title={text}
|
title={text}
|
||||||
>
|
>
|
||||||
i
|
i
|
||||||
|
|||||||
@@ -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-[#1e2130] bg-[#14161d] shadow-lg',
|
'relative flex w-full flex-col border border-border bg-card 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-[#1e2130] px-4 py-3 sm:px-6 sm:py-4">
|
<div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
|
||||||
<h2 id="modal-title" className="text-lg font-semibold text-[#e2e5eb]">
|
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
|
||||||
{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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
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-[#848b9b] transition-colors sm:p-1',
|
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]',
|
'hover:bg-accent hover:text-foreground',
|
||||||
'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-[#1e2130] px-4 py-3 sm:px-6 sm:py-4">
|
<div className="shrink-0 border-t border-border 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-[#e2e5eb]', titleClassName)}>
|
<h1 className={cn('text-2xl font-bold font-heading text-foreground', titleClassName)}>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
{description && (
|
{description && (
|
||||||
<p className={cn('mt-1 text-sm text-[#848b9b]', descriptionClassName)}>
|
<p className={cn('mt-1 text-sm text-muted-foreground', 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-[#848b9b]">Loading…</p>
|
<p className="text-sm text-muted-foreground">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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
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-[#14161d] border border-[#1e2130] rounded-xl p-3 text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
'w-full bg-card border border-border rounded-xl p-3 text-sm text-foreground placeholder:text-muted-foreground',
|
||||||
'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-[#22d3ee]">
|
<div className="flex items-center gap-2 text-sm text-primary">
|
||||||
<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-[#1e2130]">
|
<div key={upload.id} className="relative w-16 h-16 rounded-lg overflow-hidden border border-border">
|
||||||
<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-[#0c0d10]/50 flex items-center justify-center">
|
<div className="absolute inset-0 bg-background/50 flex items-center justify-center">
|
||||||
<Loader2 size={16} className="animate-spin text-[#22d3ee]" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</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-[#0c0d10]/80 border border-[#1e2130] 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-background/80 border border-border flex items-center justify-center hover:bg-rose-500/20 transition-colors"
|
||||||
>
|
>
|
||||||
<X size={10} className="text-[#848b9b]" />
|
<X size={10} className="text-muted-foreground" />
|
||||||
</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-[#848b9b]/50 mt-1">Paste screenshots with Ctrl+V</p>
|
<p className="text-[0.625rem] text-muted-foreground/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-[#0c0d10] p-8">
|
<div className="flex min-h-screen flex-col items-center justify-center bg-background 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-[#e2e5eb]">Oops!</h1>
|
<h1 className="mb-2 text-4xl font-bold text-foreground">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-[#848b9b]">{errorDetails}</p>
|
<p className="mb-4 text-muted-foreground">{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-[#1e2130] border-t-primary',
|
'animate-spin rounded-full border-border 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-[#848b9b]',
|
: 'fill-none text-muted-foreground',
|
||||||
!readonly && 'hover:text-yellow-300'
|
!readonly && 'hover:text-yellow-300'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{showCount && (
|
{showCount && (
|
||||||
<span className="ml-1 text-sm text-[#848b9b]">
|
<span className="ml-1 text-sm text-muted-foreground">
|
||||||
({value}/5)
|
({value}/5)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export function TagBadges({
|
|||||||
'rounded-full font-sans text-xs 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-[#848b9b] hover:bg-accent'
|
? 'bg-accent text-muted-foreground hover:bg-accent'
|
||||||
: 'bg-accent/50 text-[#848b9b] hover:bg-accent',
|
: 'bg-accent/50 text-muted-foreground hover:bg-accent',
|
||||||
!onTagClick && 'cursor-default'
|
!onTagClick && 'cursor-default'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -50,7 +50,7 @@ export function TagBadges({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-full font-sans text-xs',
|
'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-[#848b9b]'
|
'bg-accent/50 text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#14161d] text-[#e2e5eb]',
|
'bg-card text-foreground',
|
||||||
'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-[#1e2130]'
|
'border-border'
|
||||||
)}
|
)}
|
||||||
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-[#848b9b]'
|
'bg-accent text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{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-[#e2e5eb]',
|
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
|
||||||
'placeholder:text-[#848b9b]',
|
'placeholder:text-muted-foreground',
|
||||||
'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-[#1e2130]',
|
'absolute z-10 mt-1 w-full rounded-xl border border-border',
|
||||||
'bg-[#14161d] shadow-lg'
|
'bg-card 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-[#848b9b]',
|
'flex w-full items-center justify-between px-3 py-2 text-sm text-muted-foreground',
|
||||||
'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-[#848b9b]">
|
<span className="text-xs text-muted-foreground">
|
||||||
{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-[#1e2130] px-3 py-2 text-sm',
|
'flex w-full items-center gap-2 border-t border-border px-3 py-2 text-sm',
|
||||||
'hover:bg-accent text-[#e2e5eb]'
|
'hover:bg-accent text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{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-[#22d3ee]" />
|
<Sparkles size={16} className="text-primary" />
|
||||||
<span className="text-sm font-semibold text-[#e2e5eb]">AI Copilot</span>
|
<span className="text-sm font-semibold text-foreground">AI Copilot</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg hover:bg-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="p-1.5 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground 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-[#e2e5eb]'
|
? 'bg-primary/15 text-foreground'
|
||||||
: 'bg-[#191c25] text-[#e2e5eb] border border-[#1e2130]'
|
: 'bg-input text-foreground border border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
||||||
@@ -133,8 +133,8 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
))}
|
))}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="bg-[#191c25] border border-[#1e2130] rounded-xl px-3.5 py-2.5">
|
<div className="bg-input border border-border rounded-xl px-3.5 py-2.5">
|
||||||
<Loader2 size={16} className="animate-spin text-[#22d3ee]" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
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-[#14161d] text-[#e2e5eb] text-[0.8125rem] placeholder:text-[#848b9b] px-3.5 py-2.5 focus:outline-hidden focus:border-primary/30"
|
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"
|
||||||
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-[#22d3ee] text-white p-2.5 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
|
className="bg-primary text-white 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-[#848b9b] mt-1.5 px-1">Shift + Enter for a new line</p>
|
<p className="text-[0.625rem] text-muted-foreground 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-[#22d3ee] text-white p-3.5 rounded-full hover:brightness-110 active:scale-[0.98] transition-all"
|
className="fixed bottom-6 right-6 z-40 bg-primary text-white p-3.5 rounded-full hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
title="Open AI Copilot"
|
title="Open AI Copilot"
|
||||||
>
|
>
|
||||||
<MessageCircle size={22} />
|
<MessageCircle size={22} />
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ export function ActiveFlowPilotSessions() {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat">
|
<div className="card-flat">
|
||||||
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Active Sessions</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<div key={i} className="h-24 rounded-xl bg-[#14161d] border border-[#1e2130] animate-pulse" />
|
<div key={i} className="h-24 rounded-xl bg-card border border-border animate-pulse" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,16 +49,16 @@ export function ActiveFlowPilotSessions() {
|
|||||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Active Sessions</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Active Sessions</h3>
|
||||||
{sessions.length > 0 && (
|
{sessions.length > 0 && (
|
||||||
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-[rgba(34,211,238,0.10)] px-1.5 text-[0.625rem] font-bold text-[#22d3ee]">
|
<span className="inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-accent-dim px-1.5 text-[0.625rem] font-bold text-primary">
|
||||||
{sessions.length}
|
{sessions.length}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
to="/sessions?filter=active"
|
to="/sessions?filter=active"
|
||||||
className="flex items-center gap-1 text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
View all <ArrowRight size={10} />
|
View all <ArrowRight size={10} />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -66,8 +66,8 @@ export function ActiveFlowPilotSessions() {
|
|||||||
|
|
||||||
{sessions.length === 0 ? (
|
{sessions.length === 0 ? (
|
||||||
<div className="px-5 py-8 text-center">
|
<div className="px-5 py-8 text-center">
|
||||||
<p className="text-sm text-[#848b9b]">No active sessions</p>
|
<p className="text-sm text-muted-foreground">No active sessions</p>
|
||||||
<p className="mt-1 text-[0.6875rem] text-[#5a6170]">Start typing above to begin troubleshooting</p>
|
<p className="mt-1 text-[0.6875rem] text-text-muted">Start typing above to begin troubleshooting</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 p-4">
|
||||||
@@ -78,23 +78,23 @@ export function ActiveFlowPilotSessions() {
|
|||||||
className="card-interactive p-4 text-left"
|
className="card-interactive p-4 text-left"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2 mb-2">
|
<div className="flex items-start justify-between gap-2 mb-2">
|
||||||
<Sparkles size={14} className="shrink-0 text-[#22d3ee] mt-0.5" />
|
<Sparkles size={14} className="shrink-0 text-primary mt-0.5" />
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'font-sans text-xs text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
|
'font-sans text-xs text-[0.5625rem] uppercase px-1.5 py-0.5 rounded',
|
||||||
session.confidence_tier === 'guided' && 'bg-emerald-400/10 text-emerald-400',
|
session.confidence_tier === 'guided' && 'bg-emerald-400/10 text-emerald-400',
|
||||||
session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400',
|
session.confidence_tier === 'exploring' && 'bg-amber-400/10 text-amber-400',
|
||||||
session.confidence_tier === 'discovery' && 'bg-blue-400/10 text-blue-400',
|
session.confidence_tier === 'discovery' && 'bg-blue-400/10 text-blue-400',
|
||||||
!session.confidence_tier && 'bg-[#14161d] text-[#848b9b]',
|
!session.confidence_tier && 'bg-card text-muted-foreground',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{session.confidence_tier || 'starting'}
|
{session.confidence_tier || 'starting'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb] truncate">
|
<p className="text-sm font-medium text-foreground truncate">
|
||||||
{session.problem_summary || 'Session in progress'}
|
{session.problem_summary || 'Session in progress'}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 flex items-center gap-2 text-[0.625rem] text-[#848b9b]">
|
<div className="mt-2 flex items-center gap-2 text-[0.625rem] text-muted-foreground">
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Clock size={10} />
|
<Clock size={10} />
|
||||||
{timeAgo(session.created_at)}
|
{timeAgo(session.created_at)}
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ export function FiltersBar({ filters, activeFilter, onFilterChange }: FiltersBar
|
|||||||
className={cn(
|
className={cn(
|
||||||
'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
'shrink-0 rounded-lg border px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
||||||
activeFilter === f.id
|
activeFilter === f.id
|
||||||
? 'border-[#22d3ee]/30 bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
? 'border-[#22d3ee]/30 bg-accent-dim text-primary'
|
||||||
: 'border-[#1e2130] bg-[#14161d] text-[#848b9b] hover:border-[#1e2130]/80 hover:text-[#e2e5eb]'
|
: 'border-border bg-card text-muted-foreground hover:border-border/80 hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{f.label}
|
{f.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<div className="mx-1.5 h-5 w-px shrink-0 bg-[#1e2130]" />
|
<div className="mx-1.5 h-5 w-px shrink-0 bg-border" />
|
||||||
<button className="flex shrink-0 items-center gap-1.5 rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-[0.8125rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors">
|
<button className="flex shrink-0 items-center gap-1.5 rounded-lg border border-border bg-card px-3 py-1.5 text-[0.8125rem] text-muted-foreground hover:text-foreground transition-colors">
|
||||||
<Filter size={14} />
|
<Filter size={14} />
|
||||||
More Filters
|
More Filters
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ export function KnowledgeBaseCards() {
|
|||||||
className="flex items-center justify-between px-5 py-3"
|
className="flex items-center justify-between px-5 py-3"
|
||||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Knowledge Base</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Knowledge Base</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/trees')}
|
onClick={() => navigate('/trees')}
|
||||||
className="flex items-center gap-1 text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Browse <ArrowRight size={10} />
|
Browse <ArrowRight size={10} />
|
||||||
</button>
|
</button>
|
||||||
@@ -41,8 +41,8 @@ export function KnowledgeBaseCards() {
|
|||||||
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
|
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
|
||||||
>
|
>
|
||||||
<item.icon size={20} style={{ color: item.color }} />
|
<item.icon size={20} style={{ color: item.color }} />
|
||||||
<p className="font-heading text-xl font-extrabold text-[#e2e5eb]">{item.value}</p>
|
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p>
|
||||||
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{item.label}
|
{item.label}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function OnboardingChecklist() {
|
|||||||
{/* Progress bar */}
|
{/* Progress bar */}
|
||||||
<div className="h-1 w-full bg-[rgba(255,255,255,0.04)]">
|
<div className="h-1 w-full bg-[rgba(255,255,255,0.04)]">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-[#22d3ee] transition-all duration-500 ease-out"
|
className="h-full bg-primary transition-all duration-500 ease-out"
|
||||||
style={{ width: `${progressPercent}%` }}
|
style={{ width: `${progressPercent}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,15 +82,15 @@ export function OnboardingChecklist() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Getting Started
|
Getting Started
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-[#e2e5eb] mt-0.5">
|
<p className="text-sm text-foreground mt-0.5">
|
||||||
{isAllDone ? (
|
{isAllDone ? (
|
||||||
<span className="text-[#67e8f9] font-semibold">You're all set!</span>
|
<span className="text-accent-text font-semibold">You're all set!</span>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<span className="text-[#67e8f9] font-semibold">{completedCount}</span>
|
<span className="text-accent-text font-semibold">{completedCount}</span>
|
||||||
{' '}of {totalCount} complete
|
{' '}of {totalCount} complete
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -98,7 +98,7 @@ export function OnboardingChecklist() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleDismiss}
|
onClick={handleDismiss}
|
||||||
className="rounded-md p-1 text-[#848b9b] hover:text-[#e2e5eb] hover:bg-[rgba(255,255,255,0.04)] transition-colors"
|
className="rounded-md p-1 text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)] transition-colors"
|
||||||
aria-label="Dismiss onboarding checklist"
|
aria-label="Dismiss onboarding checklist"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
@@ -126,8 +126,8 @@ export function OnboardingChecklist() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors',
|
'flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors',
|
||||||
done
|
done
|
||||||
? 'bg-[#22d3ee] border-transparent'
|
? 'bg-primary border-transparent'
|
||||||
: 'border-[#1e2130]'
|
: 'border-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{done && <Check size={12} className="text-white" />}
|
{done && <Check size={12} className="text-white" />}
|
||||||
@@ -138,8 +138,8 @@ export function OnboardingChecklist() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1',
|
'flex-1',
|
||||||
done
|
done
|
||||||
? 'text-[#848b9b] line-through'
|
? 'text-muted-foreground line-through'
|
||||||
: 'text-[#e2e5eb]'
|
: 'text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
@@ -147,7 +147,7 @@ export function OnboardingChecklist() {
|
|||||||
|
|
||||||
{/* Arrow for incomplete items */}
|
{/* Arrow for incomplete items */}
|
||||||
{!done && (
|
{!done && (
|
||||||
<ChevronRight size={14} className="text-[#848b9b] shrink-0" />
|
<ChevronRight size={14} className="text-muted-foreground shrink-0" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ export function OpenSessions({ sessions }: OpenSessionsProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat flex flex-col h-full">
|
<div className="card-flat flex flex-col h-full">
|
||||||
<div className="flex items-center justify-between px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="flex items-center justify-between px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">My Open Sessions</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">My Open Sessions</h3>
|
||||||
<Link to="/sessions" className="text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors">
|
<Link to="/sessions" className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors">
|
||||||
View All
|
View All
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
{sessions.length === 0 ? (
|
{sessions.length === 0 ? (
|
||||||
<div className="flex-1 flex items-center justify-center">
|
<div className="flex-1 flex items-center justify-center">
|
||||||
<p className="text-sm text-[#848b9b]">No open sessions</p>
|
<p className="text-sm text-muted-foreground">No open sessions</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
sessions.map((session, i) => (
|
sessions.map((session, i) => (
|
||||||
@@ -40,8 +40,8 @@ export function OpenSessions({ sessions }: OpenSessionsProps) {
|
|||||||
>
|
>
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400" />
|
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm text-[#e2e5eb] truncate">{session.treeName}</div>
|
<div className="text-sm text-foreground truncate">{session.treeName}</div>
|
||||||
<div className="text-[0.6875rem] text-[#848b9b]">
|
<div className="text-[0.6875rem] text-muted-foreground">
|
||||||
{session.stepNumber && session.totalSteps
|
{session.stepNumber && session.totalSteps
|
||||||
? `Step ${session.stepNumber} of ${session.totalSteps}`
|
? `Step ${session.stepNumber} of ${session.totalSteps}`
|
||||||
: 'In progress'}
|
: 'In progress'}
|
||||||
@@ -52,7 +52,7 @@ export function OpenSessions({ sessions }: OpenSessionsProps) {
|
|||||||
<Link
|
<Link
|
||||||
to={getTreeNavigatePath(session.treeId, session.treeType)}
|
to={getTreeNavigatePath(session.treeId, session.treeType)}
|
||||||
state={{ sessionId: session.id }}
|
state={{ sessionId: session.id }}
|
||||||
className="shrink-0 rounded-lg bg-[#22d3ee] px-3 py-1 text-[0.6875rem] font-medium text-white hover:brightness-110 transition-opacity"
|
className="shrink-0 rounded-lg bg-primary px-3 py-1 text-[0.6875rem] font-medium text-white hover:brightness-110 transition-opacity"
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function PendingEscalations() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AlertTriangle size={14} className="text-amber-400" />
|
<AlertTriangle size={14} className="text-amber-400" />
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">
|
<h3 className="font-heading text-sm font-bold text-foreground">
|
||||||
Pending Escalations
|
Pending Escalations
|
||||||
<span className="ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-amber-400/10 px-1.5 text-[0.625rem] font-bold text-amber-400">
|
<span className="ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-amber-400/10 px-1.5 text-[0.625rem] font-bold text-amber-400">
|
||||||
{escalations.length}
|
{escalations.length}
|
||||||
@@ -46,7 +46,7 @@ export function PendingEscalations() {
|
|||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
to="/escalations"
|
to="/escalations"
|
||||||
className="text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
View all
|
View all
|
||||||
</Link>
|
</Link>
|
||||||
@@ -64,10 +64,10 @@ export function PendingEscalations() {
|
|||||||
>
|
>
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400 animate-pulse" />
|
<span className="h-2 w-2 shrink-0 rounded-full bg-amber-400 animate-pulse" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm text-[#e2e5eb] truncate">
|
<div className="text-sm text-foreground truncate">
|
||||||
{esc.problem_summary || 'Escalated session'}
|
{esc.problem_summary || 'Escalated session'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[0.6875rem] text-[#848b9b]">
|
<div className="text-[0.6875rem] text-muted-foreground">
|
||||||
{esc.problem_domain || 'General'}
|
{esc.problem_domain || 'General'}
|
||||||
<span className="mx-1.5 text-[var(--text-dimmed)]">·</span>
|
<span className="mx-1.5 text-[var(--text-dimmed)]">·</span>
|
||||||
<span className="font-sans text-xs">{timeAgo(esc.created_at)}</span>
|
<span className="font-sans text-xs">{timeAgo(esc.created_at)}</span>
|
||||||
|
|||||||
@@ -74,14 +74,14 @@ export function PerformanceCards() {
|
|||||||
style={{ animationDelay: `${400 + i * 60}ms` }}
|
style={{ animationDelay: `${400 + i * 60}ms` }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{card.label}
|
{card.label}
|
||||||
</p>
|
</p>
|
||||||
<card.icon size={14} style={{ color: card.iconColor }} />
|
<card.icon size={14} style={{ color: card.iconColor }} />
|
||||||
</div>
|
</div>
|
||||||
<p className={cn(
|
<p className={cn(
|
||||||
'font-heading text-2xl font-extrabold tracking-tight',
|
'font-heading text-2xl font-extrabold tracking-tight',
|
||||||
card.highlight ? 'text-[#67e8f9]' : 'text-[#e2e5eb]'
|
card.highlight ? 'text-accent-text' : 'text-foreground'
|
||||||
)}>
|
)}>
|
||||||
{card.value}
|
{card.value}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function PreparedSessions() {
|
|||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ClipboardList className="h-4 w-4 text-cyan-400" />
|
<ClipboardList className="h-4 w-4 text-cyan-400" />
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Prepared for You</h3>
|
<h3 className="font-heading text-sm font-semibold text-foreground">Prepared for You</h3>
|
||||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-cyan-400/20 text-[0.625rem] font-bold text-cyan-400">
|
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-cyan-400/20 text-[0.625rem] font-bold text-cyan-400">
|
||||||
{sessions.length}
|
{sessions.length}
|
||||||
</span>
|
</span>
|
||||||
@@ -51,13 +51,13 @@ export function PreparedSessions() {
|
|||||||
key={session.id}
|
key={session.id}
|
||||||
onClick={() => handleStart(session)}
|
onClick={() => handleStart(session)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'group flex w-full items-center justify-between gap-3 rounded-lg border border-[#1e2130] px-4 py-3',
|
'group flex w-full items-center justify-between gap-3 rounded-lg border border-border px-4 py-3',
|
||||||
'text-left transition-all hover:border-cyan-500/30 hover:bg-cyan-500/5'
|
'text-left transition-all hover:border-cyan-500/30 hover:bg-cyan-500/5'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-medium text-[#e2e5eb] truncate">{flowName}</p>
|
<p className="text-sm font-medium text-foreground truncate">{flowName}</p>
|
||||||
<div className="mt-1 flex items-center gap-3 text-xs text-[#848b9b]">
|
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
|
||||||
{session.ticket_number && (
|
{session.ticket_number && (
|
||||||
<span>{session.ticket_number}</span>
|
<span>{session.ticket_number}</span>
|
||||||
)}
|
)}
|
||||||
@@ -73,7 +73,7 @@ export function PreparedSessions() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight className="h-4 w-4 shrink-0 text-[#848b9b] opacity-0 transition-opacity group-hover:opacity-100" />
|
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function QuickActions() {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat flex flex-col h-full">
|
<div className="card-flat flex flex-col h-full">
|
||||||
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Quick Actions</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Quick Actions</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex flex-col justify-between p-3 gap-2">
|
<div className="flex-1 flex flex-col justify-between p-3 gap-2">
|
||||||
{ACTIONS.map(({ icon: Icon, label, description, href, color }) => (
|
{ACTIONS.map(({ icon: Icon, label, description, href, color }) => (
|
||||||
@@ -30,8 +30,8 @@ export function QuickActions() {
|
|||||||
<Icon size={18} style={{ color }} />
|
<Icon size={18} style={{ color }} />
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-sm font-medium text-[#e2e5eb]">{label}</div>
|
<div className="text-sm font-medium text-foreground">{label}</div>
|
||||||
<div className="text-[0.6875rem] text-[#848b9b] truncate">{description}</div>
|
<div className="text-[0.6875rem] text-muted-foreground truncate">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ export function QuickStats({ stats }: QuickStatsProps) {
|
|||||||
className={cn('card-interactive p-4 fade-in')}
|
className={cn('card-interactive p-4 fade-in')}
|
||||||
style={{ animationDelay: `${50 + i * 30}ms` }}
|
style={{ animationDelay: `${50 + i * 30}ms` }}
|
||||||
>
|
>
|
||||||
<p className="font-sans text-xs text-[0.625rem] font-medium uppercase tracking-widest text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.625rem] font-medium uppercase tracking-widest text-muted-foreground">
|
||||||
{stat.label}
|
{stat.label}
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 font-heading text-2xl font-extrabold tracking-tight',
|
'mt-1 font-heading text-2xl font-extrabold tracking-tight',
|
||||||
stat.gradient && 'text-[#67e8f9]',
|
stat.gradient && 'text-accent-text',
|
||||||
stat.color
|
stat.color
|
||||||
)}
|
)}
|
||||||
style={stat.color && !stat.color.startsWith('text-') ? { color: stat.color } : undefined}
|
style={stat.color && !stat.color.startsWith('text-') ? { color: stat.color } : undefined}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivi
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat">
|
<div className="card-flat">
|
||||||
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Recent Activity</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Recent Activity</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{activities.map((item, i) => (
|
{activities.map((item, i) => (
|
||||||
@@ -45,9 +45,9 @@ export function RecentActivity({ activities = DEFAULT_ACTIVITIES }: RecentActivi
|
|||||||
<item.icon size={16} style={{ color: item.iconColor }} />
|
<item.icon size={16} style={{ color: item.iconColor }} />
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1 min-w-0 pt-0.5">
|
<div className="flex-1 min-w-0 pt-0.5">
|
||||||
<p className="text-sm text-[#e2e5eb]">{item.description}</p>
|
<p className="text-sm text-foreground">{item.description}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="shrink-0 font-sans text-xs text-[0.625rem] text-[#848b9b] pt-1">
|
<span className="shrink-0 font-sans text-xs text-[0.625rem] text-muted-foreground pt-1">
|
||||||
{item.timestamp}
|
{item.timestamp}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ export function RecentFlowPilotSessions() {
|
|||||||
className="flex items-center justify-between px-5 py-3"
|
className="flex items-center justify-between px-5 py-3"
|
||||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Recent Sessions</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Recent Sessions</h3>
|
||||||
<Link
|
<Link
|
||||||
to="/sessions"
|
to="/sessions"
|
||||||
className="flex items-center gap-1 text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
History <ArrowRight size={10} />
|
History <ArrowRight size={10} />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -69,11 +69,11 @@ export function RecentFlowPilotSessions() {
|
|||||||
>
|
>
|
||||||
<StatusIcon size={14} style={{ color: config.color }} className="shrink-0" />
|
<StatusIcon size={14} style={{ color: config.color }} className="shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm text-[#e2e5eb] truncate">
|
<p className="text-sm text-foreground truncate">
|
||||||
{session.problem_summary || 'Session'}
|
{session.problem_summary || 'Session'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="shrink-0 font-sans text-xs text-[#848b9b]">
|
<span className="shrink-0 font-sans text-xs text-muted-foreground">
|
||||||
{timeAgo(session.resolved_at || session.created_at)}
|
{timeAgo(session.resolved_at || session.created_at)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ export function SectionGroup({ title, count, defaultOpen = true, delay = 150, ch
|
|||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
className="flex w-full items-center gap-2 py-2"
|
className="flex w-full items-center gap-2 py-2"
|
||||||
>
|
>
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-[#22d3ee]" />
|
<span className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
||||||
<span className="font-heading text-[0.8125rem] font-bold uppercase tracking-[0.04em] text-[#e2e5eb]">
|
<span className="font-heading text-[0.8125rem] font-bold uppercase tracking-[0.04em] text-foreground">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
{count !== undefined && (
|
{count !== undefined && (
|
||||||
<span className="rounded-full bg-secondary px-2 py-0.5 font-sans text-xs text-[#848b9b]">
|
<span className="rounded-full bg-secondary px-2 py-0.5 font-sans text-xs text-muted-foreground">
|
||||||
{count}
|
{count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
size={14}
|
size={14}
|
||||||
className={cn('text-[#848b9b] transition-transform', !open && '-rotate-90')}
|
className={cn('text-muted-foreground transition-transform', !open && '-rotate-90')}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
{open && <div className="mt-1 space-y-1">{children}</div>}
|
{open && <div className="mt-1 space-y-1">{children}</div>}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat fade-in" style={{ animationDelay: `${delay}ms` }}>
|
<div className="card-flat fade-in" style={{ animationDelay: `${delay}ms` }}>
|
||||||
<div className="flex items-center justify-between px-4 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="flex items-center justify-between px-4 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">Recent Sessions</h3>
|
<h3 className="font-heading text-sm font-semibold text-foreground">Recent Sessions</h3>
|
||||||
<Link to="/sessions" className="text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors">
|
<Link to="/sessions" className="text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors">
|
||||||
View All
|
View All
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,10 +47,10 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<span className="text-sm text-[#e2e5eb] truncate">{session.treeName}</span>
|
<span className="text-sm text-foreground truncate">{session.treeName}</span>
|
||||||
|
|
||||||
{/* Progress */}
|
{/* Progress */}
|
||||||
<span className="text-[0.6875rem] text-[#848b9b] truncate">
|
<span className="text-[0.6875rem] text-muted-foreground truncate">
|
||||||
{session.status === 'completed'
|
{session.status === 'completed'
|
||||||
? '✓ Resolved'
|
? '✓ Resolved'
|
||||||
: session.stepNumber && session.totalSteps
|
: session.stepNumber && session.totalSteps
|
||||||
@@ -59,7 +59,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Ticket */}
|
{/* Ticket */}
|
||||||
<span className="font-sans text-xs text-[0.6875rem] text-[#848b9b] truncate">
|
<span className="font-sans text-xs text-[0.6875rem] text-muted-foreground truncate">
|
||||||
{session.ticketNumber || '—'}
|
{session.ticketNumber || '—'}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function StartSessionInput() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Sparkles
|
<Sparkles
|
||||||
size={18}
|
size={18}
|
||||||
className="absolute left-4 top-1/2 -translate-y-1/2 text-[#848b9b]"
|
className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -46,7 +46,7 @@ export function StartSessionInput() {
|
|||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="What are you troubleshooting?"
|
placeholder="What are you troubleshooting?"
|
||||||
className="w-full rounded-xl border border-[#1e2130] bg-[#0c0d10] py-3.5 pl-11 pr-4 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"
|
className="w-full rounded-xl border border-border bg-background py-3.5 pl-11 pr-4 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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex items-center justify-between">
|
<div className="mt-3 flex items-center justify-between">
|
||||||
@@ -57,8 +57,8 @@ export function StartSessionInput() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
|
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||||
mode === 'guided'
|
mode === 'guided'
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
? 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Sparkles size={12} />
|
<Sparkles size={12} />
|
||||||
@@ -70,15 +70,15 @@ export function StartSessionInput() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
|
'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||||
mode === 'chat'
|
mode === 'chat'
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
? 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MessageCircle size={12} />
|
<MessageCircle size={12} />
|
||||||
Open Chat
|
Open Chat
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[0.625rem] text-[#848b9b]">
|
<span className="text-[0.625rem] text-muted-foreground">
|
||||||
Press Enter to start
|
Press Enter to start
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ export function TeamSummary() {
|
|||||||
className="flex items-center justify-between px-5 py-3"
|
className="flex items-center justify-between px-5 py-3"
|
||||||
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
style={{ borderBottom: '1px solid var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">Team Summary</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">Team Summary</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/analytics')}
|
onClick={() => navigate('/analytics')}
|
||||||
className="flex items-center gap-1 text-[0.6875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Manage <ArrowRight size={10} />
|
Manage <ArrowRight size={10} />
|
||||||
</button>
|
</button>
|
||||||
@@ -46,8 +46,8 @@ export function TeamSummary() {
|
|||||||
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
|
className="flex flex-col items-center gap-2 py-5 hover:bg-[rgba(255,255,255,0.02)] transition-colors"
|
||||||
>
|
>
|
||||||
<item.icon size={20} style={{ color: item.color }} />
|
<item.icon size={20} style={{ color: item.color }} />
|
||||||
<p className="font-heading text-xl font-extrabold text-[#e2e5eb]">{item.value}</p>
|
<p className="font-heading text-xl font-extrabold text-foreground">{item.value}</p>
|
||||||
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{item.label}
|
{item.label}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function TreeListItem({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={() => navigate(getTreeNavigatePath(id, treeType))}
|
onClick={() => navigate(getTreeNavigatePath(id, treeType))}
|
||||||
className="group grid cursor-pointer items-center gap-3 rounded-lg border border-transparent bg-[#14161d] px-4 py-3 transition-colors hover:border-[#1e2130] hover:bg-[var(--sidebar-hover)]"
|
className="group grid cursor-pointer items-center gap-3 rounded-lg border border-transparent bg-card px-4 py-3 transition-colors hover:border-border hover:bg-[var(--sidebar-hover)]"
|
||||||
style={{ gridTemplateColumns: '40px 1fr 130px 80px 100px 40px' }}
|
style={{ gridTemplateColumns: '40px 1fr 130px 80px 100px 40px' }}
|
||||||
>
|
>
|
||||||
{/* Icon box */}
|
{/* Icon box */}
|
||||||
@@ -46,15 +46,15 @@ export function TreeListItem({
|
|||||||
|
|
||||||
{/* Info */}
|
{/* Info */}
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="font-heading text-sm font-semibold text-[#e2e5eb] truncate">{name}</p>
|
<p className="font-heading text-sm font-semibold text-foreground truncate">{name}</p>
|
||||||
<div className="mt-0.5 flex items-center gap-2">
|
<div className="mt-0.5 flex items-center gap-2">
|
||||||
{tags.slice(0, 3).map(tag => (
|
{tags.slice(0, 3).map(tag => (
|
||||||
<span key={tag} className="rounded border border-[#1e2130] bg-secondary px-1.5 py-px font-sans text-xs text-[0.625rem] text-[#848b9b]">
|
<span key={tag} className="rounded border border-border bg-secondary px-1.5 py-px font-sans text-xs text-[0.625rem] text-muted-foreground">
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
{description && tags.length === 0 && (
|
{description && tags.length === 0 && (
|
||||||
<span className="text-[0.6875rem] text-[#848b9b] truncate">{description}</span>
|
<span className="text-[0.6875rem] text-muted-foreground truncate">{description}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,13 +64,13 @@ export function TreeListItem({
|
|||||||
{category && (
|
{category && (
|
||||||
<>
|
<>
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: categoryColor }} />
|
<span className="h-2 w-2 shrink-0 rounded-full" style={{ backgroundColor: categoryColor }} />
|
||||||
<span className="font-sans text-xs text-[#848b9b] truncate">{category.name}</span>
|
<span className="font-sans text-xs text-muted-foreground truncate">{category.name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Usage count */}
|
{/* Usage count */}
|
||||||
<div className="text-right font-sans text-xs text-[#848b9b]">
|
<div className="text-right font-sans text-xs text-muted-foreground">
|
||||||
{usageCount} uses
|
{usageCount} uses
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ export function TreeListItem({
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
navigate(getTreeEditorPath(id, treeType))
|
navigate(getTreeEditorPath(id, treeType))
|
||||||
}}
|
}}
|
||||||
className="flex h-8 w-8 items-center justify-center rounded-md text-[#848b9b] opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100"
|
className="flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100"
|
||||||
>
|
>
|
||||||
<MoreHorizontal size={16} />
|
<MoreHorizontal size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat flex flex-col h-full">
|
<div className="card-flat flex flex-col h-full">
|
||||||
<div className="flex items-center gap-2 px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
<div className="flex items-center gap-2 px-5 py-3" style={{ borderBottom: '1px solid var(--glass-border)' }}>
|
||||||
<Calendar size={16} className="text-[#848b9b]" />
|
<Calendar size={16} className="text-muted-foreground" />
|
||||||
<h3 className="font-heading text-sm font-bold text-[#e2e5eb]">This Week</h3>
|
<h3 className="font-heading text-sm font-bold text-foreground">This Week</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 min-h-0">
|
<div className="flex flex-1 min-h-0">
|
||||||
{days.map((day, i) => {
|
{days.map((day, i) => {
|
||||||
@@ -56,10 +56,10 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
|
|||||||
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
|
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className={`font-sans text-xs text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-[#848b9b]'}`}>
|
<span className={`font-sans text-xs text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
|
||||||
{day.label}
|
{day.label}
|
||||||
</span>
|
</span>
|
||||||
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-[#e2e5eb]' : 'text-[#848b9b]'}`}>
|
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}>
|
||||||
{day.date.getDate()}
|
{day.date.getDate()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,8 +76,8 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
|
|||||||
background: 'rgba(255, 255, 255, 0.02)',
|
background: 'rgba(255, 255, 255, 0.02)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="font-medium text-[#e2e5eb] truncate">{event.title}</div>
|
<div className="font-medium text-foreground truncate">{event.title}</div>
|
||||||
<div className="font-sans text-xs text-[0.5625rem] text-[#848b9b]">{event.time}</div>
|
<div className="font-sans text-xs text-[0.5625rem] text-muted-foreground">{event.time}</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ export function AIPromptDialog({
|
|||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
className="rounded-[10px] bg-[#191c25] border border-[#1e2130] px-4 py-2 text-sm text-foreground hover:border-[#2a2f3d] transition-colors disabled:opacity-50"
|
className="rounded-[10px] bg-input border border-border px-4 py-2 text-sm text-foreground hover:border-border-hover transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={!prompt.trim() || isGenerating}
|
disabled={!prompt.trim() || isGenerating}
|
||||||
className="flex items-center gap-2 rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isGenerating ? (
|
{isGenerating ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
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-foreground'
|
||||||
: 'bg-[#191c25] text-foreground border border-[#1e2130]'
|
: 'bg-input text-foreground border border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
|
||||||
@@ -59,7 +59,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
))}
|
))}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<div className="bg-[#191c25] border border-[#1e2130] rounded-xl px-3.5 py-2.5">
|
<div className="bg-input border border-border rounded-xl px-3.5 py-2.5">
|
||||||
<Loader2 size={16} className="animate-spin text-primary" />
|
<Loader2 size={16} className="animate-spin text-primary" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +84,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
|
|||||||
<button
|
<button
|
||||||
onClick={onSend}
|
onClick={onSend}
|
||||||
disabled={!input.trim() || isLoading}
|
disabled={!input.trim() || isLoading}
|
||||||
className="bg-[#22d3ee] text-white p-2.5 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
|
className="bg-primary text-white 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>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function EditorAIPanel({
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="p-1.5 rounded-lg hover:bg-[#1e2130] text-muted-foreground hover:text-foreground transition-colors"
|
className="p-1.5 rounded-lg hover:bg-border text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
|
|||||||
const config = STATUS_CONFIG[s.status]
|
const config = STATUS_CONFIG[s.status]
|
||||||
const StatusIcon = config.icon
|
const StatusIcon = config.icon
|
||||||
return (
|
return (
|
||||||
<div key={s.id} className="rounded-lg border border-border bg-[#14161d] px-3 py-2">
|
<div key={s.id} className="rounded-lg border border-border bg-card px-3 py-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||||
{s.action_type.replace(/_/g, ' ')}
|
{s.action_type.replace(/_/g, ' ')}
|
||||||
@@ -39,7 +39,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
|
|||||||
{s.target_node_id && (
|
{s.target_node_id && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
|
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
|
||||||
)}
|
)}
|
||||||
<p className="mt-0.5 font-label text-[0.625rem] text-[#4f5666]">
|
<p className="mt-0.5 font-label text-[0.625rem] text-text-muted">
|
||||||
{new Date(s.created_at).toLocaleDateString()}
|
{new Date(s.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ interface AISessionListItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const STATUS_CONFIG = {
|
const STATUS_CONFIG = {
|
||||||
active: { icon: Clock, color: 'text-[#22d3ee]', label: 'Active' },
|
active: { icon: Clock, color: 'text-primary', label: 'Active' },
|
||||||
paused: { icon: Pause, color: 'text-amber-400', label: 'Paused' },
|
paused: { icon: Pause, color: 'text-amber-400', label: 'Paused' },
|
||||||
resolved: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Resolved' },
|
resolved: { icon: CheckCircle2, color: 'text-emerald-400', label: 'Resolved' },
|
||||||
escalated: { icon: ArrowUpRight, color: 'text-amber-400', label: 'Escalated' },
|
escalated: { icon: ArrowUpRight, color: 'text-amber-400', label: 'Escalated' },
|
||||||
abandoned: { icon: AlertCircle, color: 'text-[#5a6170]', label: 'Abandoned' },
|
abandoned: { icon: AlertCircle, color: 'text-text-muted', label: 'Abandoned' },
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function AISessionListItem({ session }: AISessionListItemProps) {
|
export function AISessionListItem({ session }: AISessionListItemProps) {
|
||||||
@@ -26,12 +26,12 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
|
|||||||
>
|
>
|
||||||
<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">
|
||||||
<p className="text-sm font-medium text-[#e2e5eb] truncate">
|
<p className="text-sm font-medium text-foreground truncate">
|
||||||
{session.problem_summary || 'Untitled session'}
|
{session.problem_summary || 'Untitled session'}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-1.5 flex items-center gap-3 flex-wrap">
|
<div className="mt-1.5 flex items-center gap-3 flex-wrap">
|
||||||
{session.problem_domain && (
|
{session.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
|
||||||
{session.problem_domain}
|
{session.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -39,10 +39,10 @@ export function AISessionListItem({ session }: AISessionListItemProps) {
|
|||||||
<StatusIcon size={12} />
|
<StatusIcon size={12} />
|
||||||
{config.label}
|
{config.label}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[#848b9b]">
|
<span className="text-xs text-muted-foreground">
|
||||||
{session.step_count} steps
|
{session.step_count} steps
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[#5a6170]">
|
<span className="text-xs text-text-muted">
|
||||||
{new Date(session.created_at).toLocaleDateString(undefined, {
|
{new Date(session.created_at).toLocaleDateString(undefined, {
|
||||||
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
|
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ export function ConfidenceIndicator({ tier, score, className }: ConfidenceIndica
|
|||||||
return (
|
return (
|
||||||
<div className={cn('group relative inline-flex items-center gap-2', className)}>
|
<div className={cn('group relative inline-flex items-center gap-2', className)}>
|
||||||
<span className={cn('h-2 w-2 rounded-full', config.color)} />
|
<span className={cn('h-2 w-2 rounded-full', config.color)} />
|
||||||
<span className="font-sans text-xs text-xs text-[#848b9b]">{config.label}</span>
|
<span className="font-sans text-xs text-xs text-muted-foreground">{config.label}</span>
|
||||||
|
|
||||||
{/* Tooltip */}
|
{/* Tooltip */}
|
||||||
<div className="pointer-events-none absolute left-0 top-full z-50 mt-2 w-56 rounded-lg border border-[#1e2130] bg-[#14161d] p-3 opacity-0 shadow-lg transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
|
<div className="pointer-events-none absolute left-0 top-full z-50 mt-2 w-56 rounded-lg border border-border bg-card p-3 opacity-0 shadow-lg transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
|
||||||
<p className="text-xs text-[#848b9b]">{config.description}</p>
|
<p className="text-xs text-muted-foreground">{config.description}</p>
|
||||||
<p className="mt-1 font-sans text-xs text-[0.625rem] text-[#5a6170]">
|
<p className="mt-1 font-sans text-xs text-[0.625rem] text-text-muted">
|
||||||
Confidence: {Math.round(score * 100)}%
|
Confidence: {Math.round(score * 100)}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1.5 block text-sm font-medium text-[#e2e5eb]">
|
<label className="mb-1.5 block text-sm font-medium text-foreground">
|
||||||
Why are you escalating?
|
Why are you escalating?
|
||||||
</label>
|
</label>
|
||||||
<RichTextInput
|
<RichTextInput
|
||||||
@@ -54,7 +54,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
|
|||||||
placeholder="e.g. I've exhausted all networking diagnostics and suspect this is a firewall policy issue that requires senior admin access..."
|
placeholder="e.g. I've exhausted all networking diagnostics and suspect this is a firewall policy issue that requires senior admin access..."
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-[0.625rem] text-[#5a6170]">
|
<p className="mt-1 text-[0.625rem] text-text-muted">
|
||||||
Minimum 5 characters. This will be shown to the engineer who picks up.
|
Minimum 5 characters. This will be shown to the engineer who picks up.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +63,7 @@ export function EscalateModal({ open, onClose, onEscalate, isProcessing, hasPsaT
|
|||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className="flex-1 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2.5 min-h-[44px] text-sm font-medium text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
|
className="flex-1 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2.5 min-h-[44px] text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
<p className="text-sm text-rose-400">{error}</p>
|
<p className="text-sm text-rose-400">{error}</p>
|
||||||
<button
|
<button
|
||||||
onClick={loadQueue}
|
onClick={loadQueue}
|
||||||
className="mt-2 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="mt-2 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Try again
|
Try again
|
||||||
</button>
|
</button>
|
||||||
@@ -64,11 +64,11 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
if (sessions.length === 0) {
|
if (sessions.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="py-12 text-center">
|
<div className="py-12 text-center">
|
||||||
<AlertTriangle size={24} className="mx-auto mb-2 text-[#848b9b]/40" />
|
<AlertTriangle size={24} className="mx-auto mb-2 text-muted-foreground/40" />
|
||||||
<p className="text-sm text-[#848b9b]">No sessions awaiting escalation</p>
|
<p className="text-sm text-muted-foreground">No sessions awaiting escalation</p>
|
||||||
<button
|
<button
|
||||||
onClick={loadQueue}
|
onClick={loadQueue}
|
||||||
className="mt-3 flex items-center gap-1.5 mx-auto text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="mt-3 flex items-center gap-1.5 mx-auto text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<RefreshCw size={12} />
|
<RefreshCw size={12} />
|
||||||
Refresh
|
Refresh
|
||||||
@@ -80,12 +80,12 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between px-1">
|
<div className="flex items-center justify-between px-1">
|
||||||
<h3 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170]">
|
<h3 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted">
|
||||||
Awaiting pickup ({sessions.length})
|
Awaiting pickup ({sessions.length})
|
||||||
</h3>
|
</h3>
|
||||||
<button
|
<button
|
||||||
onClick={loadQueue}
|
onClick={loadQueue}
|
||||||
className="flex items-center gap-1 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<RefreshCw size={10} />
|
<RefreshCw size={10} />
|
||||||
Refresh
|
Refresh
|
||||||
@@ -95,7 +95,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
{sessions.map((session) => (
|
{sessions.map((session) => (
|
||||||
<div key={session.id} className="card-interactive p-3 sm:p-4 space-y-3">
|
<div key={session.id} className="card-interactive p-3 sm:p-4 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb]">
|
<p className="text-sm font-semibold text-foreground">
|
||||||
{session.problem_summary || 'Untitled session'}
|
{session.problem_summary || 'Untitled session'}
|
||||||
</p>
|
</p>
|
||||||
{session.escalation_reason && (
|
{session.escalation_reason && (
|
||||||
@@ -105,9 +105,9 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[#848b9b]">
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||||
{session.problem_domain && (
|
{session.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
|
||||||
{session.problem_domain}
|
{session.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -120,7 +120,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
</span>
|
</span>
|
||||||
{session.psa_ticket_id && (
|
{session.psa_ticket_id && (
|
||||||
<span className="flex items-center gap-1 text-[#22d3ee]">
|
<span className="flex items-center gap-1 text-primary">
|
||||||
<Ticket size={10} />
|
<Ticket size={10} />
|
||||||
#{session.psa_ticket_id}
|
#{session.psa_ticket_id}
|
||||||
</span>
|
</span>
|
||||||
@@ -129,7 +129,7 @@ export function EscalationQueue({ onPickup }: EscalationQueueProps) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handlePickup(session.id)}
|
onClick={() => handlePickup(session.id)}
|
||||||
className="w-full min-h-[44px] rounded-lg bg-[#22d3ee] text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
className="w-full min-h-[44px] rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
Pick Up Session
|
Pick Up Session
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function FlowPilotActionBar({
|
|||||||
<>
|
<>
|
||||||
{/* Bottom bar — fixed to viewport bottom, works regardless of height chain */}
|
{/* Bottom bar — fixed to viewport bottom, works regardless of height chain */}
|
||||||
<div
|
<div
|
||||||
className="fixed bottom-0 right-0 z-40 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 border-t border-[#1e2130] bg-[#14161d] px-3 py-3 sm:px-5"
|
className="fixed bottom-0 right-0 z-40 flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 border-t border-border bg-card px-3 py-3 sm:px-5"
|
||||||
style={{ left: 'var(--sidebar-w, 0px)' }}
|
style={{ left: 'var(--sidebar-w, 0px)' }}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 sm:gap-3">
|
<div className="flex gap-2 sm:gap-3">
|
||||||
@@ -96,7 +96,7 @@ export function FlowPilotActionBar({
|
|||||||
<button
|
<button
|
||||||
onClick={handlePause}
|
onClick={handlePause}
|
||||||
disabled={isProcessing || submitting}
|
disabled={isProcessing || submitting}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 min-h-[44px] text-sm font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
className="flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 min-h-[44px] text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
||||||
>
|
>
|
||||||
<Pause size={16} />
|
<Pause size={16} />
|
||||||
Pause
|
Pause
|
||||||
@@ -106,7 +106,7 @@ export function FlowPilotActionBar({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowAbandon(true)}
|
onClick={() => setShowAbandon(true)}
|
||||||
disabled={isProcessing || submitting}
|
disabled={isProcessing || submitting}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 min-h-[44px] text-sm font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
className="flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 min-h-[44px] text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
|
||||||
>
|
>
|
||||||
<X size={16} />
|
<X size={16} />
|
||||||
Close
|
Close
|
||||||
@@ -119,20 +119,20 @@ export function FlowPilotActionBar({
|
|||||||
{showResolve && (
|
{showResolve && (
|
||||||
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
||||||
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
||||||
<h3 className="font-heading text-lg font-semibold text-[#e2e5eb] mb-1">Resolve Session</h3>
|
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Resolve Session</h3>
|
||||||
<p className="text-sm text-[#848b9b] mb-4">Summarize what fixed the issue. This will be included in the auto-generated documentation.</p>
|
<p className="text-sm text-muted-foreground mb-4">Summarize what fixed the issue. This will be included in the auto-generated documentation.</p>
|
||||||
<textarea
|
<textarea
|
||||||
value={resolutionSummary}
|
value={resolutionSummary}
|
||||||
onChange={(e) => setResolutionSummary(e.target.value)}
|
onChange={(e) => setResolutionSummary(e.target.value)}
|
||||||
placeholder="What resolved the issue?"
|
placeholder="What resolved the issue?"
|
||||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||||
rows={4}
|
rows={4}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<div className="mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
<div className="mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowResolve(false)}
|
onClick={() => setShowResolve(false)}
|
||||||
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -152,14 +152,14 @@ export function FlowPilotActionBar({
|
|||||||
{showAbandon && (
|
{showAbandon && (
|
||||||
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/60 backdrop-blur-sm">
|
||||||
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
<div className="card-flat w-full max-w-full sm:max-w-lg mx-0 sm:mx-4 p-4 sm:p-6 rounded-t-2xl sm:rounded-2xl">
|
||||||
<h3 className="font-heading text-lg font-semibold text-[#e2e5eb] mb-1">Close Session</h3>
|
<h3 className="font-heading text-lg font-semibold text-foreground mb-1">Close Session</h3>
|
||||||
<p className="text-sm text-[#848b9b] mb-4">
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
Are you sure you want to close this session? The session history will be kept but it won't count as resolved.
|
Are you sure you want to close this session? The session history will be kept but it won't count as resolved.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAbandon(false)}
|
onClick={() => setShowAbandon(false)}
|
||||||
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -100,11 +100,11 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[50vh]">
|
<div className="flex items-center justify-center min-h-[50vh]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-[rgba(34,211,238,0.10)]">
|
<div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-2xl bg-accent-dim">
|
||||||
<Sparkles size={24} className="text-[#22d3ee] animate-pulse" />
|
<Sparkles size={24} className="text-primary animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb]">Analyzing your issue...</p>
|
<p className="text-sm font-medium text-foreground">Analyzing your issue...</p>
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">FlowPilot is classifying the problem and searching for relevant flows</p>
|
<p className="mt-1 text-xs text-muted-foreground">FlowPilot is classifying the problem and searching for relevant flows</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -118,10 +118,10 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
<div className="flex items-start justify-center px-3 sm:px-4 pt-[6vh] sm:pt-[10vh]">
|
<div className="flex items-start justify-center px-3 sm:px-4 pt-[6vh] sm:pt-[10vh]">
|
||||||
<div className="w-full max-w-2xl">
|
<div className="w-full max-w-2xl">
|
||||||
<div className="text-center mb-4 sm:mb-6">
|
<div className="text-center mb-4 sm:mb-6">
|
||||||
<h1 className="font-heading text-xl sm:text-2xl font-bold tracking-tight text-[#e2e5eb]">
|
<h1 className="font-heading text-xl sm:text-2xl font-bold tracking-tight text-foreground">
|
||||||
What are you troubleshooting?
|
What are you troubleshooting?
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-sm text-[#848b9b]">
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
Describe the issue, paste an error message, or pull context from a ticket
|
Describe the issue, paste an error message, or pull context from a ticket
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,22 +132,22 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4">
|
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb]">
|
<p className="text-sm font-semibold text-foreground">
|
||||||
<span className="text-[#22d3ee]">#{selectedTicketId}</span>
|
<span className="text-primary">#{selectedTicketId}</span>
|
||||||
{' — '}
|
{' — '}
|
||||||
{selectedTicket.summary}
|
{selectedTicket.summary}
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-1 flex flex-wrap items-center gap-x-2 text-xs text-[#848b9b]">
|
<div className="mt-1 flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground">
|
||||||
{selectedTicket.company_name && <span>{selectedTicket.company_name}</span>}
|
{selectedTicket.company_name && <span>{selectedTicket.company_name}</span>}
|
||||||
{selectedTicket.priority_name && (
|
{selectedTicket.priority_name && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[#5a6170]">•</span>
|
<span className="text-text-muted">•</span>
|
||||||
<span>{selectedTicket.priority_name}</span>
|
<span>{selectedTicket.priority_name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{selectedTicket.status_name && (
|
{selectedTicket.status_name && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[#5a6170]">•</span>
|
<span className="text-text-muted">•</span>
|
||||||
<span>{selectedTicket.status_name}</span>
|
<span>{selectedTicket.status_name}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -155,7 +155,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleClearTicket}
|
onClick={handleClearTicket}
|
||||||
className="ml-2 rounded-md p-1 text-[#848b9b] hover:bg-white/[0.06] hover:text-[#e2e5eb] transition-colors"
|
className="ml-2 rounded-md p-1 text-muted-foreground hover:bg-white/[0.06] hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<X size={14} />
|
<X size={14} />
|
||||||
</button>
|
</button>
|
||||||
@@ -166,7 +166,7 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
value={additionalContext}
|
value={additionalContext}
|
||||||
onChange={(e) => setAdditionalContext(e.target.value)}
|
onChange={(e) => setAdditionalContext(e.target.value)}
|
||||||
placeholder="Add extra context (optional) — e.g. 'User called back and said it's also affecting their second monitor'"
|
placeholder="Add extra context (optional) — e.g. 'User called back and said it's also affecting their second monitor'"
|
||||||
className="mt-3 w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
className="mt-3 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,8 +190,8 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
onClick={() => setShowLogs(!showLogs)}
|
onClick={() => setShowLogs(!showLogs)}
|
||||||
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
|
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
showLogs
|
showLogs
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border border-primary/20'
|
? 'bg-accent-dim text-primary border border-primary/20'
|
||||||
: 'bg-[#14161d]/50 text-[#848b9b] border border-[#1e2130] hover:text-[#e2e5eb]'
|
: 'bg-card/50 text-muted-foreground border border-border hover:text-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Terminal size={12} />
|
<Terminal size={12} />
|
||||||
@@ -206,8 +206,8 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
disabled={!psaChecked || !psaConnection}
|
disabled={!psaChecked || !psaConnection}
|
||||||
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
|
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-colors ${
|
||||||
psaConnection
|
psaConnection
|
||||||
? 'bg-[#14161d]/50 text-[#848b9b] border border-[#1e2130] hover:text-[#e2e5eb] hover:border-primary/20'
|
? 'bg-card/50 text-muted-foreground border border-border hover:text-foreground hover:border-primary/20'
|
||||||
: 'bg-[#14161d]/50 text-[#5a6170] border border-[#1e2130] opacity-50 cursor-not-allowed'
|
: 'bg-card/50 text-text-muted border border-border opacity-50 cursor-not-allowed'
|
||||||
}`}
|
}`}
|
||||||
title={!psaConnection ? 'Connect your PSA in Settings → Integrations' : 'Search for a ConnectWise ticket'}
|
title={!psaConnection ? 'Connect your PSA in Settings → Integrations' : 'Search for a ConnectWise ticket'}
|
||||||
>
|
>
|
||||||
@@ -229,20 +229,20 @@ export function FlowPilotIntake({ onSubmit, isLoading, defaultProblem }: FlowPil
|
|||||||
value={logContent}
|
value={logContent}
|
||||||
onChange={(e) => setLogContent(e.target.value)}
|
onChange={(e) => setLogContent(e.target.value)}
|
||||||
placeholder="Paste log output, error messages, or Event Viewer entries here..."
|
placeholder="Paste log output, error messages, or Event Viewer entries here..."
|
||||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-4 py-3 font-mono text-xs text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
className="w-full rounded-lg border border-border bg-card px-4 py-3 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||||
rows={6}
|
rows={6}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Submit */}
|
{/* Submit */}
|
||||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<p className="text-[0.6875rem] text-[#5a6170] text-center sm:text-left">
|
<p className="text-[0.6875rem] text-text-muted text-center sm:text-left">
|
||||||
FlowPilot will analyze your input and guide you through diagnosis
|
FlowPilot will analyze your input and guide you through diagnosis
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!hasContent}
|
disabled={!hasContent}
|
||||||
className="w-full sm:w-auto min-h-[44px] rounded-lg bg-[#22d3ee] text-white px-5 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all whitespace-nowrap"
|
className="w-full sm:w-auto min-h-[44px] rounded-lg bg-primary text-white px-5 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{submitLabel}
|
{submitLabel}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-end gap-2 rounded-xl border p-3 transition-colors',
|
'flex items-end gap-2 rounded-xl border p-3 transition-colors',
|
||||||
isDisabled
|
isDisabled
|
||||||
? 'border-[#1e2130]/50 opacity-50'
|
? 'border-border/50 opacity-50'
|
||||||
: 'border-[#1e2130] focus-within:border-[rgba(6,182,212,0.3)]'
|
: 'border-border focus-within:border-[rgba(6,182,212,0.3)]'
|
||||||
)}
|
)}
|
||||||
style={{ background: '#14161d' }}
|
style={{ background: '#14161d' }}
|
||||||
>
|
>
|
||||||
@@ -61,7 +61,7 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
|
|||||||
placeholder={isProcessing ? 'FlowPilot is thinking...' : 'Type a message...'}
|
placeholder={isProcessing ? 'FlowPilot is thinking...' : 'Type a message...'}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
rows={1}
|
rows={1}
|
||||||
className="flex-1 resize-none bg-transparent text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:outline-none disabled:cursor-not-allowed py-1.5 px-2"
|
className="flex-1 resize-none bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed py-1.5 px-2"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
@@ -70,8 +70,8 @@ export function FlowPilotMessageBar({ onRespond, disabled = false, isProcessing
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-all',
|
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg transition-all',
|
||||||
message.trim() && !isDisabled
|
message.trim() && !isDisabled
|
||||||
? 'bg-[#22d3ee] text-white hover:brightness-110 active:scale-[0.98]'
|
? 'bg-primary text-white hover:brightness-110 active:scale-[0.98]'
|
||||||
: 'bg-[rgba(255,255,255,0.04)] text-[#848b9b] cursor-not-allowed'
|
: 'bg-[rgba(255,255,255,0.04)] text-muted-foreground cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Send size={16} />
|
<Send size={16} />
|
||||||
|
|||||||
@@ -32,20 +32,20 @@ export function FlowPilotOptions({ options, onSelect, disabled }: FlowPilotOptio
|
|||||||
'hover:border-[rgba(6,182,212,0.3)] hover:shadow-[0_0_20px_rgba(6,182,212,0.08)]',
|
'hover:border-[rgba(6,182,212,0.3)] hover:shadow-[0_0_20px_rgba(6,182,212,0.08)]',
|
||||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40',
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40',
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary/40 bg-[rgba(34,211,238,0.10)]'
|
? 'border-primary/40 bg-accent-dim'
|
||||||
: 'border-[#1e2130] bg-[#14161d]/50',
|
: 'border-border bg-card/50',
|
||||||
disabled && 'pointer-events-none opacity-60'
|
disabled && 'pointer-events-none opacity-60'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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 font-medium text-[#e2e5eb]">{option.label}</p>
|
<p className="text-sm font-medium text-foreground">{option.label}</p>
|
||||||
{option.followup_hint && (
|
{option.followup_hint && (
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">{option.followup_hint}</p>
|
<p className="mt-1 text-xs text-muted-foreground">{option.followup_hint}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isSelected && (
|
{isSelected && (
|
||||||
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/20 text-[#22d3ee]">
|
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||||
<Check size={12} />
|
<Check size={12} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -141,9 +141,9 @@ export function FlowPilotSession({
|
|||||||
onClick={() => setShowMobileSidebar(!showMobileSidebar)}
|
onClick={() => setShowMobileSidebar(!showMobileSidebar)}
|
||||||
className="flex w-full items-center justify-between px-3 py-2 sm:px-4"
|
className="flex w-full items-center justify-between px-3 py-2 sm:px-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 text-xs text-[#848b9b] overflow-x-auto">
|
<div className="flex items-center gap-3 text-xs text-muted-foreground overflow-x-auto">
|
||||||
{session.problem_domain && (
|
{session.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-[#22d3ee] shrink-0">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary shrink-0">
|
||||||
{session.problem_domain}
|
{session.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -156,7 +156,7 @@ export function FlowPilotSession({
|
|||||||
score={currentStep?.confidence_score ?? 0}
|
score={currentStep?.confidence_score ?? 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showMobileSidebar ? <ChevronUp size={14} className="text-[#848b9b] shrink-0" /> : <ChevronDown size={14} className="text-[#848b9b] shrink-0" />}
|
{showMobileSidebar ? <ChevronUp size={14} className="text-muted-foreground shrink-0" /> : <ChevronDown size={14} className="text-muted-foreground shrink-0" />}
|
||||||
</button>
|
</button>
|
||||||
{showMobileSidebar && (
|
{showMobileSidebar && (
|
||||||
<div className="px-3 pb-3 sm:px-4 space-y-3">
|
<div className="px-3 pb-3 sm:px-4 space-y-3">
|
||||||
@@ -169,7 +169,7 @@ export function FlowPilotSession({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowTicketPicker(true)}
|
onClick={() => setShowTicketPicker(true)}
|
||||||
disabled={linkingTicket}
|
disabled={linkingTicket}
|
||||||
className="w-full flex items-center gap-2 rounded-xl border border-dashed border-[#1e2130] px-3 py-2.5 text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:border-primary/30 transition-colors disabled:opacity-50 min-h-[44px]"
|
className="w-full flex items-center gap-2 rounded-xl border border-dashed border-border px-3 py-2.5 text-xs text-muted-foreground hover:text-foreground hover:border-primary/30 transition-colors disabled:opacity-50 min-h-[44px]"
|
||||||
>
|
>
|
||||||
<Ticket size={14} />
|
<Ticket size={14} />
|
||||||
{linkingTicket ? 'Linking...' : 'Link Ticket'}
|
{linkingTicket ? 'Linking...' : 'Link Ticket'}
|
||||||
@@ -177,14 +177,14 @@ export function FlowPilotSession({
|
|||||||
) : null}
|
) : null}
|
||||||
{session.problem_summary && (
|
{session.problem_summary && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Problem</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Problem</h4>
|
||||||
<p className="text-sm text-[#e2e5eb]">{session.problem_summary}</p>
|
<p className="text-sm text-foreground">{session.problem_summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{session.matched_flow_id && (
|
{session.matched_flow_id && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Network size={14} className="text-[#848b9b]" />
|
<Network size={14} className="text-muted-foreground" />
|
||||||
<span className="text-xs text-[#e2e5eb]">
|
<span className="text-xs text-foreground">
|
||||||
{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
|
{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,7 +228,7 @@ export function FlowPilotSession({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowTicketPicker(true)}
|
onClick={() => setShowTicketPicker(true)}
|
||||||
disabled={linkingTicket}
|
disabled={linkingTicket}
|
||||||
className="w-full flex items-center gap-2 rounded-xl border border-dashed border-[#1e2130] px-3 py-2.5 text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:border-primary/30 transition-colors disabled:opacity-50"
|
className="w-full flex items-center gap-2 rounded-xl border border-dashed border-border px-3 py-2.5 text-xs text-muted-foreground hover:text-foreground hover:border-primary/30 transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Ticket size={14} />
|
<Ticket size={14} />
|
||||||
{linkingTicket ? 'Linking...' : 'Link Ticket'}
|
{linkingTicket ? 'Linking...' : 'Link Ticket'}
|
||||||
@@ -238,20 +238,20 @@ export function FlowPilotSession({
|
|||||||
{/* Problem summary */}
|
{/* Problem summary */}
|
||||||
{session.problem_summary && (
|
{session.problem_summary && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
|
||||||
Problem
|
Problem
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-[#e2e5eb]">{session.problem_summary}</p>
|
<p className="text-sm text-foreground">{session.problem_summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Domain */}
|
{/* Domain */}
|
||||||
{session.problem_domain && (
|
{session.problem_domain && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
|
||||||
Domain
|
Domain
|
||||||
</h4>
|
</h4>
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
|
||||||
{session.problem_domain}
|
{session.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,7 +259,7 @@ export function FlowPilotSession({
|
|||||||
|
|
||||||
{/* Confidence */}
|
{/* Confidence */}
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
|
||||||
Confidence
|
Confidence
|
||||||
</h4>
|
</h4>
|
||||||
<ConfidenceIndicator
|
<ConfidenceIndicator
|
||||||
@@ -271,12 +271,12 @@ export function FlowPilotSession({
|
|||||||
{/* Matched flow */}
|
{/* Matched flow */}
|
||||||
{session.matched_flow_id && (
|
{session.matched_flow_id && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">
|
||||||
Matched flow
|
Matched flow
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Network size={14} className="text-[#848b9b]" />
|
<Network size={14} className="text-muted-foreground" />
|
||||||
<span className="text-xs text-[#e2e5eb]">
|
<span className="text-xs text-foreground">
|
||||||
{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
|
{session.match_score ? `${Math.round(session.match_score * 100)}% match` : 'Match found'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -286,12 +286,12 @@ export function FlowPilotSession({
|
|||||||
{/* Steps */}
|
{/* Steps */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Hash size={12} className="text-[#848b9b]" />
|
<Hash size={12} className="text-muted-foreground" />
|
||||||
<span className="text-xs text-[#848b9b]">{session.step_count} steps</span>
|
<span className="text-xs text-muted-foreground">{session.step_count} steps</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Clock size={12} className="text-[#848b9b]" />
|
<Clock size={12} className="text-muted-foreground" />
|
||||||
<span className="text-xs text-[#848b9b]">
|
<span className="text-xs text-muted-foreground">
|
||||||
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
{new Date(session.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -330,12 +330,12 @@ export function FlowPilotSession({
|
|||||||
{/* Paused banner */}
|
{/* Paused banner */}
|
||||||
{session.status === 'paused' && onResume && (
|
{session.status === 'paused' && onResume && (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between border-t border-[#1e2130] bg-[#14161d] px-3 py-3 sm:px-5"
|
className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between border-t border-border bg-card px-3 py-3 sm:px-5"
|
||||||
>
|
>
|
||||||
<span className="text-sm text-[#848b9b]">Session paused</span>
|
<span className="text-sm text-muted-foreground">Session paused</span>
|
||||||
<button
|
<button
|
||||||
onClick={onResume}
|
onClick={onResume}
|
||||||
className="flex items-center gap-2 rounded-lg bg-[#22d3ee] text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
className="flex items-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
<Play size={14} />
|
<Play size={14} />
|
||||||
Resume Session
|
Resume Session
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
className="w-full text-left card-flat p-3 sm:p-4 opacity-70 hover:opacity-90 transition-opacity"
|
className="w-full text-left card-flat p-3 sm:p-4 opacity-70 hover:opacity-90 transition-opacity"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Icon size={16} className="shrink-0 text-[#848b9b]" />
|
<Icon size={16} className="shrink-0 text-muted-foreground" />
|
||||||
<p className="text-sm text-[#e2e5eb] truncate flex-1">{stepText}</p>
|
<p className="text-sm text-foreground truncate flex-1">{stepText}</p>
|
||||||
<ChevronDown size={14} className="shrink-0 text-[#848b9b]" />
|
<ChevronDown size={14} className="shrink-0 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -82,14 +82,14 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
className="mb-2 flex w-full items-center justify-between text-left"
|
className="mb-2 flex w-full items-center justify-between text-left"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Icon size={16} className="text-[#848b9b]" />
|
<Icon size={16} className="text-muted-foreground" />
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
|
||||||
Step {step.step_order + 1}
|
Step {step.step_order + 1}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronUp size={14} className="text-[#848b9b]" />
|
<ChevronUp size={14} className="text-muted-foreground" />
|
||||||
</button>
|
</button>
|
||||||
<MarkdownContent content={stepText} className="text-sm text-[#e2e5eb]" />
|
<MarkdownContent content={stepText} className="text-sm text-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
{/* Context message */}
|
{/* Context message */}
|
||||||
{step.context_message && (
|
{step.context_message && (
|
||||||
<div className="mb-3 rounded-lg bg-primary/5 px-3 py-2 border border-primary/10">
|
<div className="mb-3 rounded-lg bg-primary/5 px-3 py-2 border border-primary/10">
|
||||||
<MarkdownContent content={step.context_message} className="text-xs text-[#848b9b]" />
|
<MarkdownContent content={step.context_message} className="text-xs text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -113,14 +113,14 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
<div className="flex items-start gap-3 mb-4">
|
<div className="flex items-start gap-3 mb-4">
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg',
|
'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg',
|
||||||
isResolutionSuggestion ? 'bg-emerald-500/10 text-emerald-400' : 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
isResolutionSuggestion ? 'bg-emerald-500/10 text-emerald-400' : 'bg-accent-dim text-primary'
|
||||||
)}>
|
)}>
|
||||||
<Icon size={14} />
|
<Icon size={14} />
|
||||||
</span>
|
</span>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<MarkdownContent content={stepText} className="text-sm" />
|
<MarkdownContent content={stepText} className="text-sm" />
|
||||||
{isResolutionSuggestion && typeof content.resolution_summary === 'string' && (
|
{isResolutionSuggestion && typeof content.resolution_summary === 'string' && (
|
||||||
<p className="mt-2 text-sm text-[#848b9b]">
|
<p className="mt-2 text-sm text-muted-foreground">
|
||||||
{content.resolution_summary}
|
{content.resolution_summary}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -141,7 +141,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleResolutionResponse(false)}
|
onClick={() => handleResolutionResponse(false)}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[#14161d]/50 border border-[#1e2130] px-4 py-2.5 text-sm font-medium text-[#e2e5eb] hover:bg-[#14161d] transition-colors"
|
className="flex-1 min-h-[44px] rounded-lg bg-card/50 border border-border px-4 py-2.5 text-sm font-medium text-foreground hover:bg-card transition-colors"
|
||||||
>
|
>
|
||||||
No, keep investigating
|
No, keep investigating
|
||||||
</button>
|
</button>
|
||||||
@@ -180,7 +180,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
window.open('/script-builder?from=flowpilot', '_blank')
|
window.open('/script-builder?from=flowpilot', '_blank')
|
||||||
onRespond({ action_result: { success: true, details: 'Opened Script Builder' } })
|
onRespond({ action_result: { success: true, details: 'Opened Script Builder' } })
|
||||||
}}
|
}}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[#22d3ee] text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
className="flex-1 min-h-[44px] rounded-lg bg-primary text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] transition-all"
|
||||||
>
|
>
|
||||||
Open Script Builder
|
Open Script Builder
|
||||||
</button>
|
</button>
|
||||||
@@ -191,13 +191,13 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
<div className="flex flex-col gap-2 sm:flex-row">
|
<div className="flex flex-col gap-2 sm:flex-row">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleActionComplete(true)}
|
onClick={() => handleActionComplete(true)}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[rgba(34,211,238,0.10)] border border-primary/20 px-4 py-2.5 text-sm font-medium text-[#22d3ee] hover:bg-primary/20 transition-colors"
|
className="flex-1 min-h-[44px] rounded-lg bg-accent-dim border border-primary/20 px-4 py-2.5 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
|
||||||
>
|
>
|
||||||
I've completed this action
|
I've completed this action
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleActionComplete(false)}
|
onClick={() => handleActionComplete(false)}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[#14161d]/50 border border-[#1e2130] px-4 py-2.5 text-sm font-medium text-[#e2e5eb] hover:bg-[#14161d] transition-colors"
|
className="flex-1 min-h-[44px] rounded-lg bg-card/50 border border-border px-4 py-2.5 text-sm font-medium text-foreground hover:bg-card transition-colors"
|
||||||
>
|
>
|
||||||
This didn't work
|
This didn't work
|
||||||
</button>
|
</button>
|
||||||
@@ -208,7 +208,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
{!isResolutionSuggestion && step.allow_skip && (
|
{!isResolutionSuggestion && step.allow_skip && (
|
||||||
<button
|
<button
|
||||||
onClick={handleSkip}
|
onClick={handleSkip}
|
||||||
className="flex items-center gap-1.5 text-xs text-[#5a6170] hover:text-[#848b9b] transition-colors"
|
className="flex items-center gap-1.5 text-xs text-text-muted hover:text-muted-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<SkipForward size={12} />
|
<SkipForward size={12} />
|
||||||
I can't check this right now
|
I can't check this right now
|
||||||
@@ -221,7 +221,7 @@ export function FlowPilotStepCard({ step, isCurrentStep, isProcessing, sessionId
|
|||||||
{isProcessing && (
|
{isProcessing && (
|
||||||
<div className="flex items-center gap-2 pt-2">
|
<div className="flex items-center gap-2 pt-2">
|
||||||
<div className="h-1.5 w-1.5 rounded-full bg-primary animate-pulse" />
|
<div className="h-1.5 w-1.5 rounded-full bg-primary animate-pulse" />
|
||||||
<span className="text-xs text-[#848b9b]">FlowPilot is thinking...</span>
|
<span className="text-xs text-muted-foreground">FlowPilot is thinking...</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ export function InSessionScriptGenerator({
|
|||||||
return (
|
return (
|
||||||
<div className="mt-3 rounded-xl border border-primary/20 bg-primary/5 p-3 sm:p-4 space-y-3">
|
<div className="mt-3 rounded-xl border border-primary/20 bg-primary/5 p-3 sm:p-4 space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Terminal size={14} className="text-[#22d3ee]" />
|
<Terminal size={14} className="text-primary" />
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-primary">
|
||||||
Script Generator
|
Script Generator
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{instructions && (
|
{instructions && (
|
||||||
<p className="text-xs text-[#848b9b]">{instructions}</p>
|
<p className="text-xs text-muted-foreground">{instructions}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Parameter editing */}
|
{/* Parameter editing */}
|
||||||
@@ -80,7 +80,7 @@ export function InSessionScriptGenerator({
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowParams(!showParams)}
|
onClick={() => setShowParams(!showParams)}
|
||||||
className="flex items-center gap-1.5 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{showParams ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
{showParams ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
||||||
Parameters ({Object.keys(params).length})
|
Parameters ({Object.keys(params).length})
|
||||||
@@ -90,13 +90,13 @@ export function InSessionScriptGenerator({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(params).map(([key, value]) => (
|
{Object.entries(params).map(([key, value]) => (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
<label className="block text-xs font-medium text-[#848b9b] mb-1">
|
<label className="block text-xs font-medium text-muted-foreground mb-1">
|
||||||
{key.replace(/_/g, ' ')}
|
{key.replace(/_/g, ' ')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setParams(prev => ({ ...prev, [key]: e.target.value }))}
|
onChange={(e) => setParams(prev => ({ ...prev, [key]: e.target.value }))}
|
||||||
className="w-full rounded-lg 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-none"
|
className="w-full rounded-lg 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-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -106,7 +106,7 @@ export function InSessionScriptGenerator({
|
|||||||
<button
|
<button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
className="w-full min-h-[44px] flex items-center justify-center gap-2 rounded-lg bg-[#22d3ee] text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
className="w-full min-h-[44px] flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
||||||
>
|
>
|
||||||
{isGenerating ? (
|
{isGenerating ? (
|
||||||
<Loader2 size={14} className="animate-spin" />
|
<Loader2 size={14} className="animate-spin" />
|
||||||
@@ -121,12 +121,12 @@ export function InSessionScriptGenerator({
|
|||||||
{/* Generated script display */}
|
{/* Generated script display */}
|
||||||
{generatedScript && (
|
{generatedScript && (
|
||||||
<>
|
<>
|
||||||
<div className="relative rounded-lg bg-[#14161d]/80 border border-[#1e2130] overflow-hidden">
|
<div className="relative rounded-lg bg-card/80 border border-border overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-3 py-1.5 border-b border-[#1e2130]/50">
|
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border/50">
|
||||||
<span className="text-xs text-[#848b9b] font-mono">PowerShell</span>
|
<span className="text-xs text-muted-foreground font-mono">PowerShell</span>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
className="flex items-center gap-1 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{copied ? <Check size={12} className="text-emerald-400" /> : <Copy size={12} />}
|
{copied ? <Check size={12} className="text-emerald-400" /> : <Copy size={12} />}
|
||||||
{copied ? 'Copied' : 'Copy'}
|
{copied ? 'Copied' : 'Copy'}
|
||||||
@@ -141,13 +141,13 @@ export function InSessionScriptGenerator({
|
|||||||
<div className="flex flex-col gap-2 pt-1 sm:flex-row">
|
<div className="flex flex-col gap-2 pt-1 sm:flex-row">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleContinue(true)}
|
onClick={() => handleContinue(true)}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[rgba(34,211,238,0.10)] border border-primary/20 px-4 py-2 text-sm font-medium text-[#22d3ee] hover:bg-primary/20 transition-colors"
|
className="flex-1 min-h-[44px] rounded-lg bg-accent-dim border border-primary/20 px-4 py-2 text-sm font-medium text-primary hover:bg-primary/20 transition-colors"
|
||||||
>
|
>
|
||||||
Script worked — continue
|
Script worked — continue
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleContinue(false)}
|
onClick={() => handleContinue(false)}
|
||||||
className="flex-1 min-h-[44px] rounded-lg bg-[#14161d]/50 border border-[#1e2130] px-4 py-2 text-sm font-medium text-[#e2e5eb] hover:bg-[#14161d] transition-colors"
|
className="flex-1 min-h-[44px] rounded-lg bg-card/50 border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-card transition-colors"
|
||||||
>
|
>
|
||||||
Didn't resolve it
|
Didn't resolve it
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const TYPE_CONFIG = {
|
|||||||
new_flow: { label: 'New Flow', color: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20', icon: Sparkles },
|
new_flow: { label: 'New Flow', color: 'text-emerald-400 bg-emerald-400/10 border-emerald-400/20', icon: Sparkles },
|
||||||
enhancement: { label: 'Enhancement', color: 'text-amber-400 bg-amber-400/10 border-amber-400/20', icon: ArrowUpRight },
|
enhancement: { label: 'Enhancement', color: 'text-amber-400 bg-amber-400/10 border-amber-400/20', icon: ArrowUpRight },
|
||||||
branch_addition: { label: 'Branch', color: 'text-blue-400 bg-blue-400/10 border-blue-400/20', icon: GitBranch },
|
branch_addition: { label: 'Branch', color: 'text-blue-400 bg-blue-400/10 border-blue-400/20', icon: GitBranch },
|
||||||
auto_reinforced: { label: 'Reinforced', color: 'text-[#848b9b] bg-[#14161d] border-[#1e2130]', icon: Sparkles },
|
auto_reinforced: { label: 'Reinforced', color: 'text-muted-foreground bg-card border-border', icon: Sparkles },
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
interface ProposalCardProps {
|
interface ProposalCardProps {
|
||||||
@@ -24,11 +24,11 @@ export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProp
|
|||||||
className={`w-full text-left rounded-xl border p-3 space-y-2 transition-all ${
|
className={`w-full text-left rounded-xl border p-3 space-y-2 transition-all ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-primary/30 bg-primary/5'
|
? 'border-primary/30 bg-primary/5'
|
||||||
: 'border-[rgba(255,255,255,0.06)] bg-[#14161d] hover:border-[rgba(255,255,255,0.12)]'
|
: 'border-[rgba(255,255,255,0.06)] bg-card hover:border-[rgba(255,255,255,0.12)]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb] line-clamp-2">{proposal.title}</p>
|
<p className="text-sm font-semibold text-foreground line-clamp-2">{proposal.title}</p>
|
||||||
<span className={`shrink-0 flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-sans text-xs text-[0.5625rem] uppercase tracking-wider ${typeConfig.color}`}>
|
<span className={`shrink-0 flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-sans text-xs text-[0.5625rem] uppercase tracking-wider ${typeConfig.color}`}>
|
||||||
<TypeIcon size={10} />
|
<TypeIcon size={10} />
|
||||||
{typeConfig.label}
|
{typeConfig.label}
|
||||||
@@ -36,12 +36,12 @@ export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProp
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{proposal.description && (
|
{proposal.description && (
|
||||||
<p className="text-xs text-[#848b9b] line-clamp-2">{proposal.description}</p>
|
<p className="text-xs text-muted-foreground line-clamp-2">{proposal.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-[#848b9b]">
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||||
{proposal.problem_domain && (
|
{proposal.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-1.5 py-0.5 text-[0.5625rem] uppercase tracking-wider text-primary">
|
||||||
{proposal.problem_domain}
|
{proposal.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -53,7 +53,7 @@ export function ProposalCard({ proposal, isSelected, onClick }: ProposalCardProp
|
|||||||
<Clock size={10} />
|
<Clock size={10} />
|
||||||
{new Date(proposal.created_at).toLocaleDateString()}
|
{new Date(proposal.created_at).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#22d3ee]">
|
<span className="text-primary">
|
||||||
{Math.round(proposal.confidence_score * 100)}%
|
{Math.round(proposal.confidence_score * 100)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="border-b px-6 py-4" style={{ borderColor: 'var(--glass-border)' }}>
|
<div className="border-b px-6 py-4" style={{ borderColor: 'var(--glass-border)' }}>
|
||||||
<h2 className="font-heading text-lg font-semibold text-[#e2e5eb]">{proposal.title}</h2>
|
<h2 className="font-heading text-lg font-semibold text-foreground">{proposal.title}</h2>
|
||||||
{proposal.description && (
|
{proposal.description && (
|
||||||
<p className="mt-1 text-sm text-[#848b9b]">{proposal.description}</p>
|
<p className="mt-1 text-sm text-muted-foreground">{proposal.description}</p>
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-[#848b9b]">
|
<div className="mt-3 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
|
||||||
{proposal.problem_domain && (
|
{proposal.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
|
||||||
{proposal.problem_domain}
|
{proposal.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -90,11 +90,11 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
<div className="flex-1 overflow-y-auto p-6 space-y-5">
|
<div className="flex-1 overflow-y-auto p-6 space-y-5">
|
||||||
{/* Source session link */}
|
{/* Source session link */}
|
||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Source Session</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Source Session</h4>
|
||||||
<Link
|
<Link
|
||||||
to={`/pilot/${proposal.source_session_id}`}
|
to={`/pilot/${proposal.source_session_id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex items-center gap-2 text-sm text-[#22d3ee] hover:underline"
|
className="flex items-center gap-2 text-sm text-primary hover:underline"
|
||||||
>
|
>
|
||||||
<ExternalLink size={12} />
|
<ExternalLink size={12} />
|
||||||
View session that generated this proposal
|
View session that generated this proposal
|
||||||
@@ -106,15 +106,15 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
const diff = proposal.proposed_diff as { diff_description?: string; new_nodes?: Array<{ title?: string; question?: string; description?: string }> }
|
const diff = proposal.proposed_diff as { diff_description?: string; new_nodes?: Array<{ title?: string; question?: string; description?: string }> }
|
||||||
return (
|
return (
|
||||||
<div className="card-flat border-l-2 border-l-amber-500 p-4">
|
<div className="card-flat border-l-2 border-l-amber-500 p-4">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Proposed Changes</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Proposed Changes</h4>
|
||||||
{diff.diff_description && (
|
{diff.diff_description && (
|
||||||
<p className="text-sm text-[#e2e5eb]">{diff.diff_description}</p>
|
<p className="text-sm text-foreground">{diff.diff_description}</p>
|
||||||
)}
|
)}
|
||||||
{diff.new_nodes && diff.new_nodes.length > 0 && (
|
{diff.new_nodes && diff.new_nodes.length > 0 && (
|
||||||
<div className="mt-3 space-y-1.5">
|
<div className="mt-3 space-y-1.5">
|
||||||
<p className="text-xs font-medium text-[#848b9b]">New nodes:</p>
|
<p className="text-xs font-medium text-muted-foreground">New nodes:</p>
|
||||||
{diff.new_nodes.map((node, i) => (
|
{diff.new_nodes.map((node, i) => (
|
||||||
<div key={i} className="rounded-lg bg-emerald-500/5 border border-emerald-500/10 px-3 py-2 text-xs text-[#e2e5eb]">
|
<div key={i} className="rounded-lg bg-emerald-500/5 border border-emerald-500/10 px-3 py-2 text-xs text-foreground">
|
||||||
<span className="text-emerald-400">+ </span>
|
<span className="text-emerald-400">+ </span>
|
||||||
{node.title || node.question || node.description || 'New node'}
|
{node.title || node.question || node.description || 'New node'}
|
||||||
</div>
|
</div>
|
||||||
@@ -129,13 +129,13 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFlowData(!showFlowData)}
|
onClick={() => setShowFlowData(!showFlowData)}
|
||||||
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{showFlowData ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
{showFlowData ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
||||||
Flow Data (JSON)
|
Flow Data (JSON)
|
||||||
</button>
|
</button>
|
||||||
{showFlowData && (
|
{showFlowData && (
|
||||||
<pre className="mt-3 max-h-[400px] overflow-auto rounded-lg bg-[#14161d]/80 p-3 text-xs text-[#848b9b] font-mono">
|
<pre className="mt-3 max-h-[400px] overflow-auto rounded-lg bg-card/80 p-3 text-xs text-muted-foreground font-mono">
|
||||||
{JSON.stringify(proposal.proposed_flow_data, null, 2)}
|
{JSON.stringify(proposal.proposed_flow_data, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
@@ -144,7 +144,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
{/* Supporting sessions */}
|
{/* Supporting sessions */}
|
||||||
{proposal.supporting_session_ids.length > 1 && (
|
{proposal.supporting_session_ids.length > 1 && (
|
||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">
|
||||||
Supporting Sessions ({proposal.supporting_session_ids.length})
|
Supporting Sessions ({proposal.supporting_session_ids.length})
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -153,7 +153,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
key={sid}
|
key={sid}
|
||||||
to={`/pilot/${sid}`}
|
to={`/pilot/${sid}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="block text-xs text-[#22d3ee] hover:underline truncate"
|
className="block text-xs text-primary hover:underline truncate"
|
||||||
>
|
>
|
||||||
{sid}
|
{sid}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -165,13 +165,13 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
{/* Review info (for already-reviewed proposals) */}
|
{/* Review info (for already-reviewed proposals) */}
|
||||||
{proposal.reviewed_at && (
|
{proposal.reviewed_at && (
|
||||||
<div className="card-flat p-4">
|
<div className="card-flat p-4">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-2">Review</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-2">Review</h4>
|
||||||
<p className="text-sm text-[#e2e5eb]">
|
<p className="text-sm text-foreground">
|
||||||
<span className="capitalize">{proposal.status}</span> on{' '}
|
<span className="capitalize">{proposal.status}</span> on{' '}
|
||||||
{new Date(proposal.reviewed_at).toLocaleString()}
|
{new Date(proposal.reviewed_at).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
{proposal.reviewer_notes && (
|
{proposal.reviewer_notes && (
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">{proposal.reviewer_notes}</p>
|
<p className="mt-1 text-xs text-muted-foreground">{proposal.reviewer_notes}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -180,14 +180,14 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
{/* Review actions bar */}
|
{/* Review actions bar */}
|
||||||
{canReview && (
|
{canReview && (
|
||||||
<div
|
<div
|
||||||
className="border-t border-[#1e2130] bg-[#14161d] px-5 py-3 space-y-3"
|
className="border-t border-border bg-card px-5 py-3 space-y-3"
|
||||||
>
|
>
|
||||||
{/* Notes input */}
|
{/* Notes input */}
|
||||||
<input
|
<input
|
||||||
value={reviewNotes}
|
value={reviewNotes}
|
||||||
onChange={(e) => setReviewNotes(e.target.value)}
|
onChange={(e) => setReviewNotes(e.target.value)}
|
||||||
placeholder="Reviewer notes (optional)"
|
placeholder="Reviewer notes (optional)"
|
||||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none"
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Action buttons */}
|
{/* Action buttons */}
|
||||||
@@ -202,14 +202,14 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
Approve & Publish
|
Approve & Publish
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-[#848b9b] italic px-2">
|
<span className="text-xs text-muted-foreground italic px-2">
|
||||||
Enhancement proposals require Edit & Publish
|
Enhancement proposals require Edit & Publish
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={handleEditAndPublish}
|
onClick={handleEditAndPublish}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="flex items-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
className="flex items-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
||||||
>
|
>
|
||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
Edit & Publish
|
Edit & Publish
|
||||||
@@ -217,7 +217,7 @@ export function ProposalDetail({ proposal, onReview }: ProposalDetailProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleAction('dismiss')}
|
onClick={() => handleAction('dismiss')}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="flex items-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors ml-auto"
|
className="flex items-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors ml-auto"
|
||||||
>
|
>
|
||||||
<EyeOff size={14} />
|
<EyeOff size={14} />
|
||||||
Dismiss
|
Dismiss
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ export function SessionBriefing({
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat border-l-2 border-l-amber-500 p-5 space-y-4">
|
<div className="card-flat border-l-2 border-l-amber-500 p-5 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-heading text-base font-semibold text-[#e2e5eb]">
|
<h3 className="font-heading text-base font-semibold text-foreground">
|
||||||
Escalation from {originalEngineerName || 'another engineer'}
|
Escalation from {originalEngineerName || 'another engineer'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-[#848b9b]">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
Review the briefing below, then choose how to proceed.
|
Review the briefing below, then choose how to proceed.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,15 +51,15 @@ export function SessionBriefing({
|
|||||||
{/* Problem */}
|
{/* Problem */}
|
||||||
{pkg.problem_summary && (
|
{pkg.problem_summary && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Problem</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Problem</h4>
|
||||||
<p className="text-sm text-[#e2e5eb]">{pkg.problem_summary}</p>
|
<p className="text-sm text-foreground">{pkg.problem_summary}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Escalation reason */}
|
{/* Escalation reason */}
|
||||||
{pkg.escalation_reason && (
|
{pkg.escalation_reason && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Why escalated</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Why escalated</h4>
|
||||||
<p className="text-sm text-amber-400">{pkg.escalation_reason}</p>
|
<p className="text-sm text-amber-400">{pkg.escalation_reason}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -69,7 +69,7 @@ export function SessionBriefing({
|
|||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSteps(!showSteps)}
|
onClick={() => setShowSteps(!showSteps)}
|
||||||
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] hover:text-[#e2e5eb] transition-colors"
|
className="flex items-center gap-1.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
{showSteps ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
{showSteps ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
||||||
Steps taken ({pkg.steps_tried.length})
|
Steps taken ({pkg.steps_tried.length})
|
||||||
@@ -77,10 +77,10 @@ export function SessionBriefing({
|
|||||||
{showSteps && (
|
{showSteps && (
|
||||||
<div className="mt-2 space-y-1.5">
|
<div className="mt-2 space-y-1.5">
|
||||||
{pkg.steps_tried.map((step, i) => (
|
{pkg.steps_tried.map((step, i) => (
|
||||||
<div key={i} className="rounded-lg bg-[#14161d]/50 px-3 py-2 text-xs">
|
<div key={i} className="rounded-lg bg-card/50 px-3 py-2 text-xs">
|
||||||
<p className="text-[#e2e5eb]">{i + 1}. {step.description}</p>
|
<p className="text-foreground">{i + 1}. {step.description}</p>
|
||||||
{step.response && (
|
{step.response && (
|
||||||
<p className="mt-0.5 text-[#22d3ee]">→ {step.response}</p>
|
<p className="mt-0.5 text-primary">→ {step.response}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -92,11 +92,11 @@ export function SessionBriefing({
|
|||||||
{/* Remaining hypotheses */}
|
{/* Remaining hypotheses */}
|
||||||
{pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && (
|
{pkg.remaining_hypotheses && pkg.remaining_hypotheses.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Remaining hypotheses</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Remaining hypotheses</h4>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{pkg.remaining_hypotheses.map((h, i) => (
|
{pkg.remaining_hypotheses.map((h, i) => (
|
||||||
<li key={i} className="text-sm text-[#e2e5eb] flex items-start gap-2">
|
<li key={i} className="text-sm text-foreground flex items-start gap-2">
|
||||||
<span className="text-[#22d3ee] mt-0.5">•</span>
|
<span className="text-primary mt-0.5">•</span>
|
||||||
{h}
|
{h}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -107,10 +107,10 @@ export function SessionBriefing({
|
|||||||
{/* Suggested next steps */}
|
{/* Suggested next steps */}
|
||||||
{pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && (
|
{pkg.suggested_next_steps && pkg.suggested_next_steps.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170] mb-1">Suggested next steps</h4>
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted mb-1">Suggested next steps</h4>
|
||||||
<ul className="space-y-1">
|
<ul className="space-y-1">
|
||||||
{pkg.suggested_next_steps.map((s, i) => (
|
{pkg.suggested_next_steps.map((s, i) => (
|
||||||
<li key={i} className="text-sm text-[#e2e5eb] flex items-start gap-2">
|
<li key={i} className="text-sm text-foreground flex items-start gap-2">
|
||||||
<span className="text-emerald-400 mt-0.5">→</span>
|
<span className="text-emerald-400 mt-0.5">→</span>
|
||||||
{s}
|
{s}
|
||||||
</li>
|
</li>
|
||||||
@@ -125,7 +125,7 @@ export function SessionBriefing({
|
|||||||
<button
|
<button
|
||||||
onClick={onContinue}
|
onClick={onContinue}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-[#22d3ee] text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2.5 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
||||||
>
|
>
|
||||||
<ArrowRight size={14} />
|
<ArrowRight size={14} />
|
||||||
Continue Where They Left Off
|
Continue Where They Left Off
|
||||||
@@ -133,7 +133,7 @@ export function SessionBriefing({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setFreshMode(true)}
|
onClick={() => setFreshMode(true)}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2.5 text-sm font-medium text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2.5 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
||||||
>
|
>
|
||||||
<MessageSquare size={14} />
|
<MessageSquare size={14} />
|
||||||
Start Fresh With Context
|
Start Fresh With Context
|
||||||
@@ -145,7 +145,7 @@ export function SessionBriefing({
|
|||||||
value={freshContext}
|
value={freshContext}
|
||||||
onChange={(e) => setFreshContext(e.target.value)}
|
onChange={(e) => setFreshContext(e.target.value)}
|
||||||
placeholder="What additional information do you have, or what would you like to investigate first?"
|
placeholder="What additional information do you have, or what would you like to investigate first?"
|
||||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-4 py-3 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
className="w-full rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none"
|
||||||
rows={3}
|
rows={3}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@@ -153,14 +153,14 @@ export function SessionBriefing({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setFreshMode(false)}
|
onClick={() => setFreshMode(false)}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
className="rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
className="rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm font-medium text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 transition-colors"
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => freshContext.trim() && onFresh(freshContext.trim())}
|
onClick={() => freshContext.trim() && onFresh(freshContext.trim())}
|
||||||
disabled={!freshContext.trim() || isProcessing}
|
disabled={!freshContext.trim() || isProcessing}
|
||||||
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-[#22d3ee] text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
className="flex-1 flex items-center justify-center gap-2 rounded-lg bg-primary text-white px-4 py-2 text-sm font-semibold hover:brightness-110 active:scale-[0.98] disabled:opacity-40 transition-all"
|
||||||
>
|
>
|
||||||
<ArrowRight size={14} />
|
<ArrowRight size={14} />
|
||||||
Start Diagnosis
|
Start Diagnosis
|
||||||
|
|||||||
@@ -109,25 +109,25 @@ export function SessionDocView({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="card-flat p-3 sm:p-4 lg:p-5">
|
<div className="card-flat p-3 sm:p-4 lg:p-5">
|
||||||
<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-lg bg-[rgba(34,211,238,0.10)] text-[#22d3ee]">
|
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-accent-dim text-primary">
|
||||||
<FileText size={16} />
|
<FileText size={16} />
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="font-heading text-lg font-semibold text-[#e2e5eb]">Session Documentation</h3>
|
<h3 className="font-heading text-lg font-semibold text-foreground">Session Documentation</h3>
|
||||||
<p className="mt-1 text-sm text-[#848b9b]">{documentation.problem_summary}</p>
|
<p className="mt-1 text-sm text-muted-foreground">{documentation.problem_summary}</p>
|
||||||
<div className="mt-2 flex items-center gap-3 flex-wrap">
|
<div className="mt-2 flex items-center gap-3 flex-wrap">
|
||||||
{documentation.problem_domain && (
|
{documentation.problem_domain && (
|
||||||
<span className="font-sans text-xs rounded-md bg-[rgba(34,211,238,0.10)] px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-[#22d3ee]">
|
<span className="font-sans text-xs rounded-md bg-accent-dim px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-primary">
|
||||||
{documentation.problem_domain}
|
{documentation.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{documentation.duration_display && (
|
{documentation.duration_display && (
|
||||||
<span className="flex items-center gap-1 text-xs text-[#848b9b]">
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<Clock size={12} />
|
<Clock size={12} />
|
||||||
{documentation.duration_display}
|
{documentation.duration_display}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-[#848b9b]">
|
<span className="text-xs text-muted-foreground">
|
||||||
{documentation.total_steps} steps
|
{documentation.total_steps} steps
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -144,11 +144,11 @@ export function SessionDocView({
|
|||||||
) : (
|
) : (
|
||||||
<ArrowUpRight size={14} className="text-amber-400" />
|
<ArrowUpRight size={14} className="text-amber-400" />
|
||||||
)}
|
)}
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground">
|
||||||
{documentation.resolution_summary ? 'Resolved' : 'Escalated'}
|
{documentation.resolution_summary ? 'Resolved' : 'Escalated'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#e2e5eb]">
|
<p className="text-sm text-foreground">
|
||||||
{documentation.resolution_summary || documentation.escalation_reason}
|
{documentation.resolution_summary || documentation.escalation_reason}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -156,30 +156,30 @@ export function SessionDocView({
|
|||||||
|
|
||||||
{/* Intake summary */}
|
{/* Intake summary */}
|
||||||
<div className="card-flat p-3 sm:p-4">
|
<div className="card-flat p-3 sm:p-4">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b] mb-2">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground mb-2">
|
||||||
Original intake
|
Original intake
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-sm text-[#e2e5eb] whitespace-pre-wrap">{documentation.intake_summary}</p>
|
<p className="text-sm text-foreground whitespace-pre-wrap">{documentation.intake_summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Diagnostic steps */}
|
{/* Diagnostic steps */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#848b9b] px-1">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-muted-foreground px-1">
|
||||||
Diagnostic trail
|
Diagnostic trail
|
||||||
</h4>
|
</h4>
|
||||||
{documentation.diagnostic_steps.map((step) => (
|
{documentation.diagnostic_steps.map((step) => (
|
||||||
<div key={step.step_number} className="card-flat p-3 sm:p-4">
|
<div key={step.step_number} className="card-flat p-3 sm:p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="font-sans text-xs flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-[#14161d] text-[0.625rem] text-[#848b9b] border border-[#1e2130]">
|
<span className="font-sans text-xs flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-card text-[0.625rem] text-muted-foreground border border-border">
|
||||||
{step.step_number}
|
{step.step_number}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="text-sm text-[#e2e5eb]">{step.description}</p>
|
<p className="text-sm text-foreground">{step.description}</p>
|
||||||
{step.engineer_response && (
|
{step.engineer_response && (
|
||||||
<p className="mt-1 text-xs text-[#22d3ee]">→ {step.engineer_response}</p>
|
<p className="mt-1 text-xs text-primary">→ {step.engineer_response}</p>
|
||||||
)}
|
)}
|
||||||
{step.outcome && (
|
{step.outcome && (
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">Outcome: {step.outcome}</p>
|
<p className="mt-1 text-xs text-muted-foreground">Outcome: {step.outcome}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +190,7 @@ export function SessionDocView({
|
|||||||
{/* Rating */}
|
{/* Rating */}
|
||||||
{onRate && (
|
{onRate && (
|
||||||
<div className="card-flat p-3 sm:p-4 text-center">
|
<div className="card-flat p-3 sm:p-4 text-center">
|
||||||
<p className="text-sm text-[#848b9b] mb-2">How helpful was this session?</p>
|
<p className="text-sm text-muted-foreground mb-2">How helpful was this session?</p>
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-center justify-center gap-1">
|
||||||
{[1, 2, 3, 4, 5].map((star) => (
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
<button
|
<button
|
||||||
@@ -203,7 +203,7 @@ export function SessionDocView({
|
|||||||
className={
|
className={
|
||||||
(currentRating ?? 0) >= star
|
(currentRating ?? 0) >= star
|
||||||
? 'fill-amber-400 text-amber-400'
|
? 'fill-amber-400 text-amber-400'
|
||||||
: 'text-[#848b9b] hover:text-amber-400'
|
: 'text-muted-foreground hover:text-amber-400'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-xl border border-primary/20 bg-primary/5 p-3 space-y-2">
|
<div className="rounded-xl border border-primary/20 bg-primary/5 p-3 space-y-2">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#5a6170]">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-wider text-text-muted">
|
||||||
Linked Ticket
|
Linked Ticket
|
||||||
</h4>
|
</h4>
|
||||||
{ticketUrl && (
|
{ticketUrl && (
|
||||||
@@ -44,7 +44,7 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
|
|||||||
href={ticketUrl}
|
href={ticketUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-[#848b9b] hover:text-[#22d3ee] transition-colors"
|
className="text-muted-foreground hover:text-primary transition-colors"
|
||||||
title="Open in ConnectWise"
|
title="Open in ConnectWise"
|
||||||
>
|
>
|
||||||
<ExternalLink size={12} />
|
<ExternalLink size={12} />
|
||||||
@@ -52,14 +52,14 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm font-semibold text-[#e2e5eb]">
|
<p className="text-sm font-semibold text-foreground">
|
||||||
<span className="text-[#22d3ee]">#{ticketId}</span>
|
<span className="text-primary">#{ticketId}</span>
|
||||||
{ticket?.summary && (
|
{ticket?.summary && (
|
||||||
<span> — {ticket.summary}</span>
|
<span> — {ticket.summary}</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-[#848b9b]">
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
|
||||||
{company?.name && (
|
{company?.name && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<Building2 size={10} />
|
<Building2 size={10} />
|
||||||
@@ -68,33 +68,33 @@ export function SessionTicketCard({ ticketId, ticketData, siteUrl }: SessionTick
|
|||||||
)}
|
)}
|
||||||
{ticket?.priority && (
|
{ticket?.priority && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[#5a6170]">•</span>
|
<span className="text-text-muted">•</span>
|
||||||
<span>{ticket.priority}</span>
|
<span>{ticket.priority}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{ticket?.status && (
|
{ticket?.status && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[#5a6170]">•</span>
|
<span className="text-text-muted">•</span>
|
||||||
<span>{ticket.status}</span>
|
<span>{ticket.status}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{configs && configs.length > 0 && (
|
{configs && configs.length > 0 && (
|
||||||
<div className="border-t border-[#1e2130]/50 pt-2 mt-2">
|
<div className="border-t border-border/50 pt-2 mt-2">
|
||||||
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-wider text-[#5a6170] mb-1">
|
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-wider text-text-muted mb-1">
|
||||||
Devices
|
Devices
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{configs.slice(0, 3).map((cfg, i) => (
|
{configs.slice(0, 3).map((cfg, i) => (
|
||||||
<div key={i} className="flex items-center gap-1.5 text-xs text-[#848b9b]">
|
<div key={i} className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||||
<Cpu size={10} />
|
<Cpu size={10} />
|
||||||
<span>{cfg.device_identifier}</span>
|
<span>{cfg.device_identifier}</span>
|
||||||
{cfg.type && <span className="text-[#5a6170]">({cfg.type})</span>}
|
{cfg.type && <span className="text-text-muted">({cfg.type})</span>}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{configs.length > 3 && (
|
{configs.length > 3 && (
|
||||||
<p className="text-[0.625rem] text-[#5a6170]">
|
<p className="text-[0.625rem] text-text-muted">
|
||||||
+{configs.length - 3} more
|
+{configs.length - 3} more
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5 py-1">
|
<div className="flex items-center gap-1.5 py-1">
|
||||||
<Loader2 size={10} className="animate-spin text-[#848b9b]" />
|
<Loader2 size={10} className="animate-spin text-muted-foreground" />
|
||||||
<span className="text-[0.625rem] text-[#848b9b] font-sans text-xs">Loading similar sessions…</span>
|
<span className="text-[0.625rem] text-muted-foreground font-sans text-xs">Loading similar sessions…</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<h4 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
Similar Past Sessions
|
Similar Past Sessions
|
||||||
</h4>
|
</h4>
|
||||||
{sessions.map((session) => (
|
{sessions.map((session) => (
|
||||||
@@ -57,21 +57,21 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
|
|||||||
className="card-interactive p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
|
className="card-interactive p-3 block hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<p className="text-xs text-[#e2e5eb] line-clamp-2">
|
<p className="text-xs text-foreground line-clamp-2">
|
||||||
{session.problem_summary || 'Untitled session'}
|
{session.problem_summary || 'Untitled session'}
|
||||||
</p>
|
</p>
|
||||||
<span className="text-[0.625rem] font-sans text-xs text-[#22d3ee] shrink-0">
|
<span className="text-[0.625rem] font-sans text-xs text-primary shrink-0">
|
||||||
{Math.round(session.similarity * 100)}%
|
{Math.round(session.similarity * 100)}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{session.resolution_summary && (
|
{session.resolution_summary && (
|
||||||
<p className="text-[0.625rem] text-[#848b9b] mt-1 line-clamp-1">
|
<p className="text-[0.625rem] text-muted-foreground mt-1 line-clamp-1">
|
||||||
✓ {session.resolution_summary}
|
✓ {session.resolution_summary}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-2 mt-1.5">
|
<div className="flex items-center gap-2 mt-1.5">
|
||||||
{session.problem_domain && (
|
{session.problem_domain && (
|
||||||
<span className="text-[0.5rem] font-sans text-xs uppercase tracking-wider text-[#848b9b]/70">
|
<span className="text-[0.5rem] font-sans text-xs uppercase tracking-wider text-muted-foreground/70">
|
||||||
{session.problem_domain}
|
{session.problem_domain}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -82,7 +82,7 @@ export function SimilarSessions({ sessionId }: SimilarSessionsProps) {
|
|||||||
? 'text-emerald-400'
|
? 'text-emerald-400'
|
||||||
: session.status === 'escalated'
|
: session.status === 'escalated'
|
||||||
? 'text-amber-400'
|
? 'text-amber-400'
|
||||||
: 'text-[#848b9b]'
|
: 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{session.status}
|
{session.status}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||||
{node.node_type}
|
{node.node_type}
|
||||||
</span>
|
</span>
|
||||||
<span className={cn('font-sans text-xs text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
|
<span className={cn('font-sans text-xs text-[0.625rem]', confidenceTextColor(node.confidence_score))}>
|
||||||
@@ -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-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-primary/30 focus:outline-hidden"
|
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"
|
||||||
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-[#22d3ee] text-white hover:brightness-110"
|
className="px-3 py-1.5 text-xs font-medium rounded-md bg-primary 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-[#e2e5eb]"
|
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"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-[#e2e5eb]">{stepContent || question}</p>
|
<p className="text-sm text-foreground">{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-[#848b9b] hover:text-emerald-400 hover:bg-emerald-400/10 transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground 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-[#848b9b] hover:text-amber-400 hover:bg-amber-400/10 transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground 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-[#848b9b] hover:text-blue-400 hover:bg-blue-400/10 transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground 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-[#848b9b] hover:text-[#22d3ee] hover:bg-[rgba(34,211,238,0.10)] transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-accent-dim 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-[#848b9b] hover:text-[#22d3ee] hover:bg-[rgba(34,211,238,0.10)] transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground hover:text-primary hover:bg-accent-dim 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-[#848b9b] hover:text-rose-500 hover:bg-rose-500/10 transition-colors"
|
className="p-1.5 rounded-md text-muted-foreground hover:text-rose-500 hover:bg-rose-500/10 transition-colors"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 size={14} />
|
<Trash2 size={14} />
|
||||||
@@ -173,16 +173,16 @@ 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-[#848b9b] hover:text-[#e2e5eb]"
|
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
{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-[#1e2130]">
|
<div className="mt-2 space-y-1 pl-3 border-l border-border">
|
||||||
{options.map((opt, i) => (
|
{options.map((opt, i) => (
|
||||||
<p key={i} className="text-xs text-[#848b9b]">
|
<p key={i} className="text-xs text-muted-foreground">
|
||||||
{opt.label} {opt.next_node_id && <span className="text-[#5a6170]">→ {opt.next_node_id}</span>}
|
{opt.label} {opt.next_node_id && <span className="text-text-muted">→ {opt.next_node_id}</span>}
|
||||||
</p>
|
</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -192,7 +192,7 @@ export function NodeCard({ node, onEdit, onHighlight }: NodeCardProps) {
|
|||||||
|
|
||||||
{/* Source excerpt */}
|
{/* Source excerpt */}
|
||||||
{node.source_excerpt && (
|
{node.source_excerpt && (
|
||||||
<p className="mt-2 text-xs text-[#5a6170] italic truncate" title={node.source_excerpt}>
|
<p className="mt-2 text-xs text-text-muted italic truncate" title={node.source_excerpt}>
|
||||||
Source: {node.source_excerpt}
|
Source: {node.source_excerpt}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -32,21 +32,21 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="card-flat 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-[#e2e5eb]">{flowTitle}</h2>
|
<h2 className="text-lg font-heading font-semibold text-foreground">{flowTitle}</h2>
|
||||||
{flowDescription && (
|
{flowDescription && (
|
||||||
<p className="text-sm text-[#848b9b] mt-0.5">{flowDescription}</p>
|
<p className="text-sm text-muted-foreground 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-[#848b9b]" />
|
<BarChart3 size={14} className="text-muted-foreground" />
|
||||||
<span className="text-[#848b9b]">
|
<span className="text-muted-foreground">
|
||||||
Avg confidence: <span className="text-[#e2e5eb] font-medium">{Math.round(avgConfidence * 100)}%</span>
|
Avg confidence: <span className="text-foreground 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-[#848b9b]">
|
<span className="text-muted-foreground">
|
||||||
{approvedCount}/{nodes.length} approved
|
{approvedCount}/{nodes.length} approved
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,8 +90,8 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
{/* Nodes panel */}
|
{/* Nodes panel */}
|
||||||
<div className="flex flex-col card-flat 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-[#e2e5eb]">Generated Flow</span>
|
<span className="text-sm font-medium text-foreground">Generated Flow</span>
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] ml-auto">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground 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-[#848b9b] text-center py-8">
|
<p className="text-sm text-muted-foreground text-center py-8">
|
||||||
No nodes generated. Try converting again.
|
No nodes generated. Try converting again.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -120,8 +120,8 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-2.5 rounded-lg 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-[#848b9b]',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-muted-foreground',
|
||||||
'hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]',
|
'hover:text-foreground hover:border-[rgba(255,255,255,0.12)]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -132,7 +132,7 @@ export function ReviewScreen({ kbImport, onEditNode, onApproveAll, onCommit, onD
|
|||||||
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-lg 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-[#22d3ee] text-white',
|
'bg-primary text-white',
|
||||||
'hover:brightness-110 active:scale-[0.98]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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-[#e2e5eb] rounded px-0.5">{highlightExcerpt}</mark>
|
<mark className="bg-primary/20 text-foreground rounded px-0.5">{highlightExcerpt}</mark>
|
||||||
<span>{sourceText.slice(idx + highlightExcerpt.length)}</span>
|
<span>{sourceText.slice(idx + highlightExcerpt.length)}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -26,14 +26,14 @@ export function SourcePanel({ sourceText, sourceFormat, highlightExcerpt }: Sour
|
|||||||
return (
|
return (
|
||||||
<div className="card-flat 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-[#848b9b]" />
|
<FileText size={16} className="text-muted-foreground" />
|
||||||
<span className="text-sm font-medium text-[#e2e5eb]">Source Document</span>
|
<span className="text-sm font-medium text-foreground">Source Document</span>
|
||||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] ml-auto">
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground 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-[#848b9b] whitespace-pre-wrap font-sans leading-relaxed">
|
<pre className="text-sm text-muted-foreground 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-[#e2e5eb]">
|
<h2 className="text-xl font-heading font-semibold text-foreground">
|
||||||
Flow Created Successfully
|
Flow Created Successfully
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-[#848b9b] mt-2">
|
<p className="text-sm text-muted-foreground 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>
|
||||||
@@ -32,7 +32,7 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
|
|||||||
onClick={onViewFlow}
|
onClick={onViewFlow}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-6 py-2.5 rounded-lg 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-[#22d3ee] text-white',
|
'bg-primary text-white',
|
||||||
'hover:brightness-110 active:scale-[0.98]'
|
'hover:brightness-110 active:scale-[0.98]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -43,7 +43,7 @@ export function SuccessScreen({ result, onViewFlow, onConvertAnother }: SuccessS
|
|||||||
onClick={onConvertAnother}
|
onClick={onConvertAnother}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-6 py-2.5 rounded-lg 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-[#e2e5eb]',
|
'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)]'
|
'hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -84,14 +84,14 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
{quota && (
|
{quota && (
|
||||||
<div className="card-flat 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-[#22d3ee]" />
|
<Sparkles size={18} className="text-primary" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb]">
|
<p className="text-sm font-medium text-foreground">
|
||||||
{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-[#848b9b]">
|
<p className="text-xs text-muted-foreground">
|
||||||
{quota.plan.charAt(0).toUpperCase() + quota.plan.slice(1)} plan
|
{quota.plan.charAt(0).toUpperCase() + quota.plan.slice(1)} plan
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,8 +112,8 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-4 py-2 rounded-lg 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-[rgba(34,211,238,0.10)] text-[#e2e5eb] border border-primary/30'
|
? 'bg-accent-dim text-foreground border border-primary/30'
|
||||||
: '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)]'
|
: '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)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ClipboardPaste size={16} />
|
<ClipboardPaste size={16} />
|
||||||
@@ -125,8 +125,8 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 px-4 py-2 rounded-lg 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-[rgba(34,211,238,0.10)] text-[#e2e5eb] border border-primary/30'
|
? 'bg-accent-dim text-foreground border border-primary/30'
|
||||||
: '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)]'
|
: '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)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FileUp size={16} />
|
<FileUp size={16} />
|
||||||
@@ -140,7 +140,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
{mode === 'paste' ? (
|
{mode === 'paste' ? (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="kb-title" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-1.5">
|
<label htmlFor="kb-title" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-1.5">
|
<label htmlFor="kb-content" className="block font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground 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-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{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-[#22d3ee]" />
|
<FileText size={32} className="text-primary" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm font-medium text-[#e2e5eb]">{file.name}</p>
|
<p className="text-sm font-medium text-foreground">{file.name}</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">
|
<p className="text-xs text-muted-foreground 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-[#22d3ee] hover:underline"
|
className="mt-2 text-xs text-primary hover:underline"
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
@@ -199,12 +199,12 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Upload size={32} className="text-[#848b9b]" />
|
<Upload size={32} className="text-muted-foreground" />
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm text-[#e2e5eb]">
|
<p className="text-sm text-foreground">
|
||||||
Drop a file here or <span className="text-[#22d3ee]">browse</span>
|
Drop a file here or <span className="text-primary">browse</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">
|
<p className="text-xs text-muted-foreground 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-2">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground 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">
|
||||||
@@ -238,8 +238,8 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
: 'hover:border-[rgba(255,255,255,0.12)]'
|
: 'hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb]">{t.label}</p>
|
<p className="text-sm font-medium text-foreground">{t.label}</p>
|
||||||
<p className="text-xs text-[#848b9b] mt-1">{t.description}</p>
|
<p className="text-xs text-muted-foreground mt-1">{t.description}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -251,7 +251,7 @@ export function UploadScreen({ quota, onSubmitText, onSubmitFile, loading }: Upl
|
|||||||
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-lg 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-[#22d3ee] text-white',
|
'bg-primary text-white',
|
||||||
'hover:brightness-110 active:scale-[0.98]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function AppLayout() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setMobileMenuOpen(true)}
|
onClick={() => setMobileMenuOpen(true)}
|
||||||
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb] transition-colors md:hidden"
|
className="fixed left-4 top-3.5 z-50 rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors md:hidden"
|
||||||
aria-label="Open menu"
|
aria-label="Open menu"
|
||||||
>
|
>
|
||||||
<Menu size={20} />
|
<Menu size={20} />
|
||||||
@@ -101,12 +101,12 @@ export function AppLayout() {
|
|||||||
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid #1e2130' }}>
|
<div className="flex h-14 items-center justify-between px-4" style={{ borderBottom: '1px solid #1e2130' }}>
|
||||||
<Link to="/" className="flex items-center gap-2.5">
|
<Link to="/" className="flex items-center gap-2.5">
|
||||||
<BrandLogo size="sm" />
|
<BrandLogo size="sm" />
|
||||||
<span className="text-sm font-heading font-bold text-[#f0f2f5]">ResolutionFlow</span>
|
<span className="text-sm font-heading font-bold text-text-heading">ResolutionFlow</span>
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb]"
|
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground"
|
||||||
aria-label="Close menu"
|
aria-label="Close menu"
|
||||||
>
|
>
|
||||||
<X size={18} />
|
<X size={18} />
|
||||||
@@ -116,9 +116,9 @@ export function AppLayout() {
|
|||||||
<div className="flex flex-col p-3">
|
<div className="flex flex-col p-3">
|
||||||
{/* User info */}
|
{/* User info */}
|
||||||
<div className="mb-3 pb-3 px-3" style={{ borderBottom: '1px solid #1e2130' }}>
|
<div className="mb-3 pb-3 px-3" style={{ borderBottom: '1px solid #1e2130' }}>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb]">{user?.name || user?.email}</p>
|
<p className="text-sm font-medium text-foreground">{user?.name || user?.email}</p>
|
||||||
{effectiveRole && effectiveRole !== 'engineer' && (
|
{effectiveRole && effectiveRole !== 'engineer' && (
|
||||||
<span className="mt-1 inline-flex items-center gap-1 text-xs text-[#848b9b]">
|
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<Shield size={10} />
|
<Shield size={10} />
|
||||||
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
|
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
|
||||||
</span>
|
</span>
|
||||||
@@ -139,8 +139,8 @@ export function AppLayout() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
? 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon size={18} />
|
<Icon size={18} />
|
||||||
@@ -155,7 +155,7 @@ export function AppLayout() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb] transition-colors"
|
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-input hover:text-foreground transition-colors"
|
||||||
>
|
>
|
||||||
<LogOut size={18} />
|
<LogOut size={18} />
|
||||||
Logout
|
Logout
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
|
|||||||
title={label}
|
title={label}
|
||||||
>
|
>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-[#22d3ee]" />
|
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
|
||||||
)}
|
)}
|
||||||
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
|
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
|
||||||
{badge !== undefined && badge !== 0 && badge !== 'dot' && (
|
{badge !== undefined && badge !== 0 && badge !== 'dot' && (
|
||||||
@@ -76,7 +76,7 @@ export function NavItem({ href, icon: Icon, label, badge, iconColor, matchPaths,
|
|||||||
>
|
>
|
||||||
{/* Active indicator bar */}
|
{/* Active indicator bar */}
|
||||||
{isActive && !isParentDimmed && (
|
{isActive && !isParentDimmed && (
|
||||||
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-[#22d3ee]" />
|
<div className="absolute left-0 top-1/2 h-6 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
|
<Icon size={18} className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')} style={iconColor ? { color: iconColor } : undefined} />
|
||||||
|
|||||||
@@ -254,15 +254,15 @@ export function Sidebar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'group relative flex flex-col items-center justify-center rounded-lg px-1 py-2 transition-all duration-150',
|
'group relative flex flex-col items-center justify-center rounded-lg px-1 py-2 transition-all duration-150',
|
||||||
active
|
active
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#67e8f9]'
|
? 'bg-accent-dim text-accent-text'
|
||||||
: 'text-[#6b7280] hover:text-[#848b9b]'
|
: 'text-text-rail-label hover:text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
title={item.label}
|
title={item.label}
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<span className="relative">
|
||||||
<Icon size={20} className={active ? 'opacity-100' : 'opacity-60 group-hover:opacity-85'} />
|
<Icon size={20} className={active ? 'opacity-100' : 'opacity-60 group-hover:opacity-85'} />
|
||||||
{item.badge !== undefined && item.badge > 0 && (
|
{item.badge !== undefined && item.badge > 0 && (
|
||||||
<span className="absolute -right-1.5 -top-1.5 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-[#22d3ee] px-1 text-[0.5rem] font-bold text-[#0c0d10]">
|
<span className="absolute -right-1.5 -top-1.5 flex h-4 min-w-[16px] items-center justify-center rounded-full bg-primary px-1 text-[0.5rem] font-bold text-[#0c0d10]">
|
||||||
{item.badge > 99 ? '99+' : item.badge}
|
{item.badge > 99 ? '99+' : item.badge}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -293,9 +293,9 @@ export function Sidebar() {
|
|||||||
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-150',
|
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-150',
|
||||||
active
|
active
|
||||||
? isParentDimmed
|
? isParentDimmed
|
||||||
? 'bg-[rgba(34,211,238,0.05)] text-[#e2e5eb]/70'
|
? 'bg-[rgba(34,211,238,0.05)] text-foreground/70'
|
||||||
: 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
: 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{active && !isParentDimmed && (
|
{active && !isParentDimmed && (
|
||||||
@@ -307,7 +307,7 @@ export function Sidebar() {
|
|||||||
<Icon size={18} className={cn('shrink-0', active ? 'opacity-100' : 'opacity-70')} />
|
<Icon size={18} className={cn('shrink-0', active ? 'opacity-100' : 'opacity-70')} />
|
||||||
<span className="truncate">{item.label}</span>
|
<span className="truncate">{item.label}</span>
|
||||||
{item.badge !== undefined && item.badge > 0 && (
|
{item.badge !== undefined && item.badge > 0 && (
|
||||||
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-[#4f5666]"
|
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-text-muted"
|
||||||
style={{ background: '#14161d', border: '1px solid #1e2130' }}>
|
style={{ background: '#14161d', border: '1px solid #1e2130' }}>
|
||||||
{item.badge}
|
{item.badge}
|
||||||
</span>
|
</span>
|
||||||
@@ -331,13 +331,13 @@ export function Sidebar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-lg pl-9 pr-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
'flex items-center gap-2 rounded-lg pl-9 pr-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
|
||||||
childActive
|
childActive
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
? 'bg-accent-dim text-foreground'
|
||||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="truncate">{child.label}</span>
|
<span className="truncate">{child.label}</span>
|
||||||
{child.count !== undefined && (
|
{child.count !== undefined && (
|
||||||
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-[#4f5666]"
|
<span className="ml-auto shrink-0 rounded-full px-2 text-[0.6875rem] font-mono text-text-muted"
|
||||||
style={{ background: '#14161d', border: '1px solid #1e2130' }}>
|
style={{ background: '#14161d', border: '1px solid #1e2130' }}>
|
||||||
{child.count}
|
{child.count}
|
||||||
</span>
|
</span>
|
||||||
@@ -373,7 +373,7 @@ export function Sidebar() {
|
|||||||
{sections.map((section, si) => (
|
{sections.map((section, si) => (
|
||||||
<div key={section.title}>
|
<div key={section.title}>
|
||||||
{si > 0 && (
|
{si > 0 && (
|
||||||
<div className="font-mono text-[0.5625rem] uppercase tracking-[0.12em] text-[#4f5666] px-3 pt-3 pb-1">
|
<div className="font-mono text-[0.5625rem] uppercase tracking-[0.12em] text-text-muted px-3 pt-3 pb-1">
|
||||||
{section.title}
|
{section.title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -390,7 +390,7 @@ export function Sidebar() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleSidebarPinned}
|
onClick={toggleSidebarPinned}
|
||||||
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb] transition-colors"
|
className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium text-muted-foreground hover:bg-input hover:text-foreground transition-colors"
|
||||||
title="Unpin sidebar"
|
title="Unpin sidebar"
|
||||||
>
|
>
|
||||||
<PinOff size={18} className="shrink-0" />
|
<PinOff size={18} className="shrink-0" />
|
||||||
@@ -427,7 +427,7 @@ export function Sidebar() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleSidebarPinned}
|
onClick={toggleSidebarPinned}
|
||||||
className="flex flex-col items-center justify-center rounded-lg px-1 py-2 text-[#6b7280] hover:text-[#848b9b] transition-colors"
|
className="flex flex-col items-center justify-center rounded-lg px-1 py-2 text-text-rail-label hover:text-muted-foreground transition-colors"
|
||||||
title="Pin sidebar"
|
title="Pin sidebar"
|
||||||
>
|
>
|
||||||
<Pin size={18} className="opacity-60 hover:opacity-85" />
|
<Pin size={18} className="opacity-60 hover:opacity-85" />
|
||||||
@@ -455,7 +455,7 @@ export function Sidebar() {
|
|||||||
>
|
>
|
||||||
{/* Drawer header */}
|
{/* Drawer header */}
|
||||||
<div className="px-3 mb-3">
|
<div className="px-3 mb-3">
|
||||||
<h3 className="text-[0.6875rem] font-mono uppercase tracking-[0.12em] text-[#4f5666]">
|
<h3 className="text-[0.6875rem] font-mono uppercase tracking-[0.12em] text-text-muted">
|
||||||
{activeFlyoutGroup.label}
|
{activeFlyoutGroup.label}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -469,13 +469,13 @@ export function Sidebar() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center justify-between rounded-md px-3 py-2 text-[0.8125rem] transition-colors',
|
'flex items-center justify-between rounded-md px-3 py-2 text-[0.8125rem] transition-colors',
|
||||||
isChildActive(child)
|
isChildActive(child)
|
||||||
? 'bg-[rgba(34,211,238,0.10)] text-[#67e8f9]'
|
? 'bg-accent-dim text-accent-text'
|
||||||
: 'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{child.label}</span>
|
<span>{child.label}</span>
|
||||||
{child.count !== undefined && (
|
{child.count !== undefined && (
|
||||||
<span className="text-[0.6875rem] font-mono text-[#4f5666]">{child.count}</span>
|
<span className="text-[0.6875rem] font-mono text-text-muted">{child.count}</span>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
@@ -484,7 +484,7 @@ export function Sidebar() {
|
|||||||
|
|
||||||
{/* Resize handle */}
|
{/* Resize handle */}
|
||||||
<div
|
<div
|
||||||
className="w-1 cursor-col-resize hover:bg-[#22d3ee]/20 active:bg-[#22d3ee]/30 transition-colors shrink-0"
|
className="w-1 cursor-col-resize hover:bg-primary/20 active:bg-primary/30 transition-colors shrink-0"
|
||||||
onPointerDown={handleResizeStart}
|
onPointerDown={handleResizeStart}
|
||||||
title="Drag to resize"
|
title="Drag to resize"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function TopBar() {
|
|||||||
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
|
className="flex items-center gap-2.5 pr-4 transition-all duration-200"
|
||||||
>
|
>
|
||||||
<BrandLogo size="sm" />
|
<BrandLogo size="sm" />
|
||||||
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap text-[#f0f2f5]">
|
<span className="text-sm font-heading font-bold tracking-tight whitespace-nowrap text-text-heading">
|
||||||
ResolutionFlow
|
ResolutionFlow
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -81,9 +81,9 @@ export function TopBar() {
|
|||||||
className="hidden sm:relative sm:block w-full text-left"
|
className="hidden sm:relative sm:block w-full text-left"
|
||||||
style={{ maxWidth: '480px' }}
|
style={{ maxWidth: '480px' }}
|
||||||
>
|
>
|
||||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-[#848b9b]" />
|
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||||
<div
|
<div
|
||||||
className="w-full rounded-md py-2 pl-9 pr-14 text-[0.8125rem] text-[#848b9b] cursor-pointer transition-colors"
|
className="w-full rounded-md py-2 pl-9 pr-14 text-[0.8125rem] text-muted-foreground cursor-pointer transition-colors"
|
||||||
style={{
|
style={{
|
||||||
background: '#14161d',
|
background: '#14161d',
|
||||||
border: '1px solid #1e2130',
|
border: '1px solid #1e2130',
|
||||||
@@ -94,7 +94,7 @@ export function TopBar() {
|
|||||||
Search flows, sessions, tags...
|
Search flows, sessions, tags...
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 font-mono text-[0.625rem] text-[#4f5666]"
|
className="absolute right-3 top-1/2 -translate-y-1/2 rounded px-1.5 py-0.5 font-mono text-[0.625rem] text-text-muted"
|
||||||
style={{ background: '#0c0d10', border: '1px solid #1e2130' }}
|
style={{ background: '#0c0d10', border: '1px solid #1e2130' }}
|
||||||
>
|
>
|
||||||
{navigator.platform?.toLowerCase().includes('mac') ? '\u2318K' : 'Ctrl+K'}
|
{navigator.platform?.toLowerCase().includes('mac') ? '\u2318K' : 'Ctrl+K'}
|
||||||
@@ -102,7 +102,7 @@ export function TopBar() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCommandPaletteOpen(true)}
|
onClick={() => setCommandPaletteOpen(true)}
|
||||||
className="sm:hidden rounded-lg p-2 text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
className="sm:hidden rounded-lg p-2 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
title="Search"
|
title="Search"
|
||||||
>
|
>
|
||||||
<Search size={18} />
|
<Search size={18} />
|
||||||
@@ -115,14 +115,14 @@ export function TopBar() {
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setQuickLaunchOpen(true)}
|
onClick={() => setQuickLaunchOpen(true)}
|
||||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb] transition-colors"
|
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
|
||||||
title="Quick Launch"
|
title="Quick Launch"
|
||||||
>
|
>
|
||||||
<Zap size={18} />
|
<Zap size={18} />
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
to="/guides"
|
to="/guides"
|
||||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb] transition-colors"
|
className="rounded-lg p-2 text-muted-foreground hover:bg-card hover:text-foreground transition-colors"
|
||||||
title="User Guides"
|
title="User Guides"
|
||||||
>
|
>
|
||||||
<HelpCircle size={18} />
|
<HelpCircle size={18} />
|
||||||
@@ -146,9 +146,9 @@ export function TopBar() {
|
|||||||
style={{ background: '#14161d', border: '1px solid #1e2130' }}
|
style={{ background: '#14161d', border: '1px solid #1e2130' }}
|
||||||
>
|
>
|
||||||
<div className="px-3 py-2.5 mb-1" style={{ borderBottom: '1px solid #1e2130' }}>
|
<div className="px-3 py-2.5 mb-1" style={{ borderBottom: '1px solid #1e2130' }}>
|
||||||
<p className="text-sm font-medium text-[#e2e5eb] truncate">{user?.name || user?.email}</p>
|
<p className="text-sm font-medium text-foreground truncate">{user?.name || user?.email}</p>
|
||||||
{effectiveRole && effectiveRole !== 'engineer' && (
|
{effectiveRole && effectiveRole !== 'engineer' && (
|
||||||
<span className="mt-1 inline-flex items-center gap-1 text-xs text-[#848b9b]">
|
<span className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
|
||||||
<Shield size={10} />
|
<Shield size={10} />
|
||||||
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
|
{effectiveRole === 'super_admin' ? 'Super Admin' : effectiveRole === 'owner' ? 'Owner' : 'Viewer'}
|
||||||
</span>
|
</span>
|
||||||
@@ -157,7 +157,7 @@ export function TopBar() {
|
|||||||
<Link
|
<Link
|
||||||
to="/account"
|
to="/account"
|
||||||
onClick={() => setUserMenuOpen(false)}
|
onClick={() => setUserMenuOpen(false)}
|
||||||
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]"
|
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-input hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Settings size={14} />
|
<Settings size={14} />
|
||||||
Account
|
Account
|
||||||
@@ -166,7 +166,7 @@ export function TopBar() {
|
|||||||
<Link
|
<Link
|
||||||
to="/admin"
|
to="/admin"
|
||||||
onClick={() => setUserMenuOpen(false)}
|
onClick={() => setUserMenuOpen(false)}
|
||||||
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]"
|
className="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-input hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Shield size={14} />
|
<Shield size={14} />
|
||||||
Admin Panel
|
Admin Panel
|
||||||
@@ -177,7 +177,7 @@ export function TopBar() {
|
|||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm',
|
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm',
|
||||||
'text-[#848b9b] hover:bg-[#191c25] hover:text-[#e2e5eb]'
|
'text-muted-foreground hover:bg-input hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<LogOut size={14} />
|
<LogOut size={14} />
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
|
|||||||
setIsOpen(!isOpen)
|
setIsOpen(!isOpen)
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130]',
|
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
|
||||||
'bg-[#14161d] py-1 shadow-lg'
|
'bg-card py-1 shadow-lg'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="px-3 py-2 text-sm text-[#848b9b]">Loading...</div>
|
<div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
|
||||||
) : folders.length === 0 ? (
|
) : folders.length === 0 ? (
|
||||||
<div className="px-3 py-2 text-sm text-[#848b9b]">No folders yet</div>
|
<div className="px-3 py-2 text-sm text-muted-foreground">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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#e2e5eb]" />
|
<Check className="h-4 w-4 text-foreground" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="border-t border-[#1e2130] my-1" />
|
<div className="border-t border-border 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#1e2130] bg-[#14161d] shadow-xl"
|
className="w-full max-w-sm rounded-xl border border-border bg-card shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
<div className="flex items-center justify-between border-b border-border 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-[#848b9b]" />
|
<Download className="h-4 w-4 text-muted-foreground" />
|
||||||
<h2 className="text-sm font-semibold text-[#e2e5eb]">Export Flow</h2>
|
<h2 className="text-sm font-semibold text-foreground">Export Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">
|
<p className="text-sm text-muted-foreground">
|
||||||
Export <span className="font-medium text-[#e2e5eb]">{treeName}</span> as a <code className="text-xs font-sans text-xs">.rfflow</code> file (JSON format).
|
Export <span className="font-medium text-foreground">{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-[#1e2130] px-5 py-3">
|
<div className="flex justify-end gap-2 border-t border-border px-5 py-3">
|
||||||
<Button variant="secondary" onClick={onClose}>
|
<Button variant="secondary" onClick={onClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -178,12 +178,12 @@ export function FolderEditModal({
|
|||||||
<div className="absolute inset-0 bg-black/80" 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-[#14161d] border border-[#1e2130] rounded-2xl p-6 shadow-lg">
|
<div className="relative z-10 w-full max-w-md bg-card border border-border 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-[#e2e5eb]">
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
{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-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]">
|
<button onClick={onClose} className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground">
|
||||||
<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-[#e2e5eb]">
|
<label htmlFor="folder-name" className="block text-sm font-medium text-foreground">
|
||||||
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-[#14161d] text-[#e2e5eb] placeholder:text-[#848b9b]',
|
'bg-card text-foreground placeholder:text-muted-foreground',
|
||||||
'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-[#1e2130]'
|
'border-border'
|
||||||
)}
|
)}
|
||||||
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-[#e2e5eb]">
|
<label htmlFor="folder-parent" className="block text-sm font-medium text-foreground">
|
||||||
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-[#14161d] text-[#e2e5eb]',
|
'bg-card text-foreground',
|
||||||
'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-[#1e2130]'
|
'border-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
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-[#e2e5eb]">Color</label>
|
<label className="block text-sm font-medium text-foreground">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-[#e2e5eb] font-medium'
|
selectedFolderId === folder.id && 'bg-accent text-foreground 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-[#848b9b] group-hover:hidden">{folder.tree_count}</span>
|
<span className="text-xs text-muted-foreground 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-[#1e2130]',
|
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
|
||||||
'bg-[#14161d] py-1 shadow-lg'
|
'bg-card 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<FolderPlus className="h-3 w-3" />
|
<FolderPlus className="h-3 w-3" />
|
||||||
Add Subfolder
|
Add Subfolder
|
||||||
@@ -368,7 +368,7 @@ export function FolderSidebar({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'w-56 shrink-0 border-r border-[#1e2130] bg-transparent',
|
'w-56 shrink-0 border-r border-border 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-[#e2e5eb]">Folders</span>
|
<span className="text-sm font-medium text-foreground">Folders</span>
|
||||||
<button
|
<button
|
||||||
onClick={onMobileClose}
|
onClick={onMobileClose}
|
||||||
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent"
|
className="rounded-md p-1.5 text-muted-foreground 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-[#e2e5eb]"
|
className="flex w-full items-center gap-2 text-sm font-medium text-foreground"
|
||||||
>
|
>
|
||||||
{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-[#e2e5eb] font-medium'
|
selectedFolderId === null && 'bg-accent text-foreground 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-[#848b9b]">Loading...</div>
|
<div className="px-2 py-1.5 text-sm text-muted-foreground">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-[#848b9b] transition-colors hover:bg-accent hover:text-[#e2e5eb]'
|
'text-muted-foreground transition-colors hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<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-[#1e2130]',
|
'fixed z-50 w-44 rounded-md border border-border',
|
||||||
'bg-[#14161d] py-1 shadow-lg'
|
'bg-card 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#1e2130] bg-[#14161d] shadow-xl"
|
className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
<div className="flex items-center justify-between border-b border-border 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-[#848b9b]" />
|
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||||
<h2 className="text-sm font-semibold text-[#e2e5eb]">Fork Flow</h2>
|
<h2 className="text-sm font-semibold text-foreground">Fork Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#848b9b]">
|
<label htmlFor="fork-name" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
||||||
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-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground 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-[#848b9b]">
|
<label htmlFor="fork-reason" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
||||||
Reason for Forking{' '}
|
Reason for Forking{' '}
|
||||||
<span className="text-[#848b9b]/60">(optional)</span>
|
<span className="text-muted-foreground/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-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground 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-[#1e2130] bg-[#14161d] shadow-xl"
|
className="w-full max-w-md rounded-xl border border-border bg-card shadow-xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
<div className="flex items-center justify-between border-b border-border 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-[#848b9b]" />
|
<FileUp className="h-4 w-4 text-muted-foreground" />
|
||||||
<h2 className="text-sm font-semibold text-[#e2e5eb]">Import Flow</h2>
|
<h2 className="text-sm font-semibold text-foreground">Import Flow</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
className="rounded-md p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#1e2130] hover:border-[#2a2f3d]'
|
: 'border-border hover:border-border-hover'
|
||||||
)}
|
)}
|
||||||
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-[#848b9b]" />
|
<FileUp className="mb-2 h-8 w-8 text-muted-foreground" />
|
||||||
<p className="text-sm text-[#e2e5eb]">
|
<p className="text-sm text-foreground">
|
||||||
Drop .rfflow file here or <span className="text-[#22d3ee] cursor-pointer">browse</span>
|
Drop .rfflow file here or <span className="text-primary cursor-pointer">browse</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-1 text-xs text-[#848b9b]">JSON format</p>
|
<p className="mt-1 text-xs text-muted-foreground">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-[#848b9b]">
|
<label htmlFor="import-name" className="mb-1.5 block text-xs font-medium text-muted-foreground">
|
||||||
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-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||||
'placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'placeholder:text-muted-foreground 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-[#848b9b]">Type:</span>
|
<span className="text-muted-foreground">Type:</span>
|
||||||
<span className="rounded bg-[rgba(34,211,238,0.10)] px-2 py-0.5 font-sans text-xs text-[#22d3ee]">
|
<span className="rounded bg-accent-dim px-2 py-0.5 font-sans text-xs text-primary">
|
||||||
{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-[#848b9b]">Description:</span>
|
<span className="text-muted-foreground">Description:</span>
|
||||||
<p className="mt-0.5 text-[#e2e5eb] line-clamp-2">{parsed.flow.description}</p>
|
<p className="mt-0.5 text-foreground 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-[#848b9b]">Category:</span>
|
<span className="text-muted-foreground">Category:</span>
|
||||||
<span className="text-[#e2e5eb]">{parsed.flow.category.name}</span>
|
<span className="text-foreground">{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-[#848b9b]">Tags:</span>
|
<span className="text-muted-foreground">Tags:</span>
|
||||||
{parsed.flow.tags.map((tag) => (
|
{parsed.flow.tags.map((tag) => (
|
||||||
<span key={tag} className="rounded bg-[#14161d] border border-[#1e2130] px-2 py-0.5 font-sans text-xs text-[#e2e5eb]">
|
<span key={tag} className="rounded bg-card border border-border px-2 py-0.5 font-sans text-xs text-foreground">
|
||||||
{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-[#848b9b]">Original author:</span>
|
<span className="text-muted-foreground">Original author:</span>
|
||||||
<span className="text-[#e2e5eb]">{parsed.flow.author_name}</span>
|
<span className="text-foreground">{parsed.flow.author_name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[#848b9b]">Version:</span>
|
<span className="text-muted-foreground">Version:</span>
|
||||||
<span className="text-[#e2e5eb]">v{parsed.flow.version}</span>
|
<span className="text-foreground">v{parsed.flow.version}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-[#848b9b]">
|
<p className="text-xs text-muted-foreground">
|
||||||
Flow will be imported as a <span className="font-medium text-[#e2e5eb]">draft</span>.
|
Flow will be imported as a <span className="font-medium text-foreground">draft</span>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex justify-end gap-2 border-t border-[#1e2130] px-5 py-3">
|
<div className="flex justify-end gap-2 border-t border-border px-5 py-3">
|
||||||
{step === 'preview' && (
|
{step === 'preview' && (
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
|||||||
@@ -120,13 +120,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<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">
|
<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">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-6 py-4">
|
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Share Tree</h2>
|
<h2 className="text-lg font-semibold text-foreground">Share Tree</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="rounded-md p-1 text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]"
|
className="rounded-md p-1 text-muted-foreground hover:bg-accent/50 hover:text-foreground"
|
||||||
>
|
>
|
||||||
<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-[#e2e5eb]">{tree.name}</h3>
|
<h3 className="font-medium text-foreground">{tree.name}</h3>
|
||||||
{tree.description && (
|
{tree.description && (
|
||||||
<p className="mt-1 text-sm text-[#848b9b] line-clamp-2">
|
<p className="mt-1 text-sm text-muted-foreground 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-[#e2e5eb]">
|
<label className="mb-2 block text-sm font-medium text-foreground">
|
||||||
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-[#1e2130] bg-accent text-[#e2e5eb]'
|
? 'border-border bg-accent text-foreground'
|
||||||
: 'border-[#1e2130] bg-transparent text-[#848b9b] hover:border-primary/30 hover:bg-accent/50'
|
: 'border-border bg-transparent text-muted-foreground 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-[#848b9b]">
|
<div className="text-xs text-muted-foreground">
|
||||||
{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-[#e2e5eb]">
|
<label className="mb-2 block text-sm font-medium text-foreground">
|
||||||
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-[#1e2130] bg-[#14161d] text-[#e2e5eb] focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-black"
|
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"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="allow-forking"
|
htmlFor="allow-forking"
|
||||||
className="text-sm text-[#848b9b] cursor-pointer"
|
className="text-sm text-muted-foreground 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-[#1e2130] bg-[#14161d] p-3">
|
<div className="flex items-center gap-2 rounded-md border border-border bg-card 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-[#e2e5eb] outline-hidden"
|
className="flex-1 bg-transparent text-sm text-foreground outline-hidden"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyLink}
|
onClick={handleCopyLink}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-3 py-1.5 text-sm font-medium transition-colors',
|
'flex items-center gap-2 rounded-md border border-border 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-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -243,13 +243,13 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-[#848b9b]">
|
<p className="text-xs text-muted-foreground">
|
||||||
{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-[#848b9b]">
|
<p className="text-xs text-muted-foreground">
|
||||||
{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-[#1e2130] px-6 py-4">
|
<div className="flex justify-end gap-3 border-t border-border 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-[#848b9b]">
|
<span className="mr-2 flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||||
<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-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm',
|
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
|
||||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'text-foreground 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-[#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"
|
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"
|
||||||
>
|
>
|
||||||
<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-[#e2e5eb]">{tree.name}</h3>
|
<h3 className="font-semibold text-foreground">{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" />
|
||||||
@@ -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-[#848b9b]" />
|
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-4 w-4 text-[#848b9b]" />
|
<Lock className="h-4 w-4 text-muted-foreground" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.category_info && (
|
{tree.category_info && (
|
||||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
|
||||||
{tree.category_info.name}
|
{tree.category_info.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-3 text-sm text-[#848b9b] line-clamp-2">
|
<p className="mb-3 text-sm text-muted-foreground 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-[#848b9b]">
|
<span className="text-xs text-muted-foreground">
|
||||||
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-[#1e2130] p-2 text-[#848b9b]',
|
'rounded-md border border-border p-2 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-2 text-[#848b9b]',
|
'rounded-md border border-border p-2 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-2 text-[#848b9b]',
|
'rounded-md border border-border p-2 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'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-[#1e2130] p-2 text-[#848b9b]',
|
'rounded-md border border-border p-2 text-muted-foreground',
|
||||||
'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,7 +163,7 @@ 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-[#22d3ee] px-3 py-2 text-sm font-medium text-white',
|
'rounded-md bg-primary px-3 py-2 text-sm font-medium text-white',
|
||||||
'hover:brightness-110'
|
'hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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-[#14161d] border border-[#1e2130] rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-xs"
|
className="flex items-center gap-4 bg-card border border-border 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-[#e2e5eb] truncate">{tree.name}</h3>
|
<h3 className="font-semibold text-foreground 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" />
|
||||||
@@ -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-[#848b9b] shrink-0" />
|
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span title="Private tree">
|
<span title="Private tree">
|
||||||
<Lock className="h-3.5 w-3.5 text-[#848b9b] shrink-0" />
|
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-[#848b9b] truncate">
|
<p className="text-sm text-muted-foreground 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-[#848b9b] whitespace-nowrap">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground 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-[#848b9b]">
|
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
|
||||||
<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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
'hover:bg-accent hover:text-foreground'
|
||||||
)}
|
)}
|
||||||
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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'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-[#1e2130] p-1.5 text-[#848b9b]',
|
'rounded-md border border-border p-1.5 text-muted-foreground',
|
||||||
'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,7 +166,7 @@ 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-[#22d3ee] px-3 py-1.5 text-sm font-medium text-white',
|
'rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-white',
|
||||||
'hover:brightness-110 whitespace-nowrap'
|
'hover:brightness-110 whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user