refactor: migrate page components to Design System v4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -167,23 +167,23 @@ export function AccountSettingsPage() {
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Building2 className="h-8 w-8 text-muted-foreground" />
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Account Settings</h1>
|
||||
<Building2 className="h-8 w-8 text-[#848b9b]" />
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Account Settings</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Manage your account, subscription, and team
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl space-y-6">
|
||||
{/* Account Info Section */}
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<h2 className="text-lg font-semibold text-foreground">Account Information</h2>
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Account Information</h2>
|
||||
|
||||
<div className="mt-4 space-y-4">
|
||||
{/* Account Name */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Account Name
|
||||
</label>
|
||||
{isEditingName ? (
|
||||
@@ -193,8 +193,8 @@ export function AccountSettingsPage() {
|
||||
value={editedName}
|
||||
onChange={(e) => setEditedName(e.target.value)}
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
autoFocus
|
||||
@@ -226,11 +226,11 @@ export function AccountSettingsPage() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<span className="text-sm text-foreground">{account?.name}</span>
|
||||
<span className="text-sm text-[#e2e5eb]">{account?.name}</span>
|
||||
{isAccountOwner && (
|
||||
<button
|
||||
onClick={() => setIsEditingName(true)}
|
||||
className="text-xs text-foreground hover:underline"
|
||||
className="text-xs text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
@@ -241,10 +241,10 @@ export function AccountSettingsPage() {
|
||||
|
||||
{/* Display Code */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Display Code
|
||||
</label>
|
||||
<p className="mt-1 text-sm font-mono text-muted-foreground">
|
||||
<p className="mt-1 text-sm font-mono text-[#848b9b]">
|
||||
{account?.display_code}
|
||||
</p>
|
||||
</div>
|
||||
@@ -252,8 +252,8 @@ export function AccountSettingsPage() {
|
||||
</div>
|
||||
|
||||
{/* Subscription Section */}
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<h2 className="text-lg font-semibold text-foreground">Subscription</h2>
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Subscription</h2>
|
||||
|
||||
<div className="mt-4 space-y-4">
|
||||
{/* Plan & Status */}
|
||||
@@ -261,9 +261,9 @@ export function AccountSettingsPage() {
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium',
|
||||
plan === 'free' && 'bg-accent text-muted-foreground',
|
||||
plan === 'pro' && 'bg-accent text-foreground',
|
||||
plan === 'team' && 'bg-accent text-foreground'
|
||||
plan === 'free' && 'bg-accent text-[#848b9b]',
|
||||
plan === 'pro' && 'bg-accent text-[#e2e5eb]',
|
||||
plan === 'team' && 'bg-accent text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Crown className="h-3.5 w-3.5" />
|
||||
@@ -277,7 +277,7 @@ export function AccountSettingsPage() {
|
||||
sub.status === 'trialing' && 'bg-blue-500/10 text-blue-400',
|
||||
sub.status === 'past_due' && 'bg-yellow-500/10 text-yellow-400',
|
||||
sub.status === 'canceled' && 'bg-red-400/10 text-red-400',
|
||||
sub.status === 'orphaned' && 'bg-accent text-muted-foreground'
|
||||
sub.status === 'orphaned' && 'bg-accent text-[#848b9b]'
|
||||
)}
|
||||
>
|
||||
{sub.status.charAt(0).toUpperCase() + sub.status.slice(1).replace('_', ' ')}
|
||||
@@ -286,7 +286,7 @@ export function AccountSettingsPage() {
|
||||
</div>
|
||||
|
||||
{sub?.current_period_end && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Current period ends: {new Date(sub.current_period_end).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
@@ -329,14 +329,14 @@ export function AccountSettingsPage() {
|
||||
|
||||
{/* Team Members Section (owners only) */}
|
||||
{isAccountOwner && (
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Team Members</h2>
|
||||
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Team Members</h2>
|
||||
</div>
|
||||
|
||||
{members.length === 0 ? (
|
||||
<p className="mt-4 text-sm text-muted-foreground">No team members yet.</p>
|
||||
<p className="mt-4 text-sm text-[#848b9b]">No team members yet.</p>
|
||||
) : (
|
||||
<div className="mt-4 divide-y divide-border">
|
||||
{members.map((member) => (
|
||||
@@ -345,12 +345,12 @@ export function AccountSettingsPage() {
|
||||
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{member.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{member.email}</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">{member.name}</p>
|
||||
<p className="text-xs text-[#848b9b]">{member.email}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{member.account_role === 'owner' ? (
|
||||
<span className="rounded-full px-2.5 py-0.5 text-xs font-medium bg-accent text-foreground">
|
||||
<span className="rounded-full px-2.5 py-0.5 text-xs font-medium bg-accent text-[#e2e5eb]">
|
||||
owner
|
||||
</span>
|
||||
) : (
|
||||
@@ -366,8 +366,8 @@ export function AccountSettingsPage() {
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-md border border-border bg-card px-2 py-0.5 text-xs',
|
||||
'text-foreground focus:border-primary focus:outline-hidden'
|
||||
'rounded-md border border-[#1e2130] bg-[#14161d] px-2 py-0.5 text-xs',
|
||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden'
|
||||
)}
|
||||
>
|
||||
<option value="engineer">engineer</option>
|
||||
@@ -382,7 +382,7 @@ export function AccountSettingsPage() {
|
||||
{member.account_role !== 'owner' && (
|
||||
<button
|
||||
onClick={() => handleRemoveMember(member.id)}
|
||||
className="text-muted-foreground hover:text-red-400"
|
||||
className="text-[#848b9b] hover:text-red-400"
|
||||
title="Remove member"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -398,10 +398,10 @@ export function AccountSettingsPage() {
|
||||
|
||||
{/* Invite Member Section (owners only) */}
|
||||
{isAccountOwner && (
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Invite Member</h2>
|
||||
<Mail className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Invite Member</h2>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleInvite} className="mt-4 space-y-3">
|
||||
@@ -413,8 +413,8 @@ export function AccountSettingsPage() {
|
||||
onChange={(e) => setInviteEmail(e.target.value)}
|
||||
required
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -422,8 +422,8 @@ export function AccountSettingsPage() {
|
||||
value={inviteRole}
|
||||
onChange={(e) => setInviteRole(e.target.value)}
|
||||
className={cn(
|
||||
'rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
'rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<option value="engineer">Engineer</option>
|
||||
@@ -449,7 +449,7 @@ export function AccountSettingsPage() {
|
||||
{/* Pending Invites */}
|
||||
{invites.length > 0 && (
|
||||
<div className="mt-6">
|
||||
<h3 className="text-sm font-medium text-foreground">Pending Invites</h3>
|
||||
<h3 className="text-sm font-medium text-[#e2e5eb]">Pending Invites</h3>
|
||||
<div className="mt-2 divide-y divide-border">
|
||||
{invites
|
||||
.filter((inv) => !inv.used_at)
|
||||
@@ -459,21 +459,21 @@ export function AccountSettingsPage() {
|
||||
className="flex items-center justify-between py-2"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm text-foreground">{invite.email}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-sm text-[#e2e5eb]">{invite.email}</p>
|
||||
<p className="text-xs text-[#848b9b]">
|
||||
{invite.expires_at
|
||||
? `Expires ${new Date(invite.expires_at).toLocaleDateString()}`
|
||||
: 'No expiration'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs text-muted-foreground">
|
||||
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs text-[#848b9b]">
|
||||
{invite.role}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleResendInvite(invite.id)}
|
||||
disabled={resendingId === invite.id}
|
||||
className="text-muted-foreground hover:text-foreground disabled:opacity-50"
|
||||
className="text-[#848b9b] hover:text-[#e2e5eb] disabled:opacity-50"
|
||||
title="Resend invite"
|
||||
>
|
||||
{resendingId === invite.id ? (
|
||||
@@ -494,32 +494,32 @@ export function AccountSettingsPage() {
|
||||
{/* Profile Settings Link */}
|
||||
<Link
|
||||
to="/account/profile"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<UserCog className="h-5 w-5 text-muted-foreground" />
|
||||
<UserCog className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Profile Settings</h2>
|
||||
<p className="text-sm text-muted-foreground">Update your name, email, and personal details</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Profile Settings</h2>
|
||||
<p className="text-sm text-[#848b9b]">Update your name, email, and personal details</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
|
||||
{/* Team Categories Link (owners only) */}
|
||||
{isAccountOwner && (
|
||||
<Link
|
||||
to="/account/categories"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<FolderTree className="h-5 w-5 text-muted-foreground" />
|
||||
<FolderTree className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Team Categories</h2>
|
||||
<p className="text-sm text-muted-foreground">Manage tree categories for your team</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Team Categories</h2>
|
||||
<p className="text-sm text-[#848b9b]">Manage tree categories for your team</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -527,16 +527,16 @@ export function AccountSettingsPage() {
|
||||
{isAccountOwner && (
|
||||
<Link
|
||||
to="/account/target-lists"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Server className="h-5 w-5 text-muted-foreground" />
|
||||
<Server className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Target Lists</h2>
|
||||
<p className="text-sm text-muted-foreground">Saved server lists for maintenance flow batch launching</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Target Lists</h2>
|
||||
<p className="text-sm text-[#848b9b]">Saved server lists for maintenance flow batch launching</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -544,16 +544,16 @@ export function AccountSettingsPage() {
|
||||
{isAccountOwner && (
|
||||
<Link
|
||||
to="/account/chat-retention"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Clock className="h-5 w-5 text-muted-foreground" />
|
||||
<Clock className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Chat Retention</h2>
|
||||
<p className="text-sm text-muted-foreground">Configure AI assistant conversation retention policies</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Chat Retention</h2>
|
||||
<p className="text-sm text-[#848b9b]">Configure AI assistant conversation retention policies</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -561,16 +561,16 @@ export function AccountSettingsPage() {
|
||||
{isAccountOwner && (
|
||||
<Link
|
||||
to="/account/integrations"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Plug className="h-5 w-5 text-muted-foreground" />
|
||||
<Plug className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Integrations</h2>
|
||||
<p className="text-sm text-muted-foreground">Connect your PSA to sync session documentation to tickets</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Integrations</h2>
|
||||
<p className="text-sm text-[#848b9b]">Connect your PSA to sync session documentation to tickets</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -578,32 +578,32 @@ export function AccountSettingsPage() {
|
||||
{isAccountOwner && (
|
||||
<Link
|
||||
to="/account/branding"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Palette className="h-5 w-5 text-muted-foreground" />
|
||||
<Palette className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Branding</h2>
|
||||
<p className="text-sm text-muted-foreground">Customize logo, accent color, and company name</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Branding</h2>
|
||||
<p className="text-sm text-[#848b9b]">Customize logo, accent color, and company name</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Feedback Link (all users) */}
|
||||
<Link
|
||||
to="/feedback"
|
||||
className="bg-card border border-border rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-border transition-all"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 flex items-center justify-between group hover:border-[#1e2130] transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<MessageSquareText className="h-5 w-5 text-muted-foreground" />
|
||||
<MessageSquareText className="h-5 w-5 text-[#848b9b]" />
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Send Feedback</h2>
|
||||
<p className="text-sm text-muted-foreground">Report bugs, request features, or share your thoughts</p>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Send Feedback</h2>
|
||||
<p className="text-sm text-[#848b9b]">Report bugs, request features, or share your thoughts</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-muted-foreground group-hover:text-foreground transition-colors">→</span>
|
||||
<span className="text-[#848b9b] group-hover:text-[#e2e5eb] transition-colors">→</span>
|
||||
</Link>
|
||||
|
||||
{/* Branding Section (owners only) */}
|
||||
@@ -612,20 +612,20 @@ export function AccountSettingsPage() {
|
||||
)}
|
||||
|
||||
{/* Preferences Section */}
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Preferences</h2>
|
||||
<Settings className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Preferences</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="export-format"
|
||||
className="block text-sm font-medium text-foreground"
|
||||
className="block text-sm font-medium text-[#e2e5eb]"
|
||||
>
|
||||
Default Export Format
|
||||
</label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
This format will be pre-selected when exporting sessions
|
||||
</p>
|
||||
<select
|
||||
@@ -636,8 +636,8 @@ export function AccountSettingsPage() {
|
||||
toast.success('Preference saved')
|
||||
}}
|
||||
className={cn(
|
||||
'mt-2 block w-full max-w-xs rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-sm text-foreground',
|
||||
'mt-2 block w-full max-w-xs rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-sm text-[#e2e5eb]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -649,23 +649,23 @@ export function AccountSettingsPage() {
|
||||
</div>
|
||||
{/* SSO Section (Task 11) */}
|
||||
{isAccountOwner && (
|
||||
<div className="bg-card border border-border rounded-xl p-4 sm:p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<ShieldCheck className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Single Sign-On (SSO)</h2>
|
||||
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-label font-medium text-primary">
|
||||
<ShieldCheck className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Single Sign-On (SSO)</h2>
|
||||
<span className="inline-flex items-center rounded-full bg-[rgba(34,211,238,0.10)] px-2.5 py-0.5 text-xs font-sans text-xs font-medium text-[#22d3ee]">
|
||||
Enterprise
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
<p className="text-sm text-[#848b9b] mb-4">
|
||||
SAML and OIDC single sign-on is available for enterprise plans. Contact us to enable SSO for
|
||||
your organization.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:support@resolutionflow.com?subject=SSO%20Setup%20Request"
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
||||
'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]',
|
||||
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
||||
)}
|
||||
>
|
||||
@@ -678,7 +678,7 @@ export function AccountSettingsPage() {
|
||||
<div className="rounded-xl border border-rose-500/20 p-4 sm:p-6">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<AlertTriangle className="h-5 w-5 text-rose-500" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Danger Zone</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Danger Zone</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
@@ -686,8 +686,8 @@ export function AccountSettingsPage() {
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">Transfer Ownership</p>
|
||||
<p className="text-xs text-muted-foreground">Make another member the account owner</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">Transfer Ownership</p>
|
||||
<p className="text-xs text-[#848b9b]">Make another member the account owner</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -698,10 +698,10 @@ export function AccountSettingsPage() {
|
||||
Transfer
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-t border-border pt-3">
|
||||
<div className="flex items-center justify-between border-t border-[#1e2130] pt-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">Delete Account</p>
|
||||
<p className="text-xs text-muted-foreground">Permanently delete your account and all data</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">Delete Account</p>
|
||||
<p className="text-xs text-[#848b9b]">Permanently delete your account and all data</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -715,8 +715,8 @@ export function AccountSettingsPage() {
|
||||
) : (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">Leave Account</p>
|
||||
<p className="text-xs text-muted-foreground">Leave this account and create a personal one</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">Leave Account</p>
|
||||
<p className="text-xs text-[#848b9b]">Leave this account and create a personal one</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
@@ -769,16 +769,16 @@ function UsageStat({
|
||||
const isAtLimit = !isUnlimited && current >= max
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-border bg-card p-3">
|
||||
<p className="text-xs font-medium text-muted-foreground">{label}</p>
|
||||
<div className="rounded-md border border-[#1e2130] bg-[#14161d] p-3">
|
||||
<p className="text-xs font-medium text-[#848b9b]">{label}</p>
|
||||
<p
|
||||
className={cn(
|
||||
'mt-1 text-lg font-semibold',
|
||||
isAtLimit ? 'text-red-400' : isNearLimit ? 'text-yellow-400' : 'text-foreground'
|
||||
isAtLimit ? 'text-red-400' : isNearLimit ? 'text-yellow-400' : 'text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
{current}
|
||||
<span className="text-sm font-normal text-muted-foreground">
|
||||
<span className="text-sm font-normal text-[#848b9b]">
|
||||
{' '}/ {isUnlimited ? 'Unlimited' : max}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
@@ -250,13 +250,13 @@ export default function AssistantChatPage() {
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
||||
{messages.length === 0 && !loading && (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Sparkles size={28} className="text-primary" />
|
||||
<div className="w-16 h-16 rounded-full bg-[rgba(34,211,238,0.10)] flex items-center justify-center mb-4">
|
||||
<Sparkles size={28} className="text-[#22d3ee]" />
|
||||
</div>
|
||||
<h2 className="text-lg font-heading font-semibold text-foreground mb-2">
|
||||
<h2 className="text-lg font-heading font-semibold text-[#e2e5eb] mb-2">
|
||||
AI Assistant
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground max-w-md">
|
||||
<p className="text-sm text-[#848b9b] max-w-md">
|
||||
Ask me anything about IT infrastructure, networking, Active Directory,
|
||||
cloud platforms, or troubleshooting. I'll also suggest relevant flows from your team's library.
|
||||
</p>
|
||||
@@ -273,10 +273,10 @@ export default function AssistantChatPage() {
|
||||
{loading && (
|
||||
<div className="flex gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center">
|
||||
<Sparkles size={14} className="text-primary" />
|
||||
<Sparkles size={14} className="text-[#22d3ee]" />
|
||||
</div>
|
||||
<div className="bg-white/[0.04] border border-brand-border rounded-2xl px-4 py-3">
|
||||
<Loader2 size={16} className="animate-spin text-primary" />
|
||||
<Loader2 size={16} className="animate-spin text-[#22d3ee]" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -294,7 +294,7 @@ export default function AssistantChatPage() {
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Ask about IT, networking, troubleshooting..."
|
||||
rows={3}
|
||||
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-primary/30"
|
||||
className="flex-1 resize-none rounded-xl border bg-[#14161d] text-[#e2e5eb] text-sm placeholder:text-[#848b9b] px-4 py-3 focus:outline-hidden focus:border-primary/30"
|
||||
style={{ borderColor: 'var(--glass-border)' }}
|
||||
disabled={loading}
|
||||
/>
|
||||
@@ -302,7 +302,7 @@ export default function AssistantChatPage() {
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={!input.trim() || loading}
|
||||
className="bg-gradient-brand text-brand-dark p-3 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
|
||||
className="bg-[#22d3ee] text-brand-dark p-3 rounded-xl hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40"
|
||||
title="Send message"
|
||||
>
|
||||
<Send size={18} />
|
||||
@@ -311,7 +311,7 @@ export default function AssistantChatPage() {
|
||||
<button
|
||||
onClick={() => setShowConclude(true)}
|
||||
disabled={loading}
|
||||
className="p-3 rounded-xl border text-muted-foreground hover:text-amber-400 hover:border-amber-400/30 hover:bg-amber-400/10 transition-all disabled:opacity-40"
|
||||
className="p-3 rounded-xl border text-[#848b9b] hover:text-amber-400 hover:border-amber-400/30 hover:bg-amber-400/10 transition-all disabled:opacity-40"
|
||||
style={{ borderColor: 'var(--glass-border)' }}
|
||||
title="Conclude session"
|
||||
>
|
||||
@@ -320,25 +320,25 @@ export default function AssistantChatPage() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[0.625rem] text-muted-foreground mt-1.5 px-1">Shift + Enter for a new line</p>
|
||||
<p className="text-[0.625rem] text-[#848b9b] mt-1.5 px-1">Shift + Enter for a new line</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
||||
<div className="w-20 h-20 rounded-full bg-primary/10 flex items-center justify-center mb-4">
|
||||
<Sparkles size={32} className="text-primary" />
|
||||
<div className="w-20 h-20 rounded-full bg-[rgba(34,211,238,0.10)] flex items-center justify-center mb-4">
|
||||
<Sparkles size={32} className="text-[#22d3ee]" />
|
||||
</div>
|
||||
<h2 className="text-xl font-heading font-semibold text-foreground mb-2">
|
||||
<h2 className="text-xl font-heading font-semibold text-[#e2e5eb] mb-2">
|
||||
AI Assistant
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground max-w-md mb-6">
|
||||
<p className="text-sm text-[#848b9b] max-w-md mb-6">
|
||||
Your Senior Systems & Network Engineer. Ask anything about IT infrastructure,
|
||||
or start a new chat to get personalized help with your team's flows.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleNewChat}
|
||||
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-6 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="bg-[#22d3ee] text-brand-dark font-semibold text-sm rounded-lg px-6 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Start a Conversation
|
||||
</button>
|
||||
|
||||
@@ -104,7 +104,7 @@ export default function BatchStatusPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => treeId && navigate(`/flows/${treeId}/maintenance`)}
|
||||
className="flex items-center gap-1.5 text-[0.875rem] text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="flex items-center gap-1.5 text-[0.875rem] text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
{tree?.name ?? 'Maintenance Flow'}
|
||||
@@ -112,7 +112,7 @@ export default function BatchStatusPage() {
|
||||
<button
|
||||
onClick={() => loadSessions(true)}
|
||||
disabled={isRefreshing}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-[0.8125rem] text-muted-foreground hover:bg-accent hover:text-foreground transition-colors disabled:opacity-50"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-[#1e2130] px-3 py-1.5 text-[0.8125rem] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={cn('h-3.5 w-3.5', isRefreshing && 'animate-spin')} />
|
||||
Refresh
|
||||
@@ -125,9 +125,9 @@ export default function BatchStatusPage() {
|
||||
<Wrench className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold text-foreground">Batch Run</h1>
|
||||
<h1 className="text-xl font-semibold text-[#e2e5eb]">Batch Run</h1>
|
||||
{batchDate && (
|
||||
<p className="text-[0.875rem] text-muted-foreground">
|
||||
<p className="text-[0.875rem] text-[#848b9b]">
|
||||
{batchDate.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
||||
</p>
|
||||
)}
|
||||
@@ -136,18 +136,18 @@ export default function BatchStatusPage() {
|
||||
|
||||
{/* Progress bar */}
|
||||
{total > 0 && (
|
||||
<div className="rounded-xl border border-border bg-card p-5 space-y-3">
|
||||
<div className="rounded-xl border border-[#1e2130] bg-[#14161d] p-5 space-y-3">
|
||||
<div className="flex items-center justify-between text-[0.875rem]">
|
||||
<span className="font-medium text-foreground">
|
||||
<span className="font-medium text-[#e2e5eb]">
|
||||
{completed} of {total} complete
|
||||
</span>
|
||||
<span className={cn(
|
||||
'font-label text-[0.6875rem] uppercase tracking-wide rounded-full px-2 py-0.5',
|
||||
'font-sans text-xs text-[0.6875rem] uppercase tracking-wide rounded-full px-2 py-0.5',
|
||||
allDone
|
||||
? 'text-emerald-400 bg-emerald-500/10'
|
||||
: inProgress > 0
|
||||
? 'text-amber-400 bg-amber-500/10'
|
||||
: 'text-muted-foreground bg-muted'
|
||||
: 'text-[#848b9b] bg-muted'
|
||||
)}>
|
||||
{allDone ? 'Complete' : inProgress > 0 ? `${inProgress} in progress` : 'Not started'}
|
||||
</span>
|
||||
@@ -175,7 +175,7 @@ export default function BatchStatusPage() {
|
||||
<span className="text-[0.8125rem] text-amber-400">{outcomeCounts.workaround} workaround</span>
|
||||
)}
|
||||
{outcomeCounts.unresolved && (
|
||||
<span className="text-[0.8125rem] text-muted-foreground">{outcomeCounts.unresolved} unresolved</span>
|
||||
<span className="text-[0.8125rem] text-[#848b9b]">{outcomeCounts.unresolved} unresolved</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -189,13 +189,13 @@ export default function BatchStatusPage() {
|
||||
<p className="text-sm text-red-400 mb-3">{loadError}</p>
|
||||
<button
|
||||
onClick={() => loadSessions(true)}
|
||||
className="rounded-md border border-border px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
className="rounded-md border border-[#1e2130] px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
) : sessions.length === 0 ? (
|
||||
<p className="text-center text-[0.875rem] text-muted-foreground py-8">
|
||||
<p className="text-center text-[0.875rem] text-[#848b9b] py-8">
|
||||
No sessions found for this batch.
|
||||
</p>
|
||||
) : (
|
||||
|
||||
@@ -66,7 +66,7 @@ export function ChangePasswordPage() {
|
||||
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
|
||||
<h1 className="text-3xl font-bold font-heading text-[#e2e5eb] tracking-tight">
|
||||
Change Password
|
||||
</h1>
|
||||
{isForced && (
|
||||
@@ -77,7 +77,7 @@ export function ChangePasswordPage() {
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
{error && (
|
||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
||||
{error}
|
||||
@@ -85,7 +85,7 @@ export function ChangePasswordPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="current-password" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="current-password" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
Current Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -95,8 +95,8 @@ export function ChangePasswordPage() {
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -104,7 +104,7 @@ export function ChangePasswordPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
New Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -114,20 +114,20 @@ export function ChangePasswordPage() {
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
placeholder="At least 10 characters"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Must include uppercase, lowercase, and a digit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
Confirm New Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -137,8 +137,8 @@ export function ChangePasswordPage() {
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -150,7 +150,7 @@ export function ChangePasswordPage() {
|
||||
disabled={isLoading}
|
||||
className={cn(
|
||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-black',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-all'
|
||||
|
||||
@@ -9,8 +9,8 @@ export default function EscalationQueuePage() {
|
||||
<AlertTriangle size={16} className="text-amber-400" />
|
||||
</span>
|
||||
<div>
|
||||
<h1 className="font-heading text-xl font-bold text-foreground">Escalation Queue</h1>
|
||||
<p className="text-sm text-muted-foreground">Sessions from your team waiting for pickup</p>
|
||||
<h1 className="font-heading text-xl font-bold text-[#e2e5eb]">Escalation Queue</h1>
|
||||
<p className="text-sm text-[#848b9b]">Sessions from your team waiting for pickup</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -137,34 +137,34 @@ export function FeedbackPage() {
|
||||
{/* Page header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<MessageSquareText className="h-8 w-8 text-muted-foreground" />
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Send Feedback</h1>
|
||||
<MessageSquareText className="h-8 w-8 text-[#848b9b]" />
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Send Feedback</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Help us improve ResolutionFlow. Report bugs, request features, or share your thoughts.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-2xl">
|
||||
{submitted ? (
|
||||
<div className="bg-card border border-border rounded-xl p-8 text-center">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-8 text-center">
|
||||
<CheckCircle2 className="mx-auto h-12 w-12 text-green-500 mb-4" />
|
||||
<h2 className="text-xl font-semibold text-foreground mb-2">Thank you for your feedback!</h2>
|
||||
<p className="text-muted-foreground mb-6">
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-2">Thank you for your feedback!</h2>
|
||||
<p className="text-[#848b9b] mb-6">
|
||||
We've received your submission and will review it shortly. Check your email for a confirmation.
|
||||
</p>
|
||||
<button
|
||||
onClick={handleNewFeedback}
|
||||
className="bg-gradient-brand text-white font-medium px-6 py-2.5 rounded-lg shadow-lg shadow-primary/20 hover:opacity-90 transition-opacity"
|
||||
className="bg-[#22d3ee] text-white font-medium px-6 py-2.5 rounded-lg hover:brightness-110 transition-opacity"
|
||||
>
|
||||
Send More Feedback
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="bg-card border border-border rounded-xl p-4 sm:p-6 space-y-5">
|
||||
<form onSubmit={handleSubmit} className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 sm:p-6 space-y-5">
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label htmlFor="feedback-email" className="block text-sm font-medium text-foreground mb-1.5">
|
||||
<label htmlFor="feedback-email" className="block text-sm font-medium text-[#e2e5eb] mb-1.5">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
@@ -174,14 +174,14 @@ export function FeedbackPage() {
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">We'll reply to this address if we need more details.</p>
|
||||
<p className="mt-1 text-xs text-[#848b9b]">We'll reply to this address if we need more details.</p>
|
||||
</div>
|
||||
|
||||
{/* Feedback Type — custom selector with descriptions */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground mb-1.5">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb] mb-1.5">
|
||||
Feedback Type
|
||||
</label>
|
||||
<div className="relative">
|
||||
@@ -196,8 +196,8 @@ export function FeedbackPage() {
|
||||
onClick={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
||||
onKeyDown={handleDropdownKeyDown}
|
||||
className={cn(
|
||||
"w-full rounded-lg border border-border bg-card px-3 py-2 text-left flex items-center justify-between focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden",
|
||||
feedbackType ? "text-foreground" : "text-muted-foreground"
|
||||
"w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-left flex items-center justify-between focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden",
|
||||
feedbackType ? "text-[#e2e5eb]" : "text-[#848b9b]"
|
||||
)}
|
||||
>
|
||||
<span>{selectedType?.value ?? 'Select a type...'}</span>
|
||||
@@ -208,7 +208,7 @@ export function FeedbackPage() {
|
||||
ref={listboxRef}
|
||||
id="feedback-type-listbox"
|
||||
role="listbox"
|
||||
className="absolute z-10 mt-1 w-full rounded-lg border border-border bg-card shadow-lg overflow-hidden"
|
||||
className="absolute z-10 mt-1 w-full rounded-lg border border-[#1e2130] bg-[#14161d] shadow-lg overflow-hidden"
|
||||
>
|
||||
{FEEDBACK_TYPES.map((type, index) => (
|
||||
<button
|
||||
@@ -225,8 +225,8 @@ export function FeedbackPage() {
|
||||
highlightedIndex === index && feedbackType !== type.value && "bg-accent/50"
|
||||
)}
|
||||
>
|
||||
<div className="text-sm font-medium text-foreground">{type.value}</div>
|
||||
<div className="text-xs text-muted-foreground mt-0.5">{type.description}</div>
|
||||
<div className="text-sm font-medium text-[#e2e5eb]">{type.value}</div>
|
||||
<div className="text-xs text-[#848b9b] mt-0.5">{type.description}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -236,7 +236,7 @@ export function FeedbackPage() {
|
||||
|
||||
{/* Message */}
|
||||
<div>
|
||||
<label htmlFor="feedback-message" className="block text-sm font-medium text-foreground mb-1.5">
|
||||
<label htmlFor="feedback-message" className="block text-sm font-medium text-[#e2e5eb] mb-1.5">
|
||||
Your Feedback
|
||||
</label>
|
||||
<textarea
|
||||
@@ -247,9 +247,9 @@ export function FeedbackPage() {
|
||||
required
|
||||
minLength={10}
|
||||
rows={6}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden resize-y"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:ring-1 focus:ring-primary/20 focus:outline-hidden resize-y"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
{message.trim().length < 10
|
||||
? `Minimum 10 characters (${message.trim().length}/10)`
|
||||
: `${message.trim().length} characters`}
|
||||
@@ -262,10 +262,10 @@ export function FeedbackPage() {
|
||||
type="submit"
|
||||
disabled={!canSubmit || isSubmitting}
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-lg px-6 py-2.5 font-medium text-white shadow-lg shadow-primary/20 transition-opacity",
|
||||
"flex items-center gap-2 rounded-lg px-6 py-2.5 font-medium text-white transition-opacity",
|
||||
canSubmit && !isSubmitting
|
||||
? "bg-gradient-brand hover:opacity-90"
|
||||
: "bg-gradient-brand opacity-50 cursor-not-allowed"
|
||||
? "bg-[#22d3ee] hover:brightness-110"
|
||||
: "bg-[#22d3ee] opacity-50 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
<Send size={16} />
|
||||
|
||||
@@ -4,23 +4,23 @@ export default function FlowAssistPage() {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight text-foreground">
|
||||
<h1 className="font-heading text-2xl font-bold tracking-tight text-[#e2e5eb]">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<WandSparkles size={24} style={{ color: '#f472b6' }} />
|
||||
Flow Assist
|
||||
</span>
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
Build flows from natural language — describe what you need and Flow Assist will generate the decision tree or procedural steps.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass-card-static p-8 text-center">
|
||||
<WandSparkles size={40} className="mx-auto mb-4 text-muted-foreground" />
|
||||
<h2 className="font-heading text-lg font-semibold text-foreground mb-2">
|
||||
<div className="card-flat p-8 text-center">
|
||||
<WandSparkles size={40} className="mx-auto mb-4 text-[#848b9b]" />
|
||||
<h2 className="font-heading text-lg font-semibold text-[#e2e5eb] mb-2">
|
||||
Coming Soon
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground max-w-md mx-auto">
|
||||
<p className="text-sm text-[#848b9b] max-w-md mx-auto">
|
||||
Flow Assist will be available here as a dedicated conversational flow builder.
|
||||
In the meantime, use the AI panel in the Flow Editor to generate flows.
|
||||
</p>
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<Loader2 size={24} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={24} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
if (!dashboard) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-sm text-muted-foreground">Failed to load analytics</p>
|
||||
<p className="text-sm text-[#848b9b]">Failed to load analytics</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -165,21 +165,21 @@ export default function FlowPilotAnalyticsPage() {
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span title="FlowPilot Analytics">
|
||||
<BarChart3 size={24} className="text-foreground" />
|
||||
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||
</span>
|
||||
<h1 className="text-xl sm:text-2xl font-bold font-heading text-foreground">FlowPilot Analytics</h1>
|
||||
<h1 className="text-xl sm:text-2xl font-bold font-heading text-[#e2e5eb]">FlowPilot Analytics</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="/analytics"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Team Analytics
|
||||
</Link>
|
||||
<select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value)}
|
||||
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-primary/20 [&>option]:bg-[#1a1c21] [&>option]:text-foreground"
|
||||
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-primary/20 [&>option]:bg-[#1a1c21] [&>option]:text-[#e2e5eb]"
|
||||
>
|
||||
{PERIOD_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
@@ -189,7 +189,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-1 border-b border-border overflow-x-auto">
|
||||
<div className="flex gap-1 border-b border-[#1e2130] overflow-x-auto">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -197,8 +197,8 @@ export default function FlowPilotAnalyticsPage() {
|
||||
className={cn(
|
||||
'px-4 py-2 text-sm transition-colors',
|
||||
activeTab === tab.id
|
||||
? 'border-b-2 border-primary text-foreground font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'border-b-2 border-primary text-[#e2e5eb] font-medium'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
@@ -246,8 +246,8 @@ export default function FlowPilotAnalyticsPage() {
|
||||
{/* Second row — Charts */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* MTTR Trend */}
|
||||
<div className="glass-card-static p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
||||
<div className="card-flat p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
MTTR Trend
|
||||
</h3>
|
||||
{dashboard.mttr_trend.length > 0 ? (
|
||||
@@ -284,15 +284,15 @@ export default function FlowPilotAnalyticsPage() {
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[220px] text-sm text-muted-foreground">
|
||||
<div className="flex items-center justify-center h-[220px] text-sm text-[#848b9b]">
|
||||
No data for this period
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Domain Breakdown */}
|
||||
<div className="glass-card-static p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
||||
<div className="card-flat p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Sessions by Domain
|
||||
</h3>
|
||||
{dashboard.sessions_by_domain.length > 0 ? (
|
||||
@@ -315,7 +315,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-[220px] text-sm text-muted-foreground">
|
||||
<div className="flex items-center justify-center h-[220px] text-sm text-[#848b9b]">
|
||||
No domain data
|
||||
</div>
|
||||
)}
|
||||
@@ -325,8 +325,8 @@ export default function FlowPilotAnalyticsPage() {
|
||||
{/* Third row — Confidence + Knowledge Coverage */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
{/* Confidence Breakdown */}
|
||||
<div className="glass-card-static p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
||||
<div className="card-flat p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Confidence Tiers
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
@@ -355,32 +355,32 @@ export default function FlowPilotAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Knowledge Coverage */}
|
||||
<div className="glass-card-static p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
||||
<div className="card-flat p-3 sm:p-5">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Knowledge Coverage
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
<div className="rounded-lg bg-card/50 p-3">
|
||||
<p className="text-xs text-muted-foreground">Total Flows</p>
|
||||
<p className="text-lg font-semibold text-foreground">{dashboard.knowledge_coverage.total_flows}</p>
|
||||
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||
<p className="text-xs text-[#848b9b]">Total Flows</p>
|
||||
<p className="text-lg font-semibold text-[#e2e5eb]">{dashboard.knowledge_coverage.total_flows}</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-card/50 p-3">
|
||||
<p className="text-xs text-muted-foreground">AI-Generated</p>
|
||||
<p className="text-lg font-semibold text-gradient-brand">{dashboard.knowledge_coverage.ai_generated_flows}</p>
|
||||
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||
<p className="text-xs text-[#848b9b]">AI-Generated</p>
|
||||
<p className="text-lg font-semibold text-[#67e8f9]">{dashboard.knowledge_coverage.ai_generated_flows}</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-card/50 p-3">
|
||||
<p className="text-xs text-muted-foreground">Pending Review</p>
|
||||
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||
<p className="text-xs text-[#848b9b]">Pending Review</p>
|
||||
<p className="text-lg font-semibold text-amber-400">{dashboard.knowledge_coverage.total_proposals_pending}</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-card/50 p-3">
|
||||
<p className="text-xs text-muted-foreground">Approved This Period</p>
|
||||
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||
<p className="text-xs text-[#848b9b]">Approved This Period</p>
|
||||
<p className="text-lg font-semibold text-emerald-400">{dashboard.knowledge_coverage.proposals_approved_this_period}</p>
|
||||
</div>
|
||||
</div>
|
||||
{dashboard.knowledge_coverage.total_proposals_pending > 0 && (
|
||||
<Link
|
||||
to="/review-queue"
|
||||
className="flex items-center gap-2 text-xs text-primary hover:underline"
|
||||
className="flex items-center gap-2 text-xs text-[#22d3ee] hover:underline"
|
||||
>
|
||||
<ArrowUpRight size={12} />
|
||||
Review {dashboard.knowledge_coverage.total_proposals_pending} pending proposals
|
||||
@@ -394,7 +394,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
<AlertTriangle size={14} className="text-amber-400" />
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb]">
|
||||
Knowledge Gaps ({gaps.gaps.length})
|
||||
</h3>
|
||||
</div>
|
||||
@@ -405,11 +405,11 @@ export default function FlowPilotAnalyticsPage() {
|
||||
</div>
|
||||
</div>
|
||||
) : gaps && (
|
||||
<div className="glass-card-static p-4 flex items-center gap-3">
|
||||
<div className="card-flat p-4 flex items-center gap-3">
|
||||
<CheckCircle2 size={20} className="text-emerald-400 shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-foreground">No knowledge gaps detected</p>
|
||||
<p className="text-xs text-muted-foreground">Your flow coverage looks healthy for this period.</p>
|
||||
<p className="text-sm text-[#e2e5eb]">No knowledge gaps detected</p>
|
||||
<p className="text-xs text-[#848b9b]">Your flow coverage looks healthy for this period.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -422,7 +422,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
<ErrorRetry label="coverage data" onRetry={() => { setCoverageError(false); coveragePeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||
) : coverageLoading ? (
|
||||
<div className="flex items-center justify-center min-h-[200px]">
|
||||
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
) : coverageData ? (
|
||||
<CoverageHeatmap data={coverageData} />
|
||||
@@ -436,7 +436,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
<ErrorRetry label="flow quality data" onRetry={() => { setQualityError(false); qualityPeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||
) : qualityLoading ? (
|
||||
<div className="flex items-center justify-center min-h-[200px]">
|
||||
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
) : qualityData ? (
|
||||
<FlowQualityTable data={qualityData} />
|
||||
@@ -450,7 +450,7 @@ export default function FlowPilotAnalyticsPage() {
|
||||
<ErrorRetry label="PSA metrics" onRetry={() => { setPsaError(false); psaPeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||
) : psaLoading ? (
|
||||
<div className="flex items-center justify-center min-h-[200px]">
|
||||
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
) : psaData ? (
|
||||
<PsaMetricsPanel data={psaData} />
|
||||
@@ -475,7 +475,7 @@ function MetricCard({
|
||||
iconColor: string
|
||||
}) {
|
||||
return (
|
||||
<div className="glass-card-static p-3 sm:p-4">
|
||||
<div className="card-flat p-3 sm:p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg"
|
||||
@@ -484,8 +484,8 @@ function MetricCard({
|
||||
<Icon size={16} style={{ color: iconColor }} />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-label text-[0.5625rem] uppercase tracking-wider text-[#5a6170]">{label}</p>
|
||||
<p className="text-lg font-semibold text-foreground">{value}</p>
|
||||
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-wider text-[#5a6170]">{label}</p>
|
||||
<p className="text-lg font-semibold text-[#e2e5eb]">{value}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -509,12 +509,12 @@ function ConfidenceTierRow({
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-foreground font-medium">{label}</span>
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-[#e2e5eb] font-medium">{label}</span>
|
||||
<span className="text-[#848b9b]">
|
||||
{count} sessions · {rate.toFixed(1)}% resolved
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-card/80 overflow-hidden">
|
||||
<div className="h-2 rounded-full bg-[#14161d]/80 overflow-hidden">
|
||||
<div
|
||||
className="h-full rounded-full transition-all"
|
||||
style={{ width: `${pct}%`, background: color }}
|
||||
@@ -526,12 +526,12 @@ function ConfidenceTierRow({
|
||||
|
||||
function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void }) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||
<div className="flex flex-col items-center justify-center py-12 text-[#848b9b]">
|
||||
<AlertTriangle size={24} className="mb-2 text-amber-400" />
|
||||
<p className="text-sm mb-3">Failed to load {label}</p>
|
||||
<button
|
||||
onClick={onRetry}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-[10px] bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
||||
>
|
||||
<RotateCcw size={12} />
|
||||
Retry
|
||||
@@ -543,17 +543,17 @@ function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void })
|
||||
function GapCard({ gap }: { gap: KnowledgeGap }) {
|
||||
const severityStyle = SEVERITY_STYLES[gap.severity as keyof typeof SEVERITY_STYLES] ?? SEVERITY_STYLES.low
|
||||
return (
|
||||
<div className="glass-card-static p-4 space-y-2">
|
||||
<div className="card-flat p-4 space-y-2">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm font-semibold text-foreground">{gap.title}</p>
|
||||
<span className={`shrink-0 rounded-md border px-1.5 py-0.5 font-label text-[0.5625rem] uppercase tracking-wider ${severityStyle}`}>
|
||||
<p className="text-sm font-semibold text-[#e2e5eb]">{gap.title}</p>
|
||||
<span className={`shrink-0 rounded-md border px-1.5 py-0.5 font-sans text-xs text-[0.5625rem] uppercase tracking-wider ${severityStyle}`}>
|
||||
{gap.severity}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{gap.description}</p>
|
||||
<p className="text-xs text-[#848b9b]">{gap.description}</p>
|
||||
<div className="flex items-start gap-1.5 pt-1">
|
||||
<Lightbulb size={12} className="text-primary shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-primary">{gap.suggested_action}</p>
|
||||
<Lightbulb size={12} className="text-[#22d3ee] shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-[#22d3ee]">{gap.suggested_action}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -77,11 +77,11 @@ export default function FlowPilotSessionPage() {
|
||||
if (fp.error && !fp.session) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[50vh]">
|
||||
<div className="glass-card-static p-6 text-center max-w-md">
|
||||
<div className="card-flat p-6 text-center max-w-md">
|
||||
<p className="text-sm text-rose-400">{fp.error}</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-3 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="mt-3 text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
@@ -94,7 +94,7 @@ export default function FlowPilotSessionPage() {
|
||||
if (fp.isLoading && !fp.session) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[50vh]">
|
||||
<Loader2 size={24} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={24} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -134,11 +134,11 @@ export default function FlowPilotSessionPage() {
|
||||
<Sparkles size={14} className="text-amber-400" />
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="font-heading text-sm font-semibold text-foreground truncate">
|
||||
<h1 className="font-heading text-sm font-semibold text-[#e2e5eb] truncate">
|
||||
Escalation Pickup — {fp.session.problem_summary || 'FlowPilot Session'}
|
||||
</h1>
|
||||
</div>
|
||||
<span className="font-label rounded-md bg-amber-500/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-amber-400 border border-amber-500/20">
|
||||
<span className="font-sans text-xs rounded-md bg-amber-500/10 px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-amber-400 border border-amber-500/20">
|
||||
Awaiting pickup
|
||||
</span>
|
||||
</div>
|
||||
@@ -163,21 +163,21 @@ export default function FlowPilotSessionPage() {
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Navigation guard modal */}
|
||||
{blocker.state === 'blocked' && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-amber-500/10">
|
||||
<AlertTriangle size={18} className="text-amber-400" />
|
||||
</span>
|
||||
<h2 className="text-lg font-heading font-semibold text-foreground">Active Session</h2>
|
||||
<h2 className="text-lg font-heading font-semibold text-[#e2e5eb]">Active Session</h2>
|
||||
</div>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
<p className="mb-4 text-sm text-[#848b9b]">
|
||||
You have an active troubleshooting session. If you leave, your session will be paused and you can resume it later.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => blocker.reset()}
|
||||
className="flex-1 rounded-[10px] bg-gradient-to-r from-cyan-500 to-cyan-400 px-4 py-2.5 text-sm font-semibold text-[#101114] hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="flex-1 rounded-lg bg-gradient-to-r from-cyan-500 to-cyan-400 px-4 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Stay in Session
|
||||
</button>
|
||||
@@ -186,7 +186,7 @@ export default function FlowPilotSessionPage() {
|
||||
fp.pauseSession()
|
||||
blocker.proceed()
|
||||
}}
|
||||
className="flex-1 rounded-[10px] 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)] transition-all"
|
||||
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 text-sm font-medium text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||
>
|
||||
Pause & Leave
|
||||
</button>
|
||||
@@ -200,15 +200,15 @@ export default function FlowPilotSessionPage() {
|
||||
className="flex items-center gap-3 border-b px-5 py-3 shrink-0"
|
||||
style={{ borderColor: 'var(--glass-border)' }}
|
||||
>
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Sparkles size={14} className="text-primary" />
|
||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-[rgba(34,211,238,0.10)]">
|
||||
<Sparkles size={14} className="text-[#22d3ee]" />
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="font-heading text-sm font-semibold text-foreground truncate">
|
||||
<h1 className="font-heading text-sm font-semibold text-[#e2e5eb] truncate">
|
||||
{fp.session.problem_summary || 'FlowPilot Session'}
|
||||
</h1>
|
||||
</div>
|
||||
<span className="font-label rounded-md bg-card px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-muted-foreground border border-border">
|
||||
<span className="font-sans text-xs rounded-md bg-[#14161d] px-2 py-0.5 text-[0.625rem] uppercase tracking-wider text-[#848b9b] border border-[#1e2130]">
|
||||
{fp.session.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -28,26 +28,26 @@ export function ForgotPasswordPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Forgot Password" description="Reset your ResolutionFlow password" />
|
||||
<div className="flex min-h-screen items-center justify-center bg-card px-4">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#14161d] px-4">
|
||||
<div className="pointer-events-none fixed inset-0 bg-[radial-gradient(circle_at_50%_0%,rgba(100,100,120,0.03),transparent_50%)]" />
|
||||
|
||||
<div className="relative w-full max-w-md space-y-8">
|
||||
<div className="text-center">
|
||||
<div className="mb-4 flex justify-center sm:mb-6">
|
||||
<div className="w-16 h-16 rounded-2xl bg-gradient-brand flex items-center justify-center sm:w-20 sm:h-20">
|
||||
<div className="w-16 h-16 rounded-2xl bg-[#22d3ee] flex items-center justify-center sm:w-20 sm:h-20">
|
||||
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
|
||||
<h1 className="text-3xl font-bold font-heading text-[#e2e5eb] tracking-tight">
|
||||
Reset Password
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-[#848b9b]">
|
||||
Enter your email and we'll send you a link to reset your password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{submitted ? (
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
<div className="rounded-xl border border-green-400/20 bg-green-400/10 p-4 text-sm text-green-400">
|
||||
If an account with that email exists, we've sent a password reset link.
|
||||
Check your inbox and follow the instructions.
|
||||
@@ -55,7 +55,7 @@ export function ForgotPasswordPage() {
|
||||
<div className="text-center">
|
||||
<Link
|
||||
to="/login"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Back to sign in
|
||||
</Link>
|
||||
@@ -63,9 +63,9 @@ export function ForgotPasswordPage() {
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="email" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
@@ -76,8 +76,8 @@ export function ForgotPasswordPage() {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -90,7 +90,7 @@ export function ForgotPasswordPage() {
|
||||
disabled={isLoading || !email}
|
||||
className={cn(
|
||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-all'
|
||||
@@ -102,7 +102,7 @@ export function ForgotPasswordPage() {
|
||||
<div className="text-center">
|
||||
<Link
|
||||
to="/login"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Back to sign in
|
||||
</Link>
|
||||
|
||||
@@ -10,11 +10,11 @@ export default function GuideDetailPage() {
|
||||
if (!guide) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full text-center p-6 overflow-y-auto">
|
||||
<h2 className="text-lg font-heading font-semibold text-foreground mb-2">Guide Not Found</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">The guide you're looking for doesn't exist.</p>
|
||||
<h2 className="text-lg font-heading font-semibold text-[#e2e5eb] mb-2">Guide Not Found</h2>
|
||||
<p className="text-sm text-[#848b9b] mb-4">The guide you're looking for doesn't exist.</p>
|
||||
<Link
|
||||
to="/guides"
|
||||
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="bg-[#22d3ee] text-brand-dark font-semibold text-sm rounded-lg px-5 py-2 hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Back to Guides
|
||||
</Link>
|
||||
@@ -27,37 +27,37 @@ export default function GuideDetailPage() {
|
||||
return (
|
||||
<div className="overflow-y-auto h-full p-6 max-w-3xl mx-auto">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="flex items-center gap-1.5 text-xs text-muted-foreground mb-6">
|
||||
<Link to="/guides" className="hover:text-primary transition-colors">
|
||||
<nav className="flex items-center gap-1.5 text-xs text-[#848b9b] mb-6">
|
||||
<Link to="/guides" className="hover:text-[#22d3ee] transition-colors">
|
||||
User Guides
|
||||
</Link>
|
||||
<ChevronRight size={12} />
|
||||
<span className="text-foreground">{guide.title}</span>
|
||||
<span className="text-[#e2e5eb]">{guide.title}</span>
|
||||
</nav>
|
||||
|
||||
{/* Header */}
|
||||
<div className="glass-card-static rounded-2xl p-6 mb-6">
|
||||
<div className="card-flat rounded-2xl p-6 mb-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10">
|
||||
<Icon size={20} className="text-primary" />
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[rgba(34,211,238,0.10)]">
|
||||
<Icon size={20} className="text-[#22d3ee]" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-heading font-bold text-foreground">{guide.title}</h1>
|
||||
<p className="text-sm text-muted-foreground">{guide.summary}</p>
|
||||
<h1 className="text-xl font-heading font-bold text-[#e2e5eb]">{guide.title}</h1>
|
||||
<p className="text-sm text-[#848b9b]">{guide.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-4 pt-4 border-t" style={{ borderColor: 'var(--glass-border)' }}>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
||||
</span>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
{guide.sections.reduce((acc, s) => acc + s.steps.length, 0)} steps
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sections */}
|
||||
<div className="glass-card-static rounded-2xl p-6">
|
||||
<div className="card-flat rounded-2xl p-6">
|
||||
{guide.sections.map((section, i) => (
|
||||
<GuideSection key={i} section={section} index={i} />
|
||||
))}
|
||||
@@ -67,7 +67,7 @@ export default function GuideDetailPage() {
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
to="/guides"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#22d3ee] transition-colors"
|
||||
>
|
||||
<ArrowLeft size={14} />
|
||||
Back to all guides
|
||||
|
||||
@@ -11,12 +11,12 @@ export default function GuidesHubPage() {
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10">
|
||||
<BookOpen size={20} className="text-primary" />
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[rgba(34,211,238,0.10)]">
|
||||
<BookOpen size={20} className="text-[#22d3ee]" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">User Guides</h1>
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">User Guides</h1>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground ml-[52px]">
|
||||
<p className="text-sm text-[#848b9b] ml-[52px]">
|
||||
Learn how to use ResolutionFlow with step-by-step instructions for every feature.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -180,8 +180,8 @@ export default function KBAcceleratorPage() {
|
||||
<div className="flex flex-col h-full min-h-0 p-6">
|
||||
{/* Page title */}
|
||||
<div className="shrink-0 flex items-center gap-3 mb-6">
|
||||
<Sparkles size={24} className="text-primary" />
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">KB Accelerator</h1>
|
||||
<Sparkles size={24} className="text-[#22d3ee]" />
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">KB Accelerator</h1>
|
||||
</div>
|
||||
|
||||
{/* Phases */}
|
||||
@@ -196,12 +196,12 @@ export default function KBAcceleratorPage() {
|
||||
|
||||
{phase === 'processing' && (
|
||||
<div className="flex-1 flex flex-col items-center justify-center gap-4">
|
||||
<Loader2 size={40} className="text-primary animate-spin" />
|
||||
<Loader2 size={40} className="text-[#22d3ee] animate-spin" />
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-heading font-semibold text-foreground">
|
||||
<p className="text-lg font-heading font-semibold text-[#e2e5eb]">
|
||||
Converting your KB article...
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
<p className="text-sm text-[#848b9b] mt-1">
|
||||
AI is analyzing your content and generating an interactive flow.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ export function LoginPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Sign In" description="Sign in to your ResolutionFlow account" />
|
||||
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#0c0d10] px-4">
|
||||
{/* Atmosphere orbs */}
|
||||
<div
|
||||
className="pointer-events-none fixed z-0"
|
||||
@@ -75,27 +75,27 @@ export function LoginPage() {
|
||||
<div className="mb-4 flex justify-center sm:mb-6">
|
||||
<BrandLogo size="lg" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
|
||||
<span>Resolution</span><span className="text-gradient-brand">Flow</span>
|
||||
<h1 className="text-3xl font-bold font-heading text-[#e2e5eb] tracking-tight">
|
||||
<span>Resolution</span><span className="text-[#67e8f9]">Flow</span>
|
||||
</h1>
|
||||
<p className="mt-2 text-base font-medium text-muted-foreground sm:mt-3 sm:text-lg">
|
||||
<p className="mt-2 text-base font-medium text-[#848b9b] sm:mt-3 sm:text-lg">
|
||||
AI-Powered Troubleshooting for MSPs
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground/70 sm:mt-2">
|
||||
<p className="mt-1 text-sm text-[#848b9b]/70 sm:mt-2">
|
||||
Sign in to your account
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6" data-testid="login-form">
|
||||
<div className="glass-card-static p-6 space-y-4">
|
||||
<div className="card-flat p-6 space-y-4">
|
||||
{(error || localError) && (
|
||||
<div className="rounded-[10px] border border-rose-500/20 bg-rose-500/10 p-3 text-sm text-rose-400">
|
||||
<div className="rounded-lg border border-rose-500/20 bg-rose-500/10 p-3 text-sm text-rose-400">
|
||||
{localError || error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-[#e2e5eb]">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
@@ -107,8 +107,8 @@ export function LoginPage() {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2.5',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -117,7 +117,7 @@ export function LoginPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-[#e2e5eb]">
|
||||
Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -128,8 +128,8 @@ export function LoginPage() {
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2.5',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -138,7 +138,7 @@ export function LoginPage() {
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<Link to="/forgot-password" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
||||
<Link to="/forgot-password" className="text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
@@ -148,8 +148,8 @@ export function LoginPage() {
|
||||
disabled={isLoading}
|
||||
data-testid="login-submit"
|
||||
className={cn(
|
||||
'w-full rounded-[10px] px-4 py-2.5 text-sm font-semibold',
|
||||
'bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
||||
'w-full rounded-lg px-4 py-2.5 text-sm font-semibold',
|
||||
'bg-[#22d3ee] text-brand-dark hover:brightness-110 active:scale-[0.98]',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-background',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-all'
|
||||
@@ -159,9 +159,9 @@ export function LoginPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
<p className="text-center text-sm text-[#848b9b]">
|
||||
Don't have an account?{' '}
|
||||
<Link to="/register" className="font-medium text-foreground hover:underline">
|
||||
<Link to="/register" className="font-medium text-[#e2e5eb] hover:underline">
|
||||
Register
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function MaintenanceFlowDetailPage() {
|
||||
action={(
|
||||
<button
|
||||
onClick={() => navigate('/trees?type=maintenance')}
|
||||
className="rounded-md border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md border border-[#1e2130] px-4 py-2 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Back to Maintenance Flows
|
||||
</button>
|
||||
@@ -148,7 +148,7 @@ export default function MaintenanceFlowDetailPage() {
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => navigate(`/flows/${id}/edit`)}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-[0.875rem] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-[#1e2130] px-3 py-2 text-[0.875rem] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Settings className="h-3.5 w-3.5" />
|
||||
Edit Flow
|
||||
@@ -156,7 +156,7 @@ export default function MaintenanceFlowDetailPage() {
|
||||
<button
|
||||
onClick={handleRun}
|
||||
disabled={isRunning}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-4 py-2 text-[0.875rem] font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-70"
|
||||
className="flex items-center gap-1.5 rounded-lg bg-[#22d3ee] px-4 py-2 text-[0.875rem] font-medium text-white hover:brightness-110 disabled:opacity-70"
|
||||
>
|
||||
{isRunning
|
||||
? <Spinner size="sm" className="h-3.5 w-3.5 border-white border-t-transparent" />
|
||||
@@ -165,7 +165,7 @@ export default function MaintenanceFlowDetailPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowBatchModal(true)}
|
||||
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-[0.875rem] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex items-center gap-1.5 rounded-lg border border-[#1e2130] px-3 py-2 text-[0.875rem] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Batch Launch
|
||||
</button>
|
||||
@@ -174,51 +174,51 @@ export default function MaintenanceFlowDetailPage() {
|
||||
/>
|
||||
|
||||
{/* Schedule Panel */}
|
||||
<div className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="rounded-xl border border-[#1e2130] bg-[#14161d] p-5">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="font-semibold text-foreground">Schedule</h2>
|
||||
<Calendar className="h-4 w-4 text-[#848b9b]" />
|
||||
<h2 className="font-semibold text-[#e2e5eb]">Schedule</h2>
|
||||
</div>
|
||||
{schedule ? (
|
||||
<div className="space-y-2 text-[0.875rem]">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide",
|
||||
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wide",
|
||||
schedule.is_active
|
||||
? "bg-emerald-500/10 text-emerald-400"
|
||||
: "bg-muted text-muted-foreground"
|
||||
: "bg-muted text-[#848b9b]"
|
||||
)}>
|
||||
{schedule.is_active
|
||||
? <CheckCircle className="h-3 w-3" />
|
||||
: <AlertCircle className="h-3 w-3" />}
|
||||
{schedule.is_active ? 'Active' : 'Paused'}
|
||||
</span>
|
||||
<code className="rounded bg-accent px-1.5 py-0.5 text-[0.8125rem] text-foreground">
|
||||
<code className="rounded bg-accent px-1.5 py-0.5 text-[0.8125rem] text-[#e2e5eb]">
|
||||
{schedule.cron_expression}
|
||||
</code>
|
||||
<span className="text-muted-foreground">({schedule.timezone})</span>
|
||||
<span className="text-[#848b9b]">({schedule.timezone})</span>
|
||||
</div>
|
||||
{schedule.next_run_at && (
|
||||
<p className="text-muted-foreground">
|
||||
<p className="text-[#848b9b]">
|
||||
Next run: {new Date(schedule.next_run_at).toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-[0.875rem] text-muted-foreground">
|
||||
<p className="text-[0.875rem] text-[#848b9b]">
|
||||
No schedule configured. Sessions can still be launched manually via Batch Launch.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Run History */}
|
||||
<div className="rounded-xl border border-border bg-card p-5">
|
||||
<div className="rounded-xl border border-[#1e2130] bg-[#14161d] p-5">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<h2 className="font-semibold text-foreground">Run History</h2>
|
||||
<Clock className="h-4 w-4 text-[#848b9b]" />
|
||||
<h2 className="font-semibold text-[#e2e5eb]">Run History</h2>
|
||||
</div>
|
||||
{batches.length === 0 ? (
|
||||
<p className="text-[0.875rem] text-muted-foreground">No runs yet. Launch a batch to get started.</p>
|
||||
<p className="text-[0.875rem] text-[#848b9b]">No runs yet. Launch a batch to get started.</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{batches.map(([batchKey, batchSessions]) => {
|
||||
@@ -253,25 +253,25 @@ export default function MaintenanceFlowDetailPage() {
|
||||
<button
|
||||
key={batchKey}
|
||||
onClick={handleRowClick}
|
||||
className="w-full flex items-center justify-between rounded-lg border border-border px-4 py-3 hover:bg-accent transition-colors text-left"
|
||||
className="w-full flex items-center justify-between rounded-lg border border-[#1e2130] px-4 py-3 hover:bg-accent transition-colors text-left"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isActive && (
|
||||
<span className="inline-block h-2 w-2 rounded-full bg-amber-400 animate-pulse" />
|
||||
)}
|
||||
<p className="text-[0.875rem] font-medium text-foreground">
|
||||
<p className="text-[0.875rem] font-medium text-[#e2e5eb]">
|
||||
{isSingleRun ? 'Manual run' : `${total} target${total !== 1 ? 's' : ''}`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-0.5">
|
||||
{date && (
|
||||
<p className="text-[0.8125rem] text-muted-foreground">
|
||||
<p className="text-[0.8125rem] text-[#848b9b]">
|
||||
{new Date(date).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
{outcomeParts.length > 0 && (
|
||||
<p className="text-[0.8125rem] text-muted-foreground">
|
||||
<p className="text-[0.8125rem] text-[#848b9b]">
|
||||
· {outcomeParts.join(' · ')}
|
||||
</p>
|
||||
)}
|
||||
@@ -290,13 +290,13 @@ export default function MaintenanceFlowDetailPage() {
|
||||
/>
|
||||
))}
|
||||
{extraDots > 0 && (
|
||||
<span className="ml-1 text-[0.6875rem] text-muted-foreground">+{extraDots}</span>
|
||||
<span className="ml-1 text-[0.6875rem] text-[#848b9b]">+{extraDots}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span className={cn(
|
||||
"font-label text-[0.75rem] uppercase tracking-wide",
|
||||
isActive ? "text-amber-400" : completed === total ? "text-emerald-400" : "text-muted-foreground"
|
||||
"font-sans text-xs text-[0.75rem] uppercase tracking-wide",
|
||||
isActive ? "text-amber-400" : completed === total ? "text-emerald-400" : "text-[#848b9b]"
|
||||
)}>
|
||||
{isActive ? 'In Progress' : `${completed}/${total}`}
|
||||
</span>
|
||||
|
||||
@@ -76,7 +76,7 @@ export default function MyAnalyticsPage() {
|
||||
action={
|
||||
<Link
|
||||
to="/trees"
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-5 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Run Your First Session
|
||||
</Link>
|
||||
@@ -96,16 +96,16 @@ export default function MyAnalyticsPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span title="My Analytics">
|
||||
<BarChart3 size={24} className="text-foreground" />
|
||||
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||
</span>
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground">My Analytics</h1>
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb]">My Analytics</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{(isAccountOwner || isSuperAdmin) && (
|
||||
<Link
|
||||
to="/analytics"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Team Analytics
|
||||
</Link>
|
||||
@@ -113,7 +113,7 @@ export default function MyAnalyticsPage() {
|
||||
<select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
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-primary/20"
|
||||
>
|
||||
{PERIOD_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
@@ -149,8 +149,8 @@ export default function MyAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Area Chart — Sessions over Time */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
My Sessions Over Time
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
@@ -236,7 +236,7 @@ export default function MyAnalyticsPage() {
|
||||
className="h-2.5 w-2.5 rounded-full"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground capitalize">
|
||||
<span className="text-xs text-[#848b9b] capitalize">
|
||||
{key}
|
||||
</span>
|
||||
</div>
|
||||
@@ -247,27 +247,27 @@ export default function MyAnalyticsPage() {
|
||||
{/* Two-Column: Top Flows & Outcome Distribution */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* My Top Flows */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
My Top Flows
|
||||
</h2>
|
||||
{top_flows.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No flow data for this period.</p>
|
||||
<p className="text-sm text-[#848b9b]">No flow data for this period.</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left py-2 text-foreground font-medium">
|
||||
<tr className="border-b border-[#1e2130]">
|
||||
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Sessions
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Completion
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Median
|
||||
</th>
|
||||
</tr>
|
||||
@@ -276,18 +276,18 @@ export default function MyAnalyticsPage() {
|
||||
{top_flows.map((flow) => (
|
||||
<tr
|
||||
key={flow.tree_id}
|
||||
className="border-b border-border last:border-0"
|
||||
className="border-b border-[#1e2130] last:border-0"
|
||||
>
|
||||
<td className="py-2 text-muted-foreground truncate max-w-[200px]">
|
||||
<td className="py-2 text-[#848b9b] truncate max-w-[200px]">
|
||||
{flow.name}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{flow.sessions}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{(flow.completion_rate * 100).toFixed(1)}%
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{flow.median_duration_minutes} min
|
||||
</td>
|
||||
</tr>
|
||||
@@ -299,12 +299,12 @@ export default function MyAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Outcome Distribution */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Outcome Distribution
|
||||
</h2>
|
||||
{summary.total_sessions === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No session data for this period.</p>
|
||||
<p className="text-sm text-[#848b9b]">No session data for this period.</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{(
|
||||
@@ -327,11 +327,11 @@ export default function MyAnalyticsPage() {
|
||||
OUTCOME_COLORS[outcome] ?? '#94a3b8',
|
||||
}}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground capitalize">
|
||||
<span className="text-sm text-[#848b9b] capitalize">
|
||||
{outcome}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-sm text-foreground font-medium">
|
||||
<span className="text-sm text-[#e2e5eb] font-medium">
|
||||
{count} ({pct.toFixed(1)}%)
|
||||
</span>
|
||||
</div>
|
||||
@@ -366,12 +366,12 @@ function StatCard({
|
||||
value: string
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon size={16} className="text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">{label}</span>
|
||||
<Icon size={16} className="text-[#848b9b]" />
|
||||
<span className="text-sm text-[#848b9b]">{label}</span>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-foreground">{value}</p>
|
||||
<p className="text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function MySharesPage() {
|
||||
if (error) {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div className="bg-card border border-red-400/20 rounded-xl p-6">
|
||||
<div className="bg-[#14161d] border border-red-400/20 rounded-xl p-6">
|
||||
<div className="text-center">
|
||||
<p className="text-red-400 text-sm mb-4">{error}</p>
|
||||
<Button onClick={fetchShares}>
|
||||
@@ -129,7 +129,7 @@ export default function MySharesPage() {
|
||||
{/* Back link */}
|
||||
<Link
|
||||
to="/sessions"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-6"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors mb-6"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to sessions
|
||||
@@ -137,8 +137,8 @@ export default function MySharesPage() {
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">My Shared Sessions</h1>
|
||||
<p className="text-muted-foreground mt-1">Manage your session share links</p>
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">My Shared Sessions</h1>
|
||||
<p className="text-[#848b9b] mt-1">Manage your session share links</p>
|
||||
</div>
|
||||
|
||||
{/* Empty state */}
|
||||
@@ -161,10 +161,10 @@ export default function MySharesPage() {
|
||||
const isCopied = copiedId === share.id
|
||||
|
||||
return (
|
||||
<div key={share.id} className="bg-card border border-border rounded-xl p-5">
|
||||
<div key={share.id} className="bg-[#14161d] border border-[#1e2130] rounded-xl p-5">
|
||||
{/* Top row: badge + name */}
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<span className="inline-flex items-center gap-1.5 text-xs rounded-full px-2 py-0.5 bg-accent text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1.5 text-xs rounded-full px-2 py-0.5 bg-accent text-[#848b9b]">
|
||||
{share.visibility === 'public' ? (
|
||||
<Globe className="h-3 w-3" />
|
||||
) : (
|
||||
@@ -172,18 +172,18 @@ export default function MySharesPage() {
|
||||
)}
|
||||
{share.visibility === 'public' ? 'Public' : 'Account Only'}
|
||||
</span>
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
<span className="text-sm font-medium text-[#e2e5eb]">
|
||||
{share.share_name || 'Untitled share'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Session info */}
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
<p className="text-sm text-[#848b9b] mb-2">
|
||||
Session ID: {share.session_id.slice(0, 8)}...
|
||||
</p>
|
||||
|
||||
{/* Meta row */}
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground mb-4">
|
||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[#848b9b] mb-4">
|
||||
<span>Created {formatRelativeTime(share.created_at)}</span>
|
||||
<span className="hidden sm:inline">·</span>
|
||||
<span>
|
||||
@@ -214,7 +214,7 @@ export default function MySharesPage() {
|
||||
|
||||
<Link
|
||||
to={`/sessions/${share.session_id}`}
|
||||
className="inline-flex items-center gap-1.5 border border-border text-muted-foreground hover:bg-accent rounded-md px-3 py-1.5 text-sm transition-colors"
|
||||
className="inline-flex items-center gap-1.5 border border-[#1e2130] text-[#848b9b] hover:bg-accent rounded-md px-3 py-1.5 text-sm transition-colors"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
View Session
|
||||
|
||||
@@ -123,8 +123,8 @@ export function MyTreesPage() {
|
||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div className="mb-6 flex items-center justify-between sm:mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">My Flows</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">My Flows</h1>
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Your forked and custom flows
|
||||
</p>
|
||||
</div>
|
||||
@@ -140,38 +140,38 @@ export function MyTreesPage() {
|
||||
{showCreateMenu && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-10" onClick={() => setShowCreateMenu(false)} />
|
||||
<div className="absolute right-0 z-20 mt-1 w-56 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
|
||||
<div className="absolute right-0 z-20 mt-1 w-56 rounded-lg border border-[#1e2130] bg-[#14161d] p-1 shadow-xl">
|
||||
<Link
|
||||
to="/trees/new"
|
||||
onClick={() => setShowCreateMenu(false)}
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||
>
|
||||
<FolderTree className="h-4 w-4 text-muted-foreground" />
|
||||
<FolderTree className="h-4 w-4 text-[#848b9b]" />
|
||||
<div>
|
||||
<div className="font-medium">Troubleshooting Tree</div>
|
||||
<div className="text-xs text-muted-foreground">Branching decision flow</div>
|
||||
<div className="text-xs text-[#848b9b]">Branching decision flow</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
to="/flows/new"
|
||||
onClick={() => setShowCreateMenu(false)}
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||
>
|
||||
<ListOrdered className="h-4 w-4 text-muted-foreground" />
|
||||
<ListOrdered className="h-4 w-4 text-[#848b9b]" />
|
||||
<div>
|
||||
<div className="font-medium">Procedural Flow</div>
|
||||
<div className="text-xs text-muted-foreground">Step-by-step procedure</div>
|
||||
<div className="text-xs text-[#848b9b]">Step-by-step procedure</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
to="/flows/new?type=maintenance"
|
||||
onClick={() => setShowCreateMenu(false)}
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-foreground hover:bg-accent"
|
||||
className="flex items-center gap-3 rounded-md px-3 py-2.5 text-sm text-[#e2e5eb] hover:bg-accent"
|
||||
>
|
||||
<Wrench className="h-4 w-4 text-amber-400" />
|
||||
<div>
|
||||
<div className="font-medium">Maintenance Flow</div>
|
||||
<div className="text-xs text-muted-foreground">Scheduled multi-target tasks</div>
|
||||
<div className="text-xs text-[#848b9b]">Scheduled multi-target tasks</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -187,18 +187,18 @@ export function MyTreesPage() {
|
||||
<Spinner />
|
||||
</div>
|
||||
) : trees.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border bg-accent px-4 py-12 text-center">
|
||||
<FolderTree className="mx-auto mb-4 h-12 w-12 text-muted-foreground" />
|
||||
<h2 className="mb-2 text-lg font-semibold text-foreground">No personal flows yet</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
<div className="rounded-lg border border-dashed border-[#1e2130] bg-accent px-4 py-12 text-center">
|
||||
<FolderTree className="mx-auto mb-4 h-12 w-12 text-[#848b9b]" />
|
||||
<h2 className="mb-2 text-lg font-semibold text-[#e2e5eb]">No personal flows yet</h2>
|
||||
<p className="mb-4 text-sm text-[#848b9b]">
|
||||
Fork a flow from the library to customize it for your workflow
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<Link
|
||||
to="/trees"
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-4 py-2 text-sm font-medium',
|
||||
'hover:opacity-90'
|
||||
'inline-flex items-center gap-2 rounded-md bg-[#22d3ee] text-white px-4 py-2 text-sm font-medium',
|
||||
'hover:brightness-110'
|
||||
)}
|
||||
>
|
||||
Browse Library
|
||||
@@ -207,8 +207,8 @@ export function MyTreesPage() {
|
||||
<Link
|
||||
to="/trees/new"
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'inline-flex items-center gap-2 rounded-md border border-[#1e2130] px-4 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
@@ -222,18 +222,18 @@ export function MyTreesPage() {
|
||||
{trees.map((tree) => (
|
||||
<div
|
||||
key={tree.id}
|
||||
className="bg-card border border-border rounded-xl p-4 transition-all hover:border-border/80 sm:p-6"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 transition-all hover:border-[#1e2130]/80 sm:p-6"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="mb-3 flex items-start justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{tree.tree_type === 'procedural' && (
|
||||
<ListOrdered className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<ListOrdered className="h-4 w-4 shrink-0 text-[#848b9b]" />
|
||||
)}
|
||||
{tree.tree_type === 'maintenance' && (
|
||||
<Wrench className="h-4 w-4 shrink-0 text-amber-400" />
|
||||
)}
|
||||
<h3 className="font-semibold text-foreground">{tree.name}</h3>
|
||||
<h3 className="font-semibold text-[#e2e5eb]">{tree.name}</h3>
|
||||
{tree.parent_tree_id && (
|
||||
<span className="shrink-0 rounded-full bg-violet-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-violet-400">
|
||||
Fork
|
||||
@@ -252,7 +252,7 @@ export function MyTreesPage() {
|
||||
</span>
|
||||
)}
|
||||
{tree.category_info && (
|
||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
|
||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]">
|
||||
{tree.category_info.name}
|
||||
</span>
|
||||
)}
|
||||
@@ -260,19 +260,19 @@ export function MyTreesPage() {
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mb-3 text-sm text-muted-foreground line-clamp-2">
|
||||
<p className="mb-3 text-sm text-[#848b9b] line-clamp-2">
|
||||
{tree.description || 'No description available'}
|
||||
</p>
|
||||
|
||||
{/* Fork Badge */}
|
||||
{tree.parent_tree_id && (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-md bg-accent px-2 py-1.5 text-sm">
|
||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">
|
||||
<GitBranch className="h-4 w-4 text-[#848b9b]" />
|
||||
<span className="text-[#848b9b]">
|
||||
Forked from{' '}
|
||||
<Link
|
||||
to={`/trees/${tree.parent_tree_id}/navigate`}
|
||||
className="font-medium text-foreground hover:underline"
|
||||
className="font-medium text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
original
|
||||
</Link>
|
||||
@@ -288,7 +288,7 @@ export function MyTreesPage() {
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="mb-4 flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<div className="mb-4 flex items-center gap-4 text-xs text-[#848b9b]">
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-3.5 w-3.5" />
|
||||
<span>{formatDate(tree.lastUsed)}</span>
|
||||
@@ -312,8 +312,8 @@ export function MyTreesPage() {
|
||||
<Link
|
||||
to={getEditPath(tree)}
|
||||
className={cn(
|
||||
'rounded-md border border-border p-2 text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
title="Edit tree"
|
||||
>
|
||||
|
||||
@@ -5,36 +5,36 @@ export default function PrivacyPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Privacy Policy" description="ResolutionFlow Privacy Policy" />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/landing" className="text-sm text-[#848b9b] hover:text-[#e2e5eb] mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-8">Privacy Policy</h1>
|
||||
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
|
||||
<p className="text-[#848b9b] mb-6">Last updated: March 21, 2026</p>
|
||||
|
||||
<div className="space-y-6 text-muted-foreground leading-relaxed">
|
||||
<div className="space-y-6 text-[#848b9b] leading-relaxed">
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">1. Information We Collect</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">1. Information We Collect</h2>
|
||||
<p>When you use ResolutionFlow, we collect information you provide directly: your name, email address, and account credentials. We also collect usage data including session logs, flow configurations, and troubleshooting documentation generated through the platform.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">2. How We Use Your Information</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">2. How We Use Your Information</h2>
|
||||
<p>We use your information to provide and improve ResolutionFlow, including: operating your account, generating documentation from troubleshooting sessions, providing AI-powered guidance through FlowPilot, and communicating with you about the service.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">3. Data Storage & Security</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">3. Data Storage & Security</h2>
|
||||
<p>Your data is stored securely using industry-standard encryption. PSA credentials (ConnectWise, Atera, Syncro) are encrypted at rest. We do not sell your data to third parties.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">4. AI & Data Processing</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">4. AI & Data Processing</h2>
|
||||
<p>FlowPilot uses AI models to provide troubleshooting guidance. Session data sent to AI providers is used only for generating responses within your session and is not used to train AI models.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">5. Contact</h2>
|
||||
<p>Questions about this policy? Email us at <a href="mailto:hello@resolutionflow.com" className="text-primary hover:underline">hello@resolutionflow.com</a>.</p>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">5. Contact</h2>
|
||||
<p>Questions about this policy? Email us at <a href="mailto:hello@resolutionflow.com" className="text-[#22d3ee] hover:underline">hello@resolutionflow.com</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -265,37 +265,37 @@ export function ProceduralEditorPage() {
|
||||
{/* Main content column */}
|
||||
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
{/* Toolbar — sticky */}
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-border bg-card px-4 py-2">
|
||||
<div className="flex shrink-0 items-center justify-between border-b border-[#1e2130] bg-[#14161d] px-4 py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => navigate('/trees')}
|
||||
className="rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
{isMaintenance
|
||||
? <Wrench className="h-5 w-5 text-amber-400" />
|
||||
: <ListOrdered className="h-5 w-5 text-muted-foreground" />}
|
||||
<h1 className="text-lg font-bold text-foreground">
|
||||
: <ListOrdered className="h-5 w-5 text-[#848b9b]" />}
|
||||
<h1 className="text-lg font-bold text-[#e2e5eb]">
|
||||
{isEditMode ? `Edit ${flowLabel}` : `New ${flowLabel}`}
|
||||
{name && <span className="ml-2 font-normal text-muted-foreground">— {name}</span>}
|
||||
{name && <span className="ml-2 font-normal text-[#848b9b]">— {name}</span>}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{isDirty && (
|
||||
<span className="text-xs text-muted-foreground">Unsaved changes</span>
|
||||
<span className="text-xs text-[#848b9b]">Unsaved changes</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => editorAI.isOpen ? editorAI.closePanel() : editorAI.openPanel()}
|
||||
title="Toggle AI Assist panel"
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-1.5 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium transition-colors',
|
||||
editorAI.isOpen
|
||||
? 'bg-primary/10 text-primary border-primary/30'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/30'
|
||||
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
@@ -329,40 +329,40 @@ export function ProceduralEditorPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g. Domain Controller Build"
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Description</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Brief description of this procedure..."
|
||||
rows={2}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Tags</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Tags</label>
|
||||
<TagInput tags={tags} onChange={setTags} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-end pb-1">
|
||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<label className="flex items-center gap-2 text-sm text-[#848b9b]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isPublic}
|
||||
onChange={(e) => setIsPublic(e.target.checked)}
|
||||
className="rounded border-border"
|
||||
className="rounded border-[#1e2130]"
|
||||
/>
|
||||
Public (visible to all users)
|
||||
</label>
|
||||
|
||||
@@ -630,17 +630,17 @@ export function ProceduralNavigationPage() {
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col overflow-hidden">
|
||||
{/* Top bar */}
|
||||
<div className="border-b border-border px-4 py-3 sm:px-6">
|
||||
<div className="border-b border-[#1e2130] px-4 py-3 sm:px-6">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground lg:hidden"
|
||||
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] lg:hidden"
|
||||
>
|
||||
{sidebarOpen ? <ChevronLeft className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||
</button>
|
||||
<ListOrdered className="h-5 w-5 text-muted-foreground" />
|
||||
<h1 className="text-sm font-semibold text-foreground sm:text-base">{tree.name}</h1>
|
||||
<ListOrdered className="h-5 w-5 text-[#848b9b]" />
|
||||
<h1 className="text-sm font-semibold text-[#e2e5eb] sm:text-base">{tree.name}</h1>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -650,7 +650,7 @@ export function ProceduralNavigationPage() {
|
||||
navigate('/trees')
|
||||
}
|
||||
}}
|
||||
className="rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
className="rounded-md border border-[#1e2130] px-3 py-1.5 text-xs font-medium text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
<X className="mr-1 inline h-3.5 w-3.5" />
|
||||
Exit
|
||||
@@ -693,7 +693,7 @@ export function ProceduralNavigationPage() {
|
||||
{/* Left sidebar - step checklist */}
|
||||
<div
|
||||
className={cn(
|
||||
'min-h-0 border-r border-border bg-card transition-all duration-200',
|
||||
'min-h-0 border-r border-[#1e2130] bg-[#14161d] transition-all duration-200',
|
||||
sidebarOpen ? 'w-72 overflow-y-auto p-3' : 'w-0 overflow-hidden p-0'
|
||||
)}
|
||||
>
|
||||
@@ -708,7 +708,7 @@ export function ProceduralNavigationPage() {
|
||||
|
||||
{/* PSA Ticket Context Panel */}
|
||||
{session?.psa_ticket_id && (
|
||||
<div className="mt-3 border-t border-border pt-3">
|
||||
<div className="mt-3 border-t border-[#1e2130] pt-3">
|
||||
<TicketContextPanel
|
||||
context={ticketContext}
|
||||
loading={ticketContextLoading}
|
||||
@@ -720,10 +720,10 @@ export function ProceduralNavigationPage() {
|
||||
|
||||
{/* Session Variables button */}
|
||||
{intakeFields.length > 0 && (
|
||||
<div className="mt-3 border-t border-border pt-3">
|
||||
<div className="mt-3 border-t border-[#1e2130] pt-3">
|
||||
<button
|
||||
onClick={() => setParamsOpen(true)}
|
||||
className="flex w-full items-center gap-2 rounded-lg border border-border px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-muted-foreground"
|
||||
className="flex w-full items-center gap-2 rounded-lg border border-[#1e2130] px-3 py-2 text-xs text-[#848b9b] hover:bg-accent hover:text-[#848b9b]"
|
||||
>
|
||||
<Settings2 className="h-3.5 w-3.5" />
|
||||
Session Variables
|
||||
@@ -780,7 +780,7 @@ export function ProceduralNavigationPage() {
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => setShowCustomStepModal(true)}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-2.5 text-sm text-muted-foreground transition-colors hover:border-primary/50 hover:text-foreground"
|
||||
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-[#1e2130] px-4 py-2.5 text-sm text-[#848b9b] transition-colors hover:border-primary/50 hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Step
|
||||
@@ -845,15 +845,15 @@ export function ProceduralNavigationPage() {
|
||||
{paramsOpen && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div
|
||||
className="absolute inset-0 bg-background/60 backdrop-blur-xs"
|
||||
className="absolute inset-0 bg-[#0c0d10]/60"
|
||||
onClick={() => setParamsOpen(false)}
|
||||
/>
|
||||
<div className="relative w-full max-w-md rounded-2xl border border-border bg-card shadow-2xl backdrop-blur-xs">
|
||||
<div className="flex items-center justify-between border-b border-border px-5 py-4">
|
||||
<h3 className="text-sm font-semibold text-foreground">Session Variables</h3>
|
||||
<div className="relative w-full max-w-md rounded-2xl border border-[#1e2130] bg-[#14161d] shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||
<h3 className="text-sm font-semibold text-[#e2e5eb]">Session Variables</h3>
|
||||
<button
|
||||
onClick={() => setParamsOpen(false)}
|
||||
className="rounded-lg p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -872,7 +872,7 @@ export function ProceduralNavigationPage() {
|
||||
key={field.variable_name}
|
||||
className={cn(
|
||||
'rounded-lg border px-3 py-2.5',
|
||||
isFilled ? 'border-border bg-accent' : 'border-cyan-500/20 bg-cyan-500/5'
|
||||
isFilled ? 'border-[#1e2130] bg-accent' : 'border-cyan-500/20 bg-cyan-500/5'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -882,7 +882,7 @@ export function ProceduralNavigationPage() {
|
||||
) : (
|
||||
<AlertCircle className="h-3.5 w-3.5 shrink-0 text-cyan-400" />
|
||||
)}
|
||||
<span className="text-xs font-medium text-muted-foreground truncate">
|
||||
<span className="text-xs font-medium text-[#848b9b] truncate">
|
||||
{field.label}
|
||||
{field.required && <span className="ml-1 text-amber-400">*</span>}
|
||||
</span>
|
||||
@@ -890,7 +890,7 @@ export function ProceduralNavigationPage() {
|
||||
{!isEditing && (
|
||||
<button
|
||||
onClick={() => startEditingVar(field.variable_name)}
|
||||
className="shrink-0 rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-card hover:text-foreground"
|
||||
className="shrink-0 rounded px-2 py-0.5 text-xs text-[#848b9b] hover:bg-[#14161d] hover:text-[#e2e5eb]"
|
||||
>
|
||||
{isFilled ? 'Edit' : 'Fill'}
|
||||
</button>
|
||||
@@ -903,7 +903,7 @@ export function ProceduralNavigationPage() {
|
||||
value={editingVarValue}
|
||||
onChange={(e) => setEditingVarValue(e.target.value)}
|
||||
autoFocus
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-card px-2.5 py-1.5 text-sm text-foreground focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-[#14161d] px-2.5 py-1.5 text-sm text-[#e2e5eb] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
>
|
||||
<option value="">{field.placeholder || 'Select...'}</option>
|
||||
{field.options.map((opt) => (
|
||||
@@ -917,7 +917,7 @@ export function ProceduralNavigationPage() {
|
||||
autoFocus
|
||||
rows={3}
|
||||
placeholder={field.placeholder}
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-[#14161d] px-2.5 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
@@ -927,30 +927,30 @@ export function ProceduralNavigationPage() {
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') saveEditingVar() }}
|
||||
autoFocus
|
||||
placeholder={field.placeholder}
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-card px-2.5 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
className="w-full rounded-md border border-cyan-500/30 bg-[#14161d] px-2.5 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-cyan-400 focus:outline-hidden focus:ring-1 focus:ring-cyan-400/30"
|
||||
/>
|
||||
)}
|
||||
{field.help_text && (
|
||||
<p className="mt-1 text-[0.625rem] text-muted-foreground">{field.help_text}</p>
|
||||
<p className="mt-1 text-[0.625rem] text-[#848b9b]">{field.help_text}</p>
|
||||
)}
|
||||
<div className="mt-2 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => { setEditingVarName(null); setEditingVarValue('') }}
|
||||
className="rounded px-2.5 py-1 text-xs text-muted-foreground hover:text-foreground"
|
||||
className="rounded px-2.5 py-1 text-xs text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={saveEditingVar}
|
||||
disabled={!editingVarValue.trim()}
|
||||
className="rounded bg-gradient-brand px-2.5 py-1 text-xs font-medium text-[#101114] disabled:opacity-40"
|
||||
className="rounded bg-[#22d3ee] px-2.5 py-1 text-xs font-medium text-white disabled:opacity-40"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : isFilled ? (
|
||||
<p className="mt-1 text-sm text-foreground truncate">{value}</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb] truncate">{value}</p>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
@@ -961,8 +961,8 @@ export function ProceduralNavigationPage() {
|
||||
.filter(([key]) => !intakeFields.some(f => f.variable_name === key))
|
||||
.map(([key, value]) => (
|
||||
<div key={key} className="flex items-baseline justify-between gap-4 rounded-lg bg-accent px-3 py-2">
|
||||
<span className="text-xs font-medium text-muted-foreground">{key.replace(/_/g, ' ')}</span>
|
||||
<span className="text-right text-sm text-muted-foreground">{value || 'N/A'}</span>
|
||||
<span className="text-xs font-medium text-[#848b9b]">{key.replace(/_/g, ' ')}</span>
|
||||
<span className="text-right text-sm text-[#848b9b]">{value || 'N/A'}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function PublicTemplatesPage() {
|
||||
description="Browse battle-tested troubleshooting flows and scripts built by MSP engineers. Free to explore."
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
{/* Ambient orbs */}
|
||||
<div
|
||||
className="fixed top-[-20%] right-[-10%] w-[600px] h-[600px] rounded-full pointer-events-none"
|
||||
@@ -172,25 +172,25 @@ export default function PublicTemplatesPage() {
|
||||
/>
|
||||
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-40 border-b border-border bg-background/80 backdrop-blur-xl">
|
||||
<header className="sticky top-0 z-40 border-b border-[#1e2130] bg-[#0c0d10]/80">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
||||
<Link to="/landing" className="flex items-center gap-2.5">
|
||||
<BrandLogo size="sm" />
|
||||
<span className="font-heading text-lg font-semibold">
|
||||
<span className="text-foreground">Resolution</span>
|
||||
<span className="text-gradient-brand">Flow</span>
|
||||
<span className="text-[#e2e5eb]">Resolution</span>
|
||||
<span className="text-[#67e8f9]">Flow</span>
|
||||
</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="/login"
|
||||
className="px-4 py-2 rounded-[10px] text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="px-4 py-2 rounded-lg text-sm font-medium text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
<Link
|
||||
to="/register"
|
||||
className="px-4 py-2 rounded-[10px] text-sm font-semibold bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="px-4 py-2 rounded-lg text-sm font-semibold bg-[#22d3ee] text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Sign Up Free
|
||||
</Link>
|
||||
@@ -203,21 +203,21 @@ export default function PublicTemplatesPage() {
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h1 className="font-heading text-4xl lg:text-5xl font-bold tracking-tight">
|
||||
MSP Troubleshooting{' '}
|
||||
<span className="text-gradient-brand">Templates</span>
|
||||
<span className="text-[#67e8f9]">Templates</span>
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-muted-foreground max-w-2xl mx-auto">
|
||||
<p className="mt-4 text-lg text-[#848b9b] max-w-2xl mx-auto">
|
||||
Battle-tested flows and scripts built by MSP engineers. Free to browse, powerful when connected to FlowPilot.
|
||||
</p>
|
||||
|
||||
{/* Search */}
|
||||
<div className="mt-8 max-w-xl mx-auto relative">
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
|
||||
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[#848b9b]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search templates..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
className="w-full pl-12 pr-4 py-3.5 rounded-2xl text-sm bg-card border border-border text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-[rgba(6,182,212,0.3)] transition-colors"
|
||||
className="w-full pl-12 pr-4 py-3.5 rounded-2xl text-sm bg-[#14161d] border border-[#1e2130] text-[#e2e5eb] placeholder:text-[#848b9b] focus:outline-none focus:border-[rgba(6,182,212,0.3)] transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -235,8 +235,8 @@ export default function PublicTemplatesPage() {
|
||||
className={cn(
|
||||
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
||||
!activeCategory
|
||||
? 'bg-primary/10 text-foreground border-primary/30'
|
||||
: 'bg-card border-border text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||
: 'bg-[#14161d] border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||
)}
|
||||
>
|
||||
All
|
||||
@@ -249,8 +249,8 @@ export default function PublicTemplatesPage() {
|
||||
className={cn(
|
||||
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
||||
cat === activeCategory
|
||||
? 'bg-primary/10 text-foreground border-primary/30'
|
||||
: 'bg-card border-border text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||
: 'bg-[#14161d] border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||
)}
|
||||
>
|
||||
{cat}
|
||||
@@ -262,7 +262,7 @@ export default function PublicTemplatesPage() {
|
||||
{/* Type toggle + Sort */}
|
||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||
{/* Type segmented control */}
|
||||
<div className="flex items-center rounded-xl border border-border bg-card overflow-hidden">
|
||||
<div className="flex items-center rounded-xl border border-[#1e2130] bg-[#14161d] overflow-hidden">
|
||||
{(['all', 'flows', 'scripts'] as TypeFilter[]).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
@@ -271,8 +271,8 @@ export default function PublicTemplatesPage() {
|
||||
className={cn(
|
||||
'px-4 py-2 text-sm font-medium transition-colors',
|
||||
t === typeFilter
|
||||
? 'bg-primary/10 text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
{t === 'all' ? 'All' : t === 'flows' ? 'Flows' : 'Scripts'}
|
||||
@@ -285,16 +285,16 @@ export default function PublicTemplatesPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSortOpen(!sortOpen)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-[10px] text-sm font-medium bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-colors"
|
||||
>
|
||||
<SlidersHorizontal className="w-4 h-4 text-muted-foreground" />
|
||||
<SlidersHorizontal className="w-4 h-4 text-[#848b9b]" />
|
||||
{sortLabels[sort]}
|
||||
<ChevronDown className={cn('w-4 h-4 text-muted-foreground transition-transform', sortOpen && 'rotate-180')} />
|
||||
<ChevronDown className={cn('w-4 h-4 text-[#848b9b] transition-transform', sortOpen && 'rotate-180')} />
|
||||
</button>
|
||||
{sortOpen && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-10" onClick={() => setSortOpen(false)} />
|
||||
<div className="absolute right-0 mt-1 z-20 w-52 rounded-xl border border-border bg-card shadow-lg shadow-black/30 overflow-hidden">
|
||||
<div className="absolute right-0 mt-1 z-20 w-52 rounded-xl border border-[#1e2130] bg-[#14161d] shadow-lg shadow-black/30 overflow-hidden">
|
||||
{(Object.entries(sortLabels) as [SortOption, string][]).map(([key, label]) => (
|
||||
<button
|
||||
key={key}
|
||||
@@ -306,8 +306,8 @@ export default function PublicTemplatesPage() {
|
||||
className={cn(
|
||||
'w-full text-left px-4 py-2.5 text-sm transition-colors',
|
||||
key === sort
|
||||
? 'bg-primary/10 text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)]'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb] hover:bg-[rgba(255,255,255,0.04)]'
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
@@ -328,7 +328,7 @@ export default function PublicTemplatesPage() {
|
||||
{loading && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className="glass-card-static p-5 space-y-3 animate-pulse">
|
||||
<div key={i} className="card-flat p-5 space-y-3 animate-pulse">
|
||||
<div className="h-5 bg-[rgba(255,255,255,0.06)] rounded w-3/4" />
|
||||
<div className="h-4 bg-[rgba(255,255,255,0.04)] rounded w-full" />
|
||||
<div className="h-4 bg-[rgba(255,255,255,0.04)] rounded w-2/3" />
|
||||
@@ -341,11 +341,11 @@ export default function PublicTemplatesPage() {
|
||||
{/* Error */}
|
||||
{!loading && error && (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-muted-foreground text-sm">{error}</p>
|
||||
<p className="text-[#848b9b] text-sm">{error}</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={fetchGallery}
|
||||
className="mt-4 px-4 py-2 rounded-[10px] text-sm font-medium bg-gradient-brand text-[#101114] hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="mt-4 px-4 py-2 rounded-lg text-sm font-medium bg-[#22d3ee] text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
@@ -355,7 +355,7 @@ export default function PublicTemplatesPage() {
|
||||
{/* Empty */}
|
||||
{!loading && !error && flows.length === 0 && scripts.length === 0 && (
|
||||
<div className="text-center py-16">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
<p className="text-[#848b9b] text-sm">
|
||||
No templates found. Try a different search or category.
|
||||
</p>
|
||||
</div>
|
||||
@@ -368,7 +368,7 @@ export default function PublicTemplatesPage() {
|
||||
{showFlows && flows.length > 0 && (
|
||||
<div>
|
||||
{typeFilter === 'all' && (
|
||||
<h2 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-4">
|
||||
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-4">
|
||||
Flows ({data?.total_flows || flows.length})
|
||||
</h2>
|
||||
)}
|
||||
@@ -388,7 +388,7 @@ export default function PublicTemplatesPage() {
|
||||
{showScripts && scripts.length > 0 && (
|
||||
<div>
|
||||
{typeFilter === 'all' && (
|
||||
<h2 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-4">
|
||||
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-4">
|
||||
Scripts ({data?.total_scripts || scripts.length})
|
||||
</h2>
|
||||
)}
|
||||
@@ -409,18 +409,18 @@ export default function PublicTemplatesPage() {
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-border py-8 px-4 sm:px-6 lg:px-8">
|
||||
<footer className="border-t border-[#1e2130] py-8 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
||||
<Link
|
||||
to="/landing"
|
||||
className="text-muted-foreground text-sm hover:text-foreground transition-colors"
|
||||
className="text-[#848b9b] text-sm hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Powered by <span className="font-semibold">ResolutionFlow</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-4">
|
||||
<Link
|
||||
to="/register"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</Link>
|
||||
@@ -431,7 +431,7 @@ export default function PublicTemplatesPage() {
|
||||
{/* Detail loading overlay */}
|
||||
{detailLoading && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
|
||||
<Loader2 className="w-8 h-8 text-primary animate-spin" />
|
||||
<Loader2 className="w-8 h-8 text-[#22d3ee] animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export function QuickStartPage() {
|
||||
<div className="p-6 space-y-5 max-w-5xl mx-auto">
|
||||
{/* Greeting */}
|
||||
<div className="fade-in" style={{ animationDelay: '100ms' }}>
|
||||
<h1 className="font-heading text-3xl font-extrabold tracking-tight text-foreground">
|
||||
<h1 className="font-heading text-3xl font-extrabold tracking-tight text-[#e2e5eb]">
|
||||
Good{' '}
|
||||
{new Date().getHours() < 12
|
||||
? 'morning'
|
||||
@@ -27,7 +27,7 @@ export function QuickStartPage() {
|
||||
: 'evening'}
|
||||
, {user?.name?.split(' ')[0] || 'there'}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
{new Date().toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
|
||||
@@ -88,19 +88,19 @@ export function RegisterPage() {
|
||||
<div className="mb-4 flex justify-center sm:mb-6">
|
||||
<BrandLogo size="lg" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
|
||||
<h1 className="text-3xl font-bold font-heading text-[#e2e5eb] tracking-tight">
|
||||
ResolutionFlow
|
||||
</h1>
|
||||
<p className="mt-2 text-base font-medium text-muted-foreground sm:mt-3 sm:text-lg">
|
||||
<p className="mt-2 text-base font-medium text-[#848b9b] sm:mt-3 sm:text-lg">
|
||||
AI-Powered Troubleshooting for MSPs
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground sm:mt-2">
|
||||
<p className="mt-1 text-sm text-[#848b9b] sm:mt-2">
|
||||
Create your account
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
{(error || localError) && (
|
||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
||||
{localError || error}
|
||||
@@ -108,7 +108,7 @@ export function RegisterPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="inviteCode" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="inviteCode" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Invite code
|
||||
</label>
|
||||
<input
|
||||
@@ -122,18 +122,18 @@ export function RegisterPage() {
|
||||
}}
|
||||
onBlur={(e) => validateInviteCode(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-xl border bg-card px-3 py-2 font-mono tracking-wider',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-xl border bg-[#14161d] px-3 py-2 font-mono tracking-wider',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:outline-hidden focus:ring-1',
|
||||
inviteCodeStatus === 'valid' && 'border-emerald-400/50 focus:border-emerald-400 focus:ring-emerald-400/30',
|
||||
inviteCodeStatus === 'invalid' && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/30',
|
||||
inviteCodeStatus === 'idle' && 'border-border focus:border-primary focus:ring-primary/20',
|
||||
inviteCodeStatus === 'checking' && 'border-border focus:border-primary focus:ring-primary/20'
|
||||
inviteCodeStatus === 'idle' && 'border-[#1e2130] focus:border-primary focus:ring-primary/20',
|
||||
inviteCodeStatus === 'checking' && 'border-[#1e2130] focus:border-primary focus:ring-primary/20'
|
||||
)}
|
||||
placeholder="ABCD1234"
|
||||
/>
|
||||
{inviteCodeStatus === 'checking' && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">Validating...</p>
|
||||
<p className="mt-1 text-xs text-[#848b9b]">Validating...</p>
|
||||
)}
|
||||
{inviteCodeStatus === 'valid' && (
|
||||
<p className="mt-1 text-xs text-emerald-400">{inviteCodeMessage}</p>
|
||||
@@ -144,7 +144,7 @@ export function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="name" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Full name
|
||||
</label>
|
||||
<input
|
||||
@@ -156,8 +156,8 @@ export function RegisterPage() {
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
placeholder="John Smith"
|
||||
@@ -165,7 +165,7 @@ export function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
@@ -177,8 +177,8 @@ export function RegisterPage() {
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
placeholder="you@example.com"
|
||||
@@ -186,7 +186,7 @@ export function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -197,19 +197,19 @@ export function RegisterPage() {
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
placeholder="••••••••••"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Must be at least 10 characters
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-foreground">
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Confirm password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -220,8 +220,8 @@ export function RegisterPage() {
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
placeholder="••••••••••"
|
||||
@@ -233,7 +233,7 @@ export function RegisterPage() {
|
||||
disabled={isLoading}
|
||||
className={cn(
|
||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-black',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-all'
|
||||
@@ -243,9 +243,9 @@ export function RegisterPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-sm text-muted-foreground">
|
||||
<p className="text-center text-sm text-[#848b9b]">
|
||||
Already have an account?{' '}
|
||||
<Link to="/login" className="font-medium text-foreground hover:underline">
|
||||
<Link to="/login" className="font-medium text-[#e2e5eb] hover:underline">
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
@@ -85,24 +85,24 @@ export function ResetPasswordPage() {
|
||||
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold font-heading text-foreground tracking-tight">
|
||||
<h1 className="text-3xl font-bold font-heading text-[#e2e5eb] tracking-tight">
|
||||
Reset Password
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{verifying ? (
|
||||
<div className="bg-card border border-border rounded-xl p-6 text-center">
|
||||
<p className="text-muted-foreground">Verifying reset link...</p>
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 text-center">
|
||||
<p className="text-[#848b9b]">Verifying reset link...</p>
|
||||
</div>
|
||||
) : !token || !valid ? (
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-4 text-sm text-red-400">
|
||||
This reset link is invalid or has expired. Please request a new one.
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Request new reset link
|
||||
</Link>
|
||||
@@ -110,10 +110,10 @@ export function ResetPasswordPage() {
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
||||
<div className="bg-card border border-border rounded-xl p-6 space-y-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 space-y-4">
|
||||
{email && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Resetting password for <span className="font-medium text-foreground">{email}</span>
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Resetting password for <span className="font-medium text-[#e2e5eb]">{email}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -124,7 +124,7 @@ export function ResetPasswordPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="new-password" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
New Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -134,20 +134,20 @@ export function ResetPasswordPage() {
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
placeholder="At least 10 characters"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Must include uppercase, lowercase, and a digit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-foreground">
|
||||
<label htmlFor="confirm-password" className="mb-1 block text-sm font-medium text-[#e2e5eb]">
|
||||
Confirm New Password
|
||||
</label>
|
||||
<PasswordInput
|
||||
@@ -157,8 +157,8 @@ export function ResetPasswordPage() {
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
className={cn(
|
||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||
'transition-colors'
|
||||
)}
|
||||
@@ -170,7 +170,7 @@ export function ResetPasswordPage() {
|
||||
disabled={isLoading}
|
||||
className={cn(
|
||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'focus:outline-hidden focus:ring-2 focus:ring-primary/30 focus:ring-offset-2 focus:ring-offset-black',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'transition-all'
|
||||
|
||||
@@ -89,20 +89,20 @@ export default function ReviewQueuePage() {
|
||||
{/* Left panel — Proposal list */}
|
||||
<div className="w-full shrink-0 border-b lg:border-b-0 lg:border-r lg:w-[380px] overflow-y-auto" style={{ borderColor: 'var(--glass-border)' }}>
|
||||
{/* Header */}
|
||||
<div className="sticky top-0 z-10 p-3 sm:p-4 space-y-3" style={{ background: 'rgba(16, 17, 20, 0.95)', backdropFilter: 'blur(12px)' }}>
|
||||
<div className="sticky top-0 z-10 p-3 sm:p-4 space-y-3" style={{ background: 'rgba(16, 17, 20, 0.95)', }}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Lightbulb size={16} className="text-amber-400" />
|
||||
<h1 className="font-heading text-base font-semibold text-foreground">Review Queue</h1>
|
||||
<h1 className="font-heading text-base font-semibold text-[#e2e5eb]">Review Queue</h1>
|
||||
</div>
|
||||
<button onClick={loadProposals} className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
<button onClick={loadProposals} className="text-[#848b9b] hover:text-[#e2e5eb] transition-colors">
|
||||
<RefreshCw size={14} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats bar */}
|
||||
{stats && (
|
||||
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-3 text-xs text-[#848b9b]">
|
||||
<span className="flex items-center gap-1">
|
||||
<Clock size={10} className="text-amber-400" />
|
||||
{stats.pending_count} pending
|
||||
@@ -112,7 +112,7 @@ export default function ReviewQueuePage() {
|
||||
{stats.approved_this_week} approved
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Sparkles size={10} className="text-primary" />
|
||||
<Sparkles size={10} className="text-[#22d3ee]" />
|
||||
{stats.auto_reinforced_this_week} reinforced
|
||||
</span>
|
||||
</div>
|
||||
@@ -126,8 +126,8 @@ export default function ReviewQueuePage() {
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className={`shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||
activeTab === tab.key
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
@@ -144,7 +144,7 @@ export default function ReviewQueuePage() {
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy(e.target.value)}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-1.5 text-xs text-foreground"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-xs text-[#e2e5eb]"
|
||||
>
|
||||
<option value="newest">Newest first</option>
|
||||
<option value="confidence">Highest confidence</option>
|
||||
@@ -156,12 +156,12 @@ export default function ReviewQueuePage() {
|
||||
<div className="p-3 space-y-2">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
) : proposals.length === 0 ? (
|
||||
<div className="py-12 text-center">
|
||||
<Lightbulb size={24} className="mx-auto mb-2 text-muted-foreground/40" />
|
||||
<p className="text-sm text-muted-foreground">No proposals found</p>
|
||||
<Lightbulb size={24} className="mx-auto mb-2 text-[#848b9b]/40" />
|
||||
<p className="text-sm text-[#848b9b]">No proposals found</p>
|
||||
</div>
|
||||
) : (
|
||||
proposals.map((proposal) => (
|
||||
@@ -180,7 +180,7 @@ export default function ReviewQueuePage() {
|
||||
<div className={`flex-1 overflow-y-auto ${!detail && !isLoadingDetail ? 'hidden lg:flex' : ''}`}>
|
||||
{isLoadingDetail ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<Loader2 size={20} className="animate-spin text-muted-foreground" />
|
||||
<Loader2 size={20} className="animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
) : detail ? (
|
||||
<ProposalDetail
|
||||
@@ -190,8 +190,8 @@ export default function ReviewQueuePage() {
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Lightbulb size={32} className="mx-auto mb-3 text-muted-foreground/30" />
|
||||
<p className="text-sm text-muted-foreground">Select a proposal to review</p>
|
||||
<Lightbulb size={32} className="mx-auto mb-3 text-[#848b9b]/30" />
|
||||
<p className="text-sm text-[#848b9b]">Select a proposal to review</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -131,8 +131,8 @@ export default function ScriptBuilderPage() {
|
||||
<Terminal size={18} className="text-fuchsia-400" />
|
||||
</span>
|
||||
<div>
|
||||
<h1 className="text-base font-heading font-bold text-foreground">Script Builder</h1>
|
||||
<p className="text-xs text-muted-foreground">Describe what you need, AI generates the script</p>
|
||||
<h1 className="text-base font-heading font-bold text-[#e2e5eb]">Script Builder</h1>
|
||||
<p className="text-xs text-[#848b9b]">Describe what you need, AI generates the script</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -144,12 +144,12 @@ export default function ScriptBuilderPage() {
|
||||
onClick={() => !hasMessages && setLanguage(lang.value)}
|
||||
disabled={hasMessages}
|
||||
className={cn(
|
||||
"px-3 py-1.5 rounded-md text-xs font-label font-medium transition-all",
|
||||
"px-3 py-1.5 rounded-md text-xs font-sans text-xs font-medium transition-all",
|
||||
language === lang.value
|
||||
? "bg-gradient-brand text-[#101114]"
|
||||
? "bg-[#22d3ee] text-white"
|
||||
: hasMessages
|
||||
? "text-[#5a6170] cursor-not-allowed"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)]"
|
||||
: "text-[#848b9b] hover:text-[#e2e5eb] hover:bg-[rgba(255,255,255,0.04)]"
|
||||
)}
|
||||
>
|
||||
{lang.label}
|
||||
|
||||
@@ -57,22 +57,22 @@ export default function ScriptLibraryPage() {
|
||||
|
||||
const tabClass = (tab: LibraryTab) =>
|
||||
tab === activeTab
|
||||
? 'border-b-2 border-primary text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'border-b-2 border-primary text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-6 h-full">
|
||||
{/* Page header */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">Script Library</h1>
|
||||
<p className="text-sm text-[#848b9b] mt-1">
|
||||
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
||||
</p>
|
||||
{isEngineer && (
|
||||
<Link
|
||||
to="/scripts/manage"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-primary bg-primary/10 hover:bg-primary/15 px-2.5 py-1 rounded-full transition-colors mt-2 group"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-[#22d3ee] bg-[rgba(34,211,238,0.10)] hover:bg-primary/15 px-2.5 py-1 rounded-full transition-colors mt-2 group"
|
||||
>
|
||||
<Settings size={12} className="group-hover:rotate-90 transition-transform duration-300" />
|
||||
Manage Templates
|
||||
@@ -81,7 +81,7 @@ export default function ScriptLibraryPage() {
|
||||
</div>
|
||||
<Link
|
||||
to="/script-builder"
|
||||
className="inline-flex items-center gap-2 bg-gradient-brand text-[#101114] font-semibold rounded-[10px] px-4 py-2 shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="inline-flex items-center gap-2 bg-[#22d3ee] text-white font-semibold rounded-lg px-4 py-2 hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
<Wand2 size={16} />
|
||||
Build a New Script
|
||||
@@ -89,7 +89,7 @@ export default function ScriptLibraryPage() {
|
||||
</div>
|
||||
|
||||
{/* Tab bar */}
|
||||
<div className="flex gap-6 border-b border-border">
|
||||
<div className="flex gap-6 border-b border-[#1e2130]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onTabChange('mine')}
|
||||
@@ -110,7 +110,7 @@ export default function ScriptLibraryPage() {
|
||||
<div className="grid grid-cols-[320px_1fr] gap-4 flex-1 min-h-0">
|
||||
{/* Left pane — Browse or Configure mode */}
|
||||
{paneMode === 'browse' ? (
|
||||
<div className="glass-card-static flex flex-col overflow-hidden">
|
||||
<div className="card-flat flex flex-col overflow-hidden">
|
||||
<div className="p-2 pb-0">
|
||||
<ScriptFilterBar inputValue={inputValue} setInputValue={setInputValue} />
|
||||
</div>
|
||||
@@ -128,12 +128,12 @@ export default function ScriptLibraryPage() {
|
||||
|
||||
{/* Right pane — read-only ScriptPreview */}
|
||||
{selectedTemplate === null ? (
|
||||
<div className="glass-card-static h-full flex flex-col items-center justify-center gap-3 text-center p-8">
|
||||
<Terminal size={40} className="text-muted-foreground/40" />
|
||||
<p className="text-sm text-muted-foreground">Select a template to get started</p>
|
||||
<div className="card-flat h-full flex flex-col items-center justify-center gap-3 text-center p-8">
|
||||
<Terminal size={40} className="text-[#848b9b]/40" />
|
||||
<p className="text-sm text-[#848b9b]">Select a template to get started</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="glass-card-static h-full overflow-y-auto p-4">
|
||||
<div className="card-flat h-full overflow-y-auto p-4">
|
||||
<ScriptPreview />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -380,7 +380,7 @@ export function SessionDetailPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate('/sessions')}
|
||||
className="mt-4 text-foreground hover:underline"
|
||||
className="mt-4 text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
Back to sessions
|
||||
</button>
|
||||
@@ -393,7 +393,7 @@ export function SessionDetailPage() {
|
||||
resolved: { icon: <CheckCircle2 className="h-5 w-5" />, color: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/20' },
|
||||
workaround: { icon: <AlertTriangle className="h-5 w-5" />, color: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/20' },
|
||||
escalated: { icon: <ArrowUpRight className="h-5 w-5" />, color: 'text-red-400', bg: 'bg-red-500/10', border: 'border-red-500/20' },
|
||||
unresolved: { icon: <HelpCircle className="h-5 w-5" />, color: 'text-muted-foreground', bg: 'bg-muted', border: 'border-border' },
|
||||
unresolved: { icon: <HelpCircle className="h-5 w-5" />, color: 'text-[#848b9b]', bg: 'bg-muted', border: 'border-[#1e2130]' },
|
||||
}
|
||||
const outcomeConfig = session.outcome ? OUTCOME_CONFIG[session.outcome] : null
|
||||
|
||||
@@ -402,7 +402,7 @@ export function SessionDetailPage() {
|
||||
{/* Back nav */}
|
||||
<button
|
||||
onClick={() => navigate('/sessions')}
|
||||
className="mb-4 text-sm text-muted-foreground hover:text-foreground"
|
||||
className="mb-4 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
← Back to sessions
|
||||
</button>
|
||||
@@ -410,10 +410,10 @@ export function SessionDetailPage() {
|
||||
{/* Page title row */}
|
||||
<div className="mb-6 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground sm:text-3xl">
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb] sm:text-3xl">
|
||||
{session.ticket_number || 'Session Details'}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
{session.tree_snapshot?.name}
|
||||
{session.client_name && <> · Client: {session.client_name}</>}
|
||||
{session.started_at && <>{' · '}{new Date(session.started_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}</>}
|
||||
@@ -436,15 +436,15 @@ export function SessionDetailPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('text-base font-semibold', outcomeConfig.color)}>{outcomeLabel}</span>
|
||||
<span className="text-sm text-muted-foreground">· {getTotalDuration()}</span>
|
||||
<span className="text-sm text-[#848b9b]">· {getTotalDuration()}</span>
|
||||
</div>
|
||||
{session.outcome_notes && (
|
||||
<p className="mt-1 text-sm text-muted-foreground">{session.outcome_notes}</p>
|
||||
<p className="mt-1 text-sm text-[#848b9b]">{session.outcome_notes}</p>
|
||||
)}
|
||||
{session.next_steps && (
|
||||
<div className="mt-2">
|
||||
<span className="font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">Next Steps</span>
|
||||
<p className="mt-0.5 text-sm text-muted-foreground whitespace-pre-wrap">{session.next_steps}</p>
|
||||
<span className="font-sans text-xs text-[0.6875rem] uppercase tracking-wide text-[#848b9b]">Next Steps</span>
|
||||
<p className="mt-0.5 text-sm text-[#848b9b] whitespace-pre-wrap">{session.next_steps}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -464,7 +464,7 @@ export function SessionDetailPage() {
|
||||
<Button
|
||||
onClick={handleCreateFlowFromSession}
|
||||
disabled={isGeneratingFlow}
|
||||
className="bg-gradient-brand text-[#101114] font-semibold rounded-[10px] hover:opacity-90 active:scale-[0.97] disabled:opacity-60"
|
||||
className="bg-[#22d3ee] text-white font-semibold rounded-lg hover:brightness-110 active:scale-[0.98] disabled:opacity-60"
|
||||
>
|
||||
{isGeneratingFlow ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -483,7 +483,7 @@ export function SessionDetailPage() {
|
||||
<Flag className="h-4 w-4 shrink-0 text-amber-400" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-amber-300">Session in progress</p>
|
||||
<p className="text-xs text-muted-foreground">Set an outcome to finalize this session and generate documentation.</p>
|
||||
<p className="text-xs text-[#848b9b]">Set an outcome to finalize this session and generate documentation.</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={() => setShowOutcomeModal(true)} className="shrink-0">
|
||||
@@ -498,7 +498,7 @@ export function SessionDetailPage() {
|
||||
value={exportFormat}
|
||||
onChange={(e) => setExportFormat(e.target.value as typeof exportFormat)}
|
||||
aria-label="Export format"
|
||||
className="rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
>
|
||||
<option value="markdown">Markdown</option>
|
||||
<option value="text">Plain Text</option>
|
||||
@@ -511,7 +511,7 @@ export function SessionDetailPage() {
|
||||
value={maxStepIndex ?? ''}
|
||||
onChange={(e) => setMaxStepIndex(e.target.value ? Number(e.target.value) : null)}
|
||||
aria-label="Export through step"
|
||||
className="rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
>
|
||||
<option value="">All steps</option>
|
||||
{session.decisions.map((_, idx) => (
|
||||
@@ -523,7 +523,7 @@ export function SessionDetailPage() {
|
||||
value={detailLevel}
|
||||
onChange={(e) => setDetailLevel(e.target.value as 'standard' | 'full')}
|
||||
aria-label="Detail level"
|
||||
className="rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
>
|
||||
<option value="standard">Standard</option>
|
||||
<option value="full">Full Detail</option>
|
||||
@@ -532,7 +532,7 @@ export function SessionDetailPage() {
|
||||
onClick={handleCopy}
|
||||
disabled={isExporting}
|
||||
title="Copy to clipboard"
|
||||
className="rounded-md border border-border bg-card p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
className="rounded-md border border-[#1e2130] bg-[#14161d] p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||
</button>
|
||||
@@ -549,7 +549,7 @@ export function SessionDetailPage() {
|
||||
{session.completed_at && (
|
||||
<button
|
||||
onClick={handleCopyForTicket}
|
||||
className="flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex items-center gap-1.5 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
{copiedPsa ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||
{copiedPsa ? 'Copied!' : 'Copy for Ticket'}
|
||||
|
||||
@@ -280,8 +280,8 @@ export function SessionHistoryPage() {
|
||||
<PageMeta title="Sessions" />
|
||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground sm:text-3xl">Sessions</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb] sm:text-3xl">Sessions</h1>
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
View and manage all your sessions
|
||||
</p>
|
||||
</div>
|
||||
@@ -295,13 +295,13 @@ export function SessionHistoryPage() {
|
||||
<div className="flex gap-3">
|
||||
<Link
|
||||
to="/trees"
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-5 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Start a Flow
|
||||
</Link>
|
||||
<Link
|
||||
to="/pilot"
|
||||
className="inline-flex items-center gap-2 rounded-[10px] border border-border bg-[rgba(255,255,255,0.04)] px-5 py-2.5 text-sm font-semibold text-foreground hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-[#1e2130] bg-[rgba(255,255,255,0.04)] px-5 py-2.5 text-sm font-semibold text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)] transition-all"
|
||||
>
|
||||
Start AI Session
|
||||
</Link>
|
||||
@@ -314,20 +314,20 @@ export function SessionHistoryPage() {
|
||||
{/* FlowPilot Sessions Section */}
|
||||
{showAiSection && (
|
||||
<>
|
||||
<h2 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">FlowPilot Sessions</h2>
|
||||
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-3">FlowPilot Sessions</h2>
|
||||
|
||||
{/* AI Session Filter Bar */}
|
||||
<div className="glass-card-static p-3 mb-4">
|
||||
<div className="card-flat p-3 mb-4">
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
{/* Search input */}
|
||||
<div className="relative flex-1 min-w-[180px]">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-[#848b9b] pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={aiSearchInput}
|
||||
onChange={(e) => setAiSearchInput(e.target.value)}
|
||||
placeholder="Search sessions..."
|
||||
className="w-full rounded-lg border border-border bg-card pl-8 pr-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] pl-8 pr-3 py-1.5 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-[rgba(6,182,212,0.3)] focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -336,7 +336,7 @@ export function SessionHistoryPage() {
|
||||
value={aiFilters.problem_domain}
|
||||
onChange={(e) => setAiFilters((f) => ({ ...f, problem_domain: e.target.value }))}
|
||||
title="Filter by problem domain"
|
||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none [&>option]:bg-[#1a1c21] [&>option]:text-foreground"
|
||||
className="rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-none [&>option]:bg-[#1a1c21] [&>option]:text-[#e2e5eb]"
|
||||
>
|
||||
<option value="">All domains</option>
|
||||
<option value="Active Directory">Active Directory</option>
|
||||
@@ -358,10 +358,10 @@ export function SessionHistoryPage() {
|
||||
key={tier}
|
||||
onClick={() => setAiFilters((f) => ({ ...f, confidence_tier: tier }))}
|
||||
className={cn(
|
||||
'rounded-full border px-3 py-1 text-xs font-label transition-colors',
|
||||
'rounded-full border px-3 py-1 text-xs font-sans text-xs transition-colors',
|
||||
aiFilters.confidence_tier === tier
|
||||
? 'bg-primary/10 text-foreground border-primary/30'
|
||||
: 'bg-card text-muted-foreground border-border hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||
: 'bg-[#14161d] text-[#848b9b] border-[#1e2130] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||
)}
|
||||
>
|
||||
{tier === '' ? 'All' : tier.charAt(0).toUpperCase() + tier.slice(1)}
|
||||
@@ -376,15 +376,15 @@ export function SessionHistoryPage() {
|
||||
value={aiFilters.date_from}
|
||||
onChange={(e) => setAiFilters((f) => ({ ...f, date_from: e.target.value }))}
|
||||
title="From date"
|
||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none [color-scheme:dark]"
|
||||
className="rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-none [color-scheme:dark]"
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">to</span>
|
||||
<span className="text-xs text-[#848b9b]">to</span>
|
||||
<input
|
||||
type="date"
|
||||
value={aiFilters.date_to}
|
||||
onChange={(e) => setAiFilters((f) => ({ ...f, date_to: e.target.value }))}
|
||||
title="To date"
|
||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none [color-scheme:dark]"
|
||||
className="rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-none [color-scheme:dark]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -395,7 +395,7 @@ export function SessionHistoryPage() {
|
||||
setAiSearchInput('')
|
||||
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
|
||||
}}
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -417,7 +417,7 @@ export function SessionHistoryPage() {
|
||||
setAiSearchInput('')
|
||||
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
|
||||
}}
|
||||
className="text-foreground hover:underline text-sm"
|
||||
className="text-[#e2e5eb] hover:underline text-sm"
|
||||
>
|
||||
Clear all filters
|
||||
</button>
|
||||
@@ -433,7 +433,7 @@ export function SessionHistoryPage() {
|
||||
|
||||
{/* Divider between sections */}
|
||||
{showFlowSection && (
|
||||
<div className="my-8 border-t border-border" />
|
||||
<div className="my-8 border-t border-[#1e2130]" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@@ -441,10 +441,10 @@ export function SessionHistoryPage() {
|
||||
{/* Flow Sessions Section */}
|
||||
{showFlowSection && (
|
||||
<>
|
||||
<h2 className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">Flow Sessions</h2>
|
||||
<h2 className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-3">Flow Sessions</h2>
|
||||
|
||||
{/* Filter Tabs */}
|
||||
<div className="mb-6 flex gap-2 border-b border-border">
|
||||
<div className="mb-6 flex gap-2 border-b border-[#1e2130]">
|
||||
{(['active', 'prepared', 'completed', 'all'] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
@@ -452,8 +452,8 @@ export function SessionHistoryPage() {
|
||||
className={cn(
|
||||
'px-4 py-2 text-sm font-medium transition-colors',
|
||||
filter === tab
|
||||
? 'border-b-2 border-primary text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'border-b-2 border-primary text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||
@@ -481,7 +481,7 @@ export function SessionHistoryPage() {
|
||||
title="No sessions match your filters"
|
||||
description="Try adjusting your search or filters."
|
||||
action={
|
||||
<button onClick={handleClearFilters} className="text-foreground hover:underline text-sm">
|
||||
<button onClick={handleClearFilters} className="text-[#e2e5eb] hover:underline text-sm">
|
||||
Clear all filters
|
||||
</button>
|
||||
}
|
||||
@@ -494,7 +494,7 @@ export function SessionHistoryPage() {
|
||||
action={
|
||||
<Link
|
||||
to="/trees"
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-5 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Start a Session
|
||||
</Link>
|
||||
@@ -515,7 +515,7 @@ export function SessionHistoryPage() {
|
||||
style={{ '--stagger-index': i } as React.CSSProperties}
|
||||
>
|
||||
<div
|
||||
className="bg-card border border-border rounded-xl p-4 transition-all hover:bg-accent/50"
|
||||
className="bg-[#14161d] border border-[#1e2130] rounded-xl p-4 transition-all hover:bg-accent/50"
|
||||
>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="flex-1">
|
||||
@@ -527,11 +527,11 @@ export function SessionHistoryPage() {
|
||||
session.completed_at ? 'bg-green-500' : 'bg-yellow-500'
|
||||
)}
|
||||
/>
|
||||
<span className="font-medium text-foreground">
|
||||
<span className="font-medium text-[#e2e5eb]">
|
||||
{session.ticket_number || 'No ticket'}
|
||||
</span>
|
||||
{session.client_name && (
|
||||
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium text-foreground">
|
||||
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium text-[#e2e5eb]">
|
||||
{session.client_name}
|
||||
</span>
|
||||
)}
|
||||
@@ -545,7 +545,7 @@ export function SessionHistoryPage() {
|
||||
session.outcome === 'unresolved' && 'bg-rose-500/20 text-rose-300',
|
||||
session.outcome === 'cancelled' && 'bg-zinc-500/20 text-zinc-300',
|
||||
session.outcome === 'resolved_externally' && 'bg-cyan-500/20 text-cyan-300',
|
||||
!session.outcome && 'bg-accent text-muted-foreground'
|
||||
!session.outcome && 'bg-accent text-[#848b9b]'
|
||||
)}
|
||||
>
|
||||
{formatOutcomeLabel(session.outcome)}
|
||||
@@ -554,12 +554,12 @@ export function SessionHistoryPage() {
|
||||
</div>
|
||||
|
||||
{/* Tree Name */}
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
<span className="font-medium">Tree:</span> {getTreeName(session)}
|
||||
</p>
|
||||
|
||||
{/* Timestamps */}
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
Started: {session.started_at ? formatDate(session.started_at) : 'Not started'}
|
||||
{session.completed_at && (
|
||||
<> · Completed: {formatDate(session.completed_at)}</>
|
||||
@@ -567,7 +567,7 @@ export function SessionHistoryPage() {
|
||||
</p>
|
||||
|
||||
{/* Stats */}
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
{session.decisions.length} decision{session.decisions.length !== 1 ? 's' : ''} recorded
|
||||
{session.scratchpad && session.scratchpad.trim() && (
|
||||
<span> · Has notes</span>
|
||||
@@ -580,8 +580,8 @@ export function SessionHistoryPage() {
|
||||
<button
|
||||
onClick={() => navigate(`/sessions/${session.id}`)}
|
||||
className={cn(
|
||||
'rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
View Details
|
||||
@@ -595,9 +595,9 @@ export function SessionHistoryPage() {
|
||||
setCloseNotes('')
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground',
|
||||
closingSessionId === session.id && 'bg-accent text-foreground'
|
||||
'rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]',
|
||||
closingSessionId === session.id && 'bg-accent text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
Close
|
||||
@@ -605,8 +605,8 @@ export function SessionHistoryPage() {
|
||||
<button
|
||||
onClick={() => navigate(getSessionResumePath(session.tree_id, session.tree_snapshot?.tree_type), { state: { sessionId: session.id } })}
|
||||
className={cn(
|
||||
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
||||
'hover:opacity-90'
|
||||
'rounded-md bg-[#22d3ee] px-3 py-2 text-sm font-medium text-white',
|
||||
'hover:brightness-110'
|
||||
)}
|
||||
>
|
||||
Resume
|
||||
@@ -618,16 +618,16 @@ export function SessionHistoryPage() {
|
||||
{closingSessionId === session.id && (
|
||||
<div
|
||||
ref={closePopoverRef}
|
||||
className="absolute right-0 top-full z-20 mt-2 w-72 rounded-xl border border-border bg-card p-4 shadow-xl"
|
||||
className="absolute right-0 top-full z-20 mt-2 w-72 rounded-xl border border-[#1e2130] bg-[#14161d] p-4 shadow-xl"
|
||||
>
|
||||
<p className="text-sm font-heading font-medium text-foreground mb-3">Close Session</p>
|
||||
<p className="text-sm font-heading font-medium text-[#e2e5eb] mb-3">Close Session</p>
|
||||
|
||||
<label className="block text-xs font-label text-muted-foreground mb-1">Outcome</label>
|
||||
<label className="block text-xs font-sans text-xs text-[#848b9b] mb-1">Outcome</label>
|
||||
<select
|
||||
value={closeOutcome}
|
||||
onChange={(e) => setCloseOutcome(e.target.value as SessionOutcome)}
|
||||
title="Session outcome"
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none mb-3"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-none mb-3"
|
||||
>
|
||||
<option value="">Select outcome...</option>
|
||||
<option value="resolved">Resolved</option>
|
||||
@@ -638,13 +638,13 @@ export function SessionHistoryPage() {
|
||||
<option value="resolved_externally">Resolved Externally</option>
|
||||
</select>
|
||||
|
||||
<label className="block text-xs font-label text-muted-foreground mb-1">Notes (optional)</label>
|
||||
<label className="block text-xs font-sans text-xs text-[#848b9b] mb-1">Notes (optional)</label>
|
||||
<textarea
|
||||
value={closeNotes}
|
||||
onChange={(e) => setCloseNotes(e.target.value)}
|
||||
rows={2}
|
||||
placeholder="Add closure notes..."
|
||||
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 mb-3"
|
||||
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 mb-3"
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
@@ -654,7 +654,7 @@ export function SessionHistoryPage() {
|
||||
setCloseOutcome('')
|
||||
setCloseNotes('')
|
||||
}}
|
||||
className="rounded-lg px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -662,10 +662,10 @@ export function SessionHistoryPage() {
|
||||
onClick={handleCloseSession}
|
||||
disabled={!closeOutcome || closeLoading}
|
||||
className={cn(
|
||||
'rounded-lg px-4 py-1.5 text-sm font-medium shadow-lg shadow-primary/20 transition-opacity',
|
||||
'rounded-lg px-4 py-1.5 text-sm font-medium transition-opacity',
|
||||
closeOutcome
|
||||
? 'bg-gradient-brand text-[#101114] hover:opacity-90'
|
||||
: 'bg-gradient-brand text-[#101114] opacity-50 cursor-not-allowed'
|
||||
? 'bg-[#22d3ee] text-white hover:brightness-110'
|
||||
: 'bg-[#22d3ee] text-white opacity-50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{closeLoading ? 'Closing...' : 'Confirm'}
|
||||
@@ -680,11 +680,11 @@ export function SessionHistoryPage() {
|
||||
))}
|
||||
</div>
|
||||
{hasMore ? (
|
||||
<p className="text-center text-sm text-muted-foreground py-4">
|
||||
<p className="text-center text-sm text-[#848b9b] py-4">
|
||||
Showing the 50 most recent sessions
|
||||
</p>
|
||||
) : sessions.length > 0 ? (
|
||||
<p className="text-center text-sm text-muted-foreground py-4">
|
||||
<p className="text-center text-sm text-[#848b9b] py-4">
|
||||
Showing all {sessions.length} sessions
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
@@ -55,16 +55,16 @@ function ErrorCard({ error }: { error: ErrorState }) {
|
||||
const Icon = iconMap[error.type]
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
||||
<div className="bg-card border border-border w-full max-w-md rounded-xl p-8 text-center">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#0c0d10] px-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] w-full max-w-md rounded-xl p-8 text-center">
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-accent">
|
||||
<Icon className="h-8 w-8 text-muted-foreground" />
|
||||
<Icon className="h-8 w-8 text-[#848b9b]" />
|
||||
</div>
|
||||
<h1 className="mb-2 text-xl font-heading font-semibold text-foreground">{titleMap[error.type]}</h1>
|
||||
<p className="mb-6 text-sm text-muted-foreground">{error.message}</p>
|
||||
<h1 className="mb-2 text-xl font-heading font-semibold text-[#e2e5eb]">{titleMap[error.type]}</h1>
|
||||
<p className="mb-6 text-sm text-[#848b9b]">{error.message}</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-block rounded-lg bg-gradient-brand px-6 py-2.5 text-sm font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
|
||||
className="inline-block rounded-lg bg-[#22d3ee] px-6 py-2.5 text-sm font-medium text-white hover:brightness-110"
|
||||
>
|
||||
Go to ResolutionFlow
|
||||
</Link>
|
||||
@@ -144,10 +144,10 @@ export function SharedSessionPage() {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#0c0d10]">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Spinner />
|
||||
<p className="text-sm text-muted-foreground">Loading shared session...</p>
|
||||
<p className="text-sm text-[#848b9b]">Loading shared session...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -162,21 +162,21 @@ export function SharedSessionPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="min-h-screen bg-[#0c0d10]">
|
||||
<PageMeta
|
||||
title={data ? `Shared Session - ${data.tree_name}` : 'Shared Session'}
|
||||
description="View a shared troubleshooting session on ResolutionFlow"
|
||||
/>
|
||||
{/* Minimal header */}
|
||||
<header className="border-b border-border px-6 py-4">
|
||||
<header className="border-b border-[#1e2130] px-6 py-4">
|
||||
<div className="mx-auto flex max-w-7xl items-center justify-between">
|
||||
<Link to="/" className="flex items-center gap-2">
|
||||
<BrandLogo size="sm" />
|
||||
<span className="text-lg font-heading font-semibold text-foreground">ResolutionFlow</span>
|
||||
<span className="text-lg font-heading font-semibold text-[#e2e5eb]">ResolutionFlow</span>
|
||||
</Link>
|
||||
<Link
|
||||
to="/login"
|
||||
className="rounded-lg border border-border px-4 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg border border-[#1e2130] px-4 py-2 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
@@ -188,14 +188,14 @@ export function SharedSessionPage() {
|
||||
{/* Metadata section */}
|
||||
<div className="mb-8">
|
||||
{data.share_name && (
|
||||
<h1 className="mb-2 text-2xl font-heading font-bold text-foreground">{data.share_name}</h1>
|
||||
<h1 className="mb-2 text-2xl font-heading font-bold text-[#e2e5eb]">{data.share_name}</h1>
|
||||
)}
|
||||
<p className="text-lg text-muted-foreground">
|
||||
<span className="text-muted-foreground">Tree:</span> {data.tree_name}
|
||||
<p className="text-lg text-[#848b9b]">
|
||||
<span className="text-[#848b9b]">Tree:</span> {data.tree_name}
|
||||
</p>
|
||||
|
||||
{(data.ticket_number || data.client_name) && (
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
<p className="mt-1 text-sm text-[#848b9b]">
|
||||
{data.ticket_number && (
|
||||
<span>Ticket: #{data.ticket_number}</span>
|
||||
)}
|
||||
@@ -208,7 +208,7 @@ export function SharedSessionPage() {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-muted-foreground">
|
||||
<div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-[#848b9b]">
|
||||
<span>Started: {formatDate(data.started_at)}</span>
|
||||
{data.completed_at && (
|
||||
<>
|
||||
@@ -256,9 +256,9 @@ export function SharedSessionPage() {
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-8 text-center text-sm text-muted-foreground">
|
||||
<footer className="py-8 text-center text-sm text-[#848b9b]">
|
||||
Powered by{' '}
|
||||
<Link to="/" className="underline hover:text-foreground">
|
||||
<Link to="/" className="underline hover:text-[#e2e5eb]">
|
||||
ResolutionFlow
|
||||
</Link>
|
||||
</footer>
|
||||
|
||||
@@ -92,14 +92,14 @@ export default function StepLibraryPage() {
|
||||
<PageMeta title="Step Library" />
|
||||
<div className="flex h-full flex-col">
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center justify-between border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] px-6 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span title="Step Library">
|
||||
<Bookmark className="h-6 w-6 text-muted-foreground" />
|
||||
<Bookmark className="h-6 w-6 text-[#848b9b]" />
|
||||
</span>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold font-heading text-foreground">Step Library</h1>
|
||||
<p className="text-sm text-muted-foreground">Reusable steps you can insert into any flow</p>
|
||||
<h1 className="text-xl font-bold font-heading text-[#e2e5eb]">Step Library</h1>
|
||||
<p className="text-sm text-[#848b9b]">Reusable steps you can insert into any flow</p>
|
||||
</div>
|
||||
</div>
|
||||
{canCreateSteps && (
|
||||
@@ -131,19 +131,19 @@ export default function StepLibraryPage() {
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
{deletingStep && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
||||
<div className="w-full max-w-sm rounded-xl bg-card border border-border p-6 shadow-lg">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
||||
<div className="w-full max-w-sm rounded-xl bg-[#14161d] border border-[#1e2130] p-6 shadow-lg">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<div className="rounded-full bg-red-400/10 p-2">
|
||||
<Trash2 className="h-5 w-5 text-red-400" />
|
||||
</div>
|
||||
<h2 className="text-base font-semibold text-foreground">Delete Step</h2>
|
||||
<h2 className="text-base font-semibold text-[#e2e5eb]">Delete Step</h2>
|
||||
</div>
|
||||
<p className="mb-2 text-sm text-muted-foreground">
|
||||
<p className="mb-2 text-sm text-[#848b9b]">
|
||||
Are you sure you want to delete{' '}
|
||||
<span className="font-medium text-foreground">"{deletingStep.title}"</span>?
|
||||
<span className="font-medium text-[#e2e5eb]">"{deletingStep.title}"</span>?
|
||||
</p>
|
||||
<p className="mb-6 text-xs text-muted-foreground">This cannot be undone.</p>
|
||||
<p className="mb-6 text-xs text-[#848b9b]">This cannot be undone.</p>
|
||||
{deleteError && (
|
||||
<p className="mb-4 text-sm text-red-400">{deleteError}</p>
|
||||
)}
|
||||
|
||||
@@ -264,15 +264,15 @@ export default function SurveyPage() {
|
||||
|
||||
if (tokenLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||
<div className="text-muted-foreground text-sm">Loading...</div>
|
||||
<div className="min-h-screen bg-[#0c0d10] flex items-center justify-center">
|
||||
<div className="text-[#848b9b] text-sm">Loading...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (alreadyCompleted) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
<div className="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
|
||||
<div className="absolute -top-32 right-0 h-[500px] w-[500px] rounded-full opacity-[0.03]" style={{ background: 'radial-gradient(circle, var(--color-primary), transparent 70%)' }} />
|
||||
<div className="absolute -bottom-32 left-0 h-[400px] w-[400px] rounded-full opacity-[0.02]" style={{ background: 'radial-gradient(circle, #a855f7, transparent 70%)' }} />
|
||||
@@ -283,16 +283,16 @@ export default function SurveyPage() {
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" strokeWidth="2.5"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
</div>
|
||||
<h2 className="font-heading text-2xl font-bold mb-2.5">Already Submitted</h2>
|
||||
<p className="text-muted-foreground text-sm max-w-[440px] mx-auto leading-relaxed mb-3">
|
||||
<p className="text-[#848b9b] text-sm max-w-[440px] mx-auto leading-relaxed mb-3">
|
||||
{inviteName ? `Thanks ${inviteName} — y` : 'Y'}our response has already been recorded. We appreciate your time!
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/70 max-w-[400px] mx-auto leading-relaxed mb-8">
|
||||
<p className="text-sm text-[#848b9b]/70 max-w-[400px] mx-auto leading-relaxed mb-8">
|
||||
You can safely close this browser window now.
|
||||
</p>
|
||||
<div className="glass-card-static p-4 sm:p-5 max-w-[400px] mx-auto text-center">
|
||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
||||
<div className="card-flat p-4 sm:p-5 max-w-[400px] mx-auto text-center">
|
||||
<p className="text-xs text-[#848b9b] leading-relaxed">
|
||||
Have feedback unrelated to the survey?{' '}
|
||||
<a href="mailto:feedback@resolutionflow.com" className="text-primary hover:underline font-medium">
|
||||
<a href="mailto:feedback@resolutionflow.com" className="text-[#22d3ee] hover:underline font-medium">
|
||||
feedback@resolutionflow.com
|
||||
</a>
|
||||
</p>
|
||||
@@ -304,7 +304,7 @@ export default function SurveyPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={topRef} className="min-h-screen bg-background text-foreground">
|
||||
<div ref={topRef} className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
<PageMeta
|
||||
title="Product Survey"
|
||||
description="Help shape the future of ResolutionFlow by sharing your feedback"
|
||||
@@ -322,17 +322,17 @@ export default function SurveyPage() {
|
||||
</div>
|
||||
|
||||
{/* Top bar */}
|
||||
<div className="sticky top-0 z-50" style={{ backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
|
||||
<div className="sticky top-0 z-50" style={{ WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
|
||||
<div className="mx-auto flex max-w-[680px] items-center justify-between gap-3 px-4 py-3 sm:px-5 sm:py-3.5">
|
||||
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2 sm:gap-2.5 text-sm font-heading font-bold text-muted-foreground no-underline shrink-0">
|
||||
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2 sm:gap-2.5 text-sm font-heading font-bold text-[#848b9b] no-underline shrink-0">
|
||||
<BrandLogo size="sm" />
|
||||
<span className="hidden sm:inline">Resolution<span className="text-gradient-brand">Flow</span></span>
|
||||
<span className="hidden sm:inline">Resolution<span className="text-[#67e8f9]">Flow</span></span>
|
||||
</a>
|
||||
<div className="flex flex-1 items-center gap-2 sm:gap-2.5" style={{ maxWidth: '280px' }}>
|
||||
<div className="flex-1 h-[3px] rounded-full overflow-hidden" style={{ background: 'var(--color-border)' }}>
|
||||
<div className="h-full rounded-full bg-gradient-brand transition-[width] duration-500" style={{ width: `${progressPct}%` }} />
|
||||
<div className="h-full rounded-full bg-[#22d3ee] transition-[width] duration-500" style={{ width: `${progressPct}%` }} />
|
||||
</div>
|
||||
<span className="text-[11px] font-label text-muted-foreground whitespace-nowrap tabular-nums">{answeredCount}/{TOTAL_QUESTIONS}</span>
|
||||
<span className="text-[11px] font-sans text-xs text-[#848b9b] whitespace-nowrap tabular-nums">{answeredCount}/{TOTAL_QUESTIONS}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -341,17 +341,17 @@ export default function SurveyPage() {
|
||||
{/* Hero — visible only on first slide */}
|
||||
{currentSlide === 0 && !isComplete && (
|
||||
<div className="text-center pt-10 pb-8 sm:pt-[72px] sm:pb-10 animate-fade-in-up">
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] sm:text-[11px] font-semibold font-label uppercase tracking-widest mb-4 sm:mb-5" style={{ background: 'rgba(6, 182, 212, 0.1)', border: '1px solid rgba(6, 182, 212, 0.15)', color: 'var(--color-primary)' }}>
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] sm:text-[11px] font-semibold font-sans text-xs uppercase tracking-widest mb-4 sm:mb-5" style={{ background: 'rgba(6, 182, 212, 0.1)', border: '1px solid rgba(6, 182, 212, 0.15)', color: 'var(--color-primary)' }}>
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
FlowPilot Research
|
||||
</div>
|
||||
<h1 className="font-heading text-[clamp(24px,5vw,36px)] font-extrabold leading-tight mb-3">
|
||||
Help Build an AI That<br/>Thinks Like <span className="text-gradient-brand">You</span>
|
||||
Help Build an AI That<br/>Thinks Like <span className="text-[#67e8f9]">You</span>
|
||||
</h1>
|
||||
<p className="text-[14px] sm:text-[15px] text-muted-foreground max-w-[500px] mx-auto leading-relaxed">
|
||||
<p className="text-[14px] sm:text-[15px] text-[#848b9b] max-w-[500px] mx-auto leading-relaxed">
|
||||
We're building an AI assistant for MSP engineers. Your expertise shapes how it thinks. Takes about 5 minutes.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4 sm:gap-7 mt-4 sm:mt-5 text-[11px] sm:text-[12px] text-muted-foreground">
|
||||
<div className="flex flex-wrap justify-center gap-4 sm:gap-7 mt-4 sm:mt-5 text-[11px] sm:text-[12px] text-[#848b9b]">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" strokeWidth="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
~5 minutes
|
||||
@@ -393,18 +393,18 @@ export default function SurveyPage() {
|
||||
))}
|
||||
<div className="flex justify-between mt-6 sm:mt-7 gap-3">
|
||||
{si > 0 ? (
|
||||
<button onClick={() => goSlide(si - 1)} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold text-muted-foreground transition-all duration-150 hover:text-foreground" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<button onClick={() => goSlide(si - 1)} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold text-[#848b9b] transition-all duration-150 hover:text-[#e2e5eb]" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
|
||||
Back
|
||||
</button>
|
||||
) : <div />}
|
||||
{si < SLIDES.length - 1 ? (
|
||||
<button onClick={() => goSlide(si + 1)} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97]">
|
||||
<button onClick={() => goSlide(si + 1)} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98]">
|
||||
Next
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={handleSubmit} disabled={isSubmitting} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:cursor-not-allowed">
|
||||
<button onClick={handleSubmit} disabled={isSubmitting} className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 sm:py-3 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98] disabled:opacity-40 disabled:cursor-not-allowed">
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
{!isSubmitting && <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20 6L9 17l-5-5"/></svg>}
|
||||
</button>
|
||||
@@ -413,7 +413,7 @@ export default function SurveyPage() {
|
||||
{submitError && (
|
||||
<div className="mt-4 p-3 rounded-lg text-sm text-rose-400" style={{ background: 'rgba(244, 63, 94, 0.1)', border: '1px solid rgba(244, 63, 94, 0.2)' }}>
|
||||
{submitError}
|
||||
<button onClick={copyAll} className="ml-3 underline text-muted-foreground hover:text-foreground">Copy responses instead</button>
|
||||
<button onClick={copyAll} className="ml-3 underline text-[#848b9b] hover:text-[#e2e5eb]">Copy responses instead</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -427,13 +427,13 @@ export default function SurveyPage() {
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="oklch(0.76 0.15 163)" strokeWidth="2.5"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
</div>
|
||||
<h2 className="font-heading text-2xl font-bold mb-2.5">Response Submitted!</h2>
|
||||
<p className="text-muted-foreground text-sm max-w-[440px] mx-auto mb-6 sm:mb-8 leading-relaxed">
|
||||
<p className="text-[#848b9b] text-sm max-w-[440px] mx-auto mb-6 sm:mb-8 leading-relaxed">
|
||||
Your answers will directly shape how FlowPilot troubleshoots. Would you like a copy of your responses?
|
||||
</p>
|
||||
|
||||
{/* Email a copy */}
|
||||
<div className="glass-card-static p-4 sm:p-6 max-w-[420px] mx-auto mb-5">
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-3">
|
||||
<div className="card-flat p-4 sm:p-6 max-w-[420px] mx-auto mb-5">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-3">
|
||||
Email a copy to yourself
|
||||
</p>
|
||||
{!emailSent ? (
|
||||
@@ -443,7 +443,7 @@ export default function SurveyPage() {
|
||||
value={emailInput}
|
||||
onChange={e => setEmailInput(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="flex-1 rounded-[9px] px-3.5 py-2.5 text-sm text-foreground placeholder:text-brand-text-muted focus:outline-hidden"
|
||||
className="flex-1 rounded-[9px] px-3.5 py-2.5 text-sm text-[#e2e5eb] placeholder:text-brand-text-muted focus:outline-hidden"
|
||||
style={{ background: 'rgba(16, 17, 20, 0.6)', border: '1px solid var(--glass-border)' }}
|
||||
onFocus={e => { e.currentTarget.style.borderColor = 'var(--color-primary)' }}
|
||||
onBlur={e => { e.currentTarget.style.borderColor = 'var(--glass-border)' }}
|
||||
@@ -473,7 +473,7 @@ export default function SurveyPage() {
|
||||
}
|
||||
}}
|
||||
disabled={!emailInput.trim() || emailSending}
|
||||
className="inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-[9px] text-sm font-semibold bg-gradient-brand text-brand-dark transition-all duration-150 hover:opacity-90 active:scale-[0.97] disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
|
||||
className="inline-flex items-center justify-center gap-2 px-5 py-2.5 rounded-[9px] text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98] disabled:opacity-40 disabled:cursor-not-allowed whitespace-nowrap"
|
||||
>
|
||||
{emailSending ? (
|
||||
<>
|
||||
@@ -496,13 +496,13 @@ export default function SurveyPage() {
|
||||
|
||||
{/* Copy + Finish buttons */}
|
||||
<div className="flex gap-2.5 justify-center flex-wrap">
|
||||
<button onClick={copyAll} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-5 rounded-[10px] text-sm font-semibold transition-all duration-150 text-muted-foreground hover:text-foreground" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<button onClick={copyAll} className="inline-flex items-center gap-2 px-4 py-2.5 sm:px-5 rounded-lg text-sm font-semibold transition-all duration-150 text-[#848b9b] hover:text-[#e2e5eb]" style={{ background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
<button
|
||||
onClick={() => navigate('/survey/thank-you')}
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 rounded-[10px] text-sm font-semibold bg-gradient-brand text-brand-dark shadow-lg shadow-primary/20 transition-all duration-150 hover:opacity-90 active:scale-[0.97]"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 sm:px-6 rounded-lg text-sm font-semibold bg-[#22d3ee] text-brand-dark transition-all duration-150 hover:brightness-110 active:scale-[0.98]"
|
||||
>
|
||||
Finish
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12h14"/><path d="M12 5l7 7-7 7"/></svg>
|
||||
@@ -519,15 +519,15 @@ export default function SurveyPage() {
|
||||
|
||||
function ScenarioBox({ scenario }: { scenario: { title: string; symptom: string; details: string } }) {
|
||||
return (
|
||||
<div className="rounded-[10px] p-3.5 px-4 sm:p-4 sm:px-5 mb-4 text-[13px]" style={{ background: 'linear-gradient(135deg, rgba(6, 182, 212, 0.06), rgba(139, 92, 246, 0.03))', border: '1px solid color-mix(in srgb, var(--color-primary) 12%, transparent)' }}>
|
||||
<div className="font-label text-[10px] uppercase tracking-widest mb-2 font-semibold" style={{ color: 'var(--color-primary)' }}>{scenario.title}</div>
|
||||
<div className="rounded-lg p-3.5 px-4 sm:p-4 sm:px-5 mb-4 text-[13px]" style={{ background: 'linear-gradient(135deg, rgba(6, 182, 212, 0.06), rgba(139, 92, 246, 0.03))', border: '1px solid color-mix(in srgb, var(--color-primary) 12%, transparent)' }}>
|
||||
<div className="font-sans text-xs text-[10px] uppercase tracking-widest mb-2 font-semibold" style={{ color: 'var(--color-primary)' }}>{scenario.title}</div>
|
||||
<div className="sm:flex gap-2 mb-1">
|
||||
<span className="text-muted-foreground font-medium whitespace-nowrap">Symptom:</span>
|
||||
<span className="text-muted-foreground/80">{scenario.symptom}</span>
|
||||
<span className="text-[#848b9b] font-medium whitespace-nowrap">Symptom:</span>
|
||||
<span className="text-[#848b9b]/80">{scenario.symptom}</span>
|
||||
</div>
|
||||
<div className="sm:flex gap-2">
|
||||
<span className="text-muted-foreground font-medium whitespace-nowrap">Details:</span>
|
||||
<span className="text-muted-foreground/80">{scenario.details}</span>
|
||||
<span className="text-[#848b9b] font-medium whitespace-nowrap">Details:</span>
|
||||
<span className="text-[#848b9b]/80">{scenario.details}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -535,10 +535,10 @@ function ScenarioBox({ scenario }: { scenario: { title: string; symptom: string;
|
||||
|
||||
function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQuestion; answer?: string | string[]; setAnswer: (id: string, val: string | string[]) => void }) {
|
||||
return (
|
||||
<div className="glass-card-static p-4 sm:p-7 mb-3 sm:mb-4 transition-[border-color] duration-200 focus-within:border-primary/25!">
|
||||
<div className="font-label text-[11px] mb-1.5 font-medium" style={{ color: 'var(--color-primary)' }}>Q{q.num}</div>
|
||||
<div className="font-heading text-[14px] sm:text-[15px] font-semibold text-foreground leading-snug mb-1">{q.text}</div>
|
||||
{q.hint && <div className="text-[12px] text-muted-foreground mb-3 sm:mb-4 leading-snug">{q.hint}</div>}
|
||||
<div className="card-flat p-4 sm:p-7 mb-3 sm:mb-4 transition-[border-color] duration-200 focus-within:border-primary/25!">
|
||||
<div className="font-sans text-xs text-[11px] mb-1.5 font-medium" style={{ color: 'var(--color-primary)' }}>Q{q.num}</div>
|
||||
<div className="font-heading text-[14px] sm:text-[15px] font-semibold text-[#e2e5eb] leading-snug mb-1">{q.text}</div>
|
||||
{q.hint && <div className="text-[12px] text-[#848b9b] mb-3 sm:mb-4 leading-snug">{q.hint}</div>}
|
||||
{!q.hint && <div className="mb-3 sm:mb-4" />}
|
||||
|
||||
{q.type === 'mc' && q.options && (
|
||||
@@ -600,7 +600,7 @@ function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQues
|
||||
value={(answer as string) || ''}
|
||||
onChange={e => setAnswer(q.id, e.target.value)}
|
||||
placeholder="Type your answer here..."
|
||||
className="w-full min-h-[100px] rounded-[9px] p-3 sm:p-3.5 text-[13px] sm:text-sm text-foreground leading-relaxed resize-y transition-all duration-200 placeholder:text-brand-text-muted focus:outline-hidden"
|
||||
className="w-full min-h-[100px] rounded-[9px] p-3 sm:p-3.5 text-[13px] sm:text-sm text-[#e2e5eb] leading-relaxed resize-y transition-all duration-200 placeholder:text-brand-text-muted focus:outline-hidden"
|
||||
style={{
|
||||
background: 'rgba(16, 17, 20, 0.6)',
|
||||
border: '1px solid var(--glass-border)',
|
||||
@@ -621,7 +621,7 @@ function RangeInput({ question: q, value, onChange }: { question: SurveyQuestion
|
||||
const numVal = value ? parseInt(value) : q.min || 0
|
||||
return (
|
||||
<div className="py-2">
|
||||
<div className="text-center font-label text-2xl font-semibold mb-3" style={{ color: 'var(--color-primary)' }}>
|
||||
<div className="text-center font-sans text-xs text-2xl font-semibold mb-3" style={{ color: 'var(--color-primary)' }}>
|
||||
{numVal}{q.suffix || ''}
|
||||
</div>
|
||||
<input
|
||||
@@ -636,7 +636,7 @@ function RangeInput({ question: q, value, onChange }: { question: SurveyQuestion
|
||||
background: `linear-gradient(to right, var(--color-primary) 0%, var(--color-primary) ${((numVal - (q.min || 0)) / ((q.max || 10) - (q.min || 0))) * 100}%, var(--color-border) ${((numVal - (q.min || 0)) / ((q.max || 10) - (q.min || 0))) * 100}%, var(--color-border) 100%)`,
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-between text-[11px] text-muted-foreground mt-2.5">
|
||||
<div className="flex justify-between text-[11px] text-[#848b9b] mt-2.5">
|
||||
<span>{q.low_label}</span>
|
||||
<span>{q.high_label}</span>
|
||||
</div>
|
||||
@@ -739,7 +739,7 @@ function DragRank({ items, onChange }: { items: string[]; onChange: (items: stri
|
||||
<div className="shrink-0 text-brand-text-muted">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>
|
||||
</div>
|
||||
<div className="font-label text-[11px] font-semibold w-5 text-center shrink-0" style={{ color: 'var(--color-primary)' }}>{idx + 1}</div>
|
||||
<div className="font-sans text-xs text-[11px] font-semibold w-5 text-center shrink-0" style={{ color: 'var(--color-primary)' }}>{idx + 1}</div>
|
||||
<div className="flex-1 leading-snug">{item}</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -5,7 +5,7 @@ export default function SurveyThankYouPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Thank You" description="Thank you for your feedback on ResolutionFlow" />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
{/* Atmosphere orbs */}
|
||||
<div className="pointer-events-none fixed inset-0 z-0 overflow-hidden" aria-hidden="true">
|
||||
<div
|
||||
@@ -19,11 +19,11 @@ export default function SurveyThankYouPage() {
|
||||
</div>
|
||||
|
||||
{/* Top bar */}
|
||||
<div className="sticky top-0 z-50" style={{ backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
|
||||
<div className="sticky top-0 z-50" style={{ WebkitBackdropFilter: 'blur(20px)', background: 'rgba(16, 17, 20, 0.85)', borderBottom: '1px solid var(--glass-border)' }}>
|
||||
<div className="mx-auto flex max-w-[680px] items-center justify-between gap-3 px-5 py-3.5">
|
||||
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2.5 text-sm font-heading font-bold text-muted-foreground no-underline">
|
||||
<a href="https://resolutionflow.com" target="_blank" rel="noreferrer" className="flex items-center gap-2.5 text-sm font-heading font-bold text-[#848b9b] no-underline">
|
||||
<BrandLogo size="sm" />
|
||||
<span>Resolution<span className="text-gradient-brand">Flow</span></span>
|
||||
<span>Resolution<span className="text-[#67e8f9]">Flow</span></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,10 +40,10 @@ export default function SurveyThankYouPage() {
|
||||
<h1 className="font-heading text-[clamp(24px,4vw,32px)] font-extrabold leading-tight mb-3">
|
||||
Thank You!
|
||||
</h1>
|
||||
<p className="text-[15px] text-muted-foreground max-w-[460px] mx-auto leading-relaxed mb-3">
|
||||
<p className="text-[15px] text-[#848b9b] max-w-[460px] mx-auto leading-relaxed mb-3">
|
||||
Your response has been recorded. Your expertise will directly shape how FlowPilot thinks about troubleshooting.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/70 max-w-[400px] mx-auto leading-relaxed mb-10">
|
||||
<p className="text-sm text-[#848b9b]/70 max-w-[400px] mx-auto leading-relaxed mb-10">
|
||||
You can safely close this browser window now.
|
||||
</p>
|
||||
|
||||
@@ -52,21 +52,21 @@ export default function SurveyThankYouPage() {
|
||||
|
||||
{/* Feedback callout */}
|
||||
<div
|
||||
className="glass-card-static p-6 text-center max-w-[480px] mx-auto"
|
||||
className="card-flat p-6 text-center max-w-[480px] mx-auto"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2 mb-3">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" strokeWidth="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-widest text-primary font-semibold">
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#22d3ee] font-semibold">
|
||||
Have Feedback?
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
<p className="text-sm text-[#848b9b] leading-relaxed">
|
||||
If you have any feedback unrelated to the survey, we'd love to hear from you at{' '}
|
||||
<a
|
||||
href="mailto:feedback@resolutionflow.com"
|
||||
className="text-primary hover:underline font-medium"
|
||||
className="text-[#22d3ee] hover:underline font-medium"
|
||||
>
|
||||
feedback@resolutionflow.com
|
||||
</a>
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function TeamAnalyticsPage() {
|
||||
action={
|
||||
<Link
|
||||
to="/trees"
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-5 py-2.5 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-5 py-2.5 text-sm font-semibold text-white hover:brightness-110 active:scale-[0.98] transition-all"
|
||||
>
|
||||
Run Your First Session
|
||||
</Link>
|
||||
@@ -108,22 +108,22 @@ export default function TeamAnalyticsPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span title="Team Analytics">
|
||||
<BarChart3 size={24} className="text-foreground" />
|
||||
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||
</span>
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground">Team Analytics</h1>
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb]">Team Analytics</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="/analytics/me"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
My Stats
|
||||
</Link>
|
||||
<select
|
||||
value={period}
|
||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
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-primary/20"
|
||||
>
|
||||
{PERIOD_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
@@ -159,8 +159,8 @@ export default function TeamAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Area Chart — Sessions over Time */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Sessions Over Time
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
@@ -246,7 +246,7 @@ export default function TeamAnalyticsPage() {
|
||||
className="h-2.5 w-2.5 rounded-full"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground capitalize">
|
||||
<span className="text-xs text-[#848b9b] capitalize">
|
||||
{key}
|
||||
</span>
|
||||
</div>
|
||||
@@ -257,27 +257,27 @@ export default function TeamAnalyticsPage() {
|
||||
{/* Two-Column: Top Flows & Top Engineers */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Top Flows */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Top Flows
|
||||
</h2>
|
||||
{top_flows.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No flow data for this period.</p>
|
||||
<p className="text-sm text-[#848b9b]">No flow data for this period.</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left py-2 text-foreground font-medium">
|
||||
<tr className="border-b border-[#1e2130]">
|
||||
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Sessions
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Completion
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Median
|
||||
</th>
|
||||
</tr>
|
||||
@@ -286,18 +286,18 @@ export default function TeamAnalyticsPage() {
|
||||
{top_flows.map((flow) => (
|
||||
<tr
|
||||
key={flow.tree_id}
|
||||
className="border-b border-border last:border-0"
|
||||
className="border-b border-[#1e2130] last:border-0"
|
||||
>
|
||||
<td className="py-2 text-muted-foreground truncate max-w-[200px]">
|
||||
<td className="py-2 text-[#848b9b] truncate max-w-[200px]">
|
||||
{flow.name}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{flow.sessions}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{(flow.completion_rate * 100).toFixed(1)}%
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{flow.median_duration_minutes} min
|
||||
</td>
|
||||
</tr>
|
||||
@@ -309,27 +309,27 @@ export default function TeamAnalyticsPage() {
|
||||
</div>
|
||||
|
||||
{/* Top Engineers */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||
Top Engineers
|
||||
</h2>
|
||||
{top_engineers.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No engineer data for this period.</p>
|
||||
<p className="text-sm text-[#848b9b]">No engineer data for this period.</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border">
|
||||
<th className="text-left py-2 text-foreground font-medium">
|
||||
<tr className="border-b border-[#1e2130]">
|
||||
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Sessions
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Completion
|
||||
</th>
|
||||
<th className="text-right py-2 text-foreground font-medium">
|
||||
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||
Median
|
||||
</th>
|
||||
</tr>
|
||||
@@ -338,18 +338,18 @@ export default function TeamAnalyticsPage() {
|
||||
{top_engineers.map((eng) => (
|
||||
<tr
|
||||
key={eng.user_id}
|
||||
className="border-b border-border last:border-0"
|
||||
className="border-b border-[#1e2130] last:border-0"
|
||||
>
|
||||
<td className="py-2 text-muted-foreground truncate max-w-[200px]">
|
||||
<td className="py-2 text-[#848b9b] truncate max-w-[200px]">
|
||||
{eng.name}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{eng.sessions}
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{(eng.completion_rate * 100).toFixed(1)}%
|
||||
</td>
|
||||
<td className="py-2 text-right text-muted-foreground">
|
||||
<td className="py-2 text-right text-[#848b9b]">
|
||||
{eng.median_duration_minutes} min
|
||||
</td>
|
||||
</tr>
|
||||
@@ -374,12 +374,12 @@ function StatCard({
|
||||
value: string
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Icon size={16} className="text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">{label}</span>
|
||||
<Icon size={16} className="text-[#848b9b]" />
|
||||
<span className="text-sm text-[#848b9b]">{label}</span>
|
||||
</div>
|
||||
<p className="text-3xl font-bold text-foreground">{value}</p>
|
||||
<p className="text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,41 +5,41 @@ export default function TermsPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Terms of Service" description="ResolutionFlow Terms of Service" />
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<div className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||
<div className="mx-auto max-w-3xl px-6 py-16">
|
||||
<Link to="/landing" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">← Back to home</Link>
|
||||
<Link to="/landing" className="text-sm text-[#848b9b] hover:text-[#e2e5eb] mb-8 inline-block">← Back to home</Link>
|
||||
<h1 className="text-3xl font-bold font-heading mb-8">Terms of Service</h1>
|
||||
<p className="text-muted-foreground mb-6">Last updated: March 21, 2026</p>
|
||||
<p className="text-[#848b9b] mb-6">Last updated: March 21, 2026</p>
|
||||
|
||||
<div className="space-y-6 text-muted-foreground leading-relaxed">
|
||||
<div className="space-y-6 text-[#848b9b] leading-relaxed">
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">1. Acceptance of Terms</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">1. Acceptance of Terms</h2>
|
||||
<p>By accessing or using ResolutionFlow, you agree to be bound by these Terms of Service. If you are using ResolutionFlow on behalf of an organization, you represent that you have authority to bind that organization to these terms.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">2. Description of Service</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">2. Description of Service</h2>
|
||||
<p>ResolutionFlow provides AI-guided troubleshooting decision trees, automated documentation generation, and PSA integration tools for managed service providers (MSPs). The service includes FlowPilot AI copilot, flow editor, session management, and team collaboration features.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">3. Account Responsibilities</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">3. Account Responsibilities</h2>
|
||||
<p>You are responsible for maintaining the security of your account credentials and for all activities that occur under your account. You must notify us immediately of any unauthorized use.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">4. Data Ownership</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">4. Data Ownership</h2>
|
||||
<p>You retain ownership of all content you create within ResolutionFlow, including flows, session documentation, and team knowledge. We do not claim ownership of your data.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">5. Service Availability</h2>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">5. Service Availability</h2>
|
||||
<p>ResolutionFlow is currently in beta. We strive for high availability but do not guarantee uninterrupted access during the beta period. We will provide reasonable notice of planned maintenance.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-foreground mb-3">6. Contact</h2>
|
||||
<p>Questions about these terms? Email us at <a href="mailto:hello@resolutionflow.com" className="text-primary hover:underline">hello@resolutionflow.com</a>.</p>
|
||||
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-3">6. Contact</h2>
|
||||
<p>Questions about these terms? Email us at <a href="mailto:hello@resolutionflow.com" className="text-[#22d3ee] hover:underline">hello@resolutionflow.com</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -509,9 +509,9 @@ export function TreeEditorPage() {
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center px-6 text-center">
|
||||
<Monitor className="mb-4 h-12 w-12 text-muted-foreground" />
|
||||
<h2 className="mb-2 text-xl font-heading font-semibold text-foreground">Desktop Required</h2>
|
||||
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
|
||||
<Monitor className="mb-4 h-12 w-12 text-[#848b9b]" />
|
||||
<h2 className="mb-2 text-xl font-heading font-semibold text-[#e2e5eb]">Desktop Required</h2>
|
||||
<p className="mb-6 max-w-sm text-sm text-[#848b9b]">
|
||||
The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.
|
||||
</p>
|
||||
<Button onClick={() => navigate('/trees')}>
|
||||
@@ -528,10 +528,10 @@ export function TreeEditorPage() {
|
||||
|
||||
{/* Draft Restore Prompt */}
|
||||
{showDraftPrompt && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<h2 className="mb-2 text-lg font-heading font-semibold text-foreground">Restore Draft?</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<h2 className="mb-2 text-lg font-heading font-semibold text-[#e2e5eb]">Restore Draft?</h2>
|
||||
<p className="mb-4 text-sm text-[#848b9b]">
|
||||
You have an unsaved draft from a previous session. Would you like to restore it?
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
@@ -548,10 +548,10 @@ export function TreeEditorPage() {
|
||||
|
||||
{/* Unsaved Changes Dialog */}
|
||||
{blocker.state === 'blocked' && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<h2 className="mb-2 text-lg font-heading font-semibold text-foreground">Unsaved Changes</h2>
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl w-full max-w-md p-6 shadow-lg">
|
||||
<h2 className="mb-2 text-lg font-heading font-semibold text-[#e2e5eb]">Unsaved Changes</h2>
|
||||
<p className="mb-4 text-sm text-[#848b9b]">
|
||||
You have unsaved changes. Are you sure you want to leave?
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
@@ -567,17 +567,17 @@ export function TreeEditorPage() {
|
||||
)}
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center justify-between border-b border-border bg-card px-4 py-2">
|
||||
<div className="flex items-center justify-between border-b border-[#1e2130] bg-[#14161d] px-4 py-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/trees')}
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
← Back to Library
|
||||
</button>
|
||||
<h1 className="text-lg font-heading font-semibold text-foreground">
|
||||
<h1 className="text-lg font-heading font-semibold text-[#e2e5eb]">
|
||||
{isEditMode ? 'Edit Tree' : 'Create New Tree'}
|
||||
{name && <span className="ml-2 text-muted-foreground">- {name}</span>}
|
||||
{name && <span className="ml-2 text-[#848b9b]">- {name}</span>}
|
||||
</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
{treeStatus === 'draft' && (
|
||||
@@ -596,7 +596,7 @@ export function TreeEditorPage() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Mode Toggle */}
|
||||
<div className="flex items-center rounded-md border border-border">
|
||||
<div className="flex items-center rounded-md border border-[#1e2130]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditorMode('form')}
|
||||
@@ -604,8 +604,8 @@ export function TreeEditorPage() {
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-l-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
editorMode === 'form'
|
||||
? 'bg-accent text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<LayoutList className="h-3.5 w-3.5" />
|
||||
@@ -623,8 +623,8 @@ export function TreeEditorPage() {
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-r-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
editorMode === 'code'
|
||||
? 'bg-accent text-foreground'
|
||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Code2 className="h-3.5 w-3.5" />
|
||||
@@ -635,7 +635,7 @@ export function TreeEditorPage() {
|
||||
<div className="mx-1 h-6 w-px bg-border" />
|
||||
|
||||
{/* Undo/Redo */}
|
||||
<div className="flex items-center rounded-md border border-border">
|
||||
<div className="flex items-center rounded-md border border-[#1e2130]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleUndo}
|
||||
@@ -644,8 +644,8 @@ export function TreeEditorPage() {
|
||||
className={cn(
|
||||
'rounded-l-md p-2 transition-colors',
|
||||
pastStates.length > 0
|
||||
? 'text-foreground hover:bg-accent/50 active:bg-accent'
|
||||
: 'text-muted-foreground/50 cursor-not-allowed'
|
||||
? 'text-[#e2e5eb] hover:bg-accent/50 active:bg-accent'
|
||||
: 'text-[#848b9b]/50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<Undo2 className="h-4 w-4" />
|
||||
@@ -659,8 +659,8 @@ export function TreeEditorPage() {
|
||||
className={cn(
|
||||
'rounded-r-md p-2 transition-colors',
|
||||
futureStates.length > 0
|
||||
? 'text-foreground hover:bg-accent/50 active:bg-accent'
|
||||
: 'text-muted-foreground/50 cursor-not-allowed'
|
||||
? 'text-[#e2e5eb] hover:bg-accent/50 active:bg-accent'
|
||||
: 'text-[#848b9b]/50 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
<Redo2 className="h-4 w-4" />
|
||||
@@ -681,10 +681,10 @@ export function TreeEditorPage() {
|
||||
}}
|
||||
title="Edit flow metadata (name, description, category, tags)"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium transition-colors',
|
||||
isMetadataOpen
|
||||
? 'bg-accent text-foreground'
|
||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
@@ -698,10 +698,10 @@ export function TreeEditorPage() {
|
||||
onClick={() => setShowAnalytics(!showAnalytics)}
|
||||
title="Toggle flow analytics panel"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium transition-colors',
|
||||
showAnalytics
|
||||
? 'bg-accent text-foreground'
|
||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
@@ -715,8 +715,8 @@ export function TreeEditorPage() {
|
||||
onClick={() => setShowExportModal(true)}
|
||||
title="Export flow as .rfflow file"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
@@ -739,10 +739,10 @@ export function TreeEditorPage() {
|
||||
}}
|
||||
title="Toggle AI Assist panel"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium transition-colors',
|
||||
editorAI.isOpen
|
||||
? 'bg-primary/10 text-primary border-primary/30'
|
||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/30'
|
||||
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
@@ -754,8 +754,8 @@ export function TreeEditorPage() {
|
||||
disabled={isSaving}
|
||||
title="Validate tree structure (checks for errors and warnings)"
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground disabled:opacity-50'
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
@@ -800,7 +800,7 @@ export function TreeEditorPage() {
|
||||
|
||||
{/* Import provenance */}
|
||||
{importMetadata && (
|
||||
<div className="mx-4 mb-2 flex items-center gap-2 text-xs font-label text-muted-foreground">
|
||||
<div className="mx-4 mb-2 flex items-center gap-2 text-xs font-sans text-xs text-[#848b9b]">
|
||||
<FileText className="h-3 w-3" />
|
||||
<span>
|
||||
Imported{importMetadata.original_author_name ? ` from ${importMetadata.original_author_name}` : ''}
|
||||
@@ -825,7 +825,7 @@ export function TreeEditorPage() {
|
||||
|
||||
{/* Flow Analytics Panel (collapsible) */}
|
||||
{showAnalytics && id && (
|
||||
<div className="border-t border-border p-6 overflow-y-auto max-h-[50vh]">
|
||||
<div className="border-t border-[#1e2130] p-6 overflow-y-auto max-h-[50vh]">
|
||||
<FlowAnalyticsPanel treeId={id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -291,10 +291,10 @@ export function TreeLibraryPage() {
|
||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||
<div className="mb-6 flex flex-col gap-4 sm:mb-8 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">
|
||||
{typeFilter === 'procedural' ? 'Projects' : typeFilter === 'troubleshooting' ? 'Troubleshooting Flows' : typeFilter === 'maintenance' ? 'Maintenance Flows' : 'Flow Library'}
|
||||
</h1>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
{typeFilter === 'procedural'
|
||||
? 'Step-by-step projects and runbooks'
|
||||
: typeFilter === 'troubleshooting'
|
||||
@@ -333,8 +333,8 @@ export function TreeLibraryPage() {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
className={cn(
|
||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -348,8 +348,8 @@ export function TreeLibraryPage() {
|
||||
onChange={(e) => setSelectedCategoryId(e.target.value)}
|
||||
aria-label="Filter by category"
|
||||
className={cn(
|
||||
'rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
'rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
@@ -364,7 +364,7 @@ export function TreeLibraryPage() {
|
||||
{/* View Controls */}
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
{/* Type filter tabs */}
|
||||
<div className="flex rounded-lg border border-border p-0.5">
|
||||
<div className="flex rounded-lg border border-[#1e2130] p-0.5">
|
||||
{(['all', 'troubleshooting', 'procedural', 'maintenance'] as const).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
@@ -372,8 +372,8 @@ export function TreeLibraryPage() {
|
||||
className={cn(
|
||||
'rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
||||
typeFilter === t
|
||||
? 'bg-accent text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
? 'bg-accent text-[#e2e5eb]'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
{t === 'all' ? 'All' : t === 'troubleshooting' ? 'Troubleshooting' : t === 'procedural' ? 'Projects' : 'Maintenance'}
|
||||
@@ -392,9 +392,9 @@ export function TreeLibraryPage() {
|
||||
{/* Active Filters */}
|
||||
{hasActiveFilters && (
|
||||
<div className="mb-6 flex flex-wrap items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Filters:</span>
|
||||
<span className="text-sm text-[#848b9b]">Filters:</span>
|
||||
{selectedFolderId && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-foreground">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-[#e2e5eb]">
|
||||
Folder
|
||||
<button
|
||||
onClick={() => setSelectedFolderId(null)}
|
||||
@@ -405,7 +405,7 @@ export function TreeLibraryPage() {
|
||||
</span>
|
||||
)}
|
||||
{selectedCategoryId && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-foreground">
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-[#e2e5eb]">
|
||||
{categories.find((c) => c.id === selectedCategoryId)?.name}
|
||||
<button
|
||||
onClick={() => setSelectedCategoryId('')}
|
||||
@@ -418,7 +418,7 @@ export function TreeLibraryPage() {
|
||||
{selectedTags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-foreground"
|
||||
className="inline-flex items-center gap-1 rounded-full bg-accent px-3 py-1 text-sm text-[#e2e5eb]"
|
||||
>
|
||||
{tag}
|
||||
<button
|
||||
@@ -431,7 +431,7 @@ export function TreeLibraryPage() {
|
||||
))}
|
||||
<button
|
||||
onClick={clearAllFilters}
|
||||
className="text-sm text-muted-foreground hover:text-foreground"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
@@ -442,12 +442,12 @@ export function TreeLibraryPage() {
|
||||
{visibleIncompleteSessions.length > 0 && (
|
||||
<div className="mb-6 space-y-2">
|
||||
{visibleIncompleteSessions.map(s => (
|
||||
<div key={s.id} className="bg-card border border-border flex items-center justify-between rounded-xl p-4">
|
||||
<div key={s.id} className="bg-[#14161d] border border-[#1e2130] flex items-center justify-between rounded-xl p-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium text-foreground">
|
||||
<p className="truncate font-medium text-[#e2e5eb]">
|
||||
{s.tree_snapshot?.name || 'Unknown tree'}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
{s.client_name && `${s.client_name} · `}
|
||||
{s.started_at ? `Started ${formatTimeAgo(s.started_at)}` : 'Not started'}
|
||||
</p>
|
||||
@@ -462,7 +462,7 @@ export function TreeLibraryPage() {
|
||||
</Button>
|
||||
<button
|
||||
onClick={() => dismissSession(s.id)}
|
||||
className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -480,8 +480,8 @@ export function TreeLibraryPage() {
|
||||
state: { prefillClientName: lastSessionData.client_name, prefillTicketNumber: lastSessionData.ticket_number },
|
||||
})}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg border border-border px-4 py-2.5 text-sm text-muted-foreground',
|
||||
'hover:border-border hover:bg-accent hover:text-foreground'
|
||||
'flex items-center gap-2 rounded-lg border border-[#1e2130] px-4 py-2.5 text-sm text-[#848b9b]',
|
||||
'hover:border-[#1e2130] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
|
||||
@@ -603,7 +603,7 @@ export function TreeNavigationPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate('/trees')}
|
||||
className="mt-4 text-muted-foreground hover:text-foreground hover:underline"
|
||||
className="mt-4 text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
Back to trees
|
||||
</button>
|
||||
@@ -615,17 +615,17 @@ export function TreeNavigationPage() {
|
||||
if (showMetadataForm) {
|
||||
return (
|
||||
<div className="container mx-auto max-w-lg px-4 py-8">
|
||||
<h1 className="mb-2 text-2xl font-bold font-heading text-foreground">{tree.name}</h1>
|
||||
<p className="mb-6 text-muted-foreground">{tree.description}</p>
|
||||
<h1 className="mb-2 text-2xl font-bold font-heading text-[#e2e5eb]">{tree.name}</h1>
|
||||
<p className="mb-6 text-[#848b9b]">{tree.description}</p>
|
||||
|
||||
<div className="bg-card border border-border rounded-xl space-y-4 p-6">
|
||||
<h2 className="font-semibold text-foreground">Session Details</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl space-y-4 p-6">
|
||||
<h2 className="font-semibold text-[#e2e5eb]">Session Details</h2>
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Optional: Add ticket and client info for easier tracking
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Ticket Number
|
||||
</label>
|
||||
<input
|
||||
@@ -634,15 +634,15 @@ export function TreeNavigationPage() {
|
||||
onChange={(e) => setTicketNumber(e.target.value)}
|
||||
placeholder="e.g., INC0012345"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Client Name
|
||||
</label>
|
||||
<input
|
||||
@@ -651,8 +651,8 @@ export function TreeNavigationPage() {
|
||||
onChange={(e) => setClientName(e.target.value)}
|
||||
placeholder="e.g., Acme Corp"
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -685,9 +685,9 @@ export function TreeNavigationPage() {
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-xl font-bold font-heading text-foreground">{tree.name}</h1>
|
||||
<h1 className="text-xl font-bold font-heading text-[#e2e5eb]">{tree.name}</h1>
|
||||
{timerDisplay && (
|
||||
<span className="flex items-center gap-1.5 rounded-full bg-accent px-2 py-0.5 text-[0.6875rem] font-label text-muted-foreground">
|
||||
<span className="flex items-center gap-1.5 rounded-full bg-accent px-2 py-0.5 text-[0.6875rem] font-sans text-xs text-[#848b9b]">
|
||||
<Clock className="h-4 w-4" />
|
||||
{timerDisplay}
|
||||
</span>
|
||||
@@ -695,14 +695,14 @@ export function TreeNavigationPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShortcutsModalOpen(true)}
|
||||
className="flex items-center justify-center rounded-full p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex items-center justify-center rounded-full p-1 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
title="Keyboard shortcuts"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
{(ticketNumber || clientName) && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
{ticketNumber && `Ticket: ${ticketNumber}`}
|
||||
{ticketNumber && clientName && ' · '}
|
||||
{clientName && `Client: ${clientName}`}
|
||||
@@ -725,8 +725,8 @@ export function TreeNavigationPage() {
|
||||
<button
|
||||
onClick={() => setShowSharePopover(!showSharePopover)}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground transition-colors'
|
||||
'flex items-center gap-1.5 rounded-md border border-[#1e2130] px-3 py-1.5 text-xs font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb] transition-colors'
|
||||
)}
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
@@ -734,7 +734,7 @@ export function TreeNavigationPage() {
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</button>
|
||||
{showSharePopover && (
|
||||
<div className="absolute right-0 mt-1 z-50 bg-card border border-border rounded-xl p-1 min-w-[220px]">
|
||||
<div className="absolute right-0 mt-1 z-50 bg-[#14161d] border border-[#1e2130] rounded-xl p-1 min-w-[220px]">
|
||||
{/* Copy Progress Summary */}
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -743,8 +743,8 @@ export function TreeNavigationPage() {
|
||||
}}
|
||||
disabled={isCopyingForTicket}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-foreground disabled:opacity-50'
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-[#848b9b] w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
{copiedForTicket ? <Check className="h-4 w-4 text-emerald-400" /> : <Clipboard className="h-4 w-4" />}
|
||||
@@ -758,15 +758,15 @@ export function TreeNavigationPage() {
|
||||
}}
|
||||
disabled={isCopyingShareLink}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-foreground disabled:opacity-50'
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-[#848b9b] w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
{copiedShareLink ? <Check className="h-4 w-4 text-emerald-400" /> : <Link2 className="h-4 w-4" />}
|
||||
{isCopyingShareLink ? 'Loading...' : copiedShareLink ? 'Copied!' : 'Copy Share Link'}
|
||||
</button>
|
||||
{/* Divider */}
|
||||
<div className="border-t border-border my-1" />
|
||||
<div className="border-t border-[#1e2130] my-1" />
|
||||
{/* Manage Share Links */}
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -774,8 +774,8 @@ export function TreeNavigationPage() {
|
||||
setShowShareModal(true)
|
||||
}}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-muted-foreground w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'flex items-center gap-2 rounded-md px-3 py-2 text-sm text-[#848b9b] w-full text-left cursor-pointer transition-colors',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
@@ -786,7 +786,7 @@ export function TreeNavigationPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate('/sessions')}
|
||||
className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Exit
|
||||
</button>
|
||||
@@ -814,17 +814,17 @@ export function TreeNavigationPage() {
|
||||
const truncatedLabel = label.length > 30 ? `${label.slice(0, 30)}...` : label
|
||||
return (
|
||||
<span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
|
||||
{index > 0 && <span className="text-muted-foreground">→</span>}
|
||||
{index > 0 && <span className="text-[#848b9b]">→</span>}
|
||||
{index < pathTaken.length - 1 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleBreadcrumbJump(nodeId, index)}
|
||||
className="text-muted-foreground hover:text-foreground hover:underline"
|
||||
className="text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||
>
|
||||
{truncatedLabel}
|
||||
</button>
|
||||
) : (
|
||||
<span className="font-medium text-foreground">
|
||||
<span className="font-medium text-[#e2e5eb]">
|
||||
{truncatedLabel}
|
||||
</span>
|
||||
)}
|
||||
@@ -834,7 +834,7 @@ export function TreeNavigationPage() {
|
||||
</div>
|
||||
|
||||
{/* Current Node */}
|
||||
<div className="bg-card border border-border rounded-xl p-6 shadow-xs">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 shadow-xs">
|
||||
{/* Answer placeholder guard */}
|
||||
{currentNode && currentNode.type === 'answer' && (
|
||||
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-6 text-center">
|
||||
@@ -847,11 +847,11 @@ export function TreeNavigationPage() {
|
||||
{/* Decision Node */}
|
||||
{currentNode && currentNode.type === 'decision' && (
|
||||
<>
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-[#e2e5eb]">
|
||||
{currentNode.question}
|
||||
</h2>
|
||||
{currentNode.help_text && (
|
||||
<div className="mb-4 text-sm text-muted-foreground">
|
||||
<div className="mb-4 text-sm text-[#848b9b]">
|
||||
<MarkdownContent content={currentNode.help_text} />
|
||||
</div>
|
||||
)}
|
||||
@@ -862,7 +862,7 @@ export function TreeNavigationPage() {
|
||||
onClick={() => handleSelectOption(option.id, option.label, option.next_node_id)}
|
||||
disabled={!!selectingOption}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border p-3 text-left text-foreground transition-colors',
|
||||
'w-full rounded-md border border-[#1e2130] p-3 text-left text-[#e2e5eb] transition-colors',
|
||||
'hover:border-primary/30 hover:bg-accent',
|
||||
'flex items-center gap-3',
|
||||
selectingOption && selectingOption !== option.id && 'opacity-50 pointer-events-none'
|
||||
@@ -874,7 +874,7 @@ export function TreeNavigationPage() {
|
||||
<Spinner size="sm" className="h-4 w-4 border-t-foreground" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-accent text-xs font-medium text-muted-foreground">
|
||||
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-accent text-xs font-medium text-[#848b9b]">
|
||||
{index + 1}
|
||||
</span>
|
||||
)
|
||||
@@ -886,7 +886,7 @@ export function TreeNavigationPage() {
|
||||
{/* Previously-created custom steps at this node */}
|
||||
{customStepFlow.customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-[#848b9b]">
|
||||
Your Custom Steps
|
||||
</p>
|
||||
{customStepFlow.customSteps
|
||||
@@ -897,12 +897,12 @@ export function TreeNavigationPage() {
|
||||
key={cs.id}
|
||||
onClick={() => customStepFlow.handleNavigateToCustomStep(cs)}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-primary/30 bg-primary/10 p-3 text-left text-foreground transition-colors',
|
||||
'w-full rounded-md border border-primary/30 bg-[rgba(34,211,238,0.10)] p-3 text-left text-[#e2e5eb] transition-colors',
|
||||
'hover:border-primary/50 hover:bg-primary/20',
|
||||
'flex items-center gap-3'
|
||||
)}
|
||||
>
|
||||
<span className="shrink-0 rounded-full bg-primary/20 px-2 py-0.5 text-xs font-medium text-primary">
|
||||
<span className="shrink-0 rounded-full bg-primary/20 px-2 py-0.5 text-xs font-medium text-[#22d3ee]">
|
||||
Custom
|
||||
</span>
|
||||
<span>{cs.step_data.title}</span>
|
||||
@@ -914,7 +914,7 @@ export function TreeNavigationPage() {
|
||||
{/* Add Custom Step Button */}
|
||||
<button
|
||||
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
||||
className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Add Custom Step
|
||||
@@ -924,18 +924,18 @@ export function TreeNavigationPage() {
|
||||
|
||||
{/* Custom Step Node */}
|
||||
{currentCustomStep && (
|
||||
<div className="rounded-lg border border-primary/30 bg-primary/10 p-4">
|
||||
<div className="rounded-lg border border-primary/30 bg-[rgba(34,211,238,0.10)] p-4">
|
||||
{/* Custom Step Badge */}
|
||||
<span className="mb-2 inline-block rounded-full bg-primary/20 px-2 py-1 text-xs font-medium text-primary">
|
||||
<span className="mb-2 inline-block rounded-full bg-primary/20 px-2 py-1 text-xs font-medium text-[#22d3ee]">
|
||||
Custom Step
|
||||
</span>
|
||||
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-[#e2e5eb]">
|
||||
{currentCustomStep.step_data.title}
|
||||
</h2>
|
||||
|
||||
{currentCustomStep.step_data.content.instructions && (
|
||||
<div className="mb-4 text-muted-foreground">
|
||||
<div className="mb-4 text-[#848b9b]">
|
||||
<MarkdownContent content={currentCustomStep.step_data.content.instructions} />
|
||||
</div>
|
||||
)}
|
||||
@@ -948,11 +948,11 @@ export function TreeNavigationPage() {
|
||||
|
||||
{currentCustomStep.step_data.content.commands && currentCustomStep.step_data.content.commands.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
|
||||
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Commands:</p>
|
||||
<div className="space-y-2">
|
||||
{currentCustomStep.step_data.content.commands.map((cmd, index) => (
|
||||
<div key={index}>
|
||||
<p className="mb-1 text-xs text-muted-foreground">{cmd.label}</p>
|
||||
<p className="mb-1 text-xs text-[#848b9b]">{cmd.label}</p>
|
||||
<div className="group relative">
|
||||
<code className="block rounded bg-accent p-2 pr-8 text-sm font-mono">
|
||||
{cmd.command}
|
||||
@@ -966,7 +966,7 @@ export function TreeNavigationPage() {
|
||||
{copiedCommand === cmd.command ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-400" />
|
||||
) : (
|
||||
<Clipboard className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||
<Clipboard className="h-3.5 w-3.5 text-[#848b9b] hover:text-[#e2e5eb]" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -978,7 +978,7 @@ export function TreeNavigationPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCommandOutputOpen(!commandOutputOpen)}
|
||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
|
||||
className="flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Terminal className="h-3.5 w-3.5" />
|
||||
<span>Paste Output (Optional)</span>
|
||||
@@ -993,12 +993,12 @@ export function TreeNavigationPage() {
|
||||
rows={4}
|
||||
maxLength={10000}
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border bg-accent px-3 py-2',
|
||||
'font-mono text-sm text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-md border border-[#1e2130] bg-accent px-3 py-2',
|
||||
'font-mono text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-right text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-right text-xs text-[#848b9b]">
|
||||
{commandOutput.length.toLocaleString()} / 10,000
|
||||
</p>
|
||||
</div>
|
||||
@@ -1035,8 +1035,8 @@ export function TreeNavigationPage() {
|
||||
<button
|
||||
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-foreground',
|
||||
'hover:bg-accent hover:text-foreground'
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-4 py-2 text-sm font-medium text-[#848b9b]',
|
||||
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
@@ -1062,17 +1062,17 @@ export function TreeNavigationPage() {
|
||||
{/* Action Node */}
|
||||
{currentNode && currentNode.type === 'action' && (
|
||||
<>
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-[#e2e5eb]">
|
||||
{currentNode.title}
|
||||
</h2>
|
||||
{currentNode.description && (
|
||||
<div className="mb-4 text-muted-foreground">
|
||||
<div className="mb-4 text-[#848b9b]">
|
||||
<MarkdownContent content={currentNode.description} />
|
||||
</div>
|
||||
)}
|
||||
{currentNode.commands && currentNode.commands.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
|
||||
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Commands:</p>
|
||||
<div className="space-y-1">
|
||||
{currentNode.commands.map((cmd, index) => (
|
||||
<div key={index} className="group relative">
|
||||
@@ -1088,7 +1088,7 @@ export function TreeNavigationPage() {
|
||||
{copiedCommand === cmd ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-400" />
|
||||
) : (
|
||||
<Clipboard className="h-3.5 w-3.5 text-muted-foreground hover:text-foreground" />
|
||||
<Clipboard className="h-3.5 w-3.5 text-[#848b9b] hover:text-[#e2e5eb]" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1099,7 +1099,7 @@ export function TreeNavigationPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setCommandOutputOpen(!commandOutputOpen)}
|
||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
|
||||
className="flex items-center gap-1.5 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Terminal className="h-3.5 w-3.5" />
|
||||
<span>Paste Output (Optional)</span>
|
||||
@@ -1114,12 +1114,12 @@ export function TreeNavigationPage() {
|
||||
rows={4}
|
||||
maxLength={10000}
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-border bg-accent px-3 py-2',
|
||||
'font-mono text-sm text-foreground placeholder:text-muted-foreground',
|
||||
'block w-full rounded-md border border-[#1e2130] bg-accent px-3 py-2',
|
||||
'font-mono text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-right text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-right text-xs text-[#848b9b]">
|
||||
{commandOutput.length.toLocaleString()} / 10,000
|
||||
</p>
|
||||
</div>
|
||||
@@ -1128,7 +1128,7 @@ export function TreeNavigationPage() {
|
||||
</div>
|
||||
)}
|
||||
{currentNode.expected_outcome && (
|
||||
<p className="mb-4 text-sm text-muted-foreground">
|
||||
<p className="mb-4 text-sm text-[#848b9b]">
|
||||
<strong>Expected outcome:</strong> {currentNode.expected_outcome}
|
||||
</p>
|
||||
)}
|
||||
@@ -1148,18 +1148,18 @@ export function TreeNavigationPage() {
|
||||
Solution
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-foreground">
|
||||
<h2 className="mb-2 text-xl font-semibold font-heading text-[#e2e5eb]">
|
||||
{currentNode.title}
|
||||
</h2>
|
||||
{currentNode.description && (
|
||||
<div className="mb-4 text-muted-foreground">
|
||||
<div className="mb-4 text-[#848b9b]">
|
||||
<MarkdownContent content={currentNode.description} />
|
||||
</div>
|
||||
)}
|
||||
{currentNode.resolution_steps && currentNode.resolution_steps.length > 0 && (
|
||||
<div className="mb-4">
|
||||
<p className="mb-2 text-sm font-medium text-foreground">Resolution steps:</p>
|
||||
<ol className="list-inside list-decimal space-y-1 text-sm text-muted-foreground">
|
||||
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Resolution steps:</p>
|
||||
<ol className="list-inside list-decimal space-y-1 text-sm text-[#848b9b]">
|
||||
{currentNode.resolution_steps.map((step, index) => (
|
||||
<li key={index}>{step}</li>
|
||||
))}
|
||||
@@ -1181,14 +1181,14 @@ export function TreeNavigationPage() {
|
||||
|
||||
{/* Step Feedback */}
|
||||
{session && (currentNode || currentCustomStep) && (
|
||||
<div className="mt-3 flex justify-end border-t border-border pt-3">
|
||||
<div className="mt-3 flex justify-end border-t border-[#1e2130] pt-3">
|
||||
<StepFeedback stepId={currentCustomStep?.id || currentNodeId} sessionId={session.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notes */}
|
||||
<div className="mt-6 border-t border-border pt-4">
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
<div className="mt-6 border-t border-[#1e2130] pt-4">
|
||||
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||
Notes (optional)
|
||||
</label>
|
||||
<textarea
|
||||
@@ -1198,8 +1198,8 @@ export function TreeNavigationPage() {
|
||||
placeholder="Add any notes for this step..."
|
||||
rows={2}
|
||||
className={cn(
|
||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
@@ -1216,15 +1216,15 @@ export function TreeNavigationPage() {
|
||||
{pathTaken.length > 1 && (
|
||||
<button
|
||||
onClick={handleGoBack}
|
||||
className="mt-4 text-sm text-muted-foreground hover:text-foreground"
|
||||
className="mt-4 text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
← Go back <span className="text-xs text-muted-foreground">[Esc]</span>
|
||||
← Go back <span className="text-xs text-[#848b9b]">[Esc]</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Keyboard Shortcuts Hint */}
|
||||
{currentNode && (
|
||||
<div className="mt-4 border-t border-border pt-3 text-xs text-muted-foreground">
|
||||
<div className="mt-4 border-t border-[#1e2130] pt-3 text-xs text-[#848b9b]">
|
||||
<span className="font-medium">Keyboard:</span>{' '}
|
||||
{currentNode.type === 'decision' && currentOptions.length > 0 && (
|
||||
<span>1-{Math.min(currentOptions.length, 9)} select option</span>
|
||||
@@ -1301,24 +1301,24 @@ export function TreeNavigationPage() {
|
||||
>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Select option</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">1-9</span>
|
||||
<span className="text-[#848b9b]">Select option</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">1-9</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Go back</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Esc</span>
|
||||
<span className="text-[#848b9b]">Go back</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Esc</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Continue / Complete</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Enter</span>
|
||||
<span className="text-[#848b9b]">Continue / Complete</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Enter</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Focus notes</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Tab</span>
|
||||
<span className="text-[#848b9b]">Focus notes</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Tab</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Show shortcuts</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">?</span>
|
||||
<span className="text-[#848b9b]">Show shortcuts</span>
|
||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">?</span>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -26,24 +26,24 @@ export function VerifyEmailPage() {
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Verify Email" description="Verify your ResolutionFlow email address" />
|
||||
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
||||
<div className="glass-card-static w-full max-w-md p-8 text-center">
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#0c0d10] p-4">
|
||||
<div className="card-flat w-full max-w-md p-8 text-center">
|
||||
{status === 'loading' && (
|
||||
<>
|
||||
<Loader2 className="mx-auto h-12 w-12 animate-spin text-primary" />
|
||||
<p className="mt-4 text-foreground">Verifying your email...</p>
|
||||
<Loader2 className="mx-auto h-12 w-12 animate-spin text-[#22d3ee]" />
|
||||
<p className="mt-4 text-[#e2e5eb]">Verifying your email...</p>
|
||||
</>
|
||||
)}
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<CheckCircle2 className="mx-auto h-12 w-12 text-emerald-400" />
|
||||
<h1 className="mt-4 text-xl font-bold font-heading text-foreground">Email Verified</h1>
|
||||
<p className="mt-2 text-muted-foreground">Your email has been successfully verified.</p>
|
||||
<h1 className="mt-4 text-xl font-bold font-heading text-[#e2e5eb]">Email Verified</h1>
|
||||
<p className="mt-2 text-[#848b9b]">Your email has been successfully verified.</p>
|
||||
<Link
|
||||
to="/"
|
||||
className={cn(
|
||||
'mt-6 inline-flex items-center rounded-[10px] bg-gradient-brand px-6 py-2 text-sm font-semibold text-brand-dark',
|
||||
'shadow-lg shadow-primary/20 hover:opacity-90'
|
||||
'mt-6 inline-flex items-center rounded-lg bg-[#22d3ee] px-6 py-2 text-sm font-semibold text-brand-dark',
|
||||
'hover:brightness-110'
|
||||
)}
|
||||
>
|
||||
Go to Dashboard
|
||||
@@ -53,12 +53,12 @@ export function VerifyEmailPage() {
|
||||
{status === 'error' && (
|
||||
<>
|
||||
<XCircle className="mx-auto h-12 w-12 text-rose-500" />
|
||||
<h1 className="mt-4 text-xl font-bold font-heading text-foreground">Verification Failed</h1>
|
||||
<p className="mt-2 text-muted-foreground">{errorMessage}</p>
|
||||
<h1 className="mt-4 text-xl font-bold font-heading text-[#e2e5eb]">Verification Failed</h1>
|
||||
<p className="mt-2 text-[#848b9b]">{errorMessage}</p>
|
||||
<Link
|
||||
to="/"
|
||||
className={cn(
|
||||
'mt-6 inline-flex items-center rounded-[10px] bg-white/[0.04] border border-brand-border px-6 py-2 text-sm font-medium text-foreground',
|
||||
'mt-6 inline-flex items-center rounded-lg bg-white/[0.04] border border-brand-border px-6 py-2 text-sm font-medium text-[#e2e5eb]',
|
||||
'hover:border-white/[0.12]'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function BrandingSettingsPage() {
|
||||
<>
|
||||
<PageMeta title="Branding Settings" />
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Loader2 className="h-8 w-8 animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -103,27 +103,27 @@ export function BrandingSettingsPage() {
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Palette className="h-8 w-8 text-muted-foreground" />
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">
|
||||
<Palette className="h-8 w-8 text-[#848b9b]" />
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">
|
||||
Branding Settings
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Customize your account branding — logo, accent color, and company name.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-2xl space-y-6">
|
||||
{/* Branding Form */}
|
||||
<div className="glass-card-static p-6">
|
||||
<h2 className="text-lg font-semibold text-foreground mb-6">Custom Branding</h2>
|
||||
<div className="card-flat p-6">
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb] mb-6">Custom Branding</h2>
|
||||
|
||||
<div className="space-y-5">
|
||||
{/* Company Name */}
|
||||
<div>
|
||||
<label
|
||||
htmlFor="company-name"
|
||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
||||
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]"
|
||||
>
|
||||
Company Name
|
||||
</label>
|
||||
@@ -135,12 +135,12 @@ export function BrandingSettingsPage() {
|
||||
placeholder="Your Company Name"
|
||||
maxLength={200}
|
||||
className={cn(
|
||||
'mt-1 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 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 focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Displayed in the sidebar and exported documents.
|
||||
</p>
|
||||
</div>
|
||||
@@ -149,7 +149,7 @@ export function BrandingSettingsPage() {
|
||||
<div>
|
||||
<label
|
||||
htmlFor="logo-url"
|
||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
||||
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]"
|
||||
>
|
||||
Logo URL
|
||||
</label>
|
||||
@@ -161,16 +161,16 @@ export function BrandingSettingsPage() {
|
||||
placeholder="https://example.com/logo.png"
|
||||
maxLength={500}
|
||||
className={cn(
|
||||
'mt-1 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
||||
'text-foreground placeholder:text-muted-foreground font-mono',
|
||||
'mt-1 w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b] font-mono',
|
||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Publicly accessible URL to your logo image (PNG, SVG, or JPEG).
|
||||
</p>
|
||||
{logoUrl && (
|
||||
<div className="mt-3 inline-flex items-center rounded-lg border border-border bg-card/50 p-3">
|
||||
<div className="mt-3 inline-flex items-center rounded-lg border border-[#1e2130] bg-[#14161d]/50 p-3">
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt="Logo preview"
|
||||
@@ -187,7 +187,7 @@ export function BrandingSettingsPage() {
|
||||
<div>
|
||||
<label
|
||||
htmlFor="primary-color"
|
||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground"
|
||||
className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]"
|
||||
>
|
||||
Accent Color
|
||||
</label>
|
||||
@@ -197,7 +197,7 @@ export function BrandingSettingsPage() {
|
||||
type="color"
|
||||
value={previewColor}
|
||||
onChange={(e) => setPrimaryColor(e.target.value)}
|
||||
className="h-9 w-14 cursor-pointer rounded-lg border border-border bg-card p-0.5"
|
||||
className="h-9 w-14 cursor-pointer rounded-lg border border-[#1e2130] bg-[#14161d] p-0.5"
|
||||
title="Pick accent color"
|
||||
/>
|
||||
<input
|
||||
@@ -210,20 +210,20 @@ export function BrandingSettingsPage() {
|
||||
placeholder="#06b6d4"
|
||||
maxLength={7}
|
||||
className={cn(
|
||||
'w-32 rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
||||
'text-foreground placeholder:text-muted-foreground font-mono',
|
||||
'w-32 rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b] font-mono',
|
||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPrimaryColor(DEFAULT_COLOR)}
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-xs text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Reset to default
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-xs text-[#848b9b]">
|
||||
Hex color code for the primary accent color (e.g. #06b6d4).
|
||||
</p>
|
||||
</div>
|
||||
@@ -236,9 +236,9 @@ export function BrandingSettingsPage() {
|
||||
onClick={handleSave}
|
||||
disabled={isSaving || !isDirty}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
||||
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-[#22d3ee] text-white',
|
||||
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
@@ -250,15 +250,15 @@ export function BrandingSettingsPage() {
|
||||
Save Branding
|
||||
</button>
|
||||
{!isDirty && !isSaving && (
|
||||
<span className="text-xs text-muted-foreground">No changes</span>
|
||||
<span className="text-xs text-[#848b9b]">No changes</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview Section */}
|
||||
<div className="glass-card-static p-6">
|
||||
<h2 className="text-lg font-semibold text-foreground mb-4">Preview</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
<div className="card-flat p-6">
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb] mb-4">Preview</h2>
|
||||
<p className="text-sm text-[#848b9b] mb-4">
|
||||
This is how your accent color will appear on interactive elements.
|
||||
</p>
|
||||
|
||||
@@ -274,7 +274,7 @@ export function BrandingSettingsPage() {
|
||||
style={{ backgroundColor: previewColor }}
|
||||
/>
|
||||
<div
|
||||
className="h-8 rounded-lg px-4 flex items-center text-sm font-semibold text-[#101114]"
|
||||
className="h-8 rounded-lg px-4 flex items-center text-sm font-semibold text-white"
|
||||
style={{
|
||||
background: `linear-gradient(135deg, ${previewColor} 0%, ${previewColor}cc 100%)`,
|
||||
}}
|
||||
@@ -293,7 +293,7 @@ export function BrandingSettingsPage() {
|
||||
</div>
|
||||
|
||||
{companyName && (
|
||||
<p className="text-sm text-foreground">
|
||||
<p className="text-sm text-[#e2e5eb]">
|
||||
Company: <span className="font-medium">{companyName}</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function ChatRetentionSettingsPage() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="animate-spin text-primary" size={24} />
|
||||
<Loader2 className="animate-spin text-[#22d3ee]" size={24} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -53,18 +53,18 @@ export default function ChatRetentionSettingsPage() {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto py-8 px-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Clock size={20} className="text-primary" />
|
||||
<h1 className="text-xl font-heading font-bold text-foreground">Chat Retention</h1>
|
||||
<Clock size={20} className="text-[#22d3ee]" />
|
||||
<h1 className="text-xl font-heading font-bold text-[#e2e5eb]">Chat Retention</h1>
|
||||
</div>
|
||||
|
||||
<div className="glass-card-static rounded-2xl p-6 space-y-6">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<div className="card-flat rounded-2xl p-6 space-y-6">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Configure how long AI assistant conversations are retained. Pinned chats are never automatically deleted.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-1.5">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] block mb-1.5">
|
||||
Retention Period (days)
|
||||
</label>
|
||||
<input
|
||||
@@ -73,16 +73,16 @@ export default function ChatRetentionSettingsPage() {
|
||||
onChange={e => setRetentionDays(e.target.value)}
|
||||
min={1}
|
||||
max={365}
|
||||
className="w-full rounded-xl border bg-card text-foreground text-sm px-4 py-2.5 focus:outline-hidden focus:border-primary/30"
|
||||
className="w-full rounded-xl border bg-[#14161d] text-[#e2e5eb] text-sm px-4 py-2.5 focus:outline-hidden focus:border-primary/30"
|
||||
style={{ borderColor: 'var(--glass-border)' }}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-[#848b9b] mt-1">
|
||||
Chats older than this will be automatically deleted (1-365 days)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-1.5">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] block mb-1.5">
|
||||
Max Conversations
|
||||
</label>
|
||||
<input
|
||||
@@ -91,10 +91,10 @@ export default function ChatRetentionSettingsPage() {
|
||||
onChange={e => setMaxCount(e.target.value)}
|
||||
min={10}
|
||||
max={10000}
|
||||
className="w-full rounded-xl border bg-card text-foreground text-sm px-4 py-2.5 focus:outline-hidden focus:border-primary/30"
|
||||
className="w-full rounded-xl border bg-[#14161d] text-[#e2e5eb] text-sm px-4 py-2.5 focus:outline-hidden focus:border-primary/30"
|
||||
style={{ borderColor: 'var(--glass-border)' }}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
<p className="text-xs text-[#848b9b] mt-1">
|
||||
When this limit is exceeded, oldest unpinned chats are deleted
|
||||
</p>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@ export default function ChatRetentionSettingsPage() {
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40 flex items-center gap-2"
|
||||
className="bg-[#22d3ee] text-brand-dark font-semibold text-sm rounded-lg px-5 py-2.5 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-40 flex items-center gap-2"
|
||||
>
|
||||
{saving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
||||
Save Settings
|
||||
|
||||
@@ -197,7 +197,7 @@ export function IntegrationsPage() {
|
||||
<>
|
||||
<PageMeta title="Integrations" />
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Loader2 className="h-8 w-8 animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -223,16 +223,16 @@ export function IntegrationsPage() {
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Plug className="h-8 w-8 text-muted-foreground" />
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Integrations</h1>
|
||||
<Plug className="h-8 w-8 text-[#848b9b]" />
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Integrations</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Connect your PSA to post session documentation directly to tickets.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="mb-6 flex gap-1 border-b border-border">
|
||||
<div className="mb-6 flex gap-1 border-b border-[#1e2130]">
|
||||
{([
|
||||
{ id: 'connection' as Tab, label: 'Connection', icon: Plug },
|
||||
{ id: 'member-mapping' as Tab, label: 'Member Mapping', icon: Users },
|
||||
@@ -246,8 +246,8 @@ export function IntegrationsPage() {
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors -mb-px',
|
||||
activeTab === id
|
||||
? 'border-primary text-foreground'
|
||||
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'
|
||||
? 'border-primary text-[#e2e5eb]'
|
||||
: 'border-transparent text-[#848b9b] hover:text-[#e2e5eb] hover:border-[#1e2130]'
|
||||
)}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
@@ -261,7 +261,7 @@ export function IntegrationsPage() {
|
||||
<div className="max-w-3xl">
|
||||
{/* PSA Provider Grid */}
|
||||
<div className="mb-6">
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground mb-3">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] mb-3">
|
||||
Available PSA Integrations
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
@@ -270,43 +270,43 @@ export function IntegrationsPage() {
|
||||
'rounded-xl border p-4 flex flex-col gap-2',
|
||||
connection
|
||||
? 'border-primary/40 bg-primary/5'
|
||||
: 'border-border bg-card/30'
|
||||
: 'border-[#1e2130] bg-[#14161d]/30'
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-foreground">ConnectWise PSA</span>
|
||||
<span className="text-sm font-medium text-[#e2e5eb]">ConnectWise PSA</span>
|
||||
{connection ? (
|
||||
<span className="inline-flex items-center rounded-full bg-emerald-400/10 px-2 py-0.5 text-[0.625rem] font-label text-emerald-400">
|
||||
<span className="inline-flex items-center rounded-full bg-emerald-400/10 px-2 py-0.5 text-[0.625rem] font-sans text-xs text-emerald-400">
|
||||
Connected
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-[0.625rem] font-label text-primary">
|
||||
<span className="inline-flex items-center rounded-full bg-[rgba(34,211,238,0.10)] px-2 py-0.5 text-[0.625rem] font-sans text-xs text-[#22d3ee]">
|
||||
Available
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">Manage, ticket linking, time entries</p>
|
||||
<p className="text-xs text-[#848b9b]">Manage, ticket linking, time entries</p>
|
||||
</div>
|
||||
|
||||
{/* Autotask — coming soon */}
|
||||
<div className="rounded-xl border border-border bg-card/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
|
||||
<div className="rounded-xl border border-[#1e2130] bg-[#14161d]/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-foreground">Autotask PSA</span>
|
||||
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-label text-amber-400">
|
||||
<span className="text-sm font-medium text-[#e2e5eb]">Autotask PSA</span>
|
||||
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-sans text-xs text-amber-400">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">Datto / Kaseya integration</p>
|
||||
<p className="text-xs text-[#848b9b]">Datto / Kaseya integration</p>
|
||||
</div>
|
||||
|
||||
{/* Halo PSA — coming soon */}
|
||||
<div className="rounded-xl border border-border bg-card/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
|
||||
<div className="rounded-xl border border-[#1e2130] bg-[#14161d]/20 p-4 flex flex-col gap-2 opacity-60 cursor-not-allowed select-none">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium text-foreground">Halo PSA</span>
|
||||
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-label text-amber-400">
|
||||
<span className="text-sm font-medium text-[#e2e5eb]">Halo PSA</span>
|
||||
<span className="inline-flex items-center rounded-full bg-amber-400/10 px-2 py-0.5 text-[0.625rem] font-sans text-xs text-amber-400">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">HaloPSA / HaloITSM integration</p>
|
||||
<p className="text-xs text-[#848b9b]">HaloPSA / HaloITSM integration</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -325,17 +325,17 @@ export function IntegrationsPage() {
|
||||
|
||||
{/* Setup / Edit Form */}
|
||||
{(mode === 'setup' || mode === 'edit') && (
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Shield className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
<Shield className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||
{mode === 'setup' ? 'Connect to ConnectWise PSA' : 'Edit Connection'}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form onSubmit={mode === 'setup' ? handleCreate : handleUpdate} className="space-y-4">
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||
Display Name
|
||||
</label>
|
||||
<Input
|
||||
@@ -349,7 +349,7 @@ export function IntegrationsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||
Site URL
|
||||
</label>
|
||||
<Input
|
||||
@@ -363,7 +363,7 @@ export function IntegrationsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||
Company ID
|
||||
</label>
|
||||
<Input
|
||||
@@ -377,7 +377,7 @@ export function IntegrationsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||
Public Key
|
||||
</label>
|
||||
<Input
|
||||
@@ -391,7 +391,7 @@ export function IntegrationsPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">
|
||||
Private Key
|
||||
</label>
|
||||
<Input
|
||||
@@ -416,9 +416,9 @@ export function IntegrationsPage() {
|
||||
type="submit"
|
||||
disabled={isSaving}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
||||
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-[#22d3ee] text-white',
|
||||
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
@@ -430,7 +430,7 @@ export function IntegrationsPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancelEdit}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -444,13 +444,13 @@ export function IntegrationsPage() {
|
||||
{mode === 'view' && connection && (
|
||||
<div className="space-y-4">
|
||||
{/* Status Card */}
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex items-center rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-label font-medium text-primary">
|
||||
<span className="inline-flex items-center rounded-full bg-[rgba(34,211,238,0.10)] px-2.5 py-0.5 text-xs font-sans text-xs font-medium text-[#22d3ee]">
|
||||
ConnectWise
|
||||
</span>
|
||||
<h2 className="text-lg font-semibold text-foreground">{connection.display_name}</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">{connection.display_name}</h2>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
@@ -460,7 +460,7 @@ export function IntegrationsPage() {
|
||||
)}
|
||||
/>
|
||||
<span className={cn(
|
||||
'text-xs font-label',
|
||||
'text-xs font-sans text-xs',
|
||||
connection.is_active ? 'text-emerald-400' : 'text-amber-400'
|
||||
)}>
|
||||
{connection.is_active ? 'Connected' : 'Not validated'}
|
||||
@@ -470,24 +470,24 @@ export function IntegrationsPage() {
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Site URL</p>
|
||||
<p className="mt-1 text-sm text-foreground">{connection.site_url}</p>
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Site URL</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb]">{connection.site_url}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Company ID</p>
|
||||
<p className="mt-1 text-sm text-foreground">{connection.company_id}</p>
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Company ID</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb]">{connection.company_id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Public Key</p>
|
||||
<p className="mt-1 text-sm text-foreground font-mono">{connection.public_key_hint}</p>
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Public Key</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb] font-mono">{connection.public_key_hint}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Private Key</p>
|
||||
<p className="mt-1 text-sm text-foreground font-mono">{connection.private_key_hint}</p>
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Private Key</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb] font-mono">{connection.private_key_hint}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Last Validated</p>
|
||||
<p className="mt-1 text-sm text-foreground">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Last Validated</p>
|
||||
<p className="mt-1 text-sm text-[#e2e5eb]">
|
||||
{connection.last_validated_at ? formatRelativeTime(connection.last_validated_at) : 'Never'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -511,7 +511,7 @@ export function IntegrationsPage() {
|
||||
)}
|
||||
<span>{testResult.message}</span>
|
||||
{testResult.server_version && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
<span className="ml-auto text-xs text-[#848b9b]">
|
||||
v{testResult.server_version}
|
||||
</span>
|
||||
)}
|
||||
@@ -524,8 +524,8 @@ export function IntegrationsPage() {
|
||||
onClick={handleTest}
|
||||
disabled={isTesting}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
||||
'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]',
|
||||
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
@@ -541,8 +541,8 @@ export function IntegrationsPage() {
|
||||
<button
|
||||
onClick={startEdit}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
||||
'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]',
|
||||
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
||||
)}
|
||||
>
|
||||
@@ -552,18 +552,18 @@ export function IntegrationsPage() {
|
||||
|
||||
{showDeleteConfirm ? (
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
<span className="text-sm text-muted-foreground">Disconnect?</span>
|
||||
<span className="text-sm text-[#848b9b]">Disconnect?</span>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
className="inline-flex items-center gap-1.5 rounded-[10px] px-3 py-2 text-sm font-medium text-red-400 hover:bg-red-400/10 transition-colors"
|
||||
className="inline-flex items-center gap-1.5 rounded-lg px-3 py-2 text-sm font-medium text-red-400 hover:bg-red-400/10 transition-colors"
|
||||
>
|
||||
{isDeleting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -591,12 +591,12 @@ export function IntegrationsPage() {
|
||||
{/* Post History Tab */}
|
||||
{activeTab === 'post-history' && (
|
||||
<div className="max-w-3xl">
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Ticket className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Post History</h2>
|
||||
<Ticket className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Post History</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
View post history from individual sessions by clicking on linked tickets.
|
||||
When a session has a ConnectWise ticket linked, use the Update button to post
|
||||
session documentation and view previous posts.
|
||||
@@ -727,12 +727,12 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
if (!connection) {
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Member Mapping</h2>
|
||||
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Member Mapping</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Set up a PSA connection first to map team members to ConnectWise members.
|
||||
</p>
|
||||
</div>
|
||||
@@ -743,19 +743,19 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
return (
|
||||
<div className="max-w-3xl space-y-4">
|
||||
{/* Header + Auto-Match */}
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
<h2 className="text-lg font-semibold text-foreground">Member Mapping</h2>
|
||||
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Member Mapping</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAutoMatch}
|
||||
disabled={isAutoMatching || isLoadingData}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
||||
'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]',
|
||||
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
@@ -768,7 +768,7 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
Auto-Match by Email
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Map your ResolutionFlow users to ConnectWise members so session posts are attributed correctly.
|
||||
</p>
|
||||
</div>
|
||||
@@ -776,25 +776,25 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
{/* Loading state */}
|
||||
{isLoadingData && (
|
||||
<div className="flex justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
<Loader2 className="h-6 w-6 animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mapping Table */}
|
||||
{hasLoaded && !isLoadingData && (
|
||||
<div className="glass-card-static overflow-hidden">
|
||||
<div className="card-flat overflow-hidden">
|
||||
{uniqueUsers.length === 0 ? (
|
||||
<div className="p-6 text-center text-sm text-muted-foreground">
|
||||
<div className="p-6 text-center text-sm text-[#848b9b]">
|
||||
No users found. Use Auto-Match to discover and map users.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Table header */}
|
||||
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 border-b border-border px-6 py-3">
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">User</span>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Email</span>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Mapped To</span>
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground w-20 text-center">Method</span>
|
||||
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 border-b border-[#1e2130] px-6 py-3">
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">User</span>
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Email</span>
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Mapped To</span>
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] w-20 text-center">Method</span>
|
||||
</div>
|
||||
|
||||
{/* Rows */}
|
||||
@@ -803,18 +803,18 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
return (
|
||||
<div
|
||||
key={user.user_id}
|
||||
className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 items-center border-b border-border/50 px-6 py-3 last:border-b-0"
|
||||
className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 items-center border-b border-[#1e2130]/50 px-6 py-3 last:border-b-0"
|
||||
>
|
||||
<span className="text-sm text-foreground truncate">{user.user_name}</span>
|
||||
<span className="text-sm text-muted-foreground truncate">{user.user_email}</span>
|
||||
<span className="text-sm text-[#e2e5eb] truncate">{user.user_name}</span>
|
||||
<span className="text-sm text-[#848b9b] truncate">{user.user_email}</span>
|
||||
<select
|
||||
title={`Map ${user.user_name} to a ConnectWise member`}
|
||||
value={currentMapping?.external_member_id || ''}
|
||||
onChange={(e) => handleMemberChange(user.user_id, e.target.value)}
|
||||
className={cn(
|
||||
'w-full rounded-lg border bg-card px-3 py-1.5 text-sm text-foreground',
|
||||
'border-border focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||
!currentMapping && 'text-muted-foreground'
|
||||
'w-full rounded-lg border bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb]',
|
||||
'border-[#1e2130] focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||
!currentMapping && 'text-[#848b9b]'
|
||||
)}
|
||||
>
|
||||
<option value="">-- Unmapped --</option>
|
||||
@@ -827,19 +827,19 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
<span className="w-20 text-center">
|
||||
{currentMapping && !isDirty && user.matched_by ? (
|
||||
<span className={cn(
|
||||
'inline-flex items-center rounded-full px-2 py-0.5 text-[0.625rem] font-label',
|
||||
'inline-flex items-center rounded-full px-2 py-0.5 text-[0.625rem] font-sans text-xs',
|
||||
user.matched_by === 'auto_email'
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'bg-card border border-border text-muted-foreground'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
||||
: 'bg-[#14161d] border border-[#1e2130] text-[#848b9b]'
|
||||
)}>
|
||||
{user.matched_by === 'auto_email' ? 'auto' : 'manual'}
|
||||
</span>
|
||||
) : currentMapping ? (
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[0.625rem] font-label bg-card border border-border text-muted-foreground">
|
||||
<span className="inline-flex items-center rounded-full px-2 py-0.5 text-[0.625rem] font-sans text-xs bg-[#14161d] border border-[#1e2130] text-[#848b9b]">
|
||||
manual
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[0.625rem] text-muted-foreground/50">—</span>
|
||||
<span className="text-[0.625rem] text-[#848b9b]/50">—</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -858,9 +858,9 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
||||
onClick={handleSave}
|
||||
disabled={isSavingMappings}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
||||
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||
'bg-[#22d3ee] text-white',
|
||||
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
@@ -914,8 +914,8 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
||||
if (!connection) {
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
<div className="glass-card-static p-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">Connect your PSA first to configure FlowPilot settings.</p>
|
||||
<div className="card-flat p-6 text-center">
|
||||
<p className="text-sm text-[#848b9b]">Connect your PSA first to configure FlowPilot settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -924,7 +924,7 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center py-12">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
||||
<Loader2 className="h-5 w-5 animate-spin text-[#848b9b]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -933,12 +933,12 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-4">
|
||||
<div className="glass-card-static p-6">
|
||||
<div className="card-flat p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Zap className="h-5 w-5 text-primary" />
|
||||
<h2 className="text-lg font-semibold text-foreground">FlowPilot Settings</h2>
|
||||
<Zap className="h-5 w-5 text-[#22d3ee]" />
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">FlowPilot Settings</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-6">
|
||||
<p className="text-sm text-[#848b9b] mb-6">
|
||||
Configure how FlowPilot integrates with your ConnectWise PSA when sessions are resolved or escalated.
|
||||
</p>
|
||||
|
||||
@@ -1039,8 +1039,8 @@ function SettingToggle({
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">{description}</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">{label}</p>
|
||||
<p className="text-xs text-[#848b9b] mt-0.5">{description}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onChange(!checked)}
|
||||
@@ -1078,13 +1078,13 @@ function SettingSelect({
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 mb-2">{description}</p>
|
||||
<p className="text-sm font-medium text-[#e2e5eb]">{label}</p>
|
||||
<p className="text-xs text-[#848b9b] mt-0.5 mb-2">{description}</p>
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
disabled={disabled}
|
||||
className="w-full max-w-xs rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none disabled:opacity-50"
|
||||
className="w-full max-w-xs rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] focus:border-[rgba(6,182,212,0.3)] focus:outline-none disabled:opacity-50"
|
||||
>
|
||||
{options.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { toast } from '@/lib/toast'
|
||||
import type { UserUpdate } from '@/types'
|
||||
|
||||
const inputClass = cn(
|
||||
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
||||
'text-foreground placeholder:text-muted-foreground',
|
||||
'mt-1 block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||
)
|
||||
|
||||
@@ -67,52 +67,52 @@ export function ProfileSettingsPage() {
|
||||
<div>
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserIcon className="h-8 w-8 text-muted-foreground" />
|
||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Profile Settings</h1>
|
||||
<UserIcon className="h-8 w-8 text-[#848b9b]" />
|
||||
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Profile Settings</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-[#848b9b]">
|
||||
Update your name, email, and personal details
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
<form onSubmit={handleSave} className="glass-card-static p-6 space-y-5">
|
||||
<form onSubmit={handleSave} className="card-flat p-6 space-y-5">
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label htmlFor="profile-name" className="block text-sm font-medium text-foreground">Name</label>
|
||||
<label htmlFor="profile-name" className="block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<input id="profile-name" type="text" value={name} onChange={(e) => setName(e.target.value)} required className={inputClass} />
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label htmlFor="profile-email" className="block text-sm font-medium text-foreground">Email</label>
|
||||
<label htmlFor="profile-email" className="block text-sm font-medium text-[#e2e5eb]">Email</label>
|
||||
<input id="profile-email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required className={inputClass} />
|
||||
</div>
|
||||
|
||||
{/* Password confirmation for email change */}
|
||||
{emailChanged && (
|
||||
<div>
|
||||
<label htmlFor="profile-password" className="block text-sm font-medium text-foreground">Current Password</label>
|
||||
<p className="text-xs text-muted-foreground">Required to change your email address</p>
|
||||
<label htmlFor="profile-password" className="block text-sm font-medium text-[#e2e5eb]">Current Password</label>
|
||||
<p className="text-xs text-[#848b9b]">Required to change your email address</p>
|
||||
<input id="profile-password" type="password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} required className={inputClass} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Phone */}
|
||||
<div>
|
||||
<label htmlFor="profile-phone" className="block text-sm font-medium text-foreground">Phone</label>
|
||||
<label htmlFor="profile-phone" className="block text-sm font-medium text-[#e2e5eb]">Phone</label>
|
||||
<input id="profile-phone" type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="Optional" className={inputClass} />
|
||||
</div>
|
||||
|
||||
{/* Job Title */}
|
||||
<div>
|
||||
<label htmlFor="profile-job-title" className="block text-sm font-medium text-foreground">Job Title</label>
|
||||
<label htmlFor="profile-job-title" className="block text-sm font-medium text-[#e2e5eb]">Job Title</label>
|
||||
<input id="profile-job-title" type="text" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} placeholder="e.g. Network Engineer" className={inputClass} />
|
||||
</div>
|
||||
|
||||
{/* Timezone */}
|
||||
<div>
|
||||
<label htmlFor="profile-timezone" className="block text-sm font-medium text-foreground">Timezone</label>
|
||||
<label htmlFor="profile-timezone" className="block text-sm font-medium text-[#e2e5eb]">Timezone</label>
|
||||
<select id="profile-timezone" value={timezone} onChange={(e) => setTimezone(e.target.value)} className={inputClass}>
|
||||
{COMMON_TIMEZONES.map((tz) => (
|
||||
<option key={tz} value={tz}>{tz}</option>
|
||||
@@ -132,8 +132,8 @@ export function ProfileSettingsPage() {
|
||||
type="submit"
|
||||
disabled={isSaving || !hasChanges}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark',
|
||||
'shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
||||
'inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-brand-dark',
|
||||
'hover:brightness-110 active:scale-[0.98]',
|
||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
@@ -144,8 +144,8 @@ export function ProfileSettingsPage() {
|
||||
<Link
|
||||
to="/change-password"
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-[10px] px-4 py-2 text-sm font-medium',
|
||||
'bg-white/[0.04] border border-brand-border text-foreground',
|
||||
'inline-flex items-center rounded-lg px-4 py-2 text-sm font-medium',
|
||||
'bg-white/[0.04] border border-brand-border text-[#e2e5eb]',
|
||||
'hover:border-white/[0.12]'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function TargetListsPage() {
|
||||
action={(
|
||||
<button
|
||||
onClick={() => openEditor()}
|
||||
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-4 py-2 text-[0.875rem] font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90"
|
||||
className="flex items-center gap-1.5 rounded-lg bg-[#22d3ee] px-4 py-2 text-[0.875rem] font-medium text-white hover:brightness-110"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
New List
|
||||
@@ -137,14 +137,14 @@ export default function TargetListsPage() {
|
||||
{lists.map(list => (
|
||||
<div
|
||||
key={list.id}
|
||||
className="flex items-center justify-between rounded-xl border border-border bg-card px-5 py-4"
|
||||
className="flex items-center justify-between rounded-xl border border-[#1e2130] bg-[#14161d] px-5 py-4"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-foreground">{list.name}</p>
|
||||
<p className="font-medium text-[#e2e5eb]">{list.name}</p>
|
||||
{list.description && (
|
||||
<p className="text-[0.8125rem] text-muted-foreground">{list.description}</p>
|
||||
<p className="text-[0.8125rem] text-[#848b9b]">{list.description}</p>
|
||||
)}
|
||||
<p className="mt-0.5 font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
|
||||
<p className="mt-0.5 font-sans text-xs text-[0.6875rem] uppercase tracking-wide text-[#848b9b]">
|
||||
{list.targets.length} target{list.targets.length !== 1 ? 's' : ''}
|
||||
{list.targets.length > 0 && (
|
||||
<> · {list.targets.slice(0, 3).map(t => t.label).join(', ')}{list.targets.length > 3 ? '\u2026' : ''}</>
|
||||
@@ -154,14 +154,14 @@ export default function TargetListsPage() {
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => openEditor(list)}
|
||||
className="rounded-lg p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
title="Edit"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleteTarget(list)}
|
||||
className="rounded-lg p-2 text-muted-foreground hover:bg-accent hover:text-red-400"
|
||||
className="rounded-lg p-2 text-[#848b9b] hover:bg-accent hover:text-red-400"
|
||||
title="Delete"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -182,14 +182,14 @@ export default function TargetListsPage() {
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setShowEditor(false)}
|
||||
className="rounded-lg border border-border px-4 py-2 text-[0.875rem] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-lg border border-[#1e2130] px-4 py-2 text-[0.875rem] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
className="rounded-lg bg-gradient-brand px-4 py-2 text-[0.875rem] font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-50"
|
||||
className="rounded-lg bg-[#22d3ee] px-4 py-2 text-[0.875rem] font-medium text-white hover:brightness-110 disabled:opacity-50"
|
||||
>
|
||||
{isSaving ? 'Saving\u2026' : 'Save'}
|
||||
</button>
|
||||
@@ -198,38 +198,38 @@ export default function TargetListsPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
|
||||
<label className="mb-1 block font-sans text-xs text-[0.6875rem] uppercase tracking-wide text-[#848b9b]">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editorName}
|
||||
onChange={e => setEditorName(e.target.value)}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[0.875rem] text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
placeholder="e.g. RDS Farm A"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
|
||||
<label className="mb-1 block font-sans text-xs text-[0.6875rem] uppercase tracking-wide text-[#848b9b]">
|
||||
Description (optional)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editorDescription}
|
||||
onChange={e => setEditorDescription(e.target.value)}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[0.875rem] text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
placeholder="e.g. Production RDS servers"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">
|
||||
<label className="mb-1 block font-sans text-xs text-[0.6875rem] uppercase tracking-wide text-[#848b9b]">
|
||||
Targets — one per line (add notes after #)
|
||||
</label>
|
||||
<textarea
|
||||
value={editorTargets}
|
||||
onChange={e => setEditorTargets(e.target.value)}
|
||||
rows={6}
|
||||
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-[0.875rem] text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
|
||||
placeholder={"RDS-01 # 192.168.1.10\nRDS-02\nRDS-03 # Backup server"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -99,26 +99,26 @@ export function TeamCategoriesPage() {
|
||||
))}
|
||||
</div>
|
||||
) : categories.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center bg-card border border-border rounded-xl py-16">
|
||||
<FolderTree className="h-12 w-12 text-muted-foreground" />
|
||||
<h3 className="mt-4 font-medium text-foreground">No team categories</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">Create categories to organize your team's trees.</p>
|
||||
<div className="flex flex-col items-center justify-center bg-[#14161d] border border-[#1e2130] rounded-xl py-16">
|
||||
<FolderTree className="h-12 w-12 text-[#848b9b]" />
|
||||
<h3 className="mt-4 font-medium text-[#e2e5eb]">No team categories</h3>
|
||||
<p className="mt-1 text-sm text-[#848b9b]">Create categories to organize your team's trees.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{categories.map((cat) => (
|
||||
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-border px-4 py-3">
|
||||
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-[#1e2130] px-4 py-3">
|
||||
<div>
|
||||
<span className="font-medium text-foreground">{cat.name}</span>
|
||||
<span className="ml-3 text-sm text-muted-foreground">{cat.slug}</span>
|
||||
{cat.description && <span className="ml-3 text-sm text-muted-foreground">- {cat.description}</span>}
|
||||
<span className="ml-3 text-xs text-muted-foreground">{cat.tree_count} trees</span>
|
||||
<span className="font-medium text-[#e2e5eb]">{cat.name}</span>
|
||||
<span className="ml-3 text-sm text-[#848b9b]">{cat.slug}</span>
|
||||
{cat.description && <span className="ml-3 text-sm text-[#848b9b]">- {cat.description}</span>}
|
||||
<span className="ml-3 text-xs text-[#848b9b]">{cat.tree_count} trees</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground">
|
||||
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button onClick={() => handleDelete(cat.id)} className="rounded-md p-1.5 text-muted-foreground hover:bg-red-400/10 hover:text-red-400">
|
||||
<button onClick={() => handleDelete(cat.id)} className="rounded-md p-1.5 text-[#848b9b] hover:bg-red-400/10 hover:text-red-400">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -138,15 +138,15 @@ export function TeamCategoriesPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<Input type="text" value={form.name} onChange={(e) => { const name = e.target.value; setForm(f => ({ ...f, name, slug: generateSlug(name) })) }} placeholder="e.g. Networking" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,15 +163,15 @@ export function TeamCategoriesPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,7 @@ export function AuditLogsPage() {
|
||||
render: (log) => (
|
||||
<button
|
||||
onClick={() => setExpandedId(expandedId === log.id ? null : log.id)}
|
||||
className="p-1 text-muted-foreground hover:text-foreground"
|
||||
className="p-1 text-[#848b9b] hover:text-[#e2e5eb]"
|
||||
>
|
||||
{expandedId === log.id ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
@@ -78,14 +78,14 @@ export function AuditLogsPage() {
|
||||
key: 'action',
|
||||
header: 'Action',
|
||||
render: (log) => (
|
||||
<span className="text-sm font-medium text-foreground">{log.action}</span>
|
||||
<span className="text-sm font-medium text-[#e2e5eb]">{log.action}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'resource',
|
||||
header: 'Resource',
|
||||
render: (log) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
{log.resource_type}{log.resource_id ? ` (${log.resource_id.slice(0, 8)}...)` : ''}
|
||||
</span>
|
||||
),
|
||||
@@ -94,14 +94,14 @@ export function AuditLogsPage() {
|
||||
key: 'user',
|
||||
header: 'User',
|
||||
render: (log) => (
|
||||
<span className="text-sm text-muted-foreground">{log.user_email || 'System'}</span>
|
||||
<span className="text-sm text-[#848b9b]">{log.user_email || 'System'}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
header: 'Time',
|
||||
render: (log) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
{new Date(log.created_at).toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
@@ -117,8 +117,8 @@ export function AuditLogsPage() {
|
||||
<button
|
||||
onClick={handleExport}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium',
|
||||
'text-muted-foreground hover:bg-accent hover:text-foreground'
|
||||
'flex items-center gap-2 rounded-md border border-[#1e2130] px-4 py-2 text-sm font-medium',
|
||||
'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
@@ -134,8 +134,8 @@ export function AuditLogsPage() {
|
||||
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
|
||||
placeholder="Filter by action..."
|
||||
className={cn(
|
||||
'h-9 rounded-md border border-border bg-card px-3 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
'h-9 rounded-md border border-[#1e2130] bg-[#14161d] px-3 text-sm text-[#e2e5eb]',
|
||||
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
<input
|
||||
@@ -144,8 +144,8 @@ export function AuditLogsPage() {
|
||||
onChange={(e) => { setResourceFilter(e.target.value); setPage(1) }}
|
||||
placeholder="Filter by resource type..."
|
||||
className={cn(
|
||||
'h-9 rounded-md border border-border bg-card px-3 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
'h-9 rounded-md border border-[#1e2130] bg-[#14161d] px-3 text-sm text-[#e2e5eb]',
|
||||
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -166,9 +166,9 @@ export function AuditLogsPage() {
|
||||
|
||||
{/* Expanded details row */}
|
||||
{expandedId && logs.find(l => l.id === expandedId)?.details && (
|
||||
<div className="rounded-md border border-border bg-accent p-4">
|
||||
<h4 className="mb-2 text-sm font-medium text-foreground">Details</h4>
|
||||
<pre className="overflow-x-auto rounded bg-card p-3 text-xs text-muted-foreground">
|
||||
<div className="rounded-md border border-[#1e2130] bg-accent p-4">
|
||||
<h4 className="mb-2 text-sm font-medium text-[#e2e5eb]">Details</h4>
|
||||
<pre className="overflow-x-auto rounded bg-[#14161d] p-3 text-xs text-[#848b9b]">
|
||||
{JSON.stringify(logs.find(l => l.id === expandedId)?.details, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -14,13 +14,13 @@ interface MetricCardProps {
|
||||
|
||||
function MetricCard({ label, value, icon }: MetricCardProps) {
|
||||
return (
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">{label}</p>
|
||||
<p className="mt-1 text-3xl font-bold text-foreground">{value}</p>
|
||||
<p className="text-sm text-[#848b9b]">{label}</p>
|
||||
<p className="mt-1 text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-accent p-3 text-muted-foreground">{icon}</div>
|
||||
<div className="rounded-lg bg-accent p-3 text-[#848b9b]">{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -71,18 +71,18 @@ export function DashboardPage() {
|
||||
{/* Recent Activity */}
|
||||
{activity.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Recent Activity</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Recent Activity</h2>
|
||||
<div className="mt-3 space-y-2">
|
||||
{activity.slice(0, 10).map((entry) => (
|
||||
<div key={entry.id} className="flex items-center justify-between rounded-md border border-border px-4 py-3 text-sm">
|
||||
<div key={entry.id} className="flex items-center justify-between rounded-md border border-[#1e2130] px-4 py-3 text-sm">
|
||||
<div>
|
||||
<span className="font-medium text-foreground">{entry.action}</span>
|
||||
<span className="ml-2 text-muted-foreground">{entry.resource_type}</span>
|
||||
<span className="font-medium text-[#e2e5eb]">{entry.action}</span>
|
||||
<span className="ml-2 text-[#848b9b]">{entry.resource_type}</span>
|
||||
{entry.user_email && (
|
||||
<span className="ml-2 text-muted-foreground">by {entry.user_email}</span>
|
||||
<span className="ml-2 text-[#848b9b]">by {entry.user_email}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className="text-xs text-[#848b9b]">
|
||||
{new Date(entry.created_at).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
@@ -93,18 +93,18 @@ export function DashboardPage() {
|
||||
|
||||
{/* Quick Links */}
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Quick Links</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Quick Links</h2>
|
||||
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{quickLinks.map((link) => (
|
||||
<Link
|
||||
key={link.to}
|
||||
to={link.to}
|
||||
className={cn(
|
||||
'flex items-center gap-3 bg-card border border-border rounded-xl p-4',
|
||||
'text-sm font-medium text-foreground transition-colors hover:bg-accent'
|
||||
'flex items-center gap-3 bg-[#14161d] border border-[#1e2130] rounded-xl p-4',
|
||||
'text-sm font-medium text-[#e2e5eb] transition-colors hover:bg-accent'
|
||||
)}
|
||||
>
|
||||
<link.icon className="h-5 w-5 text-muted-foreground" />
|
||||
<link.icon className="h-5 w-5 text-[#848b9b]" />
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -95,11 +95,11 @@ export function FeatureFlagsPage() {
|
||||
const flagColumns: Column<FeatureFlagResponse>[] = [
|
||||
{ key: 'name', header: 'Name', render: (f) => (
|
||||
<div>
|
||||
<div className="font-medium text-foreground">{f.display_name}</div>
|
||||
<div className="text-xs text-muted-foreground">{f.flag_key}</div>
|
||||
<div className="font-medium text-[#e2e5eb]">{f.display_name}</div>
|
||||
<div className="text-xs text-[#848b9b]">{f.flag_key}</div>
|
||||
</div>
|
||||
)},
|
||||
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-muted-foreground">{f.description || '-'}</span> },
|
||||
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-[#848b9b]">{f.description || '-'}</span> },
|
||||
...PLANS.map(plan => ({
|
||||
key: plan,
|
||||
header: plan.charAt(0).toUpperCase() + plan.slice(1),
|
||||
@@ -133,10 +133,10 @@ export function FeatureFlagsPage() {
|
||||
]
|
||||
|
||||
const overrideColumns: Column<AccountFeatureOverrideResponse>[] = [
|
||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
||||
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-muted-foreground">{o.flag_display_name || o.flag_key || o.flag_id.slice(0, 8)}</span> },
|
||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-[#e2e5eb]">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
||||
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-[#848b9b]">{o.flag_display_name || o.flag_key || o.flag_id.slice(0, 8)}</span> },
|
||||
{ key: 'enabled', header: 'Enabled', render: (o) => <StatusBadge variant={o.enabled ? 'success' : 'destructive'}>{o.enabled ? 'Yes' : 'No'}</StatusBadge> },
|
||||
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-muted-foreground">{o.note || '-'}</span> },
|
||||
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-[#848b9b]">{o.note || '-'}</span> },
|
||||
{
|
||||
key: 'actions', header: '', className: 'w-12',
|
||||
render: (o) => (
|
||||
@@ -147,7 +147,7 @@ export function FeatureFlagsPage() {
|
||||
},
|
||||
]
|
||||
|
||||
const selectClass = cn('w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground', 'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20')
|
||||
const selectClass = cn('w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]', 'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20')
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -163,7 +163,7 @@ export function FeatureFlagsPage() {
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Feature Matrix</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Feature Matrix</h2>
|
||||
<div className="mt-3">
|
||||
<DataTable columns={flagColumns} data={flags} keyExtractor={(f) => f.id} isLoading={loading}
|
||||
emptyState={<EmptyState icon={<ToggleLeft className="h-12 w-12" />} title="No feature flags" description="Create feature flags to control availability per plan." />}
|
||||
@@ -173,7 +173,7 @@ export function FeatureFlagsPage() {
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-foreground">Account Overrides</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Account Overrides</h2>
|
||||
<Button onClick={() => setOverrideOpen(true)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Override
|
||||
@@ -197,15 +197,15 @@ export function FeatureFlagsPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Flag Key</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Flag Key</label>
|
||||
<Input type="text" value={createForm.flag_key} onChange={(e) => setCreateForm({ ...createForm, flag_key: e.target.value })} placeholder="e.g. custom_branding" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Display Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Display Name</label>
|
||||
<Input type="text" value={createForm.display_name} onChange={(e) => setCreateForm({ ...createForm, display_name: e.target.value })} placeholder="e.g. Custom Branding" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||
<Input type="text" value={createForm.description ?? ''} onChange={(e) => setCreateForm({ ...createForm, description: e.target.value || null })} placeholder="Optional description" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -222,22 +222,22 @@ export function FeatureFlagsPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||
<Input type="text" value={overrideForm.account_display_code} onChange={(e) => setOverrideForm({ ...overrideForm, account_display_code: e.target.value })} placeholder="e.g. ABC-1234" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Feature Flag</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Feature Flag</label>
|
||||
<select value={overrideForm.flag_id} onChange={(e) => setOverrideForm({ ...overrideForm, flag_id: e.target.value })} className={selectClass}>
|
||||
<option value="">Select a flag...</option>
|
||||
{flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-border" />
|
||||
<label htmlFor="override-enabled" className="text-sm font-medium text-foreground">Enabled</label>
|
||||
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-[#1e2130]" />
|
||||
<label htmlFor="override-enabled" className="text-sm font-medium text-[#e2e5eb]">Enabled</label>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Note</label>
|
||||
<Input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@ function SortOrderInput({
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'w-20 rounded-[8px] border border-border bg-card px-2 py-1 text-sm text-foreground',
|
||||
'w-20 rounded-[8px] border border-[#1e2130] bg-[#14161d] px-2 py-1 text-sm text-[#e2e5eb]',
|
||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
)}
|
||||
@@ -117,19 +117,19 @@ function FilterBar({
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="relative flex-1 min-w-[180px] max-w-xs">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#848b9b] pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search by name…"
|
||||
value={search}
|
||||
onChange={e => onSearchChange(e.target.value)}
|
||||
className={cn(
|
||||
'w-full rounded-[10px] border border-border bg-card pl-9 pr-3 py-2 text-sm text-foreground placeholder:text-muted-foreground',
|
||||
'w-full rounded-lg border border-[#1e2130] bg-[#14161d] pl-9 pr-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex rounded-[10px] border border-border overflow-hidden text-sm">
|
||||
<div className="flex rounded-lg border border-[#1e2130] overflow-hidden text-sm">
|
||||
{(['all', 'featured', 'unfeatured'] as FilterMode[]).map(mode => (
|
||||
<button
|
||||
key={mode}
|
||||
@@ -137,8 +137,8 @@ function FilterBar({
|
||||
className={cn(
|
||||
'px-3 py-1.5 capitalize transition-colors',
|
||||
filter === mode
|
||||
? 'bg-primary text-[#101114] font-semibold'
|
||||
: 'text-muted-foreground hover:text-foreground bg-card',
|
||||
? 'bg-primary text-white font-semibold'
|
||||
: 'text-[#848b9b] hover:text-[#e2e5eb] bg-[#14161d]',
|
||||
)}
|
||||
>
|
||||
{mode}
|
||||
@@ -166,7 +166,7 @@ function FlowsTable({
|
||||
}) {
|
||||
if (flows.length === 0) {
|
||||
return (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">No flows match the current filter.</p>
|
||||
<p className="py-8 text-center text-sm text-[#848b9b]">No flows match the current filter.</p>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -174,19 +174,19 @@ function FlowsTable({
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-left">
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Name</th>
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Type</th>
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Featured</th>
|
||||
<th className="pb-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Sort Order</th>
|
||||
<tr className="border-b border-[#1e2130] text-left">
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Name</th>
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Type</th>
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Featured</th>
|
||||
<th className="pb-3 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Sort Order</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{flows.map(flow => (
|
||||
<tr key={flow.id} className="group hover:bg-[rgba(255,255,255,0.02)] transition-colors">
|
||||
<td className="py-3 pr-4 text-foreground font-medium">{flow.name}</td>
|
||||
<td className="py-3 pr-4 text-[#e2e5eb] font-medium">{flow.name}</td>
|
||||
<td className="py-3 pr-4">
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] rounded-full px-2 py-0.5 border border-border text-muted-foreground">
|
||||
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] rounded-full px-2 py-0.5 border border-[#1e2130] text-[#848b9b]">
|
||||
{flow.tree_type}
|
||||
</span>
|
||||
</td>
|
||||
@@ -234,7 +234,7 @@ function ScriptsTable({
|
||||
}) {
|
||||
if (scripts.length === 0) {
|
||||
return (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">No scripts match the current filter.</p>
|
||||
<p className="py-8 text-center text-sm text-[#848b9b]">No scripts match the current filter.</p>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -242,24 +242,24 @@ function ScriptsTable({
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-left">
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Name</th>
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Status</th>
|
||||
<th className="pb-3 pr-4 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Featured</th>
|
||||
<th className="pb-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Sort Order</th>
|
||||
<tr className="border-b border-[#1e2130] text-left">
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Name</th>
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Status</th>
|
||||
<th className="pb-3 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Featured</th>
|
||||
<th className="pb-3 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Sort Order</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{scripts.map(script => (
|
||||
<tr key={script.id} className="group hover:bg-[rgba(255,255,255,0.02)] transition-colors">
|
||||
<td className="py-3 pr-4 text-foreground font-medium">{script.name}</td>
|
||||
<td className="py-3 pr-4 text-[#e2e5eb] font-medium">{script.name}</td>
|
||||
<td className="py-3 pr-4">
|
||||
<span
|
||||
className={cn(
|
||||
'font-label text-[0.625rem] uppercase tracking-[0.1em] rounded-full px-2 py-0.5 border',
|
||||
'font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] rounded-full px-2 py-0.5 border',
|
||||
script.is_active
|
||||
? 'border-emerald-400/30 text-emerald-400 bg-emerald-400/10'
|
||||
: 'border-border text-muted-foreground',
|
||||
: 'border-[#1e2130] text-[#848b9b]',
|
||||
)}
|
||||
>
|
||||
{script.is_active ? 'Active' : 'Inactive'}
|
||||
@@ -417,23 +417,23 @@ export default function GalleryManagementPage() {
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-16 text-muted-foreground text-sm">
|
||||
<div className="flex items-center justify-center py-16 text-[#848b9b] text-sm">
|
||||
Loading gallery items…
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* Summary stats */}
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<div className="glass-card-static rounded-[12px] px-4 py-3 flex items-center gap-3">
|
||||
<div className="card-flat rounded-[12px] px-4 py-3 flex items-center gap-3">
|
||||
<Star className="h-4 w-4 text-amber-400 fill-amber-400" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-foreground font-semibold">{featuredFlowCount}</span> featured flow{featuredFlowCount !== 1 ? 's' : ''}
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
<span className="text-[#e2e5eb] font-semibold">{featuredFlowCount}</span> featured flow{featuredFlowCount !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="glass-card-static rounded-[12px] px-4 py-3 flex items-center gap-3">
|
||||
<div className="card-flat rounded-[12px] px-4 py-3 flex items-center gap-3">
|
||||
<Star className="h-4 w-4 text-amber-400 fill-amber-400" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-foreground font-semibold">{featuredScriptCount}</span> featured script{featuredScriptCount !== 1 ? 's' : ''}
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
<span className="text-[#e2e5eb] font-semibold">{featuredScriptCount}</span> featured script{featuredScriptCount !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -442,14 +442,14 @@ export default function GalleryManagementPage() {
|
||||
<section>
|
||||
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-foreground">Featured Flows</h2>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
<h2 className="text-base font-semibold text-[#e2e5eb]">Featured Flows</h2>
|
||||
<p className="text-xs text-[#848b9b] mt-0.5">
|
||||
Toggle flows to show in the gallery. Lower sort order = shown first.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="glass-card-static rounded-[16px] p-5 space-y-4">
|
||||
<div className="card-flat rounded-lg p-5 space-y-4">
|
||||
<FilterBar
|
||||
search={flowSearch}
|
||||
onSearchChange={setFlowSearch}
|
||||
@@ -469,14 +469,14 @@ export default function GalleryManagementPage() {
|
||||
<section>
|
||||
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-foreground">Featured Scripts</h2>
|
||||
<p className="text-xs text-muted-foreground mt-0.5">
|
||||
<h2 className="text-base font-semibold text-[#e2e5eb]">Featured Scripts</h2>
|
||||
<p className="text-xs text-[#848b9b] mt-0.5">
|
||||
Toggle scripts to show in the gallery. Lower sort order = shown first.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="glass-card-static rounded-[16px] p-5 space-y-4">
|
||||
<div className="card-flat rounded-lg p-5 space-y-4">
|
||||
<FilterBar
|
||||
search={scriptSearch}
|
||||
onSearchChange={setScriptSearch}
|
||||
|
||||
@@ -73,10 +73,10 @@ export function GlobalCategoriesPage() {
|
||||
}
|
||||
|
||||
const columns: Column<AdminCategory>[] = [
|
||||
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-foreground">{c.name}</span> },
|
||||
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-muted-foreground">{c.slug}</span> },
|
||||
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-muted-foreground">{c.description || '-'}</span> },
|
||||
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-muted-foreground">{c.tree_count}</span> },
|
||||
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-[#e2e5eb]">{c.name}</span> },
|
||||
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-[#848b9b]">{c.slug}</span> },
|
||||
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-[#848b9b]">{c.description || '-'}</span> },
|
||||
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-[#848b9b]">{c.tree_count}</span> },
|
||||
{
|
||||
key: 'actions', header: '', className: 'w-12',
|
||||
render: (c) => (
|
||||
@@ -124,15 +124,15 @@ export function GlobalCategoriesPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<Input type="text" value={form.name} onChange={(e) => { const name = e.target.value; setForm(f => ({ ...f, name, slug: generateSlug(name) })) }} placeholder="e.g. Networking" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,15 +153,15 @@ export function GlobalCategoriesPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="e.g. Networking" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,8 +110,8 @@ export function InviteCodesPage() {
|
||||
}
|
||||
|
||||
const selectClass = cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)
|
||||
|
||||
const columns: Column<InviteCodeResponse>[] = [
|
||||
@@ -119,7 +119,7 @@ export function InviteCodesPage() {
|
||||
key: 'code',
|
||||
header: 'Code',
|
||||
render: (c) => (
|
||||
<code className="rounded bg-accent px-2 py-1 text-sm font-mono text-muted-foreground">{c.code}</code>
|
||||
<code className="rounded bg-accent px-2 py-1 text-sm font-mono text-[#848b9b]">{c.code}</code>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -130,12 +130,12 @@ export function InviteCodesPage() {
|
||||
{c.email_sent ? (
|
||||
<MailCheck className="h-3.5 w-3.5 text-emerald-400" />
|
||||
) : (
|
||||
<Mail className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Mail className="h-3.5 w-3.5 text-[#848b9b]" />
|
||||
)}
|
||||
<span className="text-sm text-muted-foreground">{c.email}</span>
|
||||
<span className="text-sm text-[#848b9b]">{c.email}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">—</span>
|
||||
<span className="text-sm text-[#848b9b]">—</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -151,9 +151,9 @@ export function InviteCodesPage() {
|
||||
key: 'trial',
|
||||
header: 'Trial',
|
||||
render: (c) => c.has_trial ? (
|
||||
<span className="text-sm text-muted-foreground">{c.trial_duration_days}d</span>
|
||||
<span className="text-sm text-[#848b9b]">{c.trial_duration_days}d</span>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">—</span>
|
||||
<span className="text-sm text-[#848b9b]">—</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -170,7 +170,7 @@ export function InviteCodesPage() {
|
||||
key: 'expires_at',
|
||||
header: 'Expires',
|
||||
render: (c) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
{c.expires_at ? new Date(c.expires_at).toLocaleDateString() : 'Never'}
|
||||
</span>
|
||||
),
|
||||
@@ -179,7 +179,7 @@ export function InviteCodesPage() {
|
||||
key: 'created_at',
|
||||
header: 'Created',
|
||||
render: (c) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
{new Date(c.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
),
|
||||
@@ -254,7 +254,7 @@ export function InviteCodesPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Recipient Email</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Recipient Email</label>
|
||||
<Input
|
||||
type="email"
|
||||
value={email}
|
||||
@@ -264,7 +264,7 @@ export function InviteCodesPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Plan</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Plan</label>
|
||||
<select
|
||||
aria-label="Plan"
|
||||
value={assignedPlan}
|
||||
@@ -283,7 +283,7 @@ export function InviteCodesPage() {
|
||||
|
||||
{assignedPlan !== 'free' && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Trial Duration (days)</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Trial Duration (days)</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={trialDays}
|
||||
@@ -292,12 +292,12 @@ export function InviteCodesPage() {
|
||||
min={1}
|
||||
max={90}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">Leave empty for no trial — account gets full plan immediately.</p>
|
||||
<p className="mt-1 text-xs text-[#848b9b]">Leave empty for no trial — account gets full plan immediately.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Expires in (days)</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Expires in (days)</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={expiresInDays}
|
||||
@@ -307,7 +307,7 @@ export function InviteCodesPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Note</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={note}
|
||||
|
||||
@@ -76,16 +76,16 @@ export function PlanLimitsPage() {
|
||||
}
|
||||
|
||||
const planColumns: Column<PlanLimitConfig>[] = [
|
||||
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-foreground capitalize">{p.plan}</span> },
|
||||
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-muted-foreground">{p.max_trees ?? 'Unlimited'}</span> },
|
||||
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-muted-foreground">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
|
||||
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-muted-foreground">{p.max_users ?? 'Unlimited'}</span> },
|
||||
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-[#e2e5eb] capitalize">{p.plan}</span> },
|
||||
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_trees ?? 'Unlimited'}</span> },
|
||||
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
|
||||
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_users ?? 'Unlimited'}</span> },
|
||||
{
|
||||
key: 'actions', header: '', className: 'w-12',
|
||||
render: (p) => (
|
||||
<button
|
||||
onClick={() => setEditPlan({ ...p })}
|
||||
className="rounded-md px-3 py-1 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md px-3 py-1 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
@@ -94,11 +94,11 @@ export function PlanLimitsPage() {
|
||||
]
|
||||
|
||||
const overrideColumns: Column<AccountOverrideResponse>[] = [
|
||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
||||
{ key: 'max_trees', header: 'Max Trees', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_trees ?? '-'}</span> },
|
||||
{ key: 'max_sessions', header: 'Sessions/Month', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_sessions_per_month ?? '-'}</span> },
|
||||
{ key: 'max_users', header: 'Max Users', render: (o) => <span className="text-sm text-muted-foreground">{o.override_max_users ?? '-'}</span> },
|
||||
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-muted-foreground">{o.note || '-'}</span> },
|
||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-[#e2e5eb]">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
||||
{ key: 'max_trees', header: 'Max Trees', render: (o) => <span className="text-sm text-[#848b9b]">{o.override_max_trees ?? '-'}</span> },
|
||||
{ key: 'max_sessions', header: 'Sessions/Month', render: (o) => <span className="text-sm text-[#848b9b]">{o.override_max_sessions_per_month ?? '-'}</span> },
|
||||
{ key: 'max_users', header: 'Max Users', render: (o) => <span className="text-sm text-[#848b9b]">{o.override_max_users ?? '-'}</span> },
|
||||
{ key: 'note', header: 'Note', render: (o) => <span className="text-sm text-[#848b9b]">{o.note || '-'}</span> },
|
||||
{
|
||||
key: 'actions', header: '', className: 'w-12',
|
||||
render: (o) => (
|
||||
@@ -114,7 +114,7 @@ export function PlanLimitsPage() {
|
||||
<PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" />
|
||||
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Plan Defaults</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Plan Defaults</h2>
|
||||
<div className="mt-3">
|
||||
<DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} />
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@ export function PlanLimitsPage() {
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-foreground">Account Overrides</h2>
|
||||
<h2 className="text-lg font-semibold text-[#e2e5eb]">Account Overrides</h2>
|
||||
<Button onClick={() => setCreateOverride(true)}>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Override
|
||||
@@ -155,15 +155,15 @@ export function PlanLimitsPage() {
|
||||
{editPlan && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Trees (empty = unlimited)</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Trees (empty = unlimited)</label>
|
||||
<Input type="number" value={editPlan.max_trees ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_trees: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Sessions/Month (empty = unlimited)</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Sessions/Month (empty = unlimited)</label>
|
||||
<Input type="number" value={editPlan.max_sessions_per_month ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_sessions_per_month: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Users (empty = unlimited)</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Users (empty = unlimited)</label>
|
||||
<Input type="number" value={editPlan.max_users ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_users: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -185,23 +185,23 @@ export function PlanLimitsPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||
<Input type="text" value={overrideForm.account_display_code} onChange={(e) => setOverrideForm({ ...overrideForm, account_display_code: e.target.value })} placeholder="e.g. ABC-1234" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Trees Override</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Trees Override</label>
|
||||
<Input type="number" value={overrideForm.override_max_trees ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_trees: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Sessions/Month Override</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Sessions/Month Override</label>
|
||||
<Input type="number" value={overrideForm.override_max_sessions_per_month ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_sessions_per_month: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Users Override</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Max Users Override</label>
|
||||
<Input type="number" value={overrideForm.override_max_users ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, override_max_users: e.target.value ? parseInt(e.target.value) : null })} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Note</label>
|
||||
<Input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason for override" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -47,11 +47,11 @@ export function SettingsPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader title="Platform Settings" description="Global platform configuration" />
|
||||
|
||||
<div className="max-w-xl space-y-6 bg-card border border-border rounded-xl p-6">
|
||||
<div className="max-w-xl space-y-6 bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Email Verification</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<h3 className="font-medium text-[#e2e5eb]">Email Verification</h3>
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
When enabled, unverified users see a banner prompting them to verify their email.
|
||||
</p>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@ export function SettingsPage() {
|
||||
onClick={() => setSettings({ ...settings, email_verification_enabled: !emailVerificationEnabled })}
|
||||
className={cn(
|
||||
'h-6 w-10 rounded-full transition-colors',
|
||||
emailVerificationEnabled ? 'bg-gradient-brand' : 'bg-accent'
|
||||
emailVerificationEnabled ? 'bg-[#22d3ee]' : 'bg-accent'
|
||||
)}
|
||||
>
|
||||
<div className={cn(
|
||||
@@ -71,8 +71,8 @@ export function SettingsPage() {
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Maintenance Mode</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<h3 className="font-medium text-[#e2e5eb]">Maintenance Mode</h3>
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
When enabled, users will see a maintenance message instead of the app.
|
||||
</p>
|
||||
</div>
|
||||
@@ -93,7 +93,7 @@ export function SettingsPage() {
|
||||
|
||||
{maintenanceMode && (
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Maintenance Message</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Maintenance Message</label>
|
||||
<Textarea
|
||||
value={maintenanceMessage}
|
||||
onChange={(e) => setSettings({ ...settings, maintenance_message: e.target.value })}
|
||||
@@ -103,13 +103,13 @@ export function SettingsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-border pt-4">
|
||||
<div className="border-t border-[#1e2130] pt-4">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className={cn(
|
||||
'rounded-md px-4 py-2 text-sm font-medium',
|
||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
||||
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||
'disabled:opacity-50'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -70,11 +70,11 @@ export default function SurveyInvitesPage() {
|
||||
/>
|
||||
|
||||
{/* Create Invite Section */}
|
||||
<div className="glass-card-static p-6">
|
||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">Create Invite</h3>
|
||||
<div className="card-flat p-6">
|
||||
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">Create Invite</h3>
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end">
|
||||
<div className="flex-1">
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1.5 block">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1.5 block">
|
||||
Recipient Name
|
||||
</label>
|
||||
<input
|
||||
@@ -82,11 +82,11 @@ export default function SurveyInvitesPage() {
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder="John Smith"
|
||||
className="w-full rounded-[10px] border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary/30 focus:outline-hidden"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary/30 focus:outline-hidden"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1.5 block">
|
||||
<label className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1.5 block">
|
||||
Email (optional)
|
||||
</label>
|
||||
<input
|
||||
@@ -94,14 +94,14 @@ export default function SurveyInvitesPage() {
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
placeholder="john@example.com"
|
||||
className="w-full rounded-[10px] border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary/30 focus:outline-hidden"
|
||||
className="w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] placeholder:text-[#848b9b] focus:border-primary/30 focus:outline-hidden"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => handleCreate(false)}
|
||||
disabled={creating || !name.trim()}
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-brand-dark hover:brightness-110 active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Link2 className="h-4 w-4" />}
|
||||
Generate Link
|
||||
@@ -109,7 +109,7 @@ export default function SurveyInvitesPage() {
|
||||
<button
|
||||
onClick={() => handleCreate(true)}
|
||||
disabled={creating || !name.trim() || !email.trim()}
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-white/[0.04] border border-brand-border px-4 py-2 text-sm font-medium text-foreground hover:border-white/[0.12] active:scale-[0.97] disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-white/[0.04] border border-brand-border px-4 py-2 text-sm font-medium text-[#e2e5eb] hover:border-white/[0.12] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
||||
>
|
||||
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
||||
Send Email
|
||||
@@ -122,17 +122,17 @@ export default function SurveyInvitesPage() {
|
||||
)}
|
||||
|
||||
{lastCreated && (
|
||||
<div className="mt-4 rounded-[10px] border border-primary/[0.15] bg-primary/[0.04] p-4">
|
||||
<div className="mt-4 rounded-lg border border-primary/[0.15] bg-primary/[0.04] p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs text-muted-foreground mb-1">
|
||||
<p className="text-xs text-[#848b9b] mb-1">
|
||||
{lastCreated.email_sent ? 'Email sent to ' + lastCreated.recipient_email + '! Link:' : 'Share this link with ' + lastCreated.recipient_name + ':'}
|
||||
</p>
|
||||
<p className="truncate text-sm font-mono text-foreground">{lastCreated.survey_url}</p>
|
||||
<p className="truncate text-sm font-mono text-[#e2e5eb]">{lastCreated.survey_url}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleCopy(lastCreated.survey_url)}
|
||||
className="shrink-0 rounded-lg p-2 text-muted-foreground hover:bg-brand-border hover:text-foreground transition-colors"
|
||||
className="shrink-0 rounded-lg p-2 text-[#848b9b] hover:bg-brand-border hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||
</button>
|
||||
@@ -142,33 +142,33 @@ export default function SurveyInvitesPage() {
|
||||
</div>
|
||||
|
||||
{/* Invites Table */}
|
||||
<div className="glass-card-static overflow-hidden">
|
||||
<div className="card-flat overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border">
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Name</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Email</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Status</th>
|
||||
<th className="px-4 py-3 text-center font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Sent</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Created</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Completed</th>
|
||||
<th className="px-4 py-3 text-center font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Link</th>
|
||||
<tr className="border-b border-[#1e2130]">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Name</th>
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Email</th>
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Status</th>
|
||||
<th className="px-4 py-3 text-center font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Sent</th>
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Created</th>
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Completed</th>
|
||||
<th className="px-4 py-3 text-center font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Link</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loading ? (
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-sm text-muted-foreground">Loading...</td></tr>
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-sm text-[#848b9b]">Loading...</td></tr>
|
||||
) : invites.length === 0 ? (
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-sm text-muted-foreground">No invites yet</td></tr>
|
||||
<tr><td colSpan={7} className="px-4 py-8 text-center text-sm text-[#848b9b]">No invites yet</td></tr>
|
||||
) : (
|
||||
invites.map(invite => (
|
||||
<tr key={invite.id} className="border-b border-border/50 hover:bg-white/[0.02] transition-colors">
|
||||
<td className="px-4 py-3 text-sm text-foreground">{invite.recipient_name}</td>
|
||||
<td className="px-4 py-3 text-sm text-muted-foreground">{invite.recipient_email || '—'}</td>
|
||||
<tr key={invite.id} className="border-b border-[#1e2130]/50 hover:bg-white/[0.02] transition-colors">
|
||||
<td className="px-4 py-3 text-sm text-[#e2e5eb]">{invite.recipient_name}</td>
|
||||
<td className="px-4 py-3 text-sm text-[#848b9b]">{invite.recipient_email || '—'}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={cn(
|
||||
'inline-flex items-center rounded-full px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wider',
|
||||
'inline-flex items-center rounded-full px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider',
|
||||
invite.status === 'completed'
|
||||
? 'bg-emerald-400/10 text-emerald-400'
|
||||
: 'bg-amber-400/10 text-amber-400'
|
||||
@@ -178,17 +178,17 @@ export default function SurveyInvitesPage() {
|
||||
</td>
|
||||
<td className="px-4 py-3 text-center">
|
||||
{invite.email_sent ? (
|
||||
<Mail className="mx-auto h-4 w-4 text-muted-foreground" />
|
||||
<Mail className="mx-auto h-4 w-4 text-[#848b9b]" />
|
||||
) : (
|
||||
<span className="text-muted-foreground/50">—</span>
|
||||
<span className="text-[#848b9b]/50">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 font-label text-xs text-muted-foreground">{formatDate(invite.created_at)}</td>
|
||||
<td className="px-4 py-3 font-label text-xs text-muted-foreground">{invite.completed_at ? formatDate(invite.completed_at) : '—'}</td>
|
||||
<td className="px-4 py-3 font-sans text-xs text-xs text-[#848b9b]">{formatDate(invite.created_at)}</td>
|
||||
<td className="px-4 py-3 font-sans text-xs text-xs text-[#848b9b]">{invite.completed_at ? formatDate(invite.completed_at) : '—'}</td>
|
||||
<td className="px-4 py-3 text-center">
|
||||
<button
|
||||
onClick={() => handleCopy(invite.survey_url)}
|
||||
className="rounded-lg p-1.5 text-muted-foreground hover:bg-brand-border hover:text-foreground transition-colors"
|
||||
className="rounded-lg p-1.5 text-[#848b9b] hover:bg-brand-border hover:text-[#e2e5eb] transition-colors"
|
||||
title="Copy survey link"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
|
||||
@@ -41,7 +41,7 @@ const QUESTIONS: { id: string; num: string; text: string; type: 'mc' | 'mc-multi
|
||||
|
||||
function AnswerDisplay({ value, type }: { value: string | string[] | undefined; type: string }) {
|
||||
if (!value || (Array.isArray(value) && value.length === 0)) {
|
||||
return <p className="text-sm italic text-muted-foreground/60">No answer</p>
|
||||
return <p className="text-sm italic text-[#848b9b]/60">No answer</p>
|
||||
}
|
||||
|
||||
if (type === 'mc-multi' && Array.isArray(value)) {
|
||||
@@ -50,7 +50,7 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
||||
{value.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="inline-block rounded-full bg-primary/10 px-2.5 py-0.5 font-label text-[0.625rem] uppercase tracking-wider text-primary"
|
||||
className="inline-block rounded-full bg-[rgba(34,211,238,0.10)] px-2.5 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider text-[#22d3ee]"
|
||||
>
|
||||
{v}
|
||||
</span>
|
||||
@@ -63,8 +63,8 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
||||
return (
|
||||
<ol className="space-y-1">
|
||||
{value.map((v, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-foreground/90">
|
||||
<span className="font-label text-xs font-bold text-primary">{i + 1}.</span>
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-[#e2e5eb]/90">
|
||||
<span className="font-sans text-xs text-xs font-bold text-[#22d3ee]">{i + 1}.</span>
|
||||
{v}
|
||||
</li>
|
||||
))}
|
||||
@@ -75,12 +75,12 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
||||
if (type === 'text') {
|
||||
return (
|
||||
<div className="border-l-2 border-primary/30 pl-3">
|
||||
<p className="text-sm text-foreground/90 whitespace-pre-wrap">{String(value)}</p>
|
||||
<p className="text-sm text-[#e2e5eb]/90 whitespace-pre-wrap">{String(value)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return <p className="text-sm text-foreground/90">{String(value)}</p>
|
||||
return <p className="text-sm text-[#e2e5eb]/90">{String(value)}</p>
|
||||
}
|
||||
|
||||
function ExpandedDetail({ response }: { response: SurveyResponseDetail }) {
|
||||
@@ -98,16 +98,16 @@ function ExpandedDetail({ response }: { response: SurveyResponseDetail }) {
|
||||
{QUESTIONS.map((q) => (
|
||||
<div
|
||||
key={q.id}
|
||||
className="rounded-[10px] p-4"
|
||||
className="rounded-lg p-4"
|
||||
style={{
|
||||
background: 'rgba(24, 26, 31, 0.6)',
|
||||
background: '#14161d',
|
||||
border: '1px solid var(--glass-border)',
|
||||
}}
|
||||
>
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-primary mb-1">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#22d3ee] mb-1">
|
||||
Q{q.num}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground mb-2">{q.text}</p>
|
||||
<p className="text-xs text-[#848b9b] mb-2">{q.text}</p>
|
||||
<AnswerDisplay value={response.responses[q.id]} type={q.type} />
|
||||
</div>
|
||||
))}
|
||||
@@ -150,7 +150,7 @@ function ResponseRow({
|
||||
<>
|
||||
<tr
|
||||
className={cn(
|
||||
'border-b border-border/50 transition-colors cursor-pointer',
|
||||
'border-b border-[#1e2130]/50 transition-colors cursor-pointer',
|
||||
!response.is_read && 'bg-primary/3',
|
||||
'hover:bg-white/[0.02]'
|
||||
)}
|
||||
@@ -158,16 +158,16 @@ function ResponseRow({
|
||||
{/* Checkbox */}
|
||||
<td className="px-2 py-3 w-8" onClick={e => { e.stopPropagation(); onSelect() }}>
|
||||
{isSelected ? (
|
||||
<CheckSquare className="h-4 w-4 text-primary cursor-pointer" />
|
||||
<CheckSquare className="h-4 w-4 text-[#22d3ee] cursor-pointer" />
|
||||
) : (
|
||||
<Square className="h-4 w-4 text-muted-foreground/40 cursor-pointer hover:text-muted-foreground" />
|
||||
<Square className="h-4 w-4 text-[#848b9b]/40 cursor-pointer hover:text-[#848b9b]" />
|
||||
)}
|
||||
</td>
|
||||
|
||||
{/* Unread dot */}
|
||||
<td className="px-1 py-3 w-6" onClick={onToggle}>
|
||||
{!response.is_read && (
|
||||
<Circle className="h-2.5 w-2.5 fill-primary text-primary" />
|
||||
<Circle className="h-2.5 w-2.5 fill-primary text-[#22d3ee]" />
|
||||
)}
|
||||
</td>
|
||||
|
||||
@@ -175,46 +175,46 @@ function ResponseRow({
|
||||
<td className="px-2 py-3 w-8" onClick={onToggle}>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
'h-4 w-4 text-muted-foreground transition-transform',
|
||||
'h-4 w-4 text-[#848b9b] transition-transform',
|
||||
isExpanded && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3 font-label text-xs text-muted-foreground" onClick={onToggle}>{index + 1}</td>
|
||||
<td className={cn('px-4 py-3 text-sm', !response.is_read ? 'text-foreground font-medium' : 'text-foreground')} onClick={onToggle}>
|
||||
{response.respondent_name || <span className="text-muted-foreground italic">Anonymous</span>}
|
||||
<td className="px-4 py-3 font-sans text-xs text-xs text-[#848b9b]" onClick={onToggle}>{index + 1}</td>
|
||||
<td className={cn('px-4 py-3 text-sm', !response.is_read ? 'text-[#e2e5eb] font-medium' : 'text-[#e2e5eb]')} onClick={onToggle}>
|
||||
{response.respondent_name || <span className="text-[#848b9b] italic">Anonymous</span>}
|
||||
</td>
|
||||
<td className="px-4 py-3" onClick={onToggle}>
|
||||
{response.source === 'invite' ? (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wider bg-primary/10 text-primary">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider bg-[rgba(34,211,238,0.10)] text-[#22d3ee]">
|
||||
<User className="h-3 w-3" />
|
||||
Invite
|
||||
{response.invite_name && (
|
||||
<span className="text-primary/70">({response.invite_name})</span>
|
||||
<span className="text-[#22d3ee]/70">({response.invite_name})</span>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wider bg-brand-border text-muted-foreground">
|
||||
<span className="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 font-sans text-xs text-[0.625rem] uppercase tracking-wider bg-brand-border text-[#848b9b]">
|
||||
<Link2 className="h-3 w-3" />
|
||||
Direct
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 font-label text-xs text-muted-foreground" onClick={onToggle}>
|
||||
<td className="px-4 py-3 font-sans text-xs text-xs text-[#848b9b]" onClick={onToggle}>
|
||||
{new Date(response.created_at).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-muted-foreground" onClick={onToggle}>
|
||||
<td className="px-4 py-3 text-sm text-[#848b9b]" onClick={onToggle}>
|
||||
{answeredCount} / {QUESTIONS.length}
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-3 py-3 w-10 relative">
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); setShowMenu(!showMenu) }}
|
||||
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
|
||||
className="p-1.5 rounded-lg hover:bg-brand-border text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -223,18 +223,18 @@ function ResponseRow({
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowMenu(false)} />
|
||||
<div
|
||||
className="absolute right-3 top-full z-50 mt-1 w-44 rounded-xl py-1 shadow-xl"
|
||||
style={{ background: 'rgba(24, 26, 31, 0.95)', border: '1px solid var(--glass-border)', backdropFilter: 'blur(16px)' }}
|
||||
style={{ background: '#14161d', border: '1px solid var(--glass-border)', }}
|
||||
>
|
||||
<button
|
||||
onClick={() => { onMarkRead(); setShowMenu(false) }}
|
||||
className="flex w-full items-center gap-2.5 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-white/[0.04] transition-colors"
|
||||
className="flex w-full items-center gap-2.5 px-3 py-2 text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:bg-white/[0.04] transition-colors"
|
||||
>
|
||||
{response.is_read ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
||||
{response.is_read ? 'Mark Unread' : 'Mark Read'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { onArchive(); setShowMenu(false) }}
|
||||
className="flex w-full items-center gap-2.5 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-white/[0.04] transition-colors"
|
||||
className="flex w-full items-center gap-2.5 px-3 py-2 text-xs text-[#848b9b] hover:text-[#e2e5eb] hover:bg-white/[0.04] transition-colors"
|
||||
>
|
||||
{response.archived_at ? <ArchiveRestore className="h-3.5 w-3.5" /> : <Archive className="h-3.5 w-3.5" />}
|
||||
{response.archived_at ? 'Unarchive' : 'Archive'}
|
||||
@@ -408,7 +408,7 @@ export default function SurveyResponsesPage() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
<Loader2 className="h-6 w-6 animate-spin text-[#22d3ee]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -416,7 +416,7 @@ export default function SurveyResponsesPage() {
|
||||
if (error && !data) {
|
||||
return (
|
||||
<div className="px-6 py-8">
|
||||
<div className="glass-card-static p-6 text-center text-rose-400">{error}</div>
|
||||
<div className="card-flat p-6 text-center text-rose-400">{error}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -434,10 +434,10 @@ export default function SurveyResponsesPage() {
|
||||
<button
|
||||
onClick={() => setShowArchived(!showArchived)}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-[10px] px-3 py-2 text-xs font-medium transition-colors border',
|
||||
'inline-flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-medium transition-colors border',
|
||||
showArchived
|
||||
? 'bg-primary/10 text-primary border-primary/20'
|
||||
: 'bg-white/[0.04] text-muted-foreground border-brand-border hover:border-white/[0.12]'
|
||||
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/20'
|
||||
: 'bg-white/[0.04] text-[#848b9b] border-brand-border hover:border-white/[0.12]'
|
||||
)}
|
||||
>
|
||||
<Archive className="h-3.5 w-3.5" />
|
||||
@@ -446,7 +446,7 @@ export default function SurveyResponsesPage() {
|
||||
<button
|
||||
onClick={handleExport}
|
||||
disabled={exporting || responses.length === 0}
|
||||
className="inline-flex items-center gap-2 rounded-[10px] bg-white/[0.04] border border-brand-border px-4 py-2 text-sm font-medium text-foreground transition-colors hover:border-white/[0.12] disabled:opacity-50"
|
||||
className="inline-flex items-center gap-2 rounded-lg bg-white/[0.04] border border-brand-border px-4 py-2 text-sm font-medium text-[#e2e5eb] transition-colors hover:border-white/[0.12] disabled:opacity-50"
|
||||
>
|
||||
{exporting ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -460,36 +460,36 @@ export default function SurveyResponsesPage() {
|
||||
/>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-[10px] border border-rose-500/20 bg-rose-500/10 px-4 py-2 text-sm text-rose-400">
|
||||
<div className="rounded-lg border border-rose-500/20 bg-rose-500/10 px-4 py-2 text-sm text-rose-400">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stat cards */}
|
||||
<div className="flex gap-4">
|
||||
<div className="glass-card-static px-5 py-4 flex-1">
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
||||
<div className="card-flat px-5 py-4 flex-1">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||
Total Responses
|
||||
</p>
|
||||
<p className="text-2xl font-heading font-bold text-gradient-brand">
|
||||
<p className="text-2xl font-heading font-bold text-[#67e8f9]">
|
||||
{data?.total ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-card-static px-5 py-4 flex-1">
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
||||
<div className="card-flat px-5 py-4 flex-1">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||
This Week
|
||||
</p>
|
||||
<p className="text-2xl font-heading font-bold text-foreground">
|
||||
<p className="text-2xl font-heading font-bold text-[#e2e5eb]">
|
||||
{data?.this_week ?? 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-card-static px-5 py-4 flex-1">
|
||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
||||
<div className="card-flat px-5 py-4 flex-1">
|
||||
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||
Unread
|
||||
</p>
|
||||
<p className={cn(
|
||||
'text-2xl font-heading font-bold',
|
||||
(data?.unread ?? 0) > 0 ? 'text-primary' : 'text-foreground'
|
||||
(data?.unread ?? 0) > 0 ? 'text-[#22d3ee]' : 'text-[#e2e5eb]'
|
||||
)}>
|
||||
{data?.unread ?? 0}
|
||||
</p>
|
||||
@@ -502,27 +502,27 @@ export default function SurveyResponsesPage() {
|
||||
className="flex items-center gap-3 rounded-xl px-4 py-2.5"
|
||||
style={{ background: 'rgba(6, 182, 212, 0.08)', border: '1px solid rgba(6, 182, 212, 0.15)' }}
|
||||
>
|
||||
<span className="text-sm text-primary font-medium">
|
||||
<span className="text-sm text-[#22d3ee] font-medium">
|
||||
{selectedIds.size} selected
|
||||
</span>
|
||||
<div className="flex-1" />
|
||||
<button
|
||||
onClick={() => handleBulkAction('mark_read')}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-brand-border transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:bg-brand-border transition-colors"
|
||||
>
|
||||
<Eye className="h-3.5 w-3.5" />
|
||||
Mark Read
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleBulkAction('mark_unread')}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-brand-border transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:bg-brand-border transition-colors"
|
||||
>
|
||||
<EyeOff className="h-3.5 w-3.5" />
|
||||
Mark Unread
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleBulkAction('archive')}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-brand-border transition-colors"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:bg-brand-border transition-colors"
|
||||
>
|
||||
<Archive className="h-3.5 w-3.5" />
|
||||
Archive
|
||||
@@ -536,7 +536,7 @@ export default function SurveyResponsesPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSelectedIds(new Set())}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground hover:text-foreground hover:bg-brand-border transition-colors"
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium text-[#848b9b] hover:text-[#e2e5eb] hover:bg-brand-border transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
@@ -544,14 +544,14 @@ export default function SurveyResponsesPage() {
|
||||
)}
|
||||
|
||||
{/* Table */}
|
||||
<div className="glass-card-static overflow-hidden">
|
||||
<div className="card-flat overflow-hidden">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border/50">
|
||||
<tr className="border-b border-[#1e2130]/50">
|
||||
<th className="px-2 py-3 w-8">
|
||||
<button onClick={toggleSelectAll} className="text-muted-foreground/40 hover:text-muted-foreground">
|
||||
<button onClick={toggleSelectAll} className="text-[#848b9b]/40 hover:text-[#848b9b]">
|
||||
{selectedIds.size > 0 && selectedIds.size === responses.length ? (
|
||||
<CheckSquare className="h-4 w-4 text-primary" />
|
||||
<CheckSquare className="h-4 w-4 text-[#22d3ee]" />
|
||||
) : (
|
||||
<Square className="h-4 w-4" />
|
||||
)}
|
||||
@@ -559,19 +559,19 @@ export default function SurveyResponsesPage() {
|
||||
</th>
|
||||
<th className="px-1 py-3 w-6" />
|
||||
<th className="px-2 py-3 w-8" />
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
#
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
Respondent
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
Source
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
Date
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
|
||||
<th className="px-4 py-3 text-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">
|
||||
Answered
|
||||
</th>
|
||||
<th className="px-3 py-3 w-10" />
|
||||
@@ -580,7 +580,7 @@ export default function SurveyResponsesPage() {
|
||||
<tbody>
|
||||
{responses.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={9} className="px-4 py-12 text-center text-sm text-muted-foreground">
|
||||
<td colSpan={9} className="px-4 py-12 text-center text-sm text-[#848b9b]">
|
||||
{showArchived ? 'No archived responses.' : 'No survey responses yet.'}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -189,8 +189,8 @@ export function UserDetailPage() {
|
||||
}
|
||||
|
||||
const selectClass = cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)
|
||||
|
||||
if (loading) {
|
||||
@@ -224,15 +224,15 @@ export function UserDetailPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/admin/users')}
|
||||
className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="rounded-md border border-[#1e2130] p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-xl font-heading font-semibold text-foreground">
|
||||
<h1 className="text-xl font-heading font-semibold text-[#e2e5eb]">
|
||||
{user.full_name || user.email}
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">{user.email}</p>
|
||||
<p className="text-sm text-[#848b9b]">{user.email}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{user.is_super_admin && (
|
||||
@@ -252,21 +252,21 @@ export function UserDetailPage() {
|
||||
|
||||
{/* Account & Subscription */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||
Account & Subscription
|
||||
</h2>
|
||||
<dl className="space-y-3">
|
||||
{user.account && (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Account</dt>
|
||||
<dd className="text-sm text-foreground">{user.account.name}</dd>
|
||||
<dt className="text-sm text-[#848b9b]">Account</dt>
|
||||
<dd className="text-sm text-[#e2e5eb]">{user.account.name}</dd>
|
||||
</div>
|
||||
{user.account.display_code && (
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Display Code</dt>
|
||||
<dd className="text-sm font-mono text-muted-foreground">{user.account.display_code}</dd>
|
||||
<dt className="text-sm text-[#848b9b]">Display Code</dt>
|
||||
<dd className="text-sm font-mono text-[#848b9b]">{user.account.display_code}</dd>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -274,13 +274,13 @@ export function UserDetailPage() {
|
||||
{user.subscription ? (
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Plan</dt>
|
||||
<dd className="text-sm font-semibold text-foreground">
|
||||
<dt className="text-sm text-[#848b9b]">Plan</dt>
|
||||
<dd className="text-sm font-semibold text-[#e2e5eb]">
|
||||
{user.subscription.plan.charAt(0).toUpperCase() + user.subscription.plan.slice(1)}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Status</dt>
|
||||
<dt className="text-sm text-[#848b9b]">Status</dt>
|
||||
<dd>
|
||||
<StatusBadge variant={user.subscription.status === 'trialing' ? 'warning' : 'success'}>
|
||||
{user.subscription.status}
|
||||
@@ -289,24 +289,24 @@ export function UserDetailPage() {
|
||||
</div>
|
||||
{user.subscription.current_period_end && (
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Period End</dt>
|
||||
<dd className="text-sm text-muted-foreground">{fmt(user.subscription.current_period_end)}</dd>
|
||||
<dt className="text-sm text-[#848b9b]">Period End</dt>
|
||||
<dd className="text-sm text-[#848b9b]">{fmt(user.subscription.current_period_end)}</dd>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm text-muted-foreground">No subscription</div>
|
||||
<div className="text-sm text-[#848b9b]">No subscription</div>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
<dt className="text-sm text-muted-foreground">Joined</dt>
|
||||
<dd className="text-sm text-muted-foreground">{fmt(user.created_at)}</dd>
|
||||
<dt className="text-sm text-[#848b9b]">Joined</dt>
|
||||
<dd className="text-sm text-[#848b9b]">{fmt(user.created_at)}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{/* Admin Actions */}
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||
Admin Actions
|
||||
</h2>
|
||||
<div className="space-y-3">
|
||||
@@ -317,16 +317,16 @@ export function UserDetailPage() {
|
||||
setSelectedPlan(user.subscription?.plan || 'free')
|
||||
setPlanModalOpen(true)
|
||||
}}
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-[#1e2130] px-4 py-3 text-left text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Shield className="h-4 w-4 text-muted-foreground" />
|
||||
<Shield className="h-4 w-4 text-[#848b9b]" />
|
||||
Change Plan
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTrialModalOpen(true)}
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-[#1e2130] px-4 py-3 text-left text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<Clock className="h-4 w-4 text-[#848b9b]" />
|
||||
{user.subscription?.status === 'trialing' ? 'Extend Trial' : 'Start Trial'}
|
||||
</button>
|
||||
</>
|
||||
@@ -349,9 +349,9 @@ export function UserDetailPage() {
|
||||
setResetTempPassword(null)
|
||||
setResetModalOpen(true)
|
||||
}}
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-border px-4 py-3 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
className="flex w-full items-center gap-3 rounded-lg border border-[#1e2130] px-4 py-3 text-left text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||
>
|
||||
<KeyRound className="h-4 w-4 text-muted-foreground" />
|
||||
<KeyRound className="h-4 w-4 text-[#848b9b]" />
|
||||
Reset Password
|
||||
</button>
|
||||
<button
|
||||
@@ -402,42 +402,42 @@ export function UserDetailPage() {
|
||||
|
||||
{/* Invite Code Used */}
|
||||
{user.invite_code_used && (
|
||||
<div className="bg-card border border-border rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||
<Ticket className="mr-2 inline h-4 w-4" />
|
||||
Invite Code Used
|
||||
</h2>
|
||||
<dl className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<div>
|
||||
<dt className="text-xs text-muted-foreground">Code</dt>
|
||||
<dd className="mt-1 font-mono text-sm text-muted-foreground">{user.invite_code_used.code}</dd>
|
||||
<dt className="text-xs text-[#848b9b]">Code</dt>
|
||||
<dd className="mt-1 font-mono text-sm text-[#848b9b]">{user.invite_code_used.code}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-muted-foreground">Plan Assigned</dt>
|
||||
<dd className="mt-1 text-sm text-muted-foreground">
|
||||
<dt className="text-xs text-[#848b9b]">Plan Assigned</dt>
|
||||
<dd className="mt-1 text-sm text-[#848b9b]">
|
||||
{user.invite_code_used.assigned_plan.charAt(0).toUpperCase() + user.invite_code_used.assigned_plan.slice(1)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-muted-foreground">Trial Days</dt>
|
||||
<dd className="mt-1 text-sm text-muted-foreground">{user.invite_code_used.trial_duration_days ?? '—'}</dd>
|
||||
<dt className="text-xs text-[#848b9b]">Trial Days</dt>
|
||||
<dd className="mt-1 text-sm text-[#848b9b]">{user.invite_code_used.trial_duration_days ?? '—'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-muted-foreground">Created By</dt>
|
||||
<dd className="mt-1 text-sm text-muted-foreground">{user.invite_code_used.created_by_email ?? '—'}</dd>
|
||||
<dt className="text-xs text-[#848b9b]">Created By</dt>
|
||||
<dd className="mt-1 text-sm text-[#848b9b]">{user.invite_code_used.created_by_email ?? '—'}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tabs: Sessions / Audit Logs */}
|
||||
<div className="bg-card border border-border rounded-xl">
|
||||
<div className="flex border-b border-border">
|
||||
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl">
|
||||
<div className="flex border-b border-[#1e2130]">
|
||||
<button
|
||||
onClick={() => setActiveTab('sessions')}
|
||||
className={cn(
|
||||
'px-6 py-3 text-sm font-medium',
|
||||
activeTab === 'sessions' ? 'border-b-2 border-foreground text-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||
activeTab === 'sessions' ? 'border-b-2 border-foreground text-[#e2e5eb]' : 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
Sessions ({user.total_sessions})
|
||||
@@ -446,7 +446,7 @@ export function UserDetailPage() {
|
||||
onClick={() => setActiveTab('audit')}
|
||||
className={cn(
|
||||
'px-6 py-3 text-sm font-medium',
|
||||
activeTab === 'audit' ? 'border-b-2 border-foreground text-foreground' : 'text-muted-foreground hover:text-foreground'
|
||||
activeTab === 'audit' ? 'border-b-2 border-foreground text-[#e2e5eb]' : 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||
)}
|
||||
>
|
||||
Audit Logs ({user.total_audit_logs})
|
||||
@@ -458,7 +458,7 @@ export function UserDetailPage() {
|
||||
user.recent_sessions.length > 0 ? (
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-left text-xs text-muted-foreground">
|
||||
<tr className="border-b border-[#1e2130] text-left text-xs text-[#848b9b]">
|
||||
<th className="pb-2 font-medium">Tree</th>
|
||||
<th className="pb-2 font-medium">Started</th>
|
||||
<th className="pb-2 font-medium">Completed</th>
|
||||
@@ -467,17 +467,17 @@ export function UserDetailPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{user.recent_sessions.map(s => (
|
||||
<tr key={s.id} className="border-b border-border">
|
||||
<td className="py-3 text-sm text-muted-foreground">{s.tree_name ?? '—'}</td>
|
||||
<td className="py-3 text-sm text-muted-foreground">{fmtFull(s.started_at)}</td>
|
||||
<td className="py-3 text-sm text-muted-foreground">{fmtFull(s.completed_at)}</td>
|
||||
<tr key={s.id} className="border-b border-[#1e2130]">
|
||||
<td className="py-3 text-sm text-[#848b9b]">{s.tree_name ?? '—'}</td>
|
||||
<td className="py-3 text-sm text-[#848b9b]">{fmtFull(s.started_at)}</td>
|
||||
<td className="py-3 text-sm text-[#848b9b]">{fmtFull(s.completed_at)}</td>
|
||||
<td className="py-3">
|
||||
{s.outcome ? (
|
||||
<StatusBadge variant={s.outcome === 'resolved' ? 'success' : 'default'}>
|
||||
{s.outcome}
|
||||
</StatusBadge>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">—</span>
|
||||
<span className="text-sm text-[#848b9b]">—</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -485,7 +485,7 @@ export function UserDetailPage() {
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">No sessions yet</div>
|
||||
<div className="py-8 text-center text-sm text-[#848b9b]">No sessions yet</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -493,7 +493,7 @@ export function UserDetailPage() {
|
||||
user.recent_audit_logs.length > 0 ? (
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-border text-left text-xs text-muted-foreground">
|
||||
<tr className="border-b border-[#1e2130] text-left text-xs text-[#848b9b]">
|
||||
<th className="pb-2 font-medium">Action</th>
|
||||
<th className="pb-2 font-medium">Resource</th>
|
||||
<th className="pb-2 font-medium">Time</th>
|
||||
@@ -501,16 +501,16 @@ export function UserDetailPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{user.recent_audit_logs.map(a => (
|
||||
<tr key={a.id} className="border-b border-border">
|
||||
<td className="py-3 text-sm text-muted-foreground">{a.action}</td>
|
||||
<td className="py-3 text-sm text-muted-foreground">{a.resource_type ?? '—'}</td>
|
||||
<td className="py-3 text-sm text-muted-foreground">{fmtFull(a.created_at)}</td>
|
||||
<tr key={a.id} className="border-b border-[#1e2130]">
|
||||
<td className="py-3 text-sm text-[#848b9b]">{a.action}</td>
|
||||
<td className="py-3 text-sm text-[#848b9b]">{a.resource_type ?? '—'}</td>
|
||||
<td className="py-3 text-sm text-[#848b9b]">{fmtFull(a.created_at)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">No audit logs yet</div>
|
||||
<div className="py-8 text-center text-sm text-[#848b9b]">No audit logs yet</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
@@ -530,7 +530,7 @@ export function UserDetailPage() {
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Plan</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Plan</label>
|
||||
<select
|
||||
aria-label="Subscription plan"
|
||||
value={selectedPlan}
|
||||
@@ -560,11 +560,11 @@ export function UserDetailPage() {
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Choose how to reset the password for <span className="font-medium text-foreground">{user.full_name || user.email}</span>.
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Choose how to reset the password for <span className="font-medium text-[#e2e5eb]">{user.full_name || user.email}</span>.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-start gap-3 rounded-lg border border-border p-3 cursor-pointer hover:bg-accent">
|
||||
<label className="flex items-start gap-3 rounded-lg border border-[#1e2130] p-3 cursor-pointer hover:bg-accent">
|
||||
<input
|
||||
type="radio"
|
||||
name="reset-mode"
|
||||
@@ -574,11 +574,11 @@ export function UserDetailPage() {
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">Send Reset Email</div>
|
||||
<div className="text-xs text-muted-foreground">User receives an email with a reset link (30 min expiry)</div>
|
||||
<div className="text-sm font-medium text-[#e2e5eb]">Send Reset Email</div>
|
||||
<div className="text-xs text-[#848b9b]">User receives an email with a reset link (30 min expiry)</div>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex items-start gap-3 rounded-lg border border-border p-3 cursor-pointer hover:bg-accent">
|
||||
<label className="flex items-start gap-3 rounded-lg border border-[#1e2130] p-3 cursor-pointer hover:bg-accent">
|
||||
<input
|
||||
type="radio"
|
||||
name="reset-mode"
|
||||
@@ -588,8 +588,8 @@ export function UserDetailPage() {
|
||||
className="mt-0.5"
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-foreground">Generate Temp Password</div>
|
||||
<div className="text-xs text-muted-foreground">A temporary password is generated. You share it manually.</div>
|
||||
<div className="text-sm font-medium text-[#e2e5eb]">Generate Temp Password</div>
|
||||
<div className="text-xs text-[#848b9b]">A temporary password is generated. You share it manually.</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -613,18 +613,18 @@ export function UserDetailPage() {
|
||||
This password will not be shown again. Copy it now.
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground font-mono">
|
||||
<code className="flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] font-mono">
|
||||
{resetTempPassword}
|
||||
</code>
|
||||
<button
|
||||
onClick={handleCopyResetPassword}
|
||||
className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
className="rounded-md border border-[#1e2130] p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||
title="Copy password"
|
||||
>
|
||||
{resetCopied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-[#848b9b]">
|
||||
The user will be required to change this password on next login.
|
||||
</p>
|
||||
</div>
|
||||
@@ -646,7 +646,7 @@ export function UserDetailPage() {
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Days to add</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Days to add</label>
|
||||
<Input
|
||||
type="number"
|
||||
value={trialDays}
|
||||
@@ -654,7 +654,7 @@ export function UserDetailPage() {
|
||||
min={1}
|
||||
max={90}
|
||||
/>
|
||||
<p className="mt-1 text-xs text-muted-foreground">1-90 days. Will convert to trialing status if not already.</p>
|
||||
<p className="mt-1 text-xs text-[#848b9b]">1-90 days. Will convert to trialing status if not already.</p>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -712,11 +712,11 @@ export function UserDetailPage() {
|
||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
||||
This user cannot be deleted because they have dependencies:
|
||||
</div>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<ul className="space-y-1 text-sm text-[#848b9b]">
|
||||
{Object.entries(hardDeleteBlockers).map(([key, count]) => (
|
||||
<li key={key} className="flex justify-between">
|
||||
<span>{key.replace(/_/g, ' ')}</span>
|
||||
<span className="font-mono text-muted-foreground">{count}</span>
|
||||
<span className="font-mono text-[#848b9b]">{count}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -190,8 +190,8 @@ export function UsersPage() {
|
||||
sortable: true,
|
||||
render: (u) => (
|
||||
<div>
|
||||
<div className="font-medium text-foreground">{u.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{u.email}</div>
|
||||
<div className="font-medium text-[#e2e5eb]">{u.name}</div>
|
||||
<div className="text-xs text-[#848b9b]">{u.email}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
@@ -226,7 +226,7 @@ export function UsersPage() {
|
||||
header: 'Joined',
|
||||
sortable: true,
|
||||
render: (u) => (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-[#848b9b]">
|
||||
{new Date(u.created_at).toLocaleDateString()}
|
||||
</span>
|
||||
),
|
||||
@@ -286,12 +286,12 @@ export function UsersPage() {
|
||||
placeholder="Search by name or email..."
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<label className="flex items-center gap-2 text-sm text-[#848b9b]">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showArchived}
|
||||
onChange={(e) => { setShowArchived(e.target.checked); setPage(1) }}
|
||||
className="rounded border-border bg-card"
|
||||
className="rounded border-[#1e2130] bg-[#14161d]"
|
||||
/>
|
||||
Show archived
|
||||
</label>
|
||||
@@ -326,14 +326,14 @@ export function UsersPage() {
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Changing role for <span className="font-medium text-foreground">{roleModalUser?.name}</span>
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Changing role for <span className="font-medium text-[#e2e5eb]">{roleModalUser?.name}</span>
|
||||
</p>
|
||||
<select
|
||||
value={newRole}
|
||||
onChange={(e) => setNewRole(e.target.value)}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -357,11 +357,11 @@ export function UsersPage() {
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Moving <span className="font-medium text-foreground">{moveModalUser?.name}</span> to a new account.
|
||||
<p className="text-sm text-[#848b9b]">
|
||||
Moving <span className="font-medium text-[#e2e5eb]">{moveModalUser?.name}</span> to a new account.
|
||||
</p>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={displayCode}
|
||||
@@ -389,7 +389,7 @@ export function UsersPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={createForm.name}
|
||||
@@ -398,7 +398,7 @@ export function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Email</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Email</label>
|
||||
<Input
|
||||
type="email"
|
||||
value={createForm.email}
|
||||
@@ -407,12 +407,12 @@ export function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Mode</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Mode</label>
|
||||
<select
|
||||
value={createForm.account_mode}
|
||||
onChange={(e) => setCreateForm(f => ({ ...f, account_mode: e.target.value as 'existing' | 'personal' }))}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -423,7 +423,7 @@ export function UsersPage() {
|
||||
{createForm.account_mode === 'existing' && (
|
||||
<>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={createForm.account_display_code}
|
||||
@@ -432,12 +432,12 @@ export function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Role</label>
|
||||
<select
|
||||
value={createForm.account_role}
|
||||
onChange={(e) => setCreateForm(f => ({ ...f, account_role: e.target.value as 'engineer' | 'viewer' }))}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
@@ -453,9 +453,9 @@ export function UsersPage() {
|
||||
id="send-email"
|
||||
checked={createForm.send_email}
|
||||
onChange={(e) => setCreateForm(f => ({ ...f, send_email: e.target.checked }))}
|
||||
className="rounded border-border bg-card"
|
||||
className="rounded border-[#1e2130] bg-[#14161d]"
|
||||
/>
|
||||
<label htmlFor="send-email" className="text-sm text-muted-foreground">
|
||||
<label htmlFor="send-email" className="text-sm text-[#848b9b]">
|
||||
Send welcome email with temporary password
|
||||
</label>
|
||||
</div>
|
||||
@@ -479,21 +479,21 @@ export function UsersPage() {
|
||||
This password will not be shown again. Copy it now.
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Temporary Password</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Temporary Password</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground font-mono">
|
||||
<code className="flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb] font-mono">
|
||||
{tempPassword}
|
||||
</code>
|
||||
<button
|
||||
onClick={handleCopyPassword}
|
||||
className="rounded-md border border-border p-2 text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
className="rounded-md border border-[#1e2130] p-2 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb] transition-colors"
|
||||
title="Copy password"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-xs text-[#848b9b]">
|
||||
The user will be required to change this password on first login.
|
||||
</p>
|
||||
</div>
|
||||
@@ -516,7 +516,7 @@ export function UsersPage() {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Email</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Email</label>
|
||||
<Input
|
||||
type="email"
|
||||
value={inviteForm.email}
|
||||
@@ -525,7 +525,7 @@ export function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={inviteForm.account_display_code}
|
||||
@@ -534,12 +534,12 @@ export function UsersPage() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-sm font-medium text-foreground">Role</label>
|
||||
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Role</label>
|
||||
<select
|
||||
value={inviteForm.role}
|
||||
onChange={(e) => setInviteForm(f => ({ ...f, role: e.target.value as 'engineer' | 'viewer' }))}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
||||
'w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm text-[#e2e5eb]',
|
||||
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user