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>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Building2 className="h-8 w-8 text-muted-foreground" />
|
<Building2 className="h-8 w-8 text-[#848b9b]" />
|
||||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Account Settings</h1>
|
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Account Settings</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
Manage your account, subscription, and team
|
Manage your account, subscription, and team
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-3xl space-y-6">
|
<div className="max-w-3xl space-y-6">
|
||||||
{/* Account Info Section */}
|
{/* Account Info Section */}
|
||||||
<div className="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">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Account Information</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Account Information</h2>
|
||||||
|
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
{/* Account Name */}
|
{/* Account Name */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Account Name
|
Account Name
|
||||||
</label>
|
</label>
|
||||||
{isEditingName ? (
|
{isEditingName ? (
|
||||||
@@ -193,8 +193,8 @@ export function AccountSettingsPage() {
|
|||||||
value={editedName}
|
value={editedName}
|
||||||
onChange={(e) => setEditedName(e.target.value)}
|
onChange={(e) => setEditedName(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -226,11 +226,11 @@ export function AccountSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-1 flex items-center gap-2">
|
<div className="mt-1 flex items-center gap-2">
|
||||||
<span className="text-sm text-foreground">{account?.name}</span>
|
<span className="text-sm text-[#e2e5eb]">{account?.name}</span>
|
||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsEditingName(true)}
|
onClick={() => setIsEditingName(true)}
|
||||||
className="text-xs text-foreground hover:underline"
|
className="text-xs text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -241,10 +241,10 @@ export function AccountSettingsPage() {
|
|||||||
|
|
||||||
{/* Display Code */}
|
{/* Display Code */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Display Code
|
Display Code
|
||||||
</label>
|
</label>
|
||||||
<p className="mt-1 text-sm font-mono text-muted-foreground">
|
<p className="mt-1 text-sm font-mono text-[#848b9b]">
|
||||||
{account?.display_code}
|
{account?.display_code}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -252,8 +252,8 @@ export function AccountSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Subscription Section */}
|
{/* Subscription 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">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Subscription</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Subscription</h2>
|
||||||
|
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
{/* Plan & Status */}
|
{/* Plan & Status */}
|
||||||
@@ -261,9 +261,9 @@ export function AccountSettingsPage() {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium',
|
'inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-medium',
|
||||||
plan === 'free' && 'bg-accent text-muted-foreground',
|
plan === 'free' && 'bg-accent text-[#848b9b]',
|
||||||
plan === 'pro' && 'bg-accent text-foreground',
|
plan === 'pro' && 'bg-accent text-[#e2e5eb]',
|
||||||
plan === 'team' && 'bg-accent text-foreground'
|
plan === 'team' && 'bg-accent text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Crown className="h-3.5 w-3.5" />
|
<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 === 'trialing' && 'bg-blue-500/10 text-blue-400',
|
||||||
sub.status === 'past_due' && 'bg-yellow-500/10 text-yellow-400',
|
sub.status === 'past_due' && 'bg-yellow-500/10 text-yellow-400',
|
||||||
sub.status === 'canceled' && 'bg-red-400/10 text-red-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('_', ' ')}
|
{sub.status.charAt(0).toUpperCase() + sub.status.slice(1).replace('_', ' ')}
|
||||||
@@ -286,7 +286,7 @@ export function AccountSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sub?.current_period_end && (
|
{sub?.current_period_end && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Current period ends: {new Date(sub.current_period_end).toLocaleDateString()}
|
Current period ends: {new Date(sub.current_period_end).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -329,14 +329,14 @@ export function AccountSettingsPage() {
|
|||||||
|
|
||||||
{/* Team Members Section (owners only) */}
|
{/* Team Members Section (owners only) */}
|
||||||
{isAccountOwner && (
|
{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">
|
<div className="flex items-center gap-2">
|
||||||
<Users className="h-5 w-5 text-muted-foreground" />
|
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Team Members</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Team Members</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{members.length === 0 ? (
|
{members.length === 0 ? (
|
||||||
<p className="mt-4 text-sm text-muted-foreground">No team members yet.</p>
|
<p className="mt-4 text-sm text-[#848b9b]">No team members yet.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4 divide-y divide-border">
|
<div className="mt-4 divide-y divide-border">
|
||||||
{members.map((member) => (
|
{members.map((member) => (
|
||||||
@@ -345,12 +345,12 @@ export function AccountSettingsPage() {
|
|||||||
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
|
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">{member.name}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{member.name}</p>
|
||||||
<p className="text-xs text-muted-foreground">{member.email}</p>
|
<p className="text-xs text-[#848b9b]">{member.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{member.account_role === 'owner' ? (
|
{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
|
owner
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -366,8 +366,8 @@ export function AccountSettingsPage() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-2 py-0.5 text-xs',
|
'rounded-md border border-[#1e2130] bg-[#14161d] px-2 py-0.5 text-xs',
|
||||||
'text-foreground focus:border-primary focus:outline-hidden'
|
'text-[#e2e5eb] focus:border-primary focus:outline-hidden'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="engineer">engineer</option>
|
<option value="engineer">engineer</option>
|
||||||
@@ -382,7 +382,7 @@ export function AccountSettingsPage() {
|
|||||||
{member.account_role !== 'owner' && (
|
{member.account_role !== 'owner' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveMember(member.id)}
|
onClick={() => handleRemoveMember(member.id)}
|
||||||
className="text-muted-foreground hover:text-red-400"
|
className="text-[#848b9b] hover:text-red-400"
|
||||||
title="Remove member"
|
title="Remove member"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
@@ -398,10 +398,10 @@ export function AccountSettingsPage() {
|
|||||||
|
|
||||||
{/* Invite Member Section (owners only) */}
|
{/* Invite Member Section (owners only) */}
|
||||||
{isAccountOwner && (
|
{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">
|
<div className="flex items-center gap-2">
|
||||||
<Mail className="h-5 w-5 text-muted-foreground" />
|
<Mail className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Invite Member</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Invite Member</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleInvite} className="mt-4 space-y-3">
|
<form onSubmit={handleInvite} className="mt-4 space-y-3">
|
||||||
@@ -413,8 +413,8 @@ export function AccountSettingsPage() {
|
|||||||
onChange={(e) => setInviteEmail(e.target.value)}
|
onChange={(e) => setInviteEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -422,8 +422,8 @@ export function AccountSettingsPage() {
|
|||||||
value={inviteRole}
|
value={inviteRole}
|
||||||
onChange={(e) => setInviteRole(e.target.value)}
|
onChange={(e) => setInviteRole(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-2',
|
'rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="engineer">Engineer</option>
|
<option value="engineer">Engineer</option>
|
||||||
@@ -449,7 +449,7 @@ export function AccountSettingsPage() {
|
|||||||
{/* Pending Invites */}
|
{/* Pending Invites */}
|
||||||
{invites.length > 0 && (
|
{invites.length > 0 && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<h3 className="text-sm font-medium text-foreground">Pending Invites</h3>
|
<h3 className="text-sm font-medium text-[#e2e5eb]">Pending Invites</h3>
|
||||||
<div className="mt-2 divide-y divide-border">
|
<div className="mt-2 divide-y divide-border">
|
||||||
{invites
|
{invites
|
||||||
.filter((inv) => !inv.used_at)
|
.filter((inv) => !inv.used_at)
|
||||||
@@ -459,21 +459,21 @@ export function AccountSettingsPage() {
|
|||||||
className="flex items-center justify-between py-2"
|
className="flex items-center justify-between py-2"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-foreground">{invite.email}</p>
|
<p className="text-sm text-[#e2e5eb]">{invite.email}</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-[#848b9b]">
|
||||||
{invite.expires_at
|
{invite.expires_at
|
||||||
? `Expires ${new Date(invite.expires_at).toLocaleDateString()}`
|
? `Expires ${new Date(invite.expires_at).toLocaleDateString()}`
|
||||||
: 'No expiration'}
|
: 'No expiration'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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}
|
{invite.role}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleResendInvite(invite.id)}
|
onClick={() => handleResendInvite(invite.id)}
|
||||||
disabled={resendingId === 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"
|
title="Resend invite"
|
||||||
>
|
>
|
||||||
{resendingId === invite.id ? (
|
{resendingId === invite.id ? (
|
||||||
@@ -494,32 +494,32 @@ export function AccountSettingsPage() {
|
|||||||
{/* Profile Settings Link */}
|
{/* Profile Settings Link */}
|
||||||
<Link
|
<Link
|
||||||
to="/account/profile"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Profile Settings</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Profile Settings</h2>
|
||||||
<p className="text-sm text-muted-foreground">Update your name, email, and personal details</p>
|
<p className="text-sm text-[#848b9b]">Update your name, email, and personal details</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
|
|
||||||
{/* Team Categories Link (owners only) */}
|
{/* Team Categories Link (owners only) */}
|
||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/categories"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Team Categories</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Team Categories</h2>
|
||||||
<p className="text-sm text-muted-foreground">Manage tree categories for your team</p>
|
<p className="text-sm text-[#848b9b]">Manage tree categories for your team</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -527,16 +527,16 @@ export function AccountSettingsPage() {
|
|||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/target-lists"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Target Lists</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Target Lists</h2>
|
||||||
<p className="text-sm text-muted-foreground">Saved server lists for maintenance flow batch launching</p>
|
<p className="text-sm text-[#848b9b]">Saved server lists for maintenance flow batch launching</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -544,16 +544,16 @@ export function AccountSettingsPage() {
|
|||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/chat-retention"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Chat Retention</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Chat Retention</h2>
|
||||||
<p className="text-sm text-muted-foreground">Configure AI assistant conversation retention policies</p>
|
<p className="text-sm text-[#848b9b]">Configure AI assistant conversation retention policies</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -561,16 +561,16 @@ export function AccountSettingsPage() {
|
|||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/integrations"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Integrations</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Integrations</h2>
|
||||||
<p className="text-sm text-muted-foreground">Connect your PSA to sync session documentation to tickets</p>
|
<p className="text-sm text-[#848b9b]">Connect your PSA to sync session documentation to tickets</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -578,32 +578,32 @@ export function AccountSettingsPage() {
|
|||||||
{isAccountOwner && (
|
{isAccountOwner && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/branding"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Branding</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Branding</h2>
|
||||||
<p className="text-sm text-muted-foreground">Customize logo, accent color, and company name</p>
|
<p className="text-sm text-[#848b9b]">Customize logo, accent color, and company name</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Feedback Link (all users) */}
|
{/* Feedback Link (all users) */}
|
||||||
<Link
|
<Link
|
||||||
to="/feedback"
|
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">
|
<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>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Send Feedback</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Send Feedback</h2>
|
||||||
<p className="text-sm text-muted-foreground">Report bugs, request features, or share your thoughts</p>
|
<p className="text-sm text-[#848b9b]">Report bugs, request features, or share your thoughts</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Link>
|
||||||
|
|
||||||
{/* Branding Section (owners only) */}
|
{/* Branding Section (owners only) */}
|
||||||
@@ -612,20 +612,20 @@ export function AccountSettingsPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Preferences Section */}
|
{/* 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">
|
<div className="flex items-center gap-2">
|
||||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
<Settings className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Preferences</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Preferences</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<label
|
<label
|
||||||
htmlFor="export-format"
|
htmlFor="export-format"
|
||||||
className="block text-sm font-medium text-foreground"
|
className="block text-sm font-medium text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Default Export Format
|
Default Export Format
|
||||||
</label>
|
</label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
This format will be pre-selected when exporting sessions
|
This format will be pre-selected when exporting sessions
|
||||||
</p>
|
</p>
|
||||||
<select
|
<select
|
||||||
@@ -636,8 +636,8 @@ export function AccountSettingsPage() {
|
|||||||
toast.success('Preference saved')
|
toast.success('Preference saved')
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-2 block w-full max-w-xs rounded-xl border border-border bg-card px-3 py-2',
|
'mt-2 block w-full max-w-xs rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-sm text-foreground',
|
'text-sm text-[#e2e5eb]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -649,23 +649,23 @@ export function AccountSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
{/* SSO Section (Task 11) */}
|
{/* SSO Section (Task 11) */}
|
||||||
{isAccountOwner && (
|
{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">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<ShieldCheck className="h-5 w-5 text-muted-foreground" />
|
<ShieldCheck className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Single Sign-On (SSO)</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">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">
|
<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
|
Enterprise
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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
|
SAML and OIDC single sign-on is available for enterprise plans. Contact us to enable SSO for
|
||||||
your organization.
|
your organization.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="mailto:support@resolutionflow.com?subject=SSO%20Setup%20Request"
|
href="mailto:support@resolutionflow.com?subject=SSO%20Setup%20Request"
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
||||||
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
'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="rounded-xl border border-rose-500/20 p-4 sm:p-6">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<AlertTriangle className="h-5 w-5 text-rose-500" />
|
<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>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -686,8 +686,8 @@ export function AccountSettingsPage() {
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">Transfer Ownership</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">Transfer Ownership</p>
|
||||||
<p className="text-xs text-muted-foreground">Make another member the account owner</p>
|
<p className="text-xs text-[#848b9b]">Make another member the account owner</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -698,10 +698,10 @@ export function AccountSettingsPage() {
|
|||||||
Transfer
|
Transfer
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">Delete Account</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">Delete Account</p>
|
||||||
<p className="text-xs text-muted-foreground">Permanently delete your account and all data</p>
|
<p className="text-xs text-[#848b9b]">Permanently delete your account and all data</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -715,8 +715,8 @@ export function AccountSettingsPage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">Leave Account</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">Leave Account</p>
|
||||||
<p className="text-xs text-muted-foreground">Leave this account and create a personal one</p>
|
<p className="text-xs text-[#848b9b]">Leave this account and create a personal one</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
@@ -769,16 +769,16 @@ function UsageStat({
|
|||||||
const isAtLimit = !isUnlimited && current >= max
|
const isAtLimit = !isUnlimited && current >= max
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-border bg-card p-3">
|
<div className="rounded-md border border-[#1e2130] bg-[#14161d] p-3">
|
||||||
<p className="text-xs font-medium text-muted-foreground">{label}</p>
|
<p className="text-xs font-medium text-[#848b9b]">{label}</p>
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 text-lg font-semibold',
|
'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}
|
{current}
|
||||||
<span className="text-sm font-normal text-muted-foreground">
|
<span className="text-sm font-normal text-[#848b9b]">
|
||||||
{' '}/ {isUnlimited ? 'Unlimited' : max}
|
{' '}/ {isUnlimited ? 'Unlimited' : max}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -250,13 +250,13 @@ export default function AssistantChatPage() {
|
|||||||
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-4">
|
||||||
{messages.length === 0 && !loading && (
|
{messages.length === 0 && !loading && (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
<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">
|
<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-primary" />
|
<Sparkles size={28} className="text-[#22d3ee]" />
|
||||||
</div>
|
</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
|
AI Assistant
|
||||||
</h2>
|
</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,
|
Ask me anything about IT infrastructure, networking, Active Directory,
|
||||||
cloud platforms, or troubleshooting. I'll also suggest relevant flows from your team's library.
|
cloud platforms, or troubleshooting. I'll also suggest relevant flows from your team's library.
|
||||||
</p>
|
</p>
|
||||||
@@ -273,10 +273,10 @@ export default function AssistantChatPage() {
|
|||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center">
|
<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>
|
||||||
<div className="bg-white/[0.04] border border-brand-border rounded-2xl px-4 py-3">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -294,7 +294,7 @@ export default function AssistantChatPage() {
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="Ask about IT, networking, troubleshooting..."
|
placeholder="Ask about IT, networking, troubleshooting..."
|
||||||
rows={3}
|
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)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
@@ -302,7 +302,7 @@ export default function AssistantChatPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSend}
|
onClick={handleSend}
|
||||||
disabled={!input.trim() || loading}
|
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"
|
title="Send message"
|
||||||
>
|
>
|
||||||
<Send size={18} />
|
<Send size={18} />
|
||||||
@@ -311,7 +311,7 @@ export default function AssistantChatPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowConclude(true)}
|
onClick={() => setShowConclude(true)}
|
||||||
disabled={loading}
|
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)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
title="Conclude session"
|
title="Conclude session"
|
||||||
>
|
>
|
||||||
@@ -320,25 +320,25 @@ export default function AssistantChatPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[0.625rem] text-muted-foreground mt-1.5 px-1">Shift + Enter for a new line</p>
|
<p className="text-[0.625rem] text-[#848b9b] mt-1.5 px-1">Shift + Enter for a new line</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-center">
|
<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">
|
<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-primary" />
|
<Sparkles size={32} className="text-[#22d3ee]" />
|
||||||
</div>
|
</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
|
AI Assistant
|
||||||
</h2>
|
</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,
|
Your Senior Systems & Network Engineer. Ask anything about IT infrastructure,
|
||||||
or start a new chat to get personalized help with your team's flows.
|
or start a new chat to get personalized help with your team's flows.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleNewChat}
|
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
|
Start a Conversation
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default function BatchStatusPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<button
|
<button
|
||||||
onClick={() => treeId && navigate(`/flows/${treeId}/maintenance`)}
|
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" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
{tree?.name ?? 'Maintenance Flow'}
|
{tree?.name ?? 'Maintenance Flow'}
|
||||||
@@ -112,7 +112,7 @@ export default function BatchStatusPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => loadSessions(true)}
|
onClick={() => loadSessions(true)}
|
||||||
disabled={isRefreshing}
|
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')} />
|
<RefreshCw className={cn('h-3.5 w-3.5', isRefreshing && 'animate-spin')} />
|
||||||
Refresh
|
Refresh
|
||||||
@@ -125,9 +125,9 @@ export default function BatchStatusPage() {
|
|||||||
<Wrench className="h-5 w-5" />
|
<Wrench className="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<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 && (
|
{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' })}
|
{batchDate.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' })}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -136,18 +136,18 @@ export default function BatchStatusPage() {
|
|||||||
|
|
||||||
{/* Progress bar */}
|
{/* Progress bar */}
|
||||||
{total > 0 && (
|
{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]">
|
<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
|
{completed} of {total} complete
|
||||||
</span>
|
</span>
|
||||||
<span className={cn(
|
<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
|
allDone
|
||||||
? 'text-emerald-400 bg-emerald-500/10'
|
? 'text-emerald-400 bg-emerald-500/10'
|
||||||
: inProgress > 0
|
: inProgress > 0
|
||||||
? 'text-amber-400 bg-amber-500/10'
|
? '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'}
|
{allDone ? 'Complete' : inProgress > 0 ? `${inProgress} in progress` : 'Not started'}
|
||||||
</span>
|
</span>
|
||||||
@@ -175,7 +175,7 @@ export default function BatchStatusPage() {
|
|||||||
<span className="text-[0.8125rem] text-amber-400">{outcomeCounts.workaround} workaround</span>
|
<span className="text-[0.8125rem] text-amber-400">{outcomeCounts.workaround} workaround</span>
|
||||||
)}
|
)}
|
||||||
{outcomeCounts.unresolved && (
|
{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>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -189,13 +189,13 @@ export default function BatchStatusPage() {
|
|||||||
<p className="text-sm text-red-400 mb-3">{loadError}</p>
|
<p className="text-sm text-red-400 mb-3">{loadError}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => loadSessions(true)}
|
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
|
Try again
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : sessions.length === 0 ? (
|
) : 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.
|
No sessions found for this batch.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export function ChangePasswordPage() {
|
|||||||
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||||
</div>
|
</div>
|
||||||
</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
|
Change Password
|
||||||
</h1>
|
</h1>
|
||||||
{isForced && (
|
{isForced && (
|
||||||
@@ -77,7 +77,7 @@ export function ChangePasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
<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 && (
|
{error && (
|
||||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
||||||
{error}
|
{error}
|
||||||
@@ -85,7 +85,7 @@ export function ChangePasswordPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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
|
Current Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -95,8 +95,8 @@ export function ChangePasswordPage() {
|
|||||||
value={currentPassword}
|
value={currentPassword}
|
||||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -104,7 +104,7 @@ export function ChangePasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
New Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -114,20 +114,20 @@ export function ChangePasswordPage() {
|
|||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="At least 10 characters"
|
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.
|
Must include uppercase, lowercase, and a digit.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Confirm New Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -137,8 +137,8 @@ export function ChangePasswordPage() {
|
|||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -150,7 +150,7 @@ export function ChangePasswordPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'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',
|
'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',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default function EscalationQueuePage() {
|
|||||||
<AlertTriangle size={16} className="text-amber-400" />
|
<AlertTriangle size={16} className="text-amber-400" />
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="font-heading text-xl font-bold text-foreground">Escalation Queue</h1>
|
<h1 className="font-heading text-xl font-bold text-[#e2e5eb]">Escalation Queue</h1>
|
||||||
<p className="text-sm text-muted-foreground">Sessions from your team waiting for pickup</p>
|
<p className="text-sm text-[#848b9b]">Sessions from your team waiting for pickup</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -137,34 +137,34 @@ export function FeedbackPage() {
|
|||||||
{/* Page header */}
|
{/* Page header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<MessageSquareText className="h-8 w-8 text-muted-foreground" />
|
<MessageSquareText className="h-8 w-8 text-[#848b9b]" />
|
||||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Send Feedback</h1>
|
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Send Feedback</h1>
|
||||||
</div>
|
</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.
|
Help us improve ResolutionFlow. Report bugs, request features, or share your thoughts.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-2xl">
|
||||||
{submitted ? (
|
{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" />
|
<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>
|
<h2 className="text-xl font-semibold text-[#e2e5eb] mb-2">Thank you for your feedback!</h2>
|
||||||
<p className="text-muted-foreground mb-6">
|
<p className="text-[#848b9b] mb-6">
|
||||||
We've received your submission and will review it shortly. Check your email for a confirmation.
|
We've received your submission and will review it shortly. Check your email for a confirmation.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={handleNewFeedback}
|
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
|
Send More Feedback
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 */}
|
{/* Email */}
|
||||||
<div>
|
<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
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -174,14 +174,14 @@ export function FeedbackPage() {
|
|||||||
onChange={e => setEmail(e.target.value)}
|
onChange={e => setEmail(e.target.value)}
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com"
|
||||||
required
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Feedback Type — custom selector with descriptions */}
|
{/* Feedback Type — custom selector with descriptions */}
|
||||||
<div>
|
<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
|
Feedback Type
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -196,8 +196,8 @@ export function FeedbackPage() {
|
|||||||
onClick={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
onClick={() => setTypeDropdownOpen(!typeDropdownOpen)}
|
||||||
onKeyDown={handleDropdownKeyDown}
|
onKeyDown={handleDropdownKeyDown}
|
||||||
className={cn(
|
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",
|
"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-foreground" : "text-muted-foreground"
|
feedbackType ? "text-[#e2e5eb]" : "text-[#848b9b]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>{selectedType?.value ?? 'Select a type...'}</span>
|
<span>{selectedType?.value ?? 'Select a type...'}</span>
|
||||||
@@ -208,7 +208,7 @@ export function FeedbackPage() {
|
|||||||
ref={listboxRef}
|
ref={listboxRef}
|
||||||
id="feedback-type-listbox"
|
id="feedback-type-listbox"
|
||||||
role="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) => (
|
{FEEDBACK_TYPES.map((type, index) => (
|
||||||
<button
|
<button
|
||||||
@@ -225,8 +225,8 @@ export function FeedbackPage() {
|
|||||||
highlightedIndex === index && feedbackType !== type.value && "bg-accent/50"
|
highlightedIndex === index && feedbackType !== type.value && "bg-accent/50"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="text-sm font-medium text-foreground">{type.value}</div>
|
<div className="text-sm font-medium text-[#e2e5eb]">{type.value}</div>
|
||||||
<div className="text-xs text-muted-foreground mt-0.5">{type.description}</div>
|
<div className="text-xs text-[#848b9b] mt-0.5">{type.description}</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -236,7 +236,7 @@ export function FeedbackPage() {
|
|||||||
|
|
||||||
{/* Message */}
|
{/* Message */}
|
||||||
<div>
|
<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
|
Your Feedback
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -247,9 +247,9 @@ export function FeedbackPage() {
|
|||||||
required
|
required
|
||||||
minLength={10}
|
minLength={10}
|
||||||
rows={6}
|
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
|
{message.trim().length < 10
|
||||||
? `Minimum 10 characters (${message.trim().length}/10)`
|
? `Minimum 10 characters (${message.trim().length}/10)`
|
||||||
: `${message.trim().length} characters`}
|
: `${message.trim().length} characters`}
|
||||||
@@ -262,10 +262,10 @@ export function FeedbackPage() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
disabled={!canSubmit || isSubmitting}
|
disabled={!canSubmit || isSubmitting}
|
||||||
className={cn(
|
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
|
canSubmit && !isSubmitting
|
||||||
? "bg-gradient-brand hover:opacity-90"
|
? "bg-[#22d3ee] hover:brightness-110"
|
||||||
: "bg-gradient-brand opacity-50 cursor-not-allowed"
|
: "bg-[#22d3ee] opacity-50 cursor-not-allowed"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Send size={16} />
|
<Send size={16} />
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ export default function FlowAssistPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="mb-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">
|
<span className="inline-flex items-center gap-2">
|
||||||
<WandSparkles size={24} style={{ color: '#f472b6' }} />
|
<WandSparkles size={24} style={{ color: '#f472b6' }} />
|
||||||
Flow Assist
|
Flow Assist
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</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.
|
Build flows from natural language — describe what you need and Flow Assist will generate the decision tree or procedural steps.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="glass-card-static p-8 text-center">
|
<div className="card-flat p-8 text-center">
|
||||||
<WandSparkles size={40} className="mx-auto mb-4 text-muted-foreground" />
|
<WandSparkles size={40} className="mx-auto mb-4 text-[#848b9b]" />
|
||||||
<h2 className="font-heading text-lg font-semibold text-foreground mb-2">
|
<h2 className="font-heading text-lg font-semibold text-[#e2e5eb] mb-2">
|
||||||
Coming Soon
|
Coming Soon
|
||||||
</h2>
|
</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.
|
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.
|
In the meantime, use the AI panel in the Flow Editor to generate flows.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[60vh]">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[60vh]">
|
<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>
|
</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 flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span title="FlowPilot Analytics">
|
<span title="FlowPilot Analytics">
|
||||||
<BarChart3 size={24} className="text-foreground" />
|
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||||
</span>
|
</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>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
<Link
|
||||||
to="/analytics"
|
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
|
Team Analytics
|
||||||
</Link>
|
</Link>
|
||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value)}
|
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) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
@@ -189,7 +189,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab bar */}
|
{/* 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) => (
|
{TABS.map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
@@ -197,8 +197,8 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-2 text-sm transition-colors',
|
'px-4 py-2 text-sm transition-colors',
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? 'border-b-2 border-primary text-foreground font-medium'
|
? 'border-b-2 border-primary text-[#e2e5eb] font-medium'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -246,8 +246,8 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
{/* Second row — Charts */}
|
{/* Second row — Charts */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{/* MTTR Trend */}
|
{/* MTTR Trend */}
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
MTTR Trend
|
MTTR Trend
|
||||||
</h3>
|
</h3>
|
||||||
{dashboard.mttr_trend.length > 0 ? (
|
{dashboard.mttr_trend.length > 0 ? (
|
||||||
@@ -284,15 +284,15 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</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
|
No data for this period
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Domain Breakdown */}
|
{/* Domain Breakdown */}
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Sessions by Domain
|
Sessions by Domain
|
||||||
</h3>
|
</h3>
|
||||||
{dashboard.sessions_by_domain.length > 0 ? (
|
{dashboard.sessions_by_domain.length > 0 ? (
|
||||||
@@ -315,7 +315,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</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
|
No domain data
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -325,8 +325,8 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
{/* Third row — Confidence + Knowledge Coverage */}
|
{/* Third row — Confidence + Knowledge Coverage */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
{/* Confidence Breakdown */}
|
{/* Confidence Breakdown */}
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Confidence Tiers
|
Confidence Tiers
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -355,32 +355,32 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Knowledge Coverage */}
|
{/* Knowledge Coverage */}
|
||||||
<div className="glass-card-static p-3 sm:p-5">
|
<div className="card-flat p-3 sm:p-5">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">
|
<h3 className="font-heading text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Knowledge Coverage
|
Knowledge Coverage
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||||
<div className="rounded-lg bg-card/50 p-3">
|
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||||
<p className="text-xs text-muted-foreground">Total Flows</p>
|
<p className="text-xs text-[#848b9b]">Total Flows</p>
|
||||||
<p className="text-lg font-semibold text-foreground">{dashboard.knowledge_coverage.total_flows}</p>
|
<p className="text-lg font-semibold text-[#e2e5eb]">{dashboard.knowledge_coverage.total_flows}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-card/50 p-3">
|
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||||
<p className="text-xs text-muted-foreground">AI-Generated</p>
|
<p className="text-xs text-[#848b9b]">AI-Generated</p>
|
||||||
<p className="text-lg font-semibold text-gradient-brand">{dashboard.knowledge_coverage.ai_generated_flows}</p>
|
<p className="text-lg font-semibold text-[#67e8f9]">{dashboard.knowledge_coverage.ai_generated_flows}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-card/50 p-3">
|
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||||
<p className="text-xs text-muted-foreground">Pending Review</p>
|
<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>
|
<p className="text-lg font-semibold text-amber-400">{dashboard.knowledge_coverage.total_proposals_pending}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-card/50 p-3">
|
<div className="rounded-lg bg-[#14161d]/50 p-3">
|
||||||
<p className="text-xs text-muted-foreground">Approved This Period</p>
|
<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>
|
<p className="text-lg font-semibold text-emerald-400">{dashboard.knowledge_coverage.proposals_approved_this_period}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{dashboard.knowledge_coverage.total_proposals_pending > 0 && (
|
{dashboard.knowledge_coverage.total_proposals_pending > 0 && (
|
||||||
<Link
|
<Link
|
||||||
to="/review-queue"
|
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} />
|
<ArrowUpRight size={12} />
|
||||||
Review {dashboard.knowledge_coverage.total_proposals_pending} pending proposals
|
Review {dashboard.knowledge_coverage.total_proposals_pending} pending proposals
|
||||||
@@ -394,7 +394,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2 px-1">
|
<div className="flex items-center gap-2 px-1">
|
||||||
<AlertTriangle size={14} className="text-amber-400" />
|
<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})
|
Knowledge Gaps ({gaps.gaps.length})
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -405,11 +405,11 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : gaps && (
|
) : 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" />
|
<CheckCircle2 size={20} className="text-emerald-400 shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-foreground">No knowledge gaps detected</p>
|
<p className="text-sm text-[#e2e5eb]">No knowledge gaps detected</p>
|
||||||
<p className="text-xs text-muted-foreground">Your flow coverage looks healthy for this period.</p>
|
<p className="text-xs text-[#848b9b]">Your flow coverage looks healthy for this period.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -422,7 +422,7 @@ export default function FlowPilotAnalyticsPage() {
|
|||||||
<ErrorRetry label="coverage data" onRetry={() => { setCoverageError(false); coveragePeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
<ErrorRetry label="coverage data" onRetry={() => { setCoverageError(false); coveragePeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||||
) : coverageLoading ? (
|
) : coverageLoading ? (
|
||||||
<div className="flex items-center justify-center min-h-[200px]">
|
<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>
|
</div>
|
||||||
) : coverageData ? (
|
) : coverageData ? (
|
||||||
<CoverageHeatmap data={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) }} />
|
<ErrorRetry label="flow quality data" onRetry={() => { setQualityError(false); qualityPeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||||
) : qualityLoading ? (
|
) : qualityLoading ? (
|
||||||
<div className="flex items-center justify-center min-h-[200px]">
|
<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>
|
</div>
|
||||||
) : qualityData ? (
|
) : qualityData ? (
|
||||||
<FlowQualityTable data={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) }} />
|
<ErrorRetry label="PSA metrics" onRetry={() => { setPsaError(false); psaPeriodRef.current = null; setRetryKey((k) => k + 1) }} />
|
||||||
) : psaLoading ? (
|
) : psaLoading ? (
|
||||||
<div className="flex items-center justify-center min-h-[200px]">
|
<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>
|
</div>
|
||||||
) : psaData ? (
|
) : psaData ? (
|
||||||
<PsaMetricsPanel data={psaData} />
|
<PsaMetricsPanel data={psaData} />
|
||||||
@@ -475,7 +475,7 @@ function MetricCard({
|
|||||||
iconColor: string
|
iconColor: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
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">
|
<div className="flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg"
|
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 }} />
|
<Icon size={16} style={{ color: iconColor }} />
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.5625rem] uppercase tracking-wider text-[#5a6170]">{label}</p>
|
<p className="font-sans text-xs text-[0.5625rem] uppercase tracking-wider text-[#5a6170]">{label}</p>
|
||||||
<p className="text-lg font-semibold text-foreground">{value}</p>
|
<p className="text-lg font-semibold text-[#e2e5eb]">{value}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -509,12 +509,12 @@ function ConfidenceTierRow({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between text-xs">
|
<div className="flex items-center justify-between text-xs">
|
||||||
<span className="text-foreground font-medium">{label}</span>
|
<span className="text-[#e2e5eb] font-medium">{label}</span>
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
{count} sessions · {rate.toFixed(1)}% resolved
|
{count} sessions · {rate.toFixed(1)}% resolved
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 rounded-full bg-card/80 overflow-hidden">
|
<div className="h-2 rounded-full bg-[#14161d]/80 overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-full transition-all"
|
className="h-full rounded-full transition-all"
|
||||||
style={{ width: `${pct}%`, background: color }}
|
style={{ width: `${pct}%`, background: color }}
|
||||||
@@ -526,12 +526,12 @@ function ConfidenceTierRow({
|
|||||||
|
|
||||||
function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void }) {
|
function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void }) {
|
||||||
return (
|
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" />
|
<AlertTriangle size={24} className="mb-2 text-amber-400" />
|
||||||
<p className="text-sm mb-3">Failed to load {label}</p>
|
<p className="text-sm mb-3">Failed to load {label}</p>
|
||||||
<button
|
<button
|
||||||
onClick={onRetry}
|
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} />
|
<RotateCcw size={12} />
|
||||||
Retry
|
Retry
|
||||||
@@ -543,17 +543,17 @@ function ErrorRetry({ label, onRetry }: { label: string; onRetry: () => void })
|
|||||||
function GapCard({ gap }: { gap: KnowledgeGap }) {
|
function GapCard({ gap }: { gap: KnowledgeGap }) {
|
||||||
const severityStyle = SEVERITY_STYLES[gap.severity as keyof typeof SEVERITY_STYLES] ?? SEVERITY_STYLES.low
|
const severityStyle = SEVERITY_STYLES[gap.severity as keyof typeof SEVERITY_STYLES] ?? SEVERITY_STYLES.low
|
||||||
return (
|
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">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<p className="text-sm font-semibold text-foreground">{gap.title}</p>
|
<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-label text-[0.5625rem] uppercase tracking-wider ${severityStyle}`}>
|
<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}
|
{gap.severity}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<div className="flex items-start gap-1.5 pt-1">
|
||||||
<Lightbulb size={12} className="text-primary shrink-0 mt-0.5" />
|
<Lightbulb size={12} className="text-[#22d3ee] shrink-0 mt-0.5" />
|
||||||
<p className="text-xs text-primary">{gap.suggested_action}</p>
|
<p className="text-xs text-[#22d3ee]">{gap.suggested_action}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ export default function FlowPilotSessionPage() {
|
|||||||
if (fp.error && !fp.session) {
|
if (fp.error && !fp.session) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[50vh]">
|
<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>
|
<p className="text-sm text-rose-400">{fp.error}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.reload()}
|
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
|
Try again
|
||||||
</button>
|
</button>
|
||||||
@@ -94,7 +94,7 @@ export default function FlowPilotSessionPage() {
|
|||||||
if (fp.isLoading && !fp.session) {
|
if (fp.isLoading && !fp.session) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[50vh]">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -134,11 +134,11 @@ export default function FlowPilotSessionPage() {
|
|||||||
<Sparkles size={14} className="text-amber-400" />
|
<Sparkles size={14} className="text-amber-400" />
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1 min-w-0">
|
<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'}
|
Escalation Pickup — {fp.session.problem_summary || 'FlowPilot Session'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</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
|
Awaiting pickup
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,21 +163,21 @@ export default function FlowPilotSessionPage() {
|
|||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Navigation guard modal */}
|
{/* Navigation guard modal */}
|
||||||
{blocker.state === 'blocked' && (
|
{blocker.state === 'blocked' && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
<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">
|
<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">
|
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-amber-500/10">
|
||||||
<AlertTriangle size={18} className="text-amber-400" />
|
<AlertTriangle size={18} className="text-amber-400" />
|
||||||
</span>
|
</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>
|
</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.
|
You have an active troubleshooting session. If you leave, your session will be paused and you can resume it later.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => blocker.reset()}
|
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
|
Stay in Session
|
||||||
</button>
|
</button>
|
||||||
@@ -186,7 +186,7 @@ export default function FlowPilotSessionPage() {
|
|||||||
fp.pauseSession()
|
fp.pauseSession()
|
||||||
blocker.proceed()
|
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
|
Pause & Leave
|
||||||
</button>
|
</button>
|
||||||
@@ -200,15 +200,15 @@ export default function FlowPilotSessionPage() {
|
|||||||
className="flex items-center gap-3 border-b px-5 py-3 shrink-0"
|
className="flex items-center gap-3 border-b px-5 py-3 shrink-0"
|
||||||
style={{ borderColor: 'var(--glass-border)' }}
|
style={{ borderColor: 'var(--glass-border)' }}
|
||||||
>
|
>
|
||||||
<span className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary/10">
|
<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-primary" />
|
<Sparkles size={14} className="text-[#22d3ee]" />
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1 min-w-0">
|
<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'}
|
{fp.session.problem_summary || 'FlowPilot Session'}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</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}
|
{fp.session.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,26 +28,26 @@ export function ForgotPasswordPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Forgot Password" description="Reset your ResolutionFlow password" />
|
<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="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="relative w-full max-w-md space-y-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mb-4 flex justify-center sm:mb-6">
|
<div className="mb-4 flex justify-center sm:mb-6">
|
||||||
<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" />
|
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||||
</div>
|
</div>
|
||||||
</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
|
Reset Password
|
||||||
</h1>
|
</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.
|
Enter your email and we'll send you a link to reset your password.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{submitted ? (
|
{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">
|
<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.
|
If an account with that email exists, we've sent a password reset link.
|
||||||
Check your inbox and follow the instructions.
|
Check your inbox and follow the instructions.
|
||||||
@@ -55,7 +55,7 @@ export function ForgotPasswordPage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
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
|
Back to sign in
|
||||||
</Link>
|
</Link>
|
||||||
@@ -63,9 +63,9 @@ export function ForgotPasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
<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>
|
<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
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -76,8 +76,8 @@ export function ForgotPasswordPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -90,7 +90,7 @@ export function ForgotPasswordPage() {
|
|||||||
disabled={isLoading || !email}
|
disabled={isLoading || !email}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'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',
|
'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',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
@@ -102,7 +102,7 @@ export function ForgotPasswordPage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
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
|
Back to sign in
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ export default function GuideDetailPage() {
|
|||||||
if (!guide) {
|
if (!guide) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-full text-center p-6 overflow-y-auto">
|
<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>
|
<h2 className="text-lg font-heading font-semibold text-[#e2e5eb] 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>
|
<p className="text-sm text-[#848b9b] mb-4">The guide you're looking for doesn't exist.</p>
|
||||||
<Link
|
<Link
|
||||||
to="/guides"
|
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
|
Back to Guides
|
||||||
</Link>
|
</Link>
|
||||||
@@ -27,37 +27,37 @@ export default function GuideDetailPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-y-auto h-full p-6 max-w-3xl mx-auto">
|
<div className="overflow-y-auto h-full p-6 max-w-3xl mx-auto">
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<nav className="flex items-center gap-1.5 text-xs text-muted-foreground mb-6">
|
<nav className="flex items-center gap-1.5 text-xs text-[#848b9b] mb-6">
|
||||||
<Link to="/guides" className="hover:text-primary transition-colors">
|
<Link to="/guides" className="hover:text-[#22d3ee] transition-colors">
|
||||||
User Guides
|
User Guides
|
||||||
</Link>
|
</Link>
|
||||||
<ChevronRight size={12} />
|
<ChevronRight size={12} />
|
||||||
<span className="text-foreground">{guide.title}</span>
|
<span className="text-[#e2e5eb]">{guide.title}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Header */}
|
{/* 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 items-center gap-3 mb-3">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary/10">
|
<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-primary" />
|
<Icon size={20} className="text-[#22d3ee]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-heading font-bold text-foreground">{guide.title}</h1>
|
<h1 className="text-xl font-heading font-bold text-[#e2e5eb]">{guide.title}</h1>
|
||||||
<p className="text-sm text-muted-foreground">{guide.summary}</p>
|
<p className="text-sm text-[#848b9b]">{guide.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4 mt-4 pt-4 border-t" style={{ borderColor: 'var(--glass-border)' }}>
|
<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'}
|
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
|
||||||
</span>
|
</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
|
{guide.sections.reduce((acc, s) => acc + s.steps.length, 0)} steps
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sections */}
|
{/* Sections */}
|
||||||
<div className="glass-card-static rounded-2xl p-6">
|
<div className="card-flat rounded-2xl p-6">
|
||||||
{guide.sections.map((section, i) => (
|
{guide.sections.map((section, i) => (
|
||||||
<GuideSection key={i} section={section} index={i} />
|
<GuideSection key={i} section={section} index={i} />
|
||||||
))}
|
))}
|
||||||
@@ -67,7 +67,7 @@ export default function GuideDetailPage() {
|
|||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Link
|
<Link
|
||||||
to="/guides"
|
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} />
|
<ArrowLeft size={14} />
|
||||||
Back to all guides
|
Back to all guides
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ export default function GuidesHubPage() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<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">
|
<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-primary" />
|
<BookOpen size={20} className="text-[#22d3ee]" />
|
||||||
</div>
|
</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>
|
</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.
|
Learn how to use ResolutionFlow with step-by-step instructions for every feature.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -180,8 +180,8 @@ export default function KBAcceleratorPage() {
|
|||||||
<div className="flex flex-col h-full min-h-0 p-6">
|
<div className="flex flex-col h-full min-h-0 p-6">
|
||||||
{/* Page title */}
|
{/* Page title */}
|
||||||
<div className="shrink-0 flex items-center gap-3 mb-6">
|
<div className="shrink-0 flex items-center gap-3 mb-6">
|
||||||
<Sparkles size={24} className="text-primary" />
|
<Sparkles size={24} className="text-[#22d3ee]" />
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground">KB Accelerator</h1>
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">KB Accelerator</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phases */}
|
{/* Phases */}
|
||||||
@@ -196,12 +196,12 @@ export default function KBAcceleratorPage() {
|
|||||||
|
|
||||||
{phase === 'processing' && (
|
{phase === 'processing' && (
|
||||||
<div className="flex-1 flex flex-col items-center justify-center gap-4">
|
<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">
|
<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...
|
Converting your KB article...
|
||||||
</p>
|
</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.
|
AI is analyzing your content and generating an interactive flow.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Sign In" description="Sign in to your ResolutionFlow account" />
|
<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 */}
|
{/* Atmosphere orbs */}
|
||||||
<div
|
<div
|
||||||
className="pointer-events-none fixed z-0"
|
className="pointer-events-none fixed z-0"
|
||||||
@@ -75,27 +75,27 @@ export function LoginPage() {
|
|||||||
<div className="mb-4 flex justify-center sm:mb-6">
|
<div className="mb-4 flex justify-center sm:mb-6">
|
||||||
<BrandLogo size="lg" />
|
<BrandLogo size="lg" />
|
||||||
</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">
|
||||||
<span>Resolution</span><span className="text-gradient-brand">Flow</span>
|
<span>Resolution</span><span className="text-[#67e8f9]">Flow</span>
|
||||||
</h1>
|
</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
|
AI-Powered Troubleshooting for MSPs
|
||||||
</p>
|
</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
|
Sign in to your account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6" data-testid="login-form">
|
<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) && (
|
{(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}
|
{localError || error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<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
|
Email address
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -107,8 +107,8 @@ export function LoginPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
'block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2.5',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -117,7 +117,7 @@ export function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -128,8 +128,8 @@ export function LoginPage() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-[10px] border border-border bg-card px-3 py-2.5',
|
'block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2.5',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -138,7 +138,7 @@ export function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-right">
|
<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?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,8 +148,8 @@ export function LoginPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
data-testid="login-submit"
|
data-testid="login-submit"
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-[10px] px-4 py-2.5 text-sm font-semibold',
|
'w-full rounded-lg 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]',
|
'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',
|
'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',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
@@ -159,9 +159,9 @@ export function LoginPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-center text-sm text-muted-foreground">
|
<p className="text-center text-sm text-[#848b9b]">
|
||||||
Don't have an account?{' '}
|
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
|
Register
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
action={(
|
action={(
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/trees?type=maintenance')}
|
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
|
Back to Maintenance Flows
|
||||||
</button>
|
</button>
|
||||||
@@ -148,7 +148,7 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/flows/${id}/edit`)}
|
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" />
|
<Settings className="h-3.5 w-3.5" />
|
||||||
Edit Flow
|
Edit Flow
|
||||||
@@ -156,7 +156,7 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleRun}
|
onClick={handleRun}
|
||||||
disabled={isRunning}
|
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
|
{isRunning
|
||||||
? <Spinner size="sm" className="h-3.5 w-3.5 border-white border-t-transparent" />
|
? <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>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowBatchModal(true)}
|
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
|
Batch Launch
|
||||||
</button>
|
</button>
|
||||||
@@ -174,51 +174,51 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Schedule Panel */}
|
{/* 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">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
<Calendar className="h-4 w-4 text-[#848b9b]" />
|
||||||
<h2 className="font-semibold text-foreground">Schedule</h2>
|
<h2 className="font-semibold text-[#e2e5eb]">Schedule</h2>
|
||||||
</div>
|
</div>
|
||||||
{schedule ? (
|
{schedule ? (
|
||||||
<div className="space-y-2 text-[0.875rem]">
|
<div className="space-y-2 text-[0.875rem]">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<span className={cn(
|
<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
|
schedule.is_active
|
||||||
? "bg-emerald-500/10 text-emerald-400"
|
? "bg-emerald-500/10 text-emerald-400"
|
||||||
: "bg-muted text-muted-foreground"
|
: "bg-muted text-[#848b9b]"
|
||||||
)}>
|
)}>
|
||||||
{schedule.is_active
|
{schedule.is_active
|
||||||
? <CheckCircle className="h-3 w-3" />
|
? <CheckCircle className="h-3 w-3" />
|
||||||
: <AlertCircle className="h-3 w-3" />}
|
: <AlertCircle className="h-3 w-3" />}
|
||||||
{schedule.is_active ? 'Active' : 'Paused'}
|
{schedule.is_active ? 'Active' : 'Paused'}
|
||||||
</span>
|
</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}
|
{schedule.cron_expression}
|
||||||
</code>
|
</code>
|
||||||
<span className="text-muted-foreground">({schedule.timezone})</span>
|
<span className="text-[#848b9b]">({schedule.timezone})</span>
|
||||||
</div>
|
</div>
|
||||||
{schedule.next_run_at && (
|
{schedule.next_run_at && (
|
||||||
<p className="text-muted-foreground">
|
<p className="text-[#848b9b]">
|
||||||
Next run: {new Date(schedule.next_run_at).toLocaleString()}
|
Next run: {new Date(schedule.next_run_at).toLocaleString()}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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.
|
No schedule configured. Sessions can still be launched manually via Batch Launch.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Run History */}
|
{/* 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">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
<Clock className="h-4 w-4 text-[#848b9b]" />
|
||||||
<h2 className="font-semibold text-foreground">Run History</h2>
|
<h2 className="font-semibold text-[#e2e5eb]">Run History</h2>
|
||||||
</div>
|
</div>
|
||||||
{batches.length === 0 ? (
|
{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">
|
<div className="space-y-2">
|
||||||
{batches.map(([batchKey, batchSessions]) => {
|
{batches.map(([batchKey, batchSessions]) => {
|
||||||
@@ -253,25 +253,25 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
<button
|
<button
|
||||||
key={batchKey}
|
key={batchKey}
|
||||||
onClick={handleRowClick}
|
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>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<span className="inline-block h-2 w-2 rounded-full bg-amber-400 animate-pulse" />
|
<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' : ''}`}
|
{isSingleRun ? 'Manual run' : `${total} target${total !== 1 ? 's' : ''}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 mt-0.5">
|
<div className="flex items-center gap-2 mt-0.5">
|
||||||
{date && (
|
{date && (
|
||||||
<p className="text-[0.8125rem] text-muted-foreground">
|
<p className="text-[0.8125rem] text-[#848b9b]">
|
||||||
{new Date(date).toLocaleDateString()}
|
{new Date(date).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{outcomeParts.length > 0 && (
|
{outcomeParts.length > 0 && (
|
||||||
<p className="text-[0.8125rem] text-muted-foreground">
|
<p className="text-[0.8125rem] text-[#848b9b]">
|
||||||
· {outcomeParts.join(' · ')}
|
· {outcomeParts.join(' · ')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -290,13 +290,13 @@ export default function MaintenanceFlowDetailPage() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{extraDots > 0 && (
|
{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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"font-label text-[0.75rem] uppercase tracking-wide",
|
"font-sans text-xs text-[0.75rem] uppercase tracking-wide",
|
||||||
isActive ? "text-amber-400" : completed === total ? "text-emerald-400" : "text-muted-foreground"
|
isActive ? "text-amber-400" : completed === total ? "text-emerald-400" : "text-[#848b9b]"
|
||||||
)}>
|
)}>
|
||||||
{isActive ? 'In Progress' : `${completed}/${total}`}
|
{isActive ? 'In Progress' : `${completed}/${total}`}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function MyAnalyticsPage() {
|
|||||||
action={
|
action={
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
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
|
Run Your First Session
|
||||||
</Link>
|
</Link>
|
||||||
@@ -96,16 +96,16 @@ export default function MyAnalyticsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span title="My Analytics">
|
<span title="My Analytics">
|
||||||
<BarChart3 size={24} className="text-foreground" />
|
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||||
</span>
|
</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>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{(isAccountOwner || isSuperAdmin) && (
|
{(isAccountOwner || isSuperAdmin) && (
|
||||||
<Link
|
<Link
|
||||||
to="/analytics"
|
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
|
Team Analytics
|
||||||
</Link>
|
</Link>
|
||||||
@@ -113,7 +113,7 @@ export default function MyAnalyticsPage() {
|
|||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-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) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
@@ -149,8 +149,8 @@ export default function MyAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Area Chart — Sessions over Time */}
|
{/* Area Chart — Sessions over Time */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
My Sessions Over Time
|
My Sessions Over Time
|
||||||
</h2>
|
</h2>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
@@ -236,7 +236,7 @@ export default function MyAnalyticsPage() {
|
|||||||
className="h-2.5 w-2.5 rounded-full"
|
className="h-2.5 w-2.5 rounded-full"
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-muted-foreground capitalize">
|
<span className="text-xs text-[#848b9b] capitalize">
|
||||||
{key}
|
{key}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,27 +247,27 @@ export default function MyAnalyticsPage() {
|
|||||||
{/* Two-Column: Top Flows & Outcome Distribution */}
|
{/* Two-Column: Top Flows & Outcome Distribution */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* My Top Flows */}
|
{/* My Top Flows */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
My Top Flows
|
My Top Flows
|
||||||
</h2>
|
</h2>
|
||||||
{top_flows.length === 0 ? (
|
{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">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="text-left py-2 text-foreground font-medium">
|
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Sessions
|
Sessions
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Completion
|
Completion
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Median
|
Median
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -276,18 +276,18 @@ export default function MyAnalyticsPage() {
|
|||||||
{top_flows.map((flow) => (
|
{top_flows.map((flow) => (
|
||||||
<tr
|
<tr
|
||||||
key={flow.tree_id}
|
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}
|
{flow.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{flow.sessions}
|
{flow.sessions}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{(flow.completion_rate * 100).toFixed(1)}%
|
{(flow.completion_rate * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{flow.median_duration_minutes} min
|
{flow.median_duration_minutes} min
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -299,12 +299,12 @@ export default function MyAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Outcome Distribution */}
|
{/* Outcome Distribution */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Outcome Distribution
|
Outcome Distribution
|
||||||
</h2>
|
</h2>
|
||||||
{summary.total_sessions === 0 ? (
|
{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">
|
<div className="space-y-3">
|
||||||
{(
|
{(
|
||||||
@@ -327,11 +327,11 @@ export default function MyAnalyticsPage() {
|
|||||||
OUTCOME_COLORS[outcome] ?? '#94a3b8',
|
OUTCOME_COLORS[outcome] ?? '#94a3b8',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-muted-foreground capitalize">
|
<span className="text-sm text-[#848b9b] capitalize">
|
||||||
{outcome}
|
{outcome}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm text-foreground font-medium">
|
<span className="text-sm text-[#e2e5eb] font-medium">
|
||||||
{count} ({pct.toFixed(1)}%)
|
{count} ({pct.toFixed(1)}%)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -366,12 +366,12 @@ function StatCard({
|
|||||||
value: string
|
value: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
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">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Icon size={16} className="text-muted-foreground" />
|
<Icon size={16} className="text-[#848b9b]" />
|
||||||
<span className="text-sm text-muted-foreground">{label}</span>
|
<span className="text-sm text-[#848b9b]">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-3xl font-bold text-foreground">{value}</p>
|
<p className="text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function MySharesPage() {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div className="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">
|
<div className="text-center">
|
||||||
<p className="text-red-400 text-sm mb-4">{error}</p>
|
<p className="text-red-400 text-sm mb-4">{error}</p>
|
||||||
<Button onClick={fetchShares}>
|
<Button onClick={fetchShares}>
|
||||||
@@ -129,7 +129,7 @@ export default function MySharesPage() {
|
|||||||
{/* Back link */}
|
{/* Back link */}
|
||||||
<Link
|
<Link
|
||||||
to="/sessions"
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
Back to sessions
|
Back to sessions
|
||||||
@@ -137,8 +137,8 @@ export default function MySharesPage() {
|
|||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground">My Shared Sessions</h1>
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">My Shared Sessions</h1>
|
||||||
<p className="text-muted-foreground mt-1">Manage your session share links</p>
|
<p className="text-[#848b9b] mt-1">Manage your session share links</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Empty state */}
|
{/* Empty state */}
|
||||||
@@ -161,10 +161,10 @@ export default function MySharesPage() {
|
|||||||
const isCopied = copiedId === share.id
|
const isCopied = copiedId === share.id
|
||||||
|
|
||||||
return (
|
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 */}
|
{/* Top row: badge + name */}
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<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' ? (
|
{share.visibility === 'public' ? (
|
||||||
<Globe className="h-3 w-3" />
|
<Globe className="h-3 w-3" />
|
||||||
) : (
|
) : (
|
||||||
@@ -172,18 +172,18 @@ export default function MySharesPage() {
|
|||||||
)}
|
)}
|
||||||
{share.visibility === 'public' ? 'Public' : 'Account Only'}
|
{share.visibility === 'public' ? 'Public' : 'Account Only'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-[#e2e5eb]">
|
||||||
{share.share_name || 'Untitled share'}
|
{share.share_name || 'Untitled share'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Session info */}
|
{/* 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)}...
|
Session ID: {share.session_id.slice(0, 8)}...
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Meta row */}
|
{/* 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>Created {formatRelativeTime(share.created_at)}</span>
|
||||||
<span className="hidden sm:inline">·</span>
|
<span className="hidden sm:inline">·</span>
|
||||||
<span>
|
<span>
|
||||||
@@ -214,7 +214,7 @@ export default function MySharesPage() {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/sessions/${share.session_id}`}
|
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" />
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
View Session
|
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="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 className="mb-6 flex items-center justify-between sm:mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">My Flows</h1>
|
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">My Flows</h1>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
Your forked and custom flows
|
Your forked and custom flows
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,38 +140,38 @@ export function MyTreesPage() {
|
|||||||
{showCreateMenu && (
|
{showCreateMenu && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={() => setShowCreateMenu(false)} />
|
<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
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
onClick={() => setShowCreateMenu(false)}
|
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>
|
||||||
<div className="font-medium">Troubleshooting Tree</div>
|
<div className="font-medium">Troubleshooting Tree</div>
|
||||||
<div className="text-xs text-muted-foreground">Branching decision flow</div>
|
<div className="text-xs text-[#848b9b]">Branching decision flow</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/flows/new"
|
to="/flows/new"
|
||||||
onClick={() => setShowCreateMenu(false)}
|
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>
|
||||||
<div className="font-medium">Procedural Flow</div>
|
<div className="font-medium">Procedural Flow</div>
|
||||||
<div className="text-xs text-muted-foreground">Step-by-step procedure</div>
|
<div className="text-xs text-[#848b9b]">Step-by-step procedure</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/flows/new?type=maintenance"
|
to="/flows/new?type=maintenance"
|
||||||
onClick={() => setShowCreateMenu(false)}
|
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" />
|
<Wrench className="h-4 w-4 text-amber-400" />
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">Maintenance Flow</div>
|
<div className="font-medium">Maintenance Flow</div>
|
||||||
<div className="text-xs text-muted-foreground">Scheduled multi-target tasks</div>
|
<div className="text-xs text-[#848b9b]">Scheduled multi-target tasks</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,18 +187,18 @@ export function MyTreesPage() {
|
|||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
) : trees.length === 0 ? (
|
) : trees.length === 0 ? (
|
||||||
<div className="rounded-lg border border-dashed border-border bg-accent px-4 py-12 text-center">
|
<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-muted-foreground" />
|
<FolderTree className="mx-auto mb-4 h-12 w-12 text-[#848b9b]" />
|
||||||
<h2 className="mb-2 text-lg font-semibold text-foreground">No personal flows yet</h2>
|
<h2 className="mb-2 text-lg font-semibold text-[#e2e5eb]">No personal flows yet</h2>
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p className="mb-4 text-sm text-[#848b9b]">
|
||||||
Fork a flow from the library to customize it for your workflow
|
Fork a flow from the library to customize it for your workflow
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center justify-center gap-3">
|
<div className="flex items-center justify-center gap-3">
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
to="/trees"
|
||||||
className={cn(
|
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',
|
'inline-flex items-center gap-2 rounded-md bg-[#22d3ee] text-white px-4 py-2 text-sm font-medium',
|
||||||
'hover:opacity-90'
|
'hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Browse Library
|
Browse Library
|
||||||
@@ -207,8 +207,8 @@ export function MyTreesPage() {
|
|||||||
<Link
|
<Link
|
||||||
to="/trees/new"
|
to="/trees/new"
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-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-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -222,18 +222,18 @@ export function MyTreesPage() {
|
|||||||
{trees.map((tree) => (
|
{trees.map((tree) => (
|
||||||
<div
|
<div
|
||||||
key={tree.id}
|
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 */}
|
{/* Header */}
|
||||||
<div className="mb-3 flex items-start justify-between gap-2">
|
<div className="mb-3 flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{tree.tree_type === 'procedural' && (
|
{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' && (
|
{tree.tree_type === 'maintenance' && (
|
||||||
<Wrench className="h-4 w-4 shrink-0 text-amber-400" />
|
<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 && (
|
{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">
|
<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
|
Fork
|
||||||
@@ -252,7 +252,7 @@ export function MyTreesPage() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tree.category_info && (
|
{tree.category_info && (
|
||||||
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-muted-foreground">
|
<span className="rounded-full bg-accent px-2 py-0.5 text-xs text-[#848b9b]">
|
||||||
{tree.category_info.name}
|
{tree.category_info.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -260,19 +260,19 @@ export function MyTreesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* 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'}
|
{tree.description || 'No description available'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Fork Badge */}
|
{/* Fork Badge */}
|
||||||
{tree.parent_tree_id && (
|
{tree.parent_tree_id && (
|
||||||
<div className="mb-3 flex items-center gap-2 rounded-md bg-accent px-2 py-1.5 text-sm">
|
<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" />
|
<GitBranch className="h-4 w-4 text-[#848b9b]" />
|
||||||
<span className="text-muted-foreground">
|
<span className="text-[#848b9b]">
|
||||||
Forked from{' '}
|
Forked from{' '}
|
||||||
<Link
|
<Link
|
||||||
to={`/trees/${tree.parent_tree_id}/navigate`}
|
to={`/trees/${tree.parent_tree_id}/navigate`}
|
||||||
className="font-medium text-foreground hover:underline"
|
className="font-medium text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
original
|
original
|
||||||
</Link>
|
</Link>
|
||||||
@@ -288,7 +288,7 @@ export function MyTreesPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Stats */}
|
||||||
<div className="mb-4 flex items-center gap-4 text-xs text-muted-foreground">
|
<div className="mb-4 flex items-center gap-4 text-xs text-[#848b9b]">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Clock className="h-3.5 w-3.5" />
|
<Clock className="h-3.5 w-3.5" />
|
||||||
<span>{formatDate(tree.lastUsed)}</span>
|
<span>{formatDate(tree.lastUsed)}</span>
|
||||||
@@ -312,8 +312,8 @@ export function MyTreesPage() {
|
|||||||
<Link
|
<Link
|
||||||
to={getEditPath(tree)}
|
to={getEditPath(tree)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border p-2 text-muted-foreground',
|
'rounded-md border border-[#1e2130] p-2 text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
title="Edit tree"
|
title="Edit tree"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,36 +5,36 @@ export default function PrivacyPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Privacy Policy" description="ResolutionFlow Privacy Policy" />
|
<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">
|
<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>
|
<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>
|
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-semibold text-foreground mb-3">5. Contact</h2>
|
<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-primary hover:underline">hello@resolutionflow.com</a>.</p>
|
<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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -265,37 +265,37 @@ export function ProceduralEditorPage() {
|
|||||||
{/* Main content column */}
|
{/* Main content column */}
|
||||||
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||||
{/* Toolbar — sticky */}
|
{/* 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">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/trees')}
|
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" />
|
<ArrowLeft className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isMaintenance
|
{isMaintenance
|
||||||
? <Wrench className="h-5 w-5 text-amber-400" />
|
? <Wrench className="h-5 w-5 text-amber-400" />
|
||||||
: <ListOrdered className="h-5 w-5 text-muted-foreground" />}
|
: <ListOrdered className="h-5 w-5 text-[#848b9b]" />}
|
||||||
<h1 className="text-lg font-bold text-foreground">
|
<h1 className="text-lg font-bold text-[#e2e5eb]">
|
||||||
{isEditMode ? `Edit ${flowLabel}` : `New ${flowLabel}`}
|
{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>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isDirty && (
|
{isDirty && (
|
||||||
<span className="text-xs text-muted-foreground">Unsaved changes</span>
|
<span className="text-xs text-[#848b9b]">Unsaved changes</span>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => editorAI.isOpen ? editorAI.closePanel() : editorAI.openPanel()}
|
onClick={() => editorAI.isOpen ? editorAI.closePanel() : editorAI.openPanel()}
|
||||||
title="Toggle AI Assist panel"
|
title="Toggle AI Assist panel"
|
||||||
className={cn(
|
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
|
editorAI.isOpen
|
||||||
? 'bg-primary/10 text-primary border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/30'
|
||||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Sparkles className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
@@ -329,40 +329,40 @@ export function ProceduralEditorPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
placeholder="e.g. Domain Controller Build"
|
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>
|
||||||
|
|
||||||
<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
|
<textarea
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
placeholder="Brief description of this procedure..."
|
placeholder="Brief description of this procedure..."
|
||||||
rows={2}
|
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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-muted-foreground">Tags</label>
|
<label className="mb-1 block text-sm font-medium text-[#848b9b]">Tags</label>
|
||||||
<TagInput tags={tags} onChange={setTags} />
|
<TagInput tags={tags} onChange={setTags} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-end pb-1">
|
<div className="flex items-end pb-1">
|
||||||
<label className="flex items-center gap-2 text-sm text-muted-foreground">
|
<label className="flex items-center gap-2 text-sm text-[#848b9b]">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isPublic}
|
checked={isPublic}
|
||||||
onChange={(e) => setIsPublic(e.target.checked)}
|
onChange={(e) => setIsPublic(e.target.checked)}
|
||||||
className="rounded border-border"
|
className="rounded border-[#1e2130]"
|
||||||
/>
|
/>
|
||||||
Public (visible to all users)
|
Public (visible to all users)
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -630,17 +630,17 @@ export function ProceduralNavigationPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-0 flex-col overflow-hidden">
|
<div className="flex h-full min-h-0 flex-col overflow-hidden">
|
||||||
{/* Top bar */}
|
{/* 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 justify-between gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
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" />}
|
{sidebarOpen ? <ChevronLeft className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
<ListOrdered className="h-5 w-5 text-muted-foreground" />
|
<ListOrdered className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h1 className="text-sm font-semibold text-foreground sm:text-base">{tree.name}</h1>
|
<h1 className="text-sm font-semibold text-[#e2e5eb] sm:text-base">{tree.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -650,7 +650,7 @@ export function ProceduralNavigationPage() {
|
|||||||
navigate('/trees')
|
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" />
|
<X className="mr-1 inline h-3.5 w-3.5" />
|
||||||
Exit
|
Exit
|
||||||
@@ -693,7 +693,7 @@ export function ProceduralNavigationPage() {
|
|||||||
{/* Left sidebar - step checklist */}
|
{/* Left sidebar - step checklist */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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'
|
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 */}
|
{/* PSA Ticket Context Panel */}
|
||||||
{session?.psa_ticket_id && (
|
{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
|
<TicketContextPanel
|
||||||
context={ticketContext}
|
context={ticketContext}
|
||||||
loading={ticketContextLoading}
|
loading={ticketContextLoading}
|
||||||
@@ -720,10 +720,10 @@ export function ProceduralNavigationPage() {
|
|||||||
|
|
||||||
{/* Session Variables button */}
|
{/* Session Variables button */}
|
||||||
{intakeFields.length > 0 && (
|
{intakeFields.length > 0 && (
|
||||||
<div className="mt-3 border-t border-border pt-3">
|
<div className="mt-3 border-t border-[#1e2130] pt-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setParamsOpen(true)}
|
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" />
|
<Settings2 className="h-3.5 w-3.5" />
|
||||||
Session Variables
|
Session Variables
|
||||||
@@ -780,7 +780,7 @@ export function ProceduralNavigationPage() {
|
|||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCustomStepModal(true)}
|
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" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Step
|
Add Step
|
||||||
@@ -845,15 +845,15 @@ export function ProceduralNavigationPage() {
|
|||||||
{paramsOpen && (
|
{paramsOpen && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-background/60 backdrop-blur-xs"
|
className="absolute inset-0 bg-[#0c0d10]/60"
|
||||||
onClick={() => setParamsOpen(false)}
|
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="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-border px-5 py-4">
|
<div className="flex items-center justify-between border-b border-[#1e2130] px-5 py-4">
|
||||||
<h3 className="text-sm font-semibold text-foreground">Session Variables</h3>
|
<h3 className="text-sm font-semibold text-[#e2e5eb]">Session Variables</h3>
|
||||||
<button
|
<button
|
||||||
onClick={() => setParamsOpen(false)}
|
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" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -872,7 +872,7 @@ export function ProceduralNavigationPage() {
|
|||||||
key={field.variable_name}
|
key={field.variable_name}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg border px-3 py-2.5',
|
'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">
|
<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" />
|
<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.label}
|
||||||
{field.required && <span className="ml-1 text-amber-400">*</span>}
|
{field.required && <span className="ml-1 text-amber-400">*</span>}
|
||||||
</span>
|
</span>
|
||||||
@@ -890,7 +890,7 @@ export function ProceduralNavigationPage() {
|
|||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<button
|
<button
|
||||||
onClick={() => startEditingVar(field.variable_name)}
|
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'}
|
{isFilled ? 'Edit' : 'Fill'}
|
||||||
</button>
|
</button>
|
||||||
@@ -903,7 +903,7 @@ export function ProceduralNavigationPage() {
|
|||||||
value={editingVarValue}
|
value={editingVarValue}
|
||||||
onChange={(e) => setEditingVarValue(e.target.value)}
|
onChange={(e) => setEditingVarValue(e.target.value)}
|
||||||
autoFocus
|
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>
|
<option value="">{field.placeholder || 'Select...'}</option>
|
||||||
{field.options.map((opt) => (
|
{field.options.map((opt) => (
|
||||||
@@ -917,7 +917,7 @@ export function ProceduralNavigationPage() {
|
|||||||
autoFocus
|
autoFocus
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder={field.placeholder}
|
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
|
<input
|
||||||
@@ -927,30 +927,30 @@ export function ProceduralNavigationPage() {
|
|||||||
onKeyDown={(e) => { if (e.key === 'Enter') saveEditingVar() }}
|
onKeyDown={(e) => { if (e.key === 'Enter') saveEditingVar() }}
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder={field.placeholder}
|
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 && (
|
{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">
|
<div className="mt-2 flex justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => { setEditingVarName(null); setEditingVarValue('') }}
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={saveEditingVar}
|
onClick={saveEditingVar}
|
||||||
disabled={!editingVarValue.trim()}
|
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
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : isFilled ? (
|
) : isFilled ? (
|
||||||
<p className="mt-1 text-sm text-foreground truncate">{value}</p>
|
<p className="mt-1 text-sm text-[#e2e5eb] truncate">{value}</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -961,8 +961,8 @@ export function ProceduralNavigationPage() {
|
|||||||
.filter(([key]) => !intakeFields.some(f => f.variable_name === key))
|
.filter(([key]) => !intakeFields.some(f => f.variable_name === key))
|
||||||
.map(([key, value]) => (
|
.map(([key, value]) => (
|
||||||
<div key={key} className="flex items-baseline justify-between gap-4 rounded-lg bg-accent px-3 py-2">
|
<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-xs font-medium text-[#848b9b]">{key.replace(/_/g, ' ')}</span>
|
||||||
<span className="text-right text-sm text-muted-foreground">{value || 'N/A'}</span>
|
<span className="text-right text-sm text-[#848b9b]">{value || 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</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."
|
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 */}
|
{/* Ambient orbs */}
|
||||||
<div
|
<div
|
||||||
className="fixed top-[-20%] right-[-10%] w-[600px] h-[600px] rounded-full pointer-events-none"
|
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 */}
|
||||||
<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">
|
<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">
|
<Link to="/landing" className="flex items-center gap-2.5">
|
||||||
<BrandLogo size="sm" />
|
<BrandLogo size="sm" />
|
||||||
<span className="font-heading text-lg font-semibold">
|
<span className="font-heading text-lg font-semibold">
|
||||||
<span className="text-foreground">Resolution</span>
|
<span className="text-[#e2e5eb]">Resolution</span>
|
||||||
<span className="text-gradient-brand">Flow</span>
|
<span className="text-[#67e8f9]">Flow</span>
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
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
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/register"
|
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
|
Sign Up Free
|
||||||
</Link>
|
</Link>
|
||||||
@@ -203,21 +203,21 @@ export default function PublicTemplatesPage() {
|
|||||||
<div className="max-w-3xl mx-auto text-center">
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
<h1 className="font-heading text-4xl lg:text-5xl font-bold tracking-tight">
|
<h1 className="font-heading text-4xl lg:text-5xl font-bold tracking-tight">
|
||||||
MSP Troubleshooting{' '}
|
MSP Troubleshooting{' '}
|
||||||
<span className="text-gradient-brand">Templates</span>
|
<span className="text-[#67e8f9]">Templates</span>
|
||||||
</h1>
|
</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.
|
Battle-tested flows and scripts built by MSP engineers. Free to browse, powerful when connected to FlowPilot.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<div className="mt-8 max-w-xl mx-auto relative">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search templates..."
|
placeholder="Search templates..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => handleSearchChange(e.target.value)}
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,8 +235,8 @@ export default function PublicTemplatesPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
||||||
!activeCategory
|
!activeCategory
|
||||||
? 'bg-primary/10 text-foreground border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||||
: 'bg-card border-border text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
: 'bg-[#14161d] border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
@@ -249,8 +249,8 @@ export default function PublicTemplatesPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
'px-3 py-1.5 rounded-full text-sm font-medium border transition-colors',
|
||||||
cat === activeCategory
|
cat === activeCategory
|
||||||
? 'bg-primary/10 text-foreground border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||||
: 'bg-card border-border text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
: 'bg-[#14161d] border-[#1e2130] text-[#848b9b] hover:text-[#e2e5eb] hover:border-[rgba(255,255,255,0.12)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{cat}
|
{cat}
|
||||||
@@ -262,7 +262,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{/* Type toggle + Sort */}
|
{/* Type toggle + Sort */}
|
||||||
<div className="flex items-center justify-between gap-4 flex-wrap">
|
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||||
{/* Type segmented control */}
|
{/* 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) => (
|
{(['all', 'flows', 'scripts'] as TypeFilter[]).map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
@@ -271,8 +271,8 @@ export default function PublicTemplatesPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-2 text-sm font-medium transition-colors',
|
'px-4 py-2 text-sm font-medium transition-colors',
|
||||||
t === typeFilter
|
t === typeFilter
|
||||||
? 'bg-primary/10 text-foreground'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t === 'all' ? 'All' : t === 'flows' ? 'Flows' : 'Scripts'}
|
{t === 'all' ? 'All' : t === 'flows' ? 'Flows' : 'Scripts'}
|
||||||
@@ -285,16 +285,16 @@ export default function PublicTemplatesPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSortOpen(!sortOpen)}
|
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]}
|
{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>
|
</button>
|
||||||
{sortOpen && (
|
{sortOpen && (
|
||||||
<>
|
<>
|
||||||
<div className="fixed inset-0 z-10" onClick={() => setSortOpen(false)} />
|
<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]) => (
|
{(Object.entries(sortLabels) as [SortOption, string][]).map(([key, label]) => (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
@@ -306,8 +306,8 @@ export default function PublicTemplatesPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full text-left px-4 py-2.5 text-sm transition-colors',
|
'w-full text-left px-4 py-2.5 text-sm transition-colors',
|
||||||
key === sort
|
key === sort
|
||||||
? 'bg-primary/10 text-foreground'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb]'
|
||||||
: '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)]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
@@ -328,7 +328,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{loading && (
|
{loading && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:gap-6">
|
<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) => (
|
{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-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-full" />
|
||||||
<div className="h-4 bg-[rgba(255,255,255,0.04)] rounded w-2/3" />
|
<div className="h-4 bg-[rgba(255,255,255,0.04)] rounded w-2/3" />
|
||||||
@@ -341,11 +341,11 @@ export default function PublicTemplatesPage() {
|
|||||||
{/* Error */}
|
{/* Error */}
|
||||||
{!loading && error && (
|
{!loading && error && (
|
||||||
<div className="text-center py-16">
|
<div className="text-center py-16">
|
||||||
<p className="text-muted-foreground text-sm">{error}</p>
|
<p className="text-[#848b9b] text-sm">{error}</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={fetchGallery}
|
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
|
Try Again
|
||||||
</button>
|
</button>
|
||||||
@@ -355,7 +355,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{/* Empty */}
|
{/* Empty */}
|
||||||
{!loading && !error && flows.length === 0 && scripts.length === 0 && (
|
{!loading && !error && flows.length === 0 && scripts.length === 0 && (
|
||||||
<div className="text-center py-16">
|
<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.
|
No templates found. Try a different search or category.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,7 +368,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{showFlows && flows.length > 0 && (
|
{showFlows && flows.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{typeFilter === 'all' && (
|
{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})
|
Flows ({data?.total_flows || flows.length})
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
@@ -388,7 +388,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{showScripts && scripts.length > 0 && (
|
{showScripts && scripts.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
{typeFilter === 'all' && (
|
{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})
|
Scripts ({data?.total_scripts || scripts.length})
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
@@ -409,18 +409,18 @@ export default function PublicTemplatesPage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* 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">
|
<div className="max-w-7xl mx-auto flex items-center justify-between">
|
||||||
<Link
|
<Link
|
||||||
to="/landing"
|
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>
|
Powered by <span className="font-semibold">ResolutionFlow</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link
|
<Link
|
||||||
to="/register"
|
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
|
Get Started
|
||||||
</Link>
|
</Link>
|
||||||
@@ -431,7 +431,7 @@ export default function PublicTemplatesPage() {
|
|||||||
{/* Detail loading overlay */}
|
{/* Detail loading overlay */}
|
||||||
{detailLoading && (
|
{detailLoading && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function QuickStartPage() {
|
|||||||
<div className="p-6 space-y-5 max-w-5xl mx-auto">
|
<div className="p-6 space-y-5 max-w-5xl mx-auto">
|
||||||
{/* Greeting */}
|
{/* Greeting */}
|
||||||
<div className="fade-in" style={{ animationDelay: '100ms' }}>
|
<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{' '}
|
Good{' '}
|
||||||
{new Date().getHours() < 12
|
{new Date().getHours() < 12
|
||||||
? 'morning'
|
? 'morning'
|
||||||
@@ -27,7 +27,7 @@ export function QuickStartPage() {
|
|||||||
: 'evening'}
|
: 'evening'}
|
||||||
, {user?.name?.split(' ')[0] || 'there'}
|
, {user?.name?.split(' ')[0] || 'there'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-[#848b9b]">
|
||||||
{new Date().toLocaleDateString('en-US', {
|
{new Date().toLocaleDateString('en-US', {
|
||||||
weekday: 'long',
|
weekday: 'long',
|
||||||
month: 'long',
|
month: 'long',
|
||||||
|
|||||||
@@ -88,19 +88,19 @@ export function RegisterPage() {
|
|||||||
<div className="mb-4 flex justify-center sm:mb-6">
|
<div className="mb-4 flex justify-center sm:mb-6">
|
||||||
<BrandLogo size="lg" />
|
<BrandLogo size="lg" />
|
||||||
</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">
|
||||||
ResolutionFlow
|
ResolutionFlow
|
||||||
</h1>
|
</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
|
AI-Powered Troubleshooting for MSPs
|
||||||
</p>
|
</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
|
Create your account
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
||||||
<div className="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) && (
|
{(error || localError) && (
|
||||||
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
<div className="rounded-xl border border-red-400/20 bg-red-400/10 p-3 text-sm text-red-400">
|
||||||
{localError || error}
|
{localError || error}
|
||||||
@@ -108,7 +108,7 @@ export function RegisterPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<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
|
Invite code
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -122,18 +122,18 @@ export function RegisterPage() {
|
|||||||
}}
|
}}
|
||||||
onBlur={(e) => validateInviteCode(e.target.value)}
|
onBlur={(e) => validateInviteCode(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-xl border bg-card px-3 py-2 font-mono tracking-wider',
|
'mt-1 block w-full rounded-xl border bg-[#14161d] px-3 py-2 font-mono tracking-wider',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:outline-hidden focus:ring-1',
|
'focus:outline-hidden focus:ring-1',
|
||||||
inviteCodeStatus === 'valid' && 'border-emerald-400/50 focus:border-emerald-400 focus:ring-emerald-400/30',
|
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 === '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 === 'idle' && 'border-[#1e2130] focus:border-primary focus:ring-primary/20',
|
||||||
inviteCodeStatus === 'checking' && 'border-border focus:border-primary focus:ring-primary/20'
|
inviteCodeStatus === 'checking' && 'border-[#1e2130] focus:border-primary focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
placeholder="ABCD1234"
|
placeholder="ABCD1234"
|
||||||
/>
|
/>
|
||||||
{inviteCodeStatus === 'checking' && (
|
{inviteCodeStatus === 'checking' && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground">Validating...</p>
|
<p className="mt-1 text-xs text-[#848b9b]">Validating...</p>
|
||||||
)}
|
)}
|
||||||
{inviteCodeStatus === 'valid' && (
|
{inviteCodeStatus === 'valid' && (
|
||||||
<p className="mt-1 text-xs text-emerald-400">{inviteCodeMessage}</p>
|
<p className="mt-1 text-xs text-emerald-400">{inviteCodeMessage}</p>
|
||||||
@@ -144,7 +144,7 @@ export function RegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="name" className="block text-sm font-medium text-foreground">
|
<label htmlFor="name" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Full name
|
Full name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -156,8 +156,8 @@ export function RegisterPage() {
|
|||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
placeholder="John Smith"
|
placeholder="John Smith"
|
||||||
@@ -165,7 +165,7 @@ export function RegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-foreground">
|
<label htmlFor="email" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Email address
|
Email address
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -177,8 +177,8 @@ export function RegisterPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
@@ -186,7 +186,7 @@ export function RegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="password" className="block text-sm font-medium text-foreground">
|
<label htmlFor="password" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -197,19 +197,19 @@ export function RegisterPage() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
placeholder="••••••••••"
|
placeholder="••••••••••"
|
||||||
/>
|
/>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-[#848b9b]">
|
||||||
Must be at least 10 characters
|
Must be at least 10 characters
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-foreground">
|
<label htmlFor="confirmPassword" className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Confirm password
|
Confirm password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -220,8 +220,8 @@ export function RegisterPage() {
|
|||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
placeholder="••••••••••"
|
placeholder="••••••••••"
|
||||||
@@ -233,7 +233,7 @@ export function RegisterPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'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',
|
'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',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
@@ -243,9 +243,9 @@ export function RegisterPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-center text-sm text-muted-foreground">
|
<p className="text-center text-sm text-[#848b9b]">
|
||||||
Already have an account?{' '}
|
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
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -85,24 +85,24 @@ export function ResetPasswordPage() {
|
|||||||
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
<BrandLogo size="lg" className="h-10 w-10 invert sm:h-12 sm:w-12" />
|
||||||
</div>
|
</div>
|
||||||
</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
|
Reset Password
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{verifying ? (
|
{verifying ? (
|
||||||
<div className="bg-card border border-border rounded-xl p-6 text-center">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6 text-center">
|
||||||
<p className="text-muted-foreground">Verifying reset link...</p>
|
<p className="text-[#848b9b]">Verifying reset link...</p>
|
||||||
</div>
|
</div>
|
||||||
) : !token || !valid ? (
|
) : !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">
|
<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.
|
This reset link is invalid or has expired. Please request a new one.
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
to="/forgot-password"
|
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
|
Request new reset link
|
||||||
</Link>
|
</Link>
|
||||||
@@ -110,10 +110,10 @@ export function ResetPasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
|
<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 && (
|
{email && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Resetting password for <span className="font-medium text-foreground">{email}</span>
|
Resetting password for <span className="font-medium text-[#e2e5eb]">{email}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ export function ResetPasswordPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<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
|
New Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -134,20 +134,20 @@ export function ResetPasswordPage() {
|
|||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
placeholder="At least 10 characters"
|
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.
|
Must include uppercase, lowercase, and a digit.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Confirm New Password
|
||||||
</label>
|
</label>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
@@ -157,8 +157,8 @@ export function ResetPasswordPage() {
|
|||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-xl border border-border bg-card px-3 py-2',
|
'block w-full rounded-xl border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
|
||||||
'transition-colors'
|
'transition-colors'
|
||||||
)}
|
)}
|
||||||
@@ -170,7 +170,7 @@ export function ResetPasswordPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-xl px-4 py-2.5 text-sm font-semibold btn-press',
|
'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',
|
'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',
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
'transition-all'
|
'transition-all'
|
||||||
|
|||||||
@@ -89,20 +89,20 @@ export default function ReviewQueuePage() {
|
|||||||
{/* Left panel — Proposal list */}
|
{/* 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)' }}>
|
<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 */}
|
{/* 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 justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Lightbulb size={16} className="text-amber-400" />
|
<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>
|
</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} />
|
<RefreshCw size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats bar */}
|
{/* Stats bar */}
|
||||||
{stats && (
|
{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">
|
<span className="flex items-center gap-1">
|
||||||
<Clock size={10} className="text-amber-400" />
|
<Clock size={10} className="text-amber-400" />
|
||||||
{stats.pending_count} pending
|
{stats.pending_count} pending
|
||||||
@@ -112,7 +112,7 @@ export default function ReviewQueuePage() {
|
|||||||
{stats.approved_this_week} approved
|
{stats.approved_this_week} approved
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1">
|
<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
|
{stats.auto_reinforced_this_week} reinforced
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,8 +126,8 @@ export default function ReviewQueuePage() {
|
|||||||
onClick={() => setActiveTab(tab.key)}
|
onClick={() => setActiveTab(tab.key)}
|
||||||
className={`shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
|
className={`shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||||
activeTab === tab.key
|
activeTab === tab.key
|
||||||
? 'bg-primary/10 text-primary'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
@@ -144,7 +144,7 @@ export default function ReviewQueuePage() {
|
|||||||
<select
|
<select
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(e) => setSortBy(e.target.value)}
|
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="newest">Newest first</option>
|
||||||
<option value="confidence">Highest confidence</option>
|
<option value="confidence">Highest confidence</option>
|
||||||
@@ -156,12 +156,12 @@ export default function ReviewQueuePage() {
|
|||||||
<div className="p-3 space-y-2">
|
<div className="p-3 space-y-2">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex justify-center py-12">
|
<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>
|
</div>
|
||||||
) : proposals.length === 0 ? (
|
) : proposals.length === 0 ? (
|
||||||
<div className="py-12 text-center">
|
<div className="py-12 text-center">
|
||||||
<Lightbulb size={24} className="mx-auto mb-2 text-muted-foreground/40" />
|
<Lightbulb size={24} className="mx-auto mb-2 text-[#848b9b]/40" />
|
||||||
<p className="text-sm text-muted-foreground">No proposals found</p>
|
<p className="text-sm text-[#848b9b]">No proposals found</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
proposals.map((proposal) => (
|
proposals.map((proposal) => (
|
||||||
@@ -180,7 +180,7 @@ export default function ReviewQueuePage() {
|
|||||||
<div className={`flex-1 overflow-y-auto ${!detail && !isLoadingDetail ? 'hidden lg:flex' : ''}`}>
|
<div className={`flex-1 overflow-y-auto ${!detail && !isLoadingDetail ? 'hidden lg:flex' : ''}`}>
|
||||||
{isLoadingDetail ? (
|
{isLoadingDetail ? (
|
||||||
<div className="flex items-center justify-center h-full">
|
<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>
|
</div>
|
||||||
) : detail ? (
|
) : detail ? (
|
||||||
<ProposalDetail
|
<ProposalDetail
|
||||||
@@ -190,8 +190,8 @@ export default function ReviewQueuePage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Lightbulb size={32} className="mx-auto mb-3 text-muted-foreground/30" />
|
<Lightbulb size={32} className="mx-auto mb-3 text-[#848b9b]/30" />
|
||||||
<p className="text-sm text-muted-foreground">Select a proposal to review</p>
|
<p className="text-sm text-[#848b9b]">Select a proposal to review</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ export default function ScriptBuilderPage() {
|
|||||||
<Terminal size={18} className="text-fuchsia-400" />
|
<Terminal size={18} className="text-fuchsia-400" />
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-heading font-bold text-foreground">Script Builder</h1>
|
<h1 className="text-base font-heading font-bold text-[#e2e5eb]">Script Builder</h1>
|
||||||
<p className="text-xs text-muted-foreground">Describe what you need, AI generates the script</p>
|
<p className="text-xs text-[#848b9b]">Describe what you need, AI generates the script</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -144,12 +144,12 @@ export default function ScriptBuilderPage() {
|
|||||||
onClick={() => !hasMessages && setLanguage(lang.value)}
|
onClick={() => !hasMessages && setLanguage(lang.value)}
|
||||||
disabled={hasMessages}
|
disabled={hasMessages}
|
||||||
className={cn(
|
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
|
language === lang.value
|
||||||
? "bg-gradient-brand text-[#101114]"
|
? "bg-[#22d3ee] text-white"
|
||||||
: hasMessages
|
: hasMessages
|
||||||
? "text-[#5a6170] cursor-not-allowed"
|
? "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}
|
{lang.label}
|
||||||
|
|||||||
@@ -57,22 +57,22 @@ export default function ScriptLibraryPage() {
|
|||||||
|
|
||||||
const tabClass = (tab: LibraryTab) =>
|
const tabClass = (tab: LibraryTab) =>
|
||||||
tab === activeTab
|
tab === activeTab
|
||||||
? 'border-b-2 border-primary text-foreground'
|
? 'border-b-2 border-primary text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 p-6 h-full">
|
<div className="flex flex-col gap-4 p-6 h-full">
|
||||||
{/* Page header */}
|
{/* Page header */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb]">Script Library</h1>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-[#848b9b] mt-1">
|
||||||
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
||||||
</p>
|
</p>
|
||||||
{isEngineer && (
|
{isEngineer && (
|
||||||
<Link
|
<Link
|
||||||
to="/scripts/manage"
|
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" />
|
<Settings size={12} className="group-hover:rotate-90 transition-transform duration-300" />
|
||||||
Manage Templates
|
Manage Templates
|
||||||
@@ -81,7 +81,7 @@ export default function ScriptLibraryPage() {
|
|||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
to="/script-builder"
|
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} />
|
<Wand2 size={16} />
|
||||||
Build a New Script
|
Build a New Script
|
||||||
@@ -89,7 +89,7 @@ export default function ScriptLibraryPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab bar */}
|
{/* Tab bar */}
|
||||||
<div className="flex gap-6 border-b border-border">
|
<div className="flex gap-6 border-b border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onTabChange('mine')}
|
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">
|
<div className="grid grid-cols-[320px_1fr] gap-4 flex-1 min-h-0">
|
||||||
{/* Left pane — Browse or Configure mode */}
|
{/* Left pane — Browse or Configure mode */}
|
||||||
{paneMode === 'browse' ? (
|
{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">
|
<div className="p-2 pb-0">
|
||||||
<ScriptFilterBar inputValue={inputValue} setInputValue={setInputValue} />
|
<ScriptFilterBar inputValue={inputValue} setInputValue={setInputValue} />
|
||||||
</div>
|
</div>
|
||||||
@@ -128,12 +128,12 @@ export default function ScriptLibraryPage() {
|
|||||||
|
|
||||||
{/* Right pane — read-only ScriptPreview */}
|
{/* Right pane — read-only ScriptPreview */}
|
||||||
{selectedTemplate === null ? (
|
{selectedTemplate === null ? (
|
||||||
<div className="glass-card-static h-full flex flex-col items-center justify-center gap-3 text-center p-8">
|
<div className="card-flat h-full flex flex-col items-center justify-center gap-3 text-center p-8">
|
||||||
<Terminal size={40} className="text-muted-foreground/40" />
|
<Terminal size={40} className="text-[#848b9b]/40" />
|
||||||
<p className="text-sm text-muted-foreground">Select a template to get started</p>
|
<p className="text-sm text-[#848b9b]">Select a template to get started</p>
|
||||||
</div>
|
</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 />
|
<ScriptPreview />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ export function SessionDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/sessions')}
|
onClick={() => navigate('/sessions')}
|
||||||
className="mt-4 text-foreground hover:underline"
|
className="mt-4 text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
Back to sessions
|
Back to sessions
|
||||||
</button>
|
</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' },
|
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' },
|
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' },
|
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
|
const outcomeConfig = session.outcome ? OUTCOME_CONFIG[session.outcome] : null
|
||||||
|
|
||||||
@@ -402,7 +402,7 @@ export function SessionDetailPage() {
|
|||||||
{/* Back nav */}
|
{/* Back nav */}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/sessions')}
|
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
|
← Back to sessions
|
||||||
</button>
|
</button>
|
||||||
@@ -410,10 +410,10 @@ export function SessionDetailPage() {
|
|||||||
{/* Page title row */}
|
{/* Page title row */}
|
||||||
<div className="mb-6 flex items-start justify-between gap-4">
|
<div className="mb-6 flex items-start justify-between gap-4">
|
||||||
<div>
|
<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'}
|
{session.ticket_number || 'Session Details'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-[#848b9b]">
|
||||||
{session.tree_snapshot?.name}
|
{session.tree_snapshot?.name}
|
||||||
{session.client_name && <> · Client: {session.client_name}</>}
|
{session.client_name && <> · Client: {session.client_name}</>}
|
||||||
{session.started_at && <>{' · '}{new Date(session.started_at).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}</>}
|
{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>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={cn('text-base font-semibold', outcomeConfig.color)}>{outcomeLabel}</span>
|
<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>
|
</div>
|
||||||
{session.outcome_notes && (
|
{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 && (
|
{session.next_steps && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<span className="font-label text-[0.6875rem] uppercase tracking-wide text-muted-foreground">Next Steps</span>
|
<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-muted-foreground whitespace-pre-wrap">{session.next_steps}</p>
|
<p className="mt-0.5 text-sm text-[#848b9b] whitespace-pre-wrap">{session.next_steps}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -464,7 +464,7 @@ export function SessionDetailPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleCreateFlowFromSession}
|
onClick={handleCreateFlowFromSession}
|
||||||
disabled={isGeneratingFlow}
|
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 ? (
|
{isGeneratingFlow ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<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" />
|
<Flag className="h-4 w-4 shrink-0 text-amber-400" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-amber-300">Session in progress</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => setShowOutcomeModal(true)} className="shrink-0">
|
<Button onClick={() => setShowOutcomeModal(true)} className="shrink-0">
|
||||||
@@ -498,7 +498,7 @@ export function SessionDetailPage() {
|
|||||||
value={exportFormat}
|
value={exportFormat}
|
||||||
onChange={(e) => setExportFormat(e.target.value as typeof exportFormat)}
|
onChange={(e) => setExportFormat(e.target.value as typeof exportFormat)}
|
||||||
aria-label="Export format"
|
aria-label="Export format"
|
||||||
className="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="markdown">Markdown</option>
|
||||||
<option value="text">Plain Text</option>
|
<option value="text">Plain Text</option>
|
||||||
@@ -511,7 +511,7 @@ export function SessionDetailPage() {
|
|||||||
value={maxStepIndex ?? ''}
|
value={maxStepIndex ?? ''}
|
||||||
onChange={(e) => setMaxStepIndex(e.target.value ? Number(e.target.value) : null)}
|
onChange={(e) => setMaxStepIndex(e.target.value ? Number(e.target.value) : null)}
|
||||||
aria-label="Export through step"
|
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>
|
<option value="">All steps</option>
|
||||||
{session.decisions.map((_, idx) => (
|
{session.decisions.map((_, idx) => (
|
||||||
@@ -523,7 +523,7 @@ export function SessionDetailPage() {
|
|||||||
value={detailLevel}
|
value={detailLevel}
|
||||||
onChange={(e) => setDetailLevel(e.target.value as 'standard' | 'full')}
|
onChange={(e) => setDetailLevel(e.target.value as 'standard' | 'full')}
|
||||||
aria-label="Detail level"
|
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="standard">Standard</option>
|
||||||
<option value="full">Full Detail</option>
|
<option value="full">Full Detail</option>
|
||||||
@@ -532,7 +532,7 @@ export function SessionDetailPage() {
|
|||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
disabled={isExporting}
|
disabled={isExporting}
|
||||||
title="Copy to clipboard"
|
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" />}
|
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
@@ -549,7 +549,7 @@ export function SessionDetailPage() {
|
|||||||
{session.completed_at && (
|
{session.completed_at && (
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyForTicket}
|
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 ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||||
{copiedPsa ? 'Copied!' : 'Copy for Ticket'}
|
{copiedPsa ? 'Copied!' : 'Copy for Ticket'}
|
||||||
|
|||||||
@@ -280,8 +280,8 @@ export function SessionHistoryPage() {
|
|||||||
<PageMeta title="Sessions" />
|
<PageMeta title="Sessions" />
|
||||||
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
<div className="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-2xl font-heading font-bold text-foreground sm:text-3xl">Sessions</h1>
|
<h1 className="text-2xl font-heading font-bold text-[#e2e5eb] sm:text-3xl">Sessions</h1>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
View and manage all your sessions
|
View and manage all your sessions
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -295,13 +295,13 @@ export function SessionHistoryPage() {
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
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
|
Start a Flow
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/pilot"
|
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
|
Start AI Session
|
||||||
</Link>
|
</Link>
|
||||||
@@ -314,20 +314,20 @@ export function SessionHistoryPage() {
|
|||||||
{/* FlowPilot Sessions Section */}
|
{/* FlowPilot Sessions Section */}
|
||||||
{showAiSection && (
|
{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 */}
|
{/* 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">
|
<div className="flex flex-wrap gap-3 items-center">
|
||||||
{/* Search input */}
|
{/* Search input */}
|
||||||
<div className="relative flex-1 min-w-[180px]">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={aiSearchInput}
|
value={aiSearchInput}
|
||||||
onChange={(e) => setAiSearchInput(e.target.value)}
|
onChange={(e) => setAiSearchInput(e.target.value)}
|
||||||
placeholder="Search sessions..."
|
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>
|
</div>
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ export function SessionHistoryPage() {
|
|||||||
value={aiFilters.problem_domain}
|
value={aiFilters.problem_domain}
|
||||||
onChange={(e) => setAiFilters((f) => ({ ...f, problem_domain: e.target.value }))}
|
onChange={(e) => setAiFilters((f) => ({ ...f, problem_domain: e.target.value }))}
|
||||||
title="Filter by problem domain"
|
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="">All domains</option>
|
||||||
<option value="Active Directory">Active Directory</option>
|
<option value="Active Directory">Active Directory</option>
|
||||||
@@ -358,10 +358,10 @@ export function SessionHistoryPage() {
|
|||||||
key={tier}
|
key={tier}
|
||||||
onClick={() => setAiFilters((f) => ({ ...f, confidence_tier: tier }))}
|
onClick={() => setAiFilters((f) => ({ ...f, confidence_tier: tier }))}
|
||||||
className={cn(
|
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
|
aiFilters.confidence_tier === tier
|
||||||
? 'bg-primary/10 text-foreground border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#e2e5eb] border-primary/30'
|
||||||
: 'bg-card text-muted-foreground border-border hover:text-foreground hover:border-[rgba(255,255,255,0.12)]'
|
: '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)}
|
{tier === '' ? 'All' : tier.charAt(0).toUpperCase() + tier.slice(1)}
|
||||||
@@ -376,15 +376,15 @@ export function SessionHistoryPage() {
|
|||||||
value={aiFilters.date_from}
|
value={aiFilters.date_from}
|
||||||
onChange={(e) => setAiFilters((f) => ({ ...f, date_from: e.target.value }))}
|
onChange={(e) => setAiFilters((f) => ({ ...f, date_from: e.target.value }))}
|
||||||
title="From date"
|
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
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={aiFilters.date_to}
|
value={aiFilters.date_to}
|
||||||
onChange={(e) => setAiFilters((f) => ({ ...f, date_to: e.target.value }))}
|
onChange={(e) => setAiFilters((f) => ({ ...f, date_to: e.target.value }))}
|
||||||
title="To date"
|
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>
|
</div>
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ export function SessionHistoryPage() {
|
|||||||
setAiSearchInput('')
|
setAiSearchInput('')
|
||||||
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
|
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
|
Clear
|
||||||
</button>
|
</button>
|
||||||
@@ -417,7 +417,7 @@ export function SessionHistoryPage() {
|
|||||||
setAiSearchInput('')
|
setAiSearchInput('')
|
||||||
setAiFilters({ q: '', problem_domain: '', confidence_tier: '', date_from: '', date_to: '' })
|
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
|
Clear all filters
|
||||||
</button>
|
</button>
|
||||||
@@ -433,7 +433,7 @@ export function SessionHistoryPage() {
|
|||||||
|
|
||||||
{/* Divider between sections */}
|
{/* Divider between sections */}
|
||||||
{showFlowSection && (
|
{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 */}
|
{/* Flow Sessions Section */}
|
||||||
{showFlowSection && (
|
{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 */}
|
{/* 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) => (
|
{(['active', 'prepared', 'completed', 'all'] as const).map((tab) => (
|
||||||
<button
|
<button
|
||||||
key={tab}
|
key={tab}
|
||||||
@@ -452,8 +452,8 @@ export function SessionHistoryPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-4 py-2 text-sm font-medium transition-colors',
|
'px-4 py-2 text-sm font-medium transition-colors',
|
||||||
filter === tab
|
filter === tab
|
||||||
? 'border-b-2 border-primary text-foreground'
|
? 'border-b-2 border-primary text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||||
@@ -481,7 +481,7 @@ export function SessionHistoryPage() {
|
|||||||
title="No sessions match your filters"
|
title="No sessions match your filters"
|
||||||
description="Try adjusting your search or filters."
|
description="Try adjusting your search or filters."
|
||||||
action={
|
action={
|
||||||
<button onClick={handleClearFilters} className="text-foreground hover:underline text-sm">
|
<button onClick={handleClearFilters} className="text-[#e2e5eb] hover:underline text-sm">
|
||||||
Clear all filters
|
Clear all filters
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@@ -494,7 +494,7 @@ export function SessionHistoryPage() {
|
|||||||
action={
|
action={
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
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
|
Start a Session
|
||||||
</Link>
|
</Link>
|
||||||
@@ -515,7 +515,7 @@ export function SessionHistoryPage() {
|
|||||||
style={{ '--stagger-index': i } as React.CSSProperties}
|
style={{ '--stagger-index': i } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<div
|
<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 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
@@ -527,11 +527,11 @@ export function SessionHistoryPage() {
|
|||||||
session.completed_at ? 'bg-green-500' : 'bg-yellow-500'
|
session.completed_at ? 'bg-green-500' : 'bg-yellow-500'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-[#e2e5eb]">
|
||||||
{session.ticket_number || 'No ticket'}
|
{session.ticket_number || 'No ticket'}
|
||||||
</span>
|
</span>
|
||||||
{session.client_name && (
|
{session.client_name && (
|
||||||
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium text-foreground">
|
<span className="rounded-full bg-accent px-2.5 py-0.5 text-xs font-medium text-[#e2e5eb]">
|
||||||
{session.client_name}
|
{session.client_name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -545,7 +545,7 @@ export function SessionHistoryPage() {
|
|||||||
session.outcome === 'unresolved' && 'bg-rose-500/20 text-rose-300',
|
session.outcome === 'unresolved' && 'bg-rose-500/20 text-rose-300',
|
||||||
session.outcome === 'cancelled' && 'bg-zinc-500/20 text-zinc-300',
|
session.outcome === 'cancelled' && 'bg-zinc-500/20 text-zinc-300',
|
||||||
session.outcome === 'resolved_externally' && 'bg-cyan-500/20 text-cyan-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)}
|
{formatOutcomeLabel(session.outcome)}
|
||||||
@@ -554,12 +554,12 @@ export function SessionHistoryPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tree Name */}
|
{/* 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)}
|
<span className="font-medium">Tree:</span> {getTreeName(session)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Timestamps */}
|
{/* Timestamps */}
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-[#848b9b]">
|
||||||
Started: {session.started_at ? formatDate(session.started_at) : 'Not started'}
|
Started: {session.started_at ? formatDate(session.started_at) : 'Not started'}
|
||||||
{session.completed_at && (
|
{session.completed_at && (
|
||||||
<> · Completed: {formatDate(session.completed_at)}</>
|
<> · Completed: {formatDate(session.completed_at)}</>
|
||||||
@@ -567,7 +567,7 @@ export function SessionHistoryPage() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* 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.decisions.length} decision{session.decisions.length !== 1 ? 's' : ''} recorded
|
||||||
{session.scratchpad && session.scratchpad.trim() && (
|
{session.scratchpad && session.scratchpad.trim() && (
|
||||||
<span> · Has notes</span>
|
<span> · Has notes</span>
|
||||||
@@ -580,8 +580,8 @@ export function SessionHistoryPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/sessions/${session.id}`)}
|
onClick={() => navigate(`/sessions/${session.id}`)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground',
|
'rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
View Details
|
View Details
|
||||||
@@ -595,9 +595,9 @@ export function SessionHistoryPage() {
|
|||||||
setCloseNotes('')
|
setCloseNotes('')
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border px-3 py-2 text-sm font-medium text-muted-foreground',
|
'rounded-md border border-[#1e2130] px-3 py-2 text-sm font-medium text-[#848b9b]',
|
||||||
'hover:bg-accent hover:text-foreground',
|
'hover:bg-accent hover:text-[#e2e5eb]',
|
||||||
closingSessionId === session.id && 'bg-accent text-foreground'
|
closingSessionId === session.id && 'bg-accent text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
@@ -605,8 +605,8 @@ export function SessionHistoryPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => navigate(getSessionResumePath(session.tree_id, session.tree_snapshot?.tree_type), { state: { sessionId: session.id } })}
|
onClick={() => navigate(getSessionResumePath(session.tree_id, session.tree_snapshot?.tree_type), { state: { sessionId: session.id } })}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md bg-gradient-brand px-3 py-2 text-sm font-medium text-white shadow-lg shadow-primary/20',
|
'rounded-md bg-[#22d3ee] px-3 py-2 text-sm font-medium text-white',
|
||||||
'hover:opacity-90'
|
'hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Resume
|
Resume
|
||||||
@@ -618,16 +618,16 @@ export function SessionHistoryPage() {
|
|||||||
{closingSessionId === session.id && (
|
{closingSessionId === session.id && (
|
||||||
<div
|
<div
|
||||||
ref={closePopoverRef}
|
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
|
<select
|
||||||
value={closeOutcome}
|
value={closeOutcome}
|
||||||
onChange={(e) => setCloseOutcome(e.target.value as SessionOutcome)}
|
onChange={(e) => setCloseOutcome(e.target.value as SessionOutcome)}
|
||||||
title="Session outcome"
|
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="">Select outcome...</option>
|
||||||
<option value="resolved">Resolved</option>
|
<option value="resolved">Resolved</option>
|
||||||
@@ -638,13 +638,13 @@ export function SessionHistoryPage() {
|
|||||||
<option value="resolved_externally">Resolved Externally</option>
|
<option value="resolved_externally">Resolved Externally</option>
|
||||||
</select>
|
</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
|
<textarea
|
||||||
value={closeNotes}
|
value={closeNotes}
|
||||||
onChange={(e) => setCloseNotes(e.target.value)}
|
onChange={(e) => setCloseNotes(e.target.value)}
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="Add closure notes..."
|
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">
|
<div className="flex items-center justify-end gap-2">
|
||||||
@@ -654,7 +654,7 @@ export function SessionHistoryPage() {
|
|||||||
setCloseOutcome('')
|
setCloseOutcome('')
|
||||||
setCloseNotes('')
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -662,10 +662,10 @@ export function SessionHistoryPage() {
|
|||||||
onClick={handleCloseSession}
|
onClick={handleCloseSession}
|
||||||
disabled={!closeOutcome || closeLoading}
|
disabled={!closeOutcome || closeLoading}
|
||||||
className={cn(
|
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
|
closeOutcome
|
||||||
? 'bg-gradient-brand text-[#101114] hover:opacity-90'
|
? 'bg-[#22d3ee] text-white hover:brightness-110'
|
||||||
: 'bg-gradient-brand text-[#101114] opacity-50 cursor-not-allowed'
|
: 'bg-[#22d3ee] text-white opacity-50 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{closeLoading ? 'Closing...' : 'Confirm'}
|
{closeLoading ? 'Closing...' : 'Confirm'}
|
||||||
@@ -680,11 +680,11 @@ export function SessionHistoryPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{hasMore ? (
|
{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
|
Showing the 50 most recent sessions
|
||||||
</p>
|
</p>
|
||||||
) : sessions.length > 0 ? (
|
) : 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
|
Showing all {sessions.length} sessions
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -55,16 +55,16 @@ function ErrorCard({ error }: { error: ErrorState }) {
|
|||||||
const Icon = iconMap[error.type]
|
const Icon = iconMap[error.type]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-background px-4">
|
<div className="flex min-h-screen items-center justify-center bg-[#0c0d10] px-4">
|
||||||
<div className="bg-card border border-border w-full max-w-md rounded-xl p-8 text-center">
|
<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">
|
<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>
|
</div>
|
||||||
<h1 className="mb-2 text-xl font-heading font-semibold text-foreground">{titleMap[error.type]}</h1>
|
<h1 className="mb-2 text-xl font-heading font-semibold text-[#e2e5eb]">{titleMap[error.type]}</h1>
|
||||||
<p className="mb-6 text-sm text-muted-foreground">{error.message}</p>
|
<p className="mb-6 text-sm text-[#848b9b]">{error.message}</p>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
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
|
Go to ResolutionFlow
|
||||||
</Link>
|
</Link>
|
||||||
@@ -144,10 +144,10 @@ export function SharedSessionPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
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">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<p className="text-sm text-muted-foreground">Loading shared session...</p>
|
<p className="text-sm text-[#848b9b]">Loading shared session...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -162,21 +162,21 @@ export function SharedSessionPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-[#0c0d10]">
|
||||||
<PageMeta
|
<PageMeta
|
||||||
title={data ? `Shared Session - ${data.tree_name}` : 'Shared Session'}
|
title={data ? `Shared Session - ${data.tree_name}` : 'Shared Session'}
|
||||||
description="View a shared troubleshooting session on ResolutionFlow"
|
description="View a shared troubleshooting session on ResolutionFlow"
|
||||||
/>
|
/>
|
||||||
{/* Minimal header */}
|
{/* 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">
|
<div className="mx-auto flex max-w-7xl items-center justify-between">
|
||||||
<Link to="/" className="flex items-center gap-2">
|
<Link to="/" className="flex items-center gap-2">
|
||||||
<BrandLogo size="sm" />
|
<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>
|
||||||
<Link
|
<Link
|
||||||
to="/login"
|
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
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
@@ -188,14 +188,14 @@ export function SharedSessionPage() {
|
|||||||
{/* Metadata section */}
|
{/* Metadata section */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
{data.share_name && (
|
{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">
|
<p className="text-lg text-[#848b9b]">
|
||||||
<span className="text-muted-foreground">Tree:</span> {data.tree_name}
|
<span className="text-[#848b9b]">Tree:</span> {data.tree_name}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{(data.ticket_number || data.client_name) && (
|
{(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 && (
|
{data.ticket_number && (
|
||||||
<span>Ticket: #{data.ticket_number}</span>
|
<span>Ticket: #{data.ticket_number}</span>
|
||||||
)}
|
)}
|
||||||
@@ -208,7 +208,7 @@ export function SharedSessionPage() {
|
|||||||
</p>
|
</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>
|
<span>Started: {formatDate(data.started_at)}</span>
|
||||||
{data.completed_at && (
|
{data.completed_at && (
|
||||||
<>
|
<>
|
||||||
@@ -256,9 +256,9 @@ export function SharedSessionPage() {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="py-8 text-center text-sm text-muted-foreground">
|
<footer className="py-8 text-center text-sm text-[#848b9b]">
|
||||||
Powered by{' '}
|
Powered by{' '}
|
||||||
<Link to="/" className="underline hover:text-foreground">
|
<Link to="/" className="underline hover:text-[#e2e5eb]">
|
||||||
ResolutionFlow
|
ResolutionFlow
|
||||||
</Link>
|
</Link>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ export default function StepLibraryPage() {
|
|||||||
<PageMeta title="Step Library" />
|
<PageMeta title="Step Library" />
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
{/* Page Header */}
|
{/* 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">
|
<div className="flex items-center gap-3">
|
||||||
<span title="Step Library">
|
<span title="Step Library">
|
||||||
<Bookmark className="h-6 w-6 text-muted-foreground" />
|
<Bookmark className="h-6 w-6 text-[#848b9b]" />
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold font-heading text-foreground">Step Library</h1>
|
<h1 className="text-xl font-bold font-heading text-[#e2e5eb]">Step Library</h1>
|
||||||
<p className="text-sm text-muted-foreground">Reusable steps you can insert into any flow</p>
|
<p className="text-sm text-[#848b9b]">Reusable steps you can insert into any flow</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canCreateSteps && (
|
{canCreateSteps && (
|
||||||
@@ -131,19 +131,19 @@ export default function StepLibraryPage() {
|
|||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
{/* Delete Confirmation Dialog */}
|
||||||
{deletingStep && (
|
{deletingStep && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4">
|
||||||
<div className="w-full max-w-sm rounded-xl bg-card border border-border p-6 shadow-lg">
|
<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="mb-4 flex items-center gap-3">
|
||||||
<div className="rounded-full bg-red-400/10 p-2">
|
<div className="rounded-full bg-red-400/10 p-2">
|
||||||
<Trash2 className="h-5 w-5 text-red-400" />
|
<Trash2 className="h-5 w-5 text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-base font-semibold text-foreground">Delete Step</h2>
|
<h2 className="text-base font-semibold text-[#e2e5eb]">Delete Step</h2>
|
||||||
</div>
|
</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{' '}
|
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>
|
||||||
<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 && (
|
{deleteError && (
|
||||||
<p className="mb-4 text-sm text-red-400">{deleteError}</p>
|
<p className="mb-4 text-sm text-red-400">{deleteError}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -264,15 +264,15 @@ export default function SurveyPage() {
|
|||||||
|
|
||||||
if (tokenLoading) {
|
if (tokenLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background flex items-center justify-center">
|
<div className="min-h-screen bg-[#0c0d10] flex items-center justify-center">
|
||||||
<div className="text-muted-foreground text-sm">Loading...</div>
|
<div className="text-[#848b9b] text-sm">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alreadyCompleted) {
|
if (alreadyCompleted) {
|
||||||
return (
|
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="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 -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%)' }} />
|
<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>
|
<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>
|
</div>
|
||||||
<h2 className="font-heading text-2xl font-bold mb-2.5">Already Submitted</h2>
|
<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!
|
{inviteName ? `Thanks ${inviteName} — y` : 'Y'}our response has already been recorded. We appreciate your time!
|
||||||
</p>
|
</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.
|
You can safely close this browser window now.
|
||||||
</p>
|
</p>
|
||||||
<div className="glass-card-static p-4 sm:p-5 max-w-[400px] mx-auto text-center">
|
<div className="card-flat p-4 sm:p-5 max-w-[400px] mx-auto text-center">
|
||||||
<p className="text-xs text-muted-foreground leading-relaxed">
|
<p className="text-xs text-[#848b9b] leading-relaxed">
|
||||||
Have feedback unrelated to the survey?{' '}
|
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
|
feedback@resolutionflow.com
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@@ -304,7 +304,7 @@ export default function SurveyPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={topRef} className="min-h-screen bg-background text-foreground">
|
<div ref={topRef} className="min-h-screen bg-[#0c0d10] text-[#e2e5eb]">
|
||||||
<PageMeta
|
<PageMeta
|
||||||
title="Product Survey"
|
title="Product Survey"
|
||||||
description="Help shape the future of ResolutionFlow by sharing your feedback"
|
description="Help shape the future of ResolutionFlow by sharing your feedback"
|
||||||
@@ -322,17 +322,17 @@ export default function SurveyPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top bar */}
|
{/* 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">
|
<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" />
|
<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>
|
</a>
|
||||||
<div className="flex flex-1 items-center gap-2 sm:gap-2.5" style={{ maxWidth: '280px' }}>
|
<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="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>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,17 +341,17 @@ export default function SurveyPage() {
|
|||||||
{/* Hero — visible only on first slide */}
|
{/* Hero — visible only on first slide */}
|
||||||
{currentSlide === 0 && !isComplete && (
|
{currentSlide === 0 && !isComplete && (
|
||||||
<div className="text-center pt-10 pb-8 sm:pt-[72px] sm:pb-10 animate-fade-in-up">
|
<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>
|
<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
|
FlowPilot Research
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-heading text-[clamp(24px,5vw,36px)] font-extrabold leading-tight mb-3">
|
<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>
|
</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.
|
We're building an AI assistant for MSP engineers. Your expertise shapes how it thinks. Takes about 5 minutes.
|
||||||
</p>
|
</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">
|
<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>
|
<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
|
~5 minutes
|
||||||
@@ -393,18 +393,18 @@ export default function SurveyPage() {
|
|||||||
))}
|
))}
|
||||||
<div className="flex justify-between mt-6 sm:mt-7 gap-3">
|
<div className="flex justify-between mt-6 sm:mt-7 gap-3">
|
||||||
{si > 0 ? (
|
{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>
|
<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
|
Back
|
||||||
</button>
|
</button>
|
||||||
) : <div />}
|
) : <div />}
|
||||||
{si < SLIDES.length - 1 ? (
|
{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
|
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>
|
<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>
|
||||||
) : (
|
) : (
|
||||||
<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 ? '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>}
|
{!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>
|
</button>
|
||||||
@@ -413,7 +413,7 @@ export default function SurveyPage() {
|
|||||||
{submitError && (
|
{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)' }}>
|
<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}
|
{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>
|
||||||
)}
|
)}
|
||||||
</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>
|
<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>
|
</div>
|
||||||
<h2 className="font-heading text-2xl font-bold mb-2.5">Response Submitted!</h2>
|
<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?
|
Your answers will directly shape how FlowPilot troubleshoots. Would you like a copy of your responses?
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Email a copy */}
|
{/* Email a copy */}
|
||||||
<div className="glass-card-static p-4 sm:p-6 max-w-[420px] mx-auto mb-5">
|
<div className="card-flat 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">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-3">
|
||||||
Email a copy to yourself
|
Email a copy to yourself
|
||||||
</p>
|
</p>
|
||||||
{!emailSent ? (
|
{!emailSent ? (
|
||||||
@@ -443,7 +443,7 @@ export default function SurveyPage() {
|
|||||||
value={emailInput}
|
value={emailInput}
|
||||||
onChange={e => setEmailInput(e.target.value)}
|
onChange={e => setEmailInput(e.target.value)}
|
||||||
placeholder="your@email.com"
|
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)' }}
|
style={{ background: 'rgba(16, 17, 20, 0.6)', border: '1px solid var(--glass-border)' }}
|
||||||
onFocus={e => { e.currentTarget.style.borderColor = 'var(--color-primary)' }}
|
onFocus={e => { e.currentTarget.style.borderColor = 'var(--color-primary)' }}
|
||||||
onBlur={e => { e.currentTarget.style.borderColor = 'var(--glass-border)' }}
|
onBlur={e => { e.currentTarget.style.borderColor = 'var(--glass-border)' }}
|
||||||
@@ -473,7 +473,7 @@ export default function SurveyPage() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!emailInput.trim() || emailSending}
|
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 ? (
|
{emailSending ? (
|
||||||
<>
|
<>
|
||||||
@@ -496,13 +496,13 @@ export default function SurveyPage() {
|
|||||||
|
|
||||||
{/* Copy + Finish buttons */}
|
{/* Copy + Finish buttons */}
|
||||||
<div className="flex gap-2.5 justify-center flex-wrap">
|
<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>
|
<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
|
Copy to Clipboard
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/survey/thank-you')}
|
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
|
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>
|
<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 } }) {
|
function ScenarioBox({ scenario }: { scenario: { title: string; symptom: string; details: string } }) {
|
||||||
return (
|
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="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-label text-[10px] uppercase tracking-widest mb-2 font-semibold" style={{ color: 'var(--color-primary)' }}>{scenario.title}</div>
|
<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">
|
<div className="sm:flex gap-2 mb-1">
|
||||||
<span className="text-muted-foreground font-medium whitespace-nowrap">Symptom:</span>
|
<span className="text-[#848b9b] font-medium whitespace-nowrap">Symptom:</span>
|
||||||
<span className="text-muted-foreground/80">{scenario.symptom}</span>
|
<span className="text-[#848b9b]/80">{scenario.symptom}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:flex gap-2">
|
<div className="sm:flex gap-2">
|
||||||
<span className="text-muted-foreground font-medium whitespace-nowrap">Details:</span>
|
<span className="text-[#848b9b] font-medium whitespace-nowrap">Details:</span>
|
||||||
<span className="text-muted-foreground/80">{scenario.details}</span>
|
<span className="text-[#848b9b]/80">{scenario.details}</span>
|
||||||
</div>
|
</div>
|
||||||
</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 }) {
|
function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQuestion; answer?: string | string[]; setAnswer: (id: string, val: string | string[]) => void }) {
|
||||||
return (
|
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="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-label text-[11px] mb-1.5 font-medium" style={{ color: 'var(--color-primary)' }}>Q{q.num}</div>
|
<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-foreground leading-snug mb-1">{q.text}</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-muted-foreground mb-3 sm:mb-4 leading-snug">{q.hint}</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.hint && <div className="mb-3 sm:mb-4" />}
|
||||||
|
|
||||||
{q.type === 'mc' && q.options && (
|
{q.type === 'mc' && q.options && (
|
||||||
@@ -600,7 +600,7 @@ function QuestionCard({ question: q, answer, setAnswer }: { question: SurveyQues
|
|||||||
value={(answer as string) || ''}
|
value={(answer as string) || ''}
|
||||||
onChange={e => setAnswer(q.id, e.target.value)}
|
onChange={e => setAnswer(q.id, e.target.value)}
|
||||||
placeholder="Type your answer here..."
|
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={{
|
style={{
|
||||||
background: 'rgba(16, 17, 20, 0.6)',
|
background: 'rgba(16, 17, 20, 0.6)',
|
||||||
border: '1px solid var(--glass-border)',
|
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
|
const numVal = value ? parseInt(value) : q.min || 0
|
||||||
return (
|
return (
|
||||||
<div className="py-2">
|
<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 || ''}
|
{numVal}{q.suffix || ''}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<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%)`,
|
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.low_label}</span>
|
||||||
<span>{q.high_label}</span>
|
<span>{q.high_label}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -739,7 +739,7 @@ function DragRank({ items, onChange }: { items: string[]; onChange: (items: stri
|
|||||||
<div className="shrink-0 text-brand-text-muted">
|
<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>
|
<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>
|
||||||
<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 className="flex-1 leading-snug">{item}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default function SurveyThankYouPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Thank You" description="Thank you for your feedback on ResolutionFlow" />
|
<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 */}
|
{/* Atmosphere orbs */}
|
||||||
<div className="pointer-events-none fixed inset-0 z-0 overflow-hidden" aria-hidden="true">
|
<div className="pointer-events-none fixed inset-0 z-0 overflow-hidden" aria-hidden="true">
|
||||||
<div
|
<div
|
||||||
@@ -19,11 +19,11 @@ export default function SurveyThankYouPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top bar */}
|
{/* 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">
|
<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" />
|
<BrandLogo size="sm" />
|
||||||
<span>Resolution<span className="text-gradient-brand">Flow</span></span>
|
<span>Resolution<span className="text-[#67e8f9]">Flow</span></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<h1 className="font-heading text-[clamp(24px,4vw,32px)] font-extrabold leading-tight mb-3">
|
||||||
Thank You!
|
Thank You!
|
||||||
</h1>
|
</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.
|
Your response has been recorded. Your expertise will directly shape how FlowPilot thinks about troubleshooting.
|
||||||
</p>
|
</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.
|
You can safely close this browser window now.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -52,21 +52,21 @@ export default function SurveyThankYouPage() {
|
|||||||
|
|
||||||
{/* Feedback callout */}
|
{/* Feedback callout */}
|
||||||
<div
|
<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">
|
<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">
|
<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"/>
|
<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>
|
</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?
|
Have Feedback?
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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{' '}
|
If you have any feedback unrelated to the survey, we'd love to hear from you at{' '}
|
||||||
<a
|
<a
|
||||||
href="mailto:feedback@resolutionflow.com"
|
href="mailto:feedback@resolutionflow.com"
|
||||||
className="text-primary hover:underline font-medium"
|
className="text-[#22d3ee] hover:underline font-medium"
|
||||||
>
|
>
|
||||||
feedback@resolutionflow.com
|
feedback@resolutionflow.com
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default function TeamAnalyticsPage() {
|
|||||||
action={
|
action={
|
||||||
<Link
|
<Link
|
||||||
to="/trees"
|
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
|
Run Your First Session
|
||||||
</Link>
|
</Link>
|
||||||
@@ -108,22 +108,22 @@ export default function TeamAnalyticsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span title="Team Analytics">
|
<span title="Team Analytics">
|
||||||
<BarChart3 size={24} className="text-foreground" />
|
<BarChart3 size={24} className="text-[#e2e5eb]" />
|
||||||
</span>
|
</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>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link
|
<Link
|
||||||
to="/analytics/me"
|
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
|
My Stats
|
||||||
</Link>
|
</Link>
|
||||||
<select
|
<select
|
||||||
value={period}
|
value={period}
|
||||||
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
|
||||||
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-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) => (
|
{PERIOD_OPTIONS.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>
|
<option key={opt.value} value={opt.value}>
|
||||||
@@ -159,8 +159,8 @@ export default function TeamAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Area Chart — Sessions over Time */}
|
{/* Area Chart — Sessions over Time */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Sessions Over Time
|
Sessions Over Time
|
||||||
</h2>
|
</h2>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
@@ -246,7 +246,7 @@ export default function TeamAnalyticsPage() {
|
|||||||
className="h-2.5 w-2.5 rounded-full"
|
className="h-2.5 w-2.5 rounded-full"
|
||||||
style={{ backgroundColor: color }}
|
style={{ backgroundColor: color }}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-muted-foreground capitalize">
|
<span className="text-xs text-[#848b9b] capitalize">
|
||||||
{key}
|
{key}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -257,27 +257,27 @@ export default function TeamAnalyticsPage() {
|
|||||||
{/* Two-Column: Top Flows & Top Engineers */}
|
{/* Two-Column: Top Flows & Top Engineers */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* Top Flows */}
|
{/* Top Flows */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Top Flows
|
Top Flows
|
||||||
</h2>
|
</h2>
|
||||||
{top_flows.length === 0 ? (
|
{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">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="text-left py-2 text-foreground font-medium">
|
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Sessions
|
Sessions
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Completion
|
Completion
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Median
|
Median
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -286,18 +286,18 @@ export default function TeamAnalyticsPage() {
|
|||||||
{top_flows.map((flow) => (
|
{top_flows.map((flow) => (
|
||||||
<tr
|
<tr
|
||||||
key={flow.tree_id}
|
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}
|
{flow.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{flow.sessions}
|
{flow.sessions}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{(flow.completion_rate * 100).toFixed(1)}%
|
{(flow.completion_rate * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{flow.median_duration_minutes} min
|
{flow.median_duration_minutes} min
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -309,27 +309,27 @@ export default function TeamAnalyticsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top Engineers */}
|
{/* Top Engineers */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="text-sm font-semibold text-foreground mb-4">
|
<h2 className="text-sm font-semibold text-[#e2e5eb] mb-4">
|
||||||
Top Engineers
|
Top Engineers
|
||||||
</h2>
|
</h2>
|
||||||
{top_engineers.length === 0 ? (
|
{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">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="text-left py-2 text-foreground font-medium">
|
<th className="text-left py-2 text-[#e2e5eb] font-medium">
|
||||||
Name
|
Name
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Sessions
|
Sessions
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Completion
|
Completion
|
||||||
</th>
|
</th>
|
||||||
<th className="text-right py-2 text-foreground font-medium">
|
<th className="text-right py-2 text-[#e2e5eb] font-medium">
|
||||||
Median
|
Median
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -338,18 +338,18 @@ export default function TeamAnalyticsPage() {
|
|||||||
{top_engineers.map((eng) => (
|
{top_engineers.map((eng) => (
|
||||||
<tr
|
<tr
|
||||||
key={eng.user_id}
|
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}
|
{eng.name}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{eng.sessions}
|
{eng.sessions}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{(eng.completion_rate * 100).toFixed(1)}%
|
{(eng.completion_rate * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 text-right text-muted-foreground">
|
<td className="py-2 text-right text-[#848b9b]">
|
||||||
{eng.median_duration_minutes} min
|
{eng.median_duration_minutes} min
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -374,12 +374,12 @@ function StatCard({
|
|||||||
value: string
|
value: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
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">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<Icon size={16} className="text-muted-foreground" />
|
<Icon size={16} className="text-[#848b9b]" />
|
||||||
<span className="text-sm text-muted-foreground">{label}</span>
|
<span className="text-sm text-[#848b9b]">{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-3xl font-bold text-foreground">{value}</p>
|
<p className="text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,41 +5,41 @@ export default function TermsPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Terms of Service" description="ResolutionFlow Terms of Service" />
|
<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">
|
<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>
|
<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>
|
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xl font-semibold text-foreground mb-3">6. Contact</h2>
|
<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-primary hover:underline">hello@resolutionflow.com</a>.</p>
|
<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>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -509,9 +509,9 @@ export function TreeEditorPage() {
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col items-center justify-center px-6 text-center">
|
<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" />
|
<Monitor className="mb-4 h-12 w-12 text-[#848b9b]" />
|
||||||
<h2 className="mb-2 text-xl font-heading font-semibold text-foreground">Desktop Required</h2>
|
<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-muted-foreground">
|
<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.
|
The tree editor requires a larger screen for the best experience. Please open this page on a desktop or tablet in landscape mode.
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => navigate('/trees')}>
|
<Button onClick={() => navigate('/trees')}>
|
||||||
@@ -528,10 +528,10 @@ export function TreeEditorPage() {
|
|||||||
|
|
||||||
{/* Draft Restore Prompt */}
|
{/* Draft Restore Prompt */}
|
||||||
{showDraftPrompt && (
|
{showDraftPrompt && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
<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-foreground">Restore Draft?</h2>
|
<h2 className="mb-2 text-lg font-heading font-semibold text-[#e2e5eb]">Restore Draft?</h2>
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p className="mb-4 text-sm text-[#848b9b]">
|
||||||
You have an unsaved draft from a previous session. Would you like to restore it?
|
You have an unsaved draft from a previous session. Would you like to restore it?
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -548,10 +548,10 @@ export function TreeEditorPage() {
|
|||||||
|
|
||||||
{/* Unsaved Changes Dialog */}
|
{/* Unsaved Changes Dialog */}
|
||||||
{blocker.state === 'blocked' && (
|
{blocker.state === 'blocked' && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
|
||||||
<div className="bg-card border border-border rounded-xl w-full max-w-md p-6 shadow-lg">
|
<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-foreground">Unsaved Changes</h2>
|
<h2 className="mb-2 text-lg font-heading font-semibold text-[#e2e5eb]">Unsaved Changes</h2>
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p className="mb-4 text-sm text-[#848b9b]">
|
||||||
You have unsaved changes. Are you sure you want to leave?
|
You have unsaved changes. Are you sure you want to leave?
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -567,17 +567,17 @@ export function TreeEditorPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="flex items-center justify-between border-b border-border bg-card px-4 py-2">
|
<div className="flex items-center justify-between border-b border-[#1e2130] bg-[#14161d] px-4 py-2">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/trees')}
|
onClick={() => navigate('/trees')}
|
||||||
className="text-sm text-muted-foreground hover:text-foreground"
|
className="text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
← Back to Library
|
← Back to Library
|
||||||
</button>
|
</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'}
|
{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>
|
</h1>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{treeStatus === 'draft' && (
|
{treeStatus === 'draft' && (
|
||||||
@@ -596,7 +596,7 @@ export function TreeEditorPage() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Mode Toggle */}
|
{/* Mode Toggle */}
|
||||||
<div className="flex items-center rounded-md border border-border">
|
<div className="flex items-center rounded-md border border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setEditorMode('form')}
|
onClick={() => setEditorMode('form')}
|
||||||
@@ -604,8 +604,8 @@ export function TreeEditorPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-l-md px-3 py-1.5 text-xs font-medium transition-colors',
|
'flex items-center gap-1.5 rounded-l-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||||
editorMode === 'form'
|
editorMode === 'form'
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<LayoutList className="h-3.5 w-3.5" />
|
<LayoutList className="h-3.5 w-3.5" />
|
||||||
@@ -623,8 +623,8 @@ export function TreeEditorPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-r-md px-3 py-1.5 text-xs font-medium transition-colors',
|
'flex items-center gap-1.5 rounded-r-md px-3 py-1.5 text-xs font-medium transition-colors',
|
||||||
editorMode === 'code'
|
editorMode === 'code'
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:bg-accent/50 hover:text-foreground'
|
: 'text-[#848b9b] hover:bg-accent/50 hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Code2 className="h-3.5 w-3.5" />
|
<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" />
|
<div className="mx-1 h-6 w-px bg-border" />
|
||||||
|
|
||||||
{/* Undo/Redo */}
|
{/* Undo/Redo */}
|
||||||
<div className="flex items-center rounded-md border border-border">
|
<div className="flex items-center rounded-md border border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleUndo}
|
onClick={handleUndo}
|
||||||
@@ -644,8 +644,8 @@ export function TreeEditorPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-l-md p-2 transition-colors',
|
'rounded-l-md p-2 transition-colors',
|
||||||
pastStates.length > 0
|
pastStates.length > 0
|
||||||
? 'text-foreground hover:bg-accent/50 active:bg-accent'
|
? 'text-[#e2e5eb] hover:bg-accent/50 active:bg-accent'
|
||||||
: 'text-muted-foreground/50 cursor-not-allowed'
|
: 'text-[#848b9b]/50 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Undo2 className="h-4 w-4" />
|
<Undo2 className="h-4 w-4" />
|
||||||
@@ -659,8 +659,8 @@ export function TreeEditorPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-r-md p-2 transition-colors',
|
'rounded-r-md p-2 transition-colors',
|
||||||
futureStates.length > 0
|
futureStates.length > 0
|
||||||
? 'text-foreground hover:bg-accent/50 active:bg-accent'
|
? 'text-[#e2e5eb] hover:bg-accent/50 active:bg-accent'
|
||||||
: 'text-muted-foreground/50 cursor-not-allowed'
|
: 'text-[#848b9b]/50 cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Redo2 className="h-4 w-4" />
|
<Redo2 className="h-4 w-4" />
|
||||||
@@ -681,10 +681,10 @@ export function TreeEditorPage() {
|
|||||||
}}
|
}}
|
||||||
title="Edit flow metadata (name, description, category, tags)"
|
title="Edit flow metadata (name, description, category, tags)"
|
||||||
className={cn(
|
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
|
isMetadataOpen
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
@@ -698,10 +698,10 @@ export function TreeEditorPage() {
|
|||||||
onClick={() => setShowAnalytics(!showAnalytics)}
|
onClick={() => setShowAnalytics(!showAnalytics)}
|
||||||
title="Toggle flow analytics panel"
|
title="Toggle flow analytics panel"
|
||||||
className={cn(
|
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
|
showAnalytics
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<BarChart3 className="h-4 w-4" />
|
<BarChart3 className="h-4 w-4" />
|
||||||
@@ -715,8 +715,8 @@ export function TreeEditorPage() {
|
|||||||
onClick={() => setShowExportModal(true)}
|
onClick={() => setShowExportModal(true)}
|
||||||
title="Export flow as .rfflow file"
|
title="Export flow as .rfflow file"
|
||||||
className={cn(
|
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',
|
'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-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
@@ -739,10 +739,10 @@ export function TreeEditorPage() {
|
|||||||
}}
|
}}
|
||||||
title="Toggle AI Assist panel"
|
title="Toggle AI Assist panel"
|
||||||
className={cn(
|
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
|
editorAI.isOpen
|
||||||
? 'bg-primary/10 text-primary border-primary/30'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/30'
|
||||||
: 'bg-card text-muted-foreground hover:bg-accent hover:text-foreground'
|
: 'bg-[#14161d] text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Sparkles className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
@@ -754,8 +754,8 @@ export function TreeEditorPage() {
|
|||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
title="Validate tree structure (checks for errors and warnings)"
|
title="Validate tree structure (checks for errors and warnings)"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-muted-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-foreground disabled:opacity-50'
|
'hover:bg-accent hover:text-[#e2e5eb] disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CheckCircle2 className="h-4 w-4" />
|
<CheckCircle2 className="h-4 w-4" />
|
||||||
@@ -800,7 +800,7 @@ export function TreeEditorPage() {
|
|||||||
|
|
||||||
{/* Import provenance */}
|
{/* Import provenance */}
|
||||||
{importMetadata && (
|
{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" />
|
<FileText className="h-3 w-3" />
|
||||||
<span>
|
<span>
|
||||||
Imported{importMetadata.original_author_name ? ` from ${importMetadata.original_author_name}` : ''}
|
Imported{importMetadata.original_author_name ? ` from ${importMetadata.original_author_name}` : ''}
|
||||||
@@ -825,7 +825,7 @@ export function TreeEditorPage() {
|
|||||||
|
|
||||||
{/* Flow Analytics Panel (collapsible) */}
|
{/* Flow Analytics Panel (collapsible) */}
|
||||||
{showAnalytics && id && (
|
{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} />
|
<FlowAnalyticsPanel treeId={id} />
|
||||||
</div>
|
</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="container mx-auto px-4 py-6 sm:px-6 sm:py-8">
|
||||||
<div className="mb-6 flex flex-col gap-4 sm:mb-8 sm:flex-row sm:items-start sm:justify-between">
|
<div className="mb-6 flex flex-col gap-4 sm:mb-8 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold 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'}
|
{typeFilter === 'procedural' ? 'Projects' : typeFilter === 'troubleshooting' ? 'Troubleshooting Flows' : typeFilter === 'maintenance' ? 'Maintenance Flows' : 'Flow Library'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
{typeFilter === 'procedural'
|
{typeFilter === 'procedural'
|
||||||
? 'Step-by-step projects and runbooks'
|
? 'Step-by-step projects and runbooks'
|
||||||
: typeFilter === 'troubleshooting'
|
: typeFilter === 'troubleshooting'
|
||||||
@@ -333,8 +333,8 @@ export function TreeLibraryPage() {
|
|||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-md border border-border bg-card px-3 py-2',
|
'flex-1 rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -348,8 +348,8 @@ export function TreeLibraryPage() {
|
|||||||
onChange={(e) => setSelectedCategoryId(e.target.value)}
|
onChange={(e) => setSelectedCategoryId(e.target.value)}
|
||||||
aria-label="Filter by category"
|
aria-label="Filter by category"
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md border border-border bg-card px-3 py-2',
|
'rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'text-[#e2e5eb] focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">All Categories</option>
|
<option value="">All Categories</option>
|
||||||
@@ -364,7 +364,7 @@ export function TreeLibraryPage() {
|
|||||||
{/* View Controls */}
|
{/* View Controls */}
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
{/* Type filter tabs */}
|
{/* 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) => (
|
{(['all', 'troubleshooting', 'procedural', 'maintenance'] as const).map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t}
|
key={t}
|
||||||
@@ -372,8 +372,8 @@ export function TreeLibraryPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
'rounded-md px-3 py-1 text-xs font-medium transition-colors',
|
||||||
typeFilter === t
|
typeFilter === t
|
||||||
? 'bg-accent text-foreground'
|
? 'bg-accent text-[#e2e5eb]'
|
||||||
: 'text-muted-foreground hover:text-foreground'
|
: 'text-[#848b9b] hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t === 'all' ? 'All' : t === 'troubleshooting' ? 'Troubleshooting' : t === 'procedural' ? 'Projects' : 'Maintenance'}
|
{t === 'all' ? 'All' : t === 'troubleshooting' ? 'Troubleshooting' : t === 'procedural' ? 'Projects' : 'Maintenance'}
|
||||||
@@ -392,9 +392,9 @@ export function TreeLibraryPage() {
|
|||||||
{/* Active Filters */}
|
{/* Active Filters */}
|
||||||
{hasActiveFilters && (
|
{hasActiveFilters && (
|
||||||
<div className="mb-6 flex flex-wrap items-center gap-2">
|
<div className="mb-6 flex flex-wrap items-center gap-2">
|
||||||
<span className="text-sm text-muted-foreground">Filters:</span>
|
<span className="text-sm text-[#848b9b]">Filters:</span>
|
||||||
{selectedFolderId && (
|
{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
|
Folder
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedFolderId(null)}
|
onClick={() => setSelectedFolderId(null)}
|
||||||
@@ -405,7 +405,7 @@ export function TreeLibraryPage() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{selectedCategoryId && (
|
{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}
|
{categories.find((c) => c.id === selectedCategoryId)?.name}
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedCategoryId('')}
|
onClick={() => setSelectedCategoryId('')}
|
||||||
@@ -418,7 +418,7 @@ export function TreeLibraryPage() {
|
|||||||
{selectedTags.map((tag) => (
|
{selectedTags.map((tag) => (
|
||||||
<span
|
<span
|
||||||
key={tag}
|
key={tag}
|
||||||
className="inline-flex items-center gap-1 rounded-full bg-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}
|
{tag}
|
||||||
<button
|
<button
|
||||||
@@ -431,7 +431,7 @@ export function TreeLibraryPage() {
|
|||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
onClick={clearAllFilters}
|
onClick={clearAllFilters}
|
||||||
className="text-sm text-muted-foreground hover:text-foreground"
|
className="text-sm text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Clear all
|
Clear all
|
||||||
</button>
|
</button>
|
||||||
@@ -442,12 +442,12 @@ export function TreeLibraryPage() {
|
|||||||
{visibleIncompleteSessions.length > 0 && (
|
{visibleIncompleteSessions.length > 0 && (
|
||||||
<div className="mb-6 space-y-2">
|
<div className="mb-6 space-y-2">
|
||||||
{visibleIncompleteSessions.map(s => (
|
{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">
|
<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'}
|
{s.tree_snapshot?.name || 'Unknown tree'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
{s.client_name && `${s.client_name} · `}
|
{s.client_name && `${s.client_name} · `}
|
||||||
{s.started_at ? `Started ${formatTimeAgo(s.started_at)}` : 'Not started'}
|
{s.started_at ? `Started ${formatTimeAgo(s.started_at)}` : 'Not started'}
|
||||||
</p>
|
</p>
|
||||||
@@ -462,7 +462,7 @@ export function TreeLibraryPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<button
|
<button
|
||||||
onClick={() => dismissSession(s.id)}
|
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" />
|
<X className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -480,8 +480,8 @@ export function TreeLibraryPage() {
|
|||||||
state: { prefillClientName: lastSessionData.client_name, prefillTicketNumber: lastSessionData.ticket_number },
|
state: { prefillClientName: lastSessionData.client_name, prefillTicketNumber: lastSessionData.ticket_number },
|
||||||
})}
|
})}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-lg border border-border px-4 py-2.5 text-sm text-muted-foreground',
|
'flex items-center gap-2 rounded-lg border border-[#1e2130] px-4 py-2.5 text-sm text-[#848b9b]',
|
||||||
'hover:border-border hover:bg-accent hover:text-foreground'
|
'hover:border-[#1e2130] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<RotateCcw className="h-4 w-4" />
|
<RotateCcw className="h-4 w-4" />
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ export function TreeNavigationPage() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/trees')}
|
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
|
Back to trees
|
||||||
</button>
|
</button>
|
||||||
@@ -615,17 +615,17 @@ export function TreeNavigationPage() {
|
|||||||
if (showMetadataForm) {
|
if (showMetadataForm) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-lg px-4 py-8">
|
<div className="container mx-auto max-w-lg px-4 py-8">
|
||||||
<h1 className="mb-2 text-2xl font-bold font-heading text-foreground">{tree.name}</h1>
|
<h1 className="mb-2 text-2xl font-bold font-heading text-[#e2e5eb]">{tree.name}</h1>
|
||||||
<p className="mb-6 text-muted-foreground">{tree.description}</p>
|
<p className="mb-6 text-[#848b9b]">{tree.description}</p>
|
||||||
|
|
||||||
<div className="bg-card border border-border rounded-xl space-y-4 p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl space-y-4 p-6">
|
||||||
<h2 className="font-semibold text-foreground">Session Details</h2>
|
<h2 className="font-semibold text-[#e2e5eb]">Session Details</h2>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Optional: Add ticket and client info for easier tracking
|
Optional: Add ticket and client info for easier tracking
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Ticket Number
|
Ticket Number
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -634,15 +634,15 @@ export function TreeNavigationPage() {
|
|||||||
onChange={(e) => setTicketNumber(e.target.value)}
|
onChange={(e) => setTicketNumber(e.target.value)}
|
||||||
placeholder="e.g., INC0012345"
|
placeholder="e.g., INC0012345"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Client Name
|
Client Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -651,8 +651,8 @@ export function TreeNavigationPage() {
|
|||||||
onChange={(e) => setClientName(e.target.value)}
|
onChange={(e) => setClientName(e.target.value)}
|
||||||
placeholder="e.g., Acme Corp"
|
placeholder="e.g., Acme Corp"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -685,9 +685,9 @@ export function TreeNavigationPage() {
|
|||||||
<div className="mb-6 flex items-center justify-between">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3">
|
<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 && (
|
{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" />
|
<Clock className="h-4 w-4" />
|
||||||
{timerDisplay}
|
{timerDisplay}
|
||||||
</span>
|
</span>
|
||||||
@@ -695,14 +695,14 @@ export function TreeNavigationPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShortcutsModalOpen(true)}
|
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"
|
title="Keyboard shortcuts"
|
||||||
>
|
>
|
||||||
<HelpCircle className="h-4 w-4" />
|
<HelpCircle className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{(ticketNumber || clientName) && (
|
{(ticketNumber || clientName) && (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
{ticketNumber && `Ticket: ${ticketNumber}`}
|
{ticketNumber && `Ticket: ${ticketNumber}`}
|
||||||
{ticketNumber && clientName && ' · '}
|
{ticketNumber && clientName && ' · '}
|
||||||
{clientName && `Client: ${clientName}`}
|
{clientName && `Client: ${clientName}`}
|
||||||
@@ -725,8 +725,8 @@ export function TreeNavigationPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowSharePopover(!showSharePopover)}
|
onClick={() => setShowSharePopover(!showSharePopover)}
|
||||||
className={cn(
|
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',
|
'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-foreground transition-colors'
|
'hover:bg-accent hover:text-[#e2e5eb] transition-colors'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Copy className="h-3.5 w-3.5" />
|
<Copy className="h-3.5 w-3.5" />
|
||||||
@@ -734,7 +734,7 @@ export function TreeNavigationPage() {
|
|||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
{showSharePopover && (
|
{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 */}
|
{/* Copy Progress Summary */}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -743,8 +743,8 @@ export function TreeNavigationPage() {
|
|||||||
}}
|
}}
|
||||||
disabled={isCopyingForTicket}
|
disabled={isCopyingForTicket}
|
||||||
className={cn(
|
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',
|
'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-foreground disabled:opacity-50'
|
'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" />}
|
{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}
|
disabled={isCopyingShareLink}
|
||||||
className={cn(
|
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',
|
'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-foreground disabled:opacity-50'
|
'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" />}
|
{copiedShareLink ? <Check className="h-4 w-4 text-emerald-400" /> : <Link2 className="h-4 w-4" />}
|
||||||
{isCopyingShareLink ? 'Loading...' : copiedShareLink ? 'Copied!' : 'Copy Share Link'}
|
{isCopyingShareLink ? 'Loading...' : copiedShareLink ? 'Copied!' : 'Copy Share Link'}
|
||||||
</button>
|
</button>
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<div className="border-t border-border my-1" />
|
<div className="border-t border-[#1e2130] my-1" />
|
||||||
{/* Manage Share Links */}
|
{/* Manage Share Links */}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -774,8 +774,8 @@ export function TreeNavigationPage() {
|
|||||||
setShowShareModal(true)
|
setShowShareModal(true)
|
||||||
}}
|
}}
|
||||||
className={cn(
|
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',
|
'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-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
@@ -786,7 +786,7 @@ export function TreeNavigationPage() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/sessions')}
|
onClick={() => navigate('/sessions')}
|
||||||
className="rounded-md px-3 py-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md px-3 py-1.5 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Exit
|
Exit
|
||||||
</button>
|
</button>
|
||||||
@@ -814,17 +814,17 @@ export function TreeNavigationPage() {
|
|||||||
const truncatedLabel = label.length > 30 ? `${label.slice(0, 30)}...` : label
|
const truncatedLabel = label.length > 30 ? `${label.slice(0, 30)}...` : label
|
||||||
return (
|
return (
|
||||||
<span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
|
<span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
|
||||||
{index > 0 && <span className="text-muted-foreground">→</span>}
|
{index > 0 && <span className="text-[#848b9b]">→</span>}
|
||||||
{index < pathTaken.length - 1 ? (
|
{index < pathTaken.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleBreadcrumbJump(nodeId, index)}
|
onClick={() => handleBreadcrumbJump(nodeId, index)}
|
||||||
className="text-muted-foreground hover:text-foreground hover:underline"
|
className="text-[#848b9b] hover:text-[#e2e5eb] hover:underline"
|
||||||
>
|
>
|
||||||
{truncatedLabel}
|
{truncatedLabel}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-[#e2e5eb]">
|
||||||
{truncatedLabel}
|
{truncatedLabel}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -834,7 +834,7 @@ export function TreeNavigationPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Current Node */}
|
{/* 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 */}
|
{/* Answer placeholder guard */}
|
||||||
{currentNode && currentNode.type === 'answer' && (
|
{currentNode && currentNode.type === 'answer' && (
|
||||||
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-6 text-center">
|
<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 */}
|
{/* Decision Node */}
|
||||||
{currentNode && currentNode.type === 'decision' && (
|
{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}
|
{currentNode.question}
|
||||||
</h2>
|
</h2>
|
||||||
{currentNode.help_text && (
|
{currentNode.help_text && (
|
||||||
<div className="mb-4 text-sm text-muted-foreground">
|
<div className="mb-4 text-sm text-[#848b9b]">
|
||||||
<MarkdownContent content={currentNode.help_text} />
|
<MarkdownContent content={currentNode.help_text} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -862,7 +862,7 @@ export function TreeNavigationPage() {
|
|||||||
onClick={() => handleSelectOption(option.id, option.label, option.next_node_id)}
|
onClick={() => handleSelectOption(option.id, option.label, option.next_node_id)}
|
||||||
disabled={!!selectingOption}
|
disabled={!!selectingOption}
|
||||||
className={cn(
|
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',
|
'hover:border-primary/30 hover:bg-accent',
|
||||||
'flex items-center gap-3',
|
'flex items-center gap-3',
|
||||||
selectingOption && selectingOption !== option.id && 'opacity-50 pointer-events-none'
|
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" />
|
<Spinner size="sm" className="h-4 w-4 border-t-foreground" />
|
||||||
</span>
|
</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}
|
{index + 1}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -886,7 +886,7 @@ export function TreeNavigationPage() {
|
|||||||
{/* Previously-created custom steps at this node */}
|
{/* Previously-created custom steps at this node */}
|
||||||
{customStepFlow.customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
|
{customStepFlow.customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
|
||||||
<div className="mt-2 space-y-2">
|
<div className="mt-2 space-y-2">
|
||||||
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
<p className="text-xs font-medium uppercase tracking-wide text-[#848b9b]">
|
||||||
Your Custom Steps
|
Your Custom Steps
|
||||||
</p>
|
</p>
|
||||||
{customStepFlow.customSteps
|
{customStepFlow.customSteps
|
||||||
@@ -897,12 +897,12 @@ export function TreeNavigationPage() {
|
|||||||
key={cs.id}
|
key={cs.id}
|
||||||
onClick={() => customStepFlow.handleNavigateToCustomStep(cs)}
|
onClick={() => customStepFlow.handleNavigateToCustomStep(cs)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-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',
|
'hover:border-primary/50 hover:bg-primary/20',
|
||||||
'flex items-center gap-3'
|
'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
|
Custom
|
||||||
</span>
|
</span>
|
||||||
<span>{cs.step_data.title}</span>
|
<span>{cs.step_data.title}</span>
|
||||||
@@ -914,7 +914,7 @@ export function TreeNavigationPage() {
|
|||||||
{/* Add Custom Step Button */}
|
{/* Add Custom Step Button */}
|
||||||
<button
|
<button
|
||||||
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
||||||
className="mt-2 inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-sm text-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" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
Add Custom Step
|
Add Custom Step
|
||||||
@@ -924,18 +924,18 @@ export function TreeNavigationPage() {
|
|||||||
|
|
||||||
{/* Custom Step Node */}
|
{/* Custom Step Node */}
|
||||||
{currentCustomStep && (
|
{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 */}
|
{/* 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
|
Custom Step
|
||||||
</span>
|
</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}
|
{currentCustomStep.step_data.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{currentCustomStep.step_data.content.instructions && (
|
{currentCustomStep.step_data.content.instructions && (
|
||||||
<div className="mb-4 text-muted-foreground">
|
<div className="mb-4 text-[#848b9b]">
|
||||||
<MarkdownContent content={currentCustomStep.step_data.content.instructions} />
|
<MarkdownContent content={currentCustomStep.step_data.content.instructions} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -948,11 +948,11 @@ export function TreeNavigationPage() {
|
|||||||
|
|
||||||
{currentCustomStep.step_data.content.commands && currentCustomStep.step_data.content.commands.length > 0 && (
|
{currentCustomStep.step_data.content.commands && currentCustomStep.step_data.content.commands.length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
|
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Commands:</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{currentCustomStep.step_data.content.commands.map((cmd, index) => (
|
{currentCustomStep.step_data.content.commands.map((cmd, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<p className="mb-1 text-xs text-muted-foreground">{cmd.label}</p>
|
<p className="mb-1 text-xs text-[#848b9b]">{cmd.label}</p>
|
||||||
<div className="group relative">
|
<div className="group relative">
|
||||||
<code className="block rounded bg-accent p-2 pr-8 text-sm font-mono">
|
<code className="block rounded bg-accent p-2 pr-8 text-sm font-mono">
|
||||||
{cmd.command}
|
{cmd.command}
|
||||||
@@ -966,7 +966,7 @@ export function TreeNavigationPage() {
|
|||||||
{copiedCommand === cmd.command ? (
|
{copiedCommand === cmd.command ? (
|
||||||
<Check className="h-3.5 w-3.5 text-green-400" />
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -978,7 +978,7 @@ export function TreeNavigationPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCommandOutputOpen(!commandOutputOpen)}
|
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" />
|
<Terminal className="h-3.5 w-3.5" />
|
||||||
<span>Paste Output (Optional)</span>
|
<span>Paste Output (Optional)</span>
|
||||||
@@ -993,12 +993,12 @@ export function TreeNavigationPage() {
|
|||||||
rows={4}
|
rows={4}
|
||||||
maxLength={10000}
|
maxLength={10000}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border bg-accent px-3 py-2',
|
'block w-full rounded-md border border-[#1e2130] bg-accent px-3 py-2',
|
||||||
'font-mono text-sm text-foreground placeholder:text-muted-foreground',
|
'font-mono text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<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
|
{commandOutput.length.toLocaleString()} / 10,000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1035,8 +1035,8 @@ export function TreeNavigationPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
onClick={() => customStepFlow.setShowCustomStepModal(true)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-muted-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-foreground'
|
'hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
@@ -1062,17 +1062,17 @@ export function TreeNavigationPage() {
|
|||||||
{/* Action Node */}
|
{/* Action Node */}
|
||||||
{currentNode && currentNode.type === 'action' && (
|
{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}
|
{currentNode.title}
|
||||||
</h2>
|
</h2>
|
||||||
{currentNode.description && (
|
{currentNode.description && (
|
||||||
<div className="mb-4 text-muted-foreground">
|
<div className="mb-4 text-[#848b9b]">
|
||||||
<MarkdownContent content={currentNode.description} />
|
<MarkdownContent content={currentNode.description} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currentNode.commands && currentNode.commands.length > 0 && (
|
{currentNode.commands && currentNode.commands.length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="mb-2 text-sm font-medium text-foreground">Commands:</p>
|
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Commands:</p>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{currentNode.commands.map((cmd, index) => (
|
{currentNode.commands.map((cmd, index) => (
|
||||||
<div key={index} className="group relative">
|
<div key={index} className="group relative">
|
||||||
@@ -1088,7 +1088,7 @@ export function TreeNavigationPage() {
|
|||||||
{copiedCommand === cmd ? (
|
{copiedCommand === cmd ? (
|
||||||
<Check className="h-3.5 w-3.5 text-green-400" />
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1099,7 +1099,7 @@ export function TreeNavigationPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCommandOutputOpen(!commandOutputOpen)}
|
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" />
|
<Terminal className="h-3.5 w-3.5" />
|
||||||
<span>Paste Output (Optional)</span>
|
<span>Paste Output (Optional)</span>
|
||||||
@@ -1114,12 +1114,12 @@ export function TreeNavigationPage() {
|
|||||||
rows={4}
|
rows={4}
|
||||||
maxLength={10000}
|
maxLength={10000}
|
||||||
className={cn(
|
className={cn(
|
||||||
'block w-full rounded-md border border-border bg-accent px-3 py-2',
|
'block w-full rounded-md border border-[#1e2130] bg-accent px-3 py-2',
|
||||||
'font-mono text-sm text-foreground placeholder:text-muted-foreground',
|
'font-mono text-sm text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<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
|
{commandOutput.length.toLocaleString()} / 10,000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1128,7 +1128,7 @@ export function TreeNavigationPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currentNode.expected_outcome && (
|
{currentNode.expected_outcome && (
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p className="mb-4 text-sm text-[#848b9b]">
|
||||||
<strong>Expected outcome:</strong> {currentNode.expected_outcome}
|
<strong>Expected outcome:</strong> {currentNode.expected_outcome}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -1148,18 +1148,18 @@ export function TreeNavigationPage() {
|
|||||||
Solution
|
Solution
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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}
|
{currentNode.title}
|
||||||
</h2>
|
</h2>
|
||||||
{currentNode.description && (
|
{currentNode.description && (
|
||||||
<div className="mb-4 text-muted-foreground">
|
<div className="mb-4 text-[#848b9b]">
|
||||||
<MarkdownContent content={currentNode.description} />
|
<MarkdownContent content={currentNode.description} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currentNode.resolution_steps && currentNode.resolution_steps.length > 0 && (
|
{currentNode.resolution_steps && currentNode.resolution_steps.length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="mb-2 text-sm font-medium text-foreground">Resolution steps:</p>
|
<p className="mb-2 text-sm font-medium text-[#e2e5eb]">Resolution steps:</p>
|
||||||
<ol className="list-inside list-decimal space-y-1 text-sm text-muted-foreground">
|
<ol className="list-inside list-decimal space-y-1 text-sm text-[#848b9b]">
|
||||||
{currentNode.resolution_steps.map((step, index) => (
|
{currentNode.resolution_steps.map((step, index) => (
|
||||||
<li key={index}>{step}</li>
|
<li key={index}>{step}</li>
|
||||||
))}
|
))}
|
||||||
@@ -1181,14 +1181,14 @@ export function TreeNavigationPage() {
|
|||||||
|
|
||||||
{/* Step Feedback */}
|
{/* Step Feedback */}
|
||||||
{session && (currentNode || currentCustomStep) && (
|
{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} />
|
<StepFeedback stepId={currentCustomStep?.id || currentNodeId} sessionId={session.id} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
<div className="mt-6 border-t border-border pt-4">
|
<div className="mt-6 border-t border-[#1e2130] pt-4">
|
||||||
<label className="block text-sm font-medium text-foreground">
|
<label className="block text-sm font-medium text-[#e2e5eb]">
|
||||||
Notes (optional)
|
Notes (optional)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -1198,8 +1198,8 @@ export function TreeNavigationPage() {
|
|||||||
placeholder="Add any notes for this step..."
|
placeholder="Add any notes for this step..."
|
||||||
rows={2}
|
rows={2}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-md border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -1216,15 +1216,15 @@ export function TreeNavigationPage() {
|
|||||||
{pathTaken.length > 1 && (
|
{pathTaken.length > 1 && (
|
||||||
<button
|
<button
|
||||||
onClick={handleGoBack}
|
onClick={handleGoBack}
|
||||||
className="mt-4 text-sm text-muted-foreground hover:text-foreground"
|
className="mt-4 text-sm text-[#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>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Keyboard Shortcuts Hint */}
|
{/* Keyboard Shortcuts Hint */}
|
||||||
{currentNode && (
|
{currentNode && (
|
||||||
<div className="mt-4 border-t border-border pt-3 text-xs text-muted-foreground">
|
<div className="mt-4 border-t border-[#1e2130] pt-3 text-xs text-[#848b9b]">
|
||||||
<span className="font-medium">Keyboard:</span>{' '}
|
<span className="font-medium">Keyboard:</span>{' '}
|
||||||
{currentNode.type === 'decision' && currentOptions.length > 0 && (
|
{currentNode.type === 'decision' && currentOptions.length > 0 && (
|
||||||
<span>1-{Math.min(currentOptions.length, 9)} select option</span>
|
<span>1-{Math.min(currentOptions.length, 9)} select option</span>
|
||||||
@@ -1301,24 +1301,24 @@ export function TreeNavigationPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-3 text-sm">
|
<div className="space-y-3 text-sm">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">Select option</span>
|
<span className="text-[#848b9b]">Select option</span>
|
||||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">1-9</span>
|
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">1-9</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">Go back</span>
|
<span className="text-[#848b9b]">Go back</span>
|
||||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Esc</span>
|
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Esc</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">Continue / Complete</span>
|
<span className="text-[#848b9b]">Continue / Complete</span>
|
||||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Enter</span>
|
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Enter</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">Focus notes</span>
|
<span className="text-[#848b9b]">Focus notes</span>
|
||||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">Tab</span>
|
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">Tab</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground">Show shortcuts</span>
|
<span className="text-[#848b9b]">Show shortcuts</span>
|
||||||
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-foreground">?</span>
|
<span className="rounded bg-accent px-2 py-0.5 font-mono text-xs text-[#e2e5eb]">?</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -26,24 +26,24 @@ export function VerifyEmailPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageMeta title="Verify Email" description="Verify your ResolutionFlow email address" />
|
<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="flex min-h-screen items-center justify-center bg-[#0c0d10] p-4">
|
||||||
<div className="glass-card-static w-full max-w-md p-8 text-center">
|
<div className="card-flat w-full max-w-md p-8 text-center">
|
||||||
{status === 'loading' && (
|
{status === 'loading' && (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mx-auto h-12 w-12 animate-spin text-primary" />
|
<Loader2 className="mx-auto h-12 w-12 animate-spin text-[#22d3ee]" />
|
||||||
<p className="mt-4 text-foreground">Verifying your email...</p>
|
<p className="mt-4 text-[#e2e5eb]">Verifying your email...</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{status === 'success' && (
|
{status === 'success' && (
|
||||||
<>
|
<>
|
||||||
<CheckCircle2 className="mx-auto h-12 w-12 text-emerald-400" />
|
<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>
|
<h1 className="mt-4 text-xl font-bold font-heading text-[#e2e5eb]">Email Verified</h1>
|
||||||
<p className="mt-2 text-muted-foreground">Your email has been successfully verified.</p>
|
<p className="mt-2 text-[#848b9b]">Your email has been successfully verified.</p>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-6 inline-flex items-center rounded-[10px] bg-gradient-brand px-6 py-2 text-sm font-semibold text-brand-dark',
|
'mt-6 inline-flex items-center rounded-lg bg-[#22d3ee] px-6 py-2 text-sm font-semibold text-brand-dark',
|
||||||
'shadow-lg shadow-primary/20 hover:opacity-90'
|
'hover:brightness-110'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Go to Dashboard
|
Go to Dashboard
|
||||||
@@ -53,12 +53,12 @@ export function VerifyEmailPage() {
|
|||||||
{status === 'error' && (
|
{status === 'error' && (
|
||||||
<>
|
<>
|
||||||
<XCircle className="mx-auto h-12 w-12 text-rose-500" />
|
<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>
|
<h1 className="mt-4 text-xl font-bold font-heading text-[#e2e5eb]">Verification Failed</h1>
|
||||||
<p className="mt-2 text-muted-foreground">{errorMessage}</p>
|
<p className="mt-2 text-[#848b9b]">{errorMessage}</p>
|
||||||
<Link
|
<Link
|
||||||
to="/"
|
to="/"
|
||||||
className={cn(
|
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]'
|
'hover:border-white/[0.12]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function BrandingSettingsPage() {
|
|||||||
<>
|
<>
|
||||||
<PageMeta title="Branding Settings" />
|
<PageMeta title="Branding Settings" />
|
||||||
<div className="flex justify-center py-12">
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -103,27 +103,27 @@ export function BrandingSettingsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Palette className="h-8 w-8 text-muted-foreground" />
|
<Palette className="h-8 w-8 text-[#848b9b]" />
|
||||||
<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">
|
||||||
Branding Settings
|
Branding Settings
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
Customize your account branding — logo, accent color, and company name.
|
Customize your account branding — logo, accent color, and company name.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-2xl space-y-6">
|
<div className="max-w-2xl space-y-6">
|
||||||
{/* Branding Form */}
|
{/* Branding Form */}
|
||||||
<div className="glass-card-static p-6">
|
<div className="card-flat p-6">
|
||||||
<h2 className="text-lg font-semibold text-foreground mb-6">Custom Branding</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb] mb-6">Custom Branding</h2>
|
||||||
|
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{/* Company Name */}
|
{/* Company Name */}
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="company-name"
|
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
|
Company Name
|
||||||
</label>
|
</label>
|
||||||
@@ -135,12 +135,12 @@ export function BrandingSettingsPage() {
|
|||||||
placeholder="Your Company Name"
|
placeholder="Your Company Name"
|
||||||
maxLength={200}
|
maxLength={200}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
'mt-1 w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20'
|
'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.
|
Displayed in the sidebar and exported documents.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +149,7 @@ export function BrandingSettingsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="logo-url"
|
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
|
Logo URL
|
||||||
</label>
|
</label>
|
||||||
@@ -161,16 +161,16 @@ export function BrandingSettingsPage() {
|
|||||||
placeholder="https://example.com/logo.png"
|
placeholder="https://example.com/logo.png"
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
className={cn(
|
className={cn(
|
||||||
'mt-1 w-full rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
'mt-1 w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm',
|
||||||
'text-foreground placeholder:text-muted-foreground font-mono',
|
'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'
|
'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).
|
Publicly accessible URL to your logo image (PNG, SVG, or JPEG).
|
||||||
</p>
|
</p>
|
||||||
{logoUrl && (
|
{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
|
<img
|
||||||
src={logoUrl}
|
src={logoUrl}
|
||||||
alt="Logo preview"
|
alt="Logo preview"
|
||||||
@@ -187,7 +187,7 @@ export function BrandingSettingsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="primary-color"
|
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
|
Accent Color
|
||||||
</label>
|
</label>
|
||||||
@@ -197,7 +197,7 @@ export function BrandingSettingsPage() {
|
|||||||
type="color"
|
type="color"
|
||||||
value={previewColor}
|
value={previewColor}
|
||||||
onChange={(e) => setPrimaryColor(e.target.value)}
|
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"
|
title="Pick accent color"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@@ -210,20 +210,20 @@ export function BrandingSettingsPage() {
|
|||||||
placeholder="#06b6d4"
|
placeholder="#06b6d4"
|
||||||
maxLength={7}
|
maxLength={7}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-32 rounded-lg border border-border bg-card px-3 py-2 text-sm',
|
'w-32 rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2 text-sm',
|
||||||
'text-foreground placeholder:text-muted-foreground font-mono',
|
'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'
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setPrimaryColor(DEFAULT_COLOR)}
|
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
|
Reset to default
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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).
|
Hex color code for the primary accent color (e.g. #06b6d4).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,9 +236,9 @@ export function BrandingSettingsPage() {
|
|||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving || !isDirty}
|
disabled={isSaving || !isDirty}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -250,15 +250,15 @@ export function BrandingSettingsPage() {
|
|||||||
Save Branding
|
Save Branding
|
||||||
</button>
|
</button>
|
||||||
{!isDirty && !isSaving && (
|
{!isDirty && !isSaving && (
|
||||||
<span className="text-xs text-muted-foreground">No changes</span>
|
<span className="text-xs text-[#848b9b]">No changes</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Preview Section */}
|
{/* Preview Section */}
|
||||||
<div className="glass-card-static p-6">
|
<div className="card-flat p-6">
|
||||||
<h2 className="text-lg font-semibold text-foreground mb-4">Preview</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb] mb-4">Preview</h2>
|
||||||
<p className="text-sm text-muted-foreground mb-4">
|
<p className="text-sm text-[#848b9b] mb-4">
|
||||||
This is how your accent color will appear on interactive elements.
|
This is how your accent color will appear on interactive elements.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ export function BrandingSettingsPage() {
|
|||||||
style={{ backgroundColor: previewColor }}
|
style={{ backgroundColor: previewColor }}
|
||||||
/>
|
/>
|
||||||
<div
|
<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={{
|
style={{
|
||||||
background: `linear-gradient(135deg, ${previewColor} 0%, ${previewColor}cc 100%)`,
|
background: `linear-gradient(135deg, ${previewColor} 0%, ${previewColor}cc 100%)`,
|
||||||
}}
|
}}
|
||||||
@@ -293,7 +293,7 @@ export function BrandingSettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{companyName && (
|
{companyName && (
|
||||||
<p className="text-sm text-foreground">
|
<p className="text-sm text-[#e2e5eb]">
|
||||||
Company: <span className="font-medium">{companyName}</span>
|
Company: <span className="font-medium">{companyName}</span>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function ChatRetentionSettingsPage() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-20">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -53,18 +53,18 @@ export default function ChatRetentionSettingsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto py-8 px-6">
|
<div className="max-w-2xl mx-auto py-8 px-6">
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<Clock size={20} className="text-primary" />
|
<Clock size={20} className="text-[#22d3ee]" />
|
||||||
<h1 className="text-xl font-heading font-bold text-foreground">Chat Retention</h1>
|
<h1 className="text-xl font-heading font-bold text-[#e2e5eb]">Chat Retention</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="glass-card-static rounded-2xl p-6 space-y-6">
|
<div className="card-flat rounded-2xl p-6 space-y-6">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Configure how long AI assistant conversations are retained. Pinned chats are never automatically deleted.
|
Configure how long AI assistant conversations are retained. Pinned chats are never automatically deleted.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<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">
|
||||||
Retention Period (days)
|
Retention Period (days)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -73,16 +73,16 @@ export default function ChatRetentionSettingsPage() {
|
|||||||
onChange={e => setRetentionDays(e.target.value)}
|
onChange={e => setRetentionDays(e.target.value)}
|
||||||
min={1}
|
min={1}
|
||||||
max={365}
|
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)' }}
|
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)
|
Chats older than this will be automatically deleted (1-365 days)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Max Conversations
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -91,10 +91,10 @@ export default function ChatRetentionSettingsPage() {
|
|||||||
onChange={e => setMaxCount(e.target.value)}
|
onChange={e => setMaxCount(e.target.value)}
|
||||||
min={10}
|
min={10}
|
||||||
max={10000}
|
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)' }}
|
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
|
When this limit is exceeded, oldest unpinned chats are deleted
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +104,7 @@ export default function ChatRetentionSettingsPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
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} />}
|
{saving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
||||||
Save Settings
|
Save Settings
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export function IntegrationsPage() {
|
|||||||
<>
|
<>
|
||||||
<PageMeta title="Integrations" />
|
<PageMeta title="Integrations" />
|
||||||
<div className="flex justify-center py-12">
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -223,16 +223,16 @@ export function IntegrationsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Plug className="h-8 w-8 text-muted-foreground" />
|
<Plug className="h-8 w-8 text-[#848b9b]" />
|
||||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Integrations</h1>
|
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Integrations</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
Connect your PSA to post session documentation directly to tickets.
|
Connect your PSA to post session documentation directly to tickets.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* 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: 'connection' as Tab, label: 'Connection', icon: Plug },
|
||||||
{ id: 'member-mapping' as Tab, label: 'Member Mapping', icon: Users },
|
{ id: 'member-mapping' as Tab, label: 'Member Mapping', icon: Users },
|
||||||
@@ -246,8 +246,8 @@ export function IntegrationsPage() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors -mb-px',
|
'inline-flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors -mb-px',
|
||||||
activeTab === id
|
activeTab === id
|
||||||
? 'border-primary text-foreground'
|
? 'border-primary text-[#e2e5eb]'
|
||||||
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'
|
: 'border-transparent text-[#848b9b] hover:text-[#e2e5eb] hover:border-[#1e2130]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
@@ -261,7 +261,7 @@ export function IntegrationsPage() {
|
|||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
{/* PSA Provider Grid */}
|
{/* PSA Provider Grid */}
|
||||||
<div className="mb-6">
|
<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
|
Available PSA Integrations
|
||||||
</p>
|
</p>
|
||||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
<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',
|
'rounded-xl border p-4 flex flex-col gap-2',
|
||||||
connection
|
connection
|
||||||
? 'border-primary/40 bg-primary/5'
|
? 'border-primary/40 bg-primary/5'
|
||||||
: 'border-border bg-card/30'
|
: 'border-[#1e2130] bg-[#14161d]/30'
|
||||||
)}>
|
)}>
|
||||||
<div className="flex items-center justify-between">
|
<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 ? (
|
{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
|
Connected
|
||||||
</span>
|
</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
|
Available
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Autotask — coming soon */}
|
{/* 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">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium text-foreground">Autotask PSA</span>
|
<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-label text-amber-400">
|
<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
|
Coming Soon
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">Datto / Kaseya integration</p>
|
<p className="text-xs text-[#848b9b]">Datto / Kaseya integration</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Halo PSA — coming soon */}
|
{/* 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">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-medium text-foreground">Halo PSA</span>
|
<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-label text-amber-400">
|
<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
|
Coming Soon
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -325,17 +325,17 @@ export function IntegrationsPage() {
|
|||||||
|
|
||||||
{/* Setup / Edit Form */}
|
{/* Setup / Edit Form */}
|
||||||
{(mode === 'setup' || mode === 'edit') && (
|
{(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">
|
<div className="flex items-center gap-2 mb-6">
|
||||||
<Shield className="h-5 w-5 text-muted-foreground" />
|
<Shield className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">
|
||||||
{mode === 'setup' ? 'Connect to ConnectWise PSA' : 'Edit Connection'}
|
{mode === 'setup' ? 'Connect to ConnectWise PSA' : 'Edit Connection'}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={mode === 'setup' ? handleCreate : handleUpdate} className="space-y-4">
|
<form onSubmit={mode === 'setup' ? handleCreate : handleUpdate} className="space-y-4">
|
||||||
<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]">
|
||||||
Display Name
|
Display Name
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -349,7 +349,7 @@ export function IntegrationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Site URL
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -363,7 +363,7 @@ export function IntegrationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Company ID
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -377,7 +377,7 @@ export function IntegrationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Public Key
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -391,7 +391,7 @@ export function IntegrationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Private Key
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -416,9 +416,9 @@ export function IntegrationsPage() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -430,7 +430,7 @@ export function IntegrationsPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={cancelEdit}
|
onClick={cancelEdit}
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
className="text-sm text-[#848b9b] hover:text-[#e2e5eb] transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -444,13 +444,13 @@ export function IntegrationsPage() {
|
|||||||
{mode === 'view' && connection && (
|
{mode === 'view' && connection && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Status Card */}
|
{/* 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 justify-between mb-4">
|
||||||
<div className="flex items-center gap-3">
|
<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
|
ConnectWise
|
||||||
</span>
|
</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>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
@@ -460,7 +460,7 @@ export function IntegrationsPage() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className={cn(
|
<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 ? 'text-emerald-400' : 'text-amber-400'
|
||||||
)}>
|
)}>
|
||||||
{connection.is_active ? 'Connected' : 'Not validated'}
|
{connection.is_active ? 'Connected' : 'Not validated'}
|
||||||
@@ -470,24 +470,24 @@ export function IntegrationsPage() {
|
|||||||
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">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-foreground">{connection.site_url}</p>
|
<p className="mt-1 text-sm text-[#e2e5eb]">{connection.site_url}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">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-foreground">{connection.company_id}</p>
|
<p className="mt-1 text-sm text-[#e2e5eb]">{connection.company_id}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Public Key</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-foreground font-mono">{connection.public_key_hint}</p>
|
<p className="mt-1 text-sm text-[#e2e5eb] font-mono">{connection.public_key_hint}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Private Key</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-foreground font-mono">{connection.private_key_hint}</p>
|
<p className="mt-1 text-sm text-[#e2e5eb] font-mono">{connection.private_key_hint}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Last Validated</p>
|
<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-foreground">
|
<p className="mt-1 text-sm text-[#e2e5eb]">
|
||||||
{connection.last_validated_at ? formatRelativeTime(connection.last_validated_at) : 'Never'}
|
{connection.last_validated_at ? formatRelativeTime(connection.last_validated_at) : 'Never'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -511,7 +511,7 @@ export function IntegrationsPage() {
|
|||||||
)}
|
)}
|
||||||
<span>{testResult.message}</span>
|
<span>{testResult.message}</span>
|
||||||
{testResult.server_version && (
|
{testResult.server_version && (
|
||||||
<span className="ml-auto text-xs text-muted-foreground">
|
<span className="ml-auto text-xs text-[#848b9b]">
|
||||||
v{testResult.server_version}
|
v{testResult.server_version}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -524,8 +524,8 @@ export function IntegrationsPage() {
|
|||||||
onClick={handleTest}
|
onClick={handleTest}
|
||||||
disabled={isTesting}
|
disabled={isTesting}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
||||||
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
@@ -541,8 +541,8 @@ export function IntegrationsPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={startEdit}
|
onClick={startEdit}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
||||||
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
'hover:border-[rgba(255,255,255,0.12)] transition-all'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -552,18 +552,18 @@ export function IntegrationsPage() {
|
|||||||
|
|
||||||
{showDeleteConfirm ? (
|
{showDeleteConfirm ? (
|
||||||
<div className="flex items-center gap-2 ml-auto">
|
<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
|
<button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={isDeleting}
|
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" />}
|
{isDeleting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
|
||||||
Confirm
|
Confirm
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDeleteConfirm(false)}
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -591,12 +591,12 @@ export function IntegrationsPage() {
|
|||||||
{/* Post History Tab */}
|
{/* Post History Tab */}
|
||||||
{activeTab === 'post-history' && (
|
{activeTab === 'post-history' && (
|
||||||
<div className="max-w-3xl">
|
<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">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Ticket className="h-5 w-5 text-muted-foreground" />
|
<Ticket className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Post History</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Post History</h2>
|
||||||
</div>
|
</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.
|
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
|
When a session has a ConnectWise ticket linked, use the Update button to post
|
||||||
session documentation and view previous posts.
|
session documentation and view previous posts.
|
||||||
@@ -727,12 +727,12 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
if (!connection) {
|
if (!connection) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl">
|
<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">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Users className="h-5 w-5 text-muted-foreground" />
|
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Member Mapping</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Member Mapping</h2>
|
||||||
</div>
|
</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.
|
Set up a PSA connection first to map team members to ConnectWise members.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -743,19 +743,19 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-3xl space-y-4">
|
<div className="max-w-3xl space-y-4">
|
||||||
{/* Header + Auto-Match */}
|
{/* 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 justify-between mb-2">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Users className="h-5 w-5 text-muted-foreground" />
|
<Users className="h-5 w-5 text-[#848b9b]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">Member Mapping</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Member Mapping</h2>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleAutoMatch}
|
onClick={handleAutoMatch}
|
||||||
disabled={isAutoMatching || isLoadingData}
|
disabled={isAutoMatching || isLoadingData}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-4 py-2 text-sm font-medium',
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground',
|
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-[#e2e5eb]',
|
||||||
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
'hover:border-[rgba(255,255,255,0.12)] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
@@ -768,7 +768,7 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
Auto-Match by Email
|
Auto-Match by Email
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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.
|
Map your ResolutionFlow users to ConnectWise members so session posts are attributed correctly.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -776,25 +776,25 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
{/* Loading state */}
|
{/* Loading state */}
|
||||||
{isLoadingData && (
|
{isLoadingData && (
|
||||||
<div className="flex justify-center py-8">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Mapping Table */}
|
{/* Mapping Table */}
|
||||||
{hasLoaded && !isLoadingData && (
|
{hasLoaded && !isLoadingData && (
|
||||||
<div className="glass-card-static overflow-hidden">
|
<div className="card-flat overflow-hidden">
|
||||||
{uniqueUsers.length === 0 ? (
|
{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.
|
No users found. Use Auto-Match to discover and map users.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Table header */}
|
{/* Table header */}
|
||||||
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 border-b border-border px-6 py-3">
|
<div className="grid grid-cols-[1fr_1fr_1fr_auto] gap-4 border-b border-[#1e2130] px-6 py-3">
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">User</span>
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">User</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Email</span>
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Email</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Mapped To</span>
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Mapped To</span>
|
||||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground w-20 text-center">Method</span>
|
<span className="font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b] w-20 text-center">Method</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rows */}
|
{/* Rows */}
|
||||||
@@ -803,18 +803,18 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={user.user_id}
|
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-[#e2e5eb] truncate">{user.user_name}</span>
|
||||||
<span className="text-sm text-muted-foreground truncate">{user.user_email}</span>
|
<span className="text-sm text-[#848b9b] truncate">{user.user_email}</span>
|
||||||
<select
|
<select
|
||||||
title={`Map ${user.user_name} to a ConnectWise member`}
|
title={`Map ${user.user_name} to a ConnectWise member`}
|
||||||
value={currentMapping?.external_member_id || ''}
|
value={currentMapping?.external_member_id || ''}
|
||||||
onChange={(e) => handleMemberChange(user.user_id, e.target.value)}
|
onChange={(e) => handleMemberChange(user.user_id, e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-lg border bg-card px-3 py-1.5 text-sm text-foreground',
|
'w-full rounded-lg border bg-[#14161d] px-3 py-1.5 text-sm text-[#e2e5eb]',
|
||||||
'border-border focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
'border-[#1e2130] focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||||
!currentMapping && 'text-muted-foreground'
|
!currentMapping && 'text-[#848b9b]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="">-- Unmapped --</option>
|
<option value="">-- Unmapped --</option>
|
||||||
@@ -827,19 +827,19 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
<span className="w-20 text-center">
|
<span className="w-20 text-center">
|
||||||
{currentMapping && !isDirty && user.matched_by ? (
|
{currentMapping && !isDirty && user.matched_by ? (
|
||||||
<span className={cn(
|
<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'
|
user.matched_by === 'auto_email'
|
||||||
? 'bg-primary/10 text-primary'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee]'
|
||||||
: 'bg-card border border-border text-muted-foreground'
|
: 'bg-[#14161d] border border-[#1e2130] text-[#848b9b]'
|
||||||
)}>
|
)}>
|
||||||
{user.matched_by === 'auto_email' ? 'auto' : 'manual'}
|
{user.matched_by === 'auto_email' ? 'auto' : 'manual'}
|
||||||
</span>
|
</span>
|
||||||
) : currentMapping ? (
|
) : 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
|
manual
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-[0.625rem] text-muted-foreground/50">—</span>
|
<span className="text-[0.625rem] text-[#848b9b]/50">—</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -858,9 +858,9 @@ function MemberMappingTab({ connection }: { connection: PsaConnectionResponse |
|
|||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSavingMappings}
|
disabled={isSavingMappings}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] px-5 py-2.5 text-sm font-semibold',
|
'inline-flex items-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold',
|
||||||
'bg-gradient-brand text-[#101114] shadow-lg shadow-primary/20',
|
'bg-[#22d3ee] text-white',
|
||||||
'hover:opacity-90 active:scale-[0.97] transition-all',
|
'hover:brightness-110 active:scale-[0.98] transition-all',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -914,8 +914,8 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
|||||||
if (!connection) {
|
if (!connection) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl">
|
<div className="max-w-3xl">
|
||||||
<div className="glass-card-static p-6 text-center">
|
<div className="card-flat p-6 text-center">
|
||||||
<p className="text-sm text-muted-foreground">Connect your PSA first to configure FlowPilot settings.</p>
|
<p className="text-sm text-[#848b9b]">Connect your PSA first to configure FlowPilot settings.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -924,7 +924,7 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center py-12">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -933,12 +933,12 @@ function FlowPilotSettingsTab({ connection }: { connection: PsaConnectionRespons
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl space-y-4">
|
<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">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Zap className="h-5 w-5 text-primary" />
|
<Zap className="h-5 w-5 text-[#22d3ee]" />
|
||||||
<h2 className="text-lg font-semibold text-foreground">FlowPilot Settings</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">FlowPilot Settings</h2>
|
||||||
</div>
|
</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.
|
Configure how FlowPilot integrates with your ConnectWise PSA when sessions are resolved or escalated.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -1039,8 +1039,8 @@ function SettingToggle({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{label}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">{description}</p>
|
<p className="text-xs text-[#848b9b] mt-0.5">{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => onChange(!checked)}
|
onClick={() => onChange(!checked)}
|
||||||
@@ -1078,13 +1078,13 @@ function SettingSelect({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-foreground">{label}</p>
|
<p className="text-sm font-medium text-[#e2e5eb]">{label}</p>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5 mb-2">{description}</p>
|
<p className="text-xs text-[#848b9b] mt-0.5 mb-2">{description}</p>
|
||||||
<select
|
<select
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
disabled={disabled}
|
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) => (
|
{options.map((opt) => (
|
||||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { toast } from '@/lib/toast'
|
|||||||
import type { UserUpdate } from '@/types'
|
import type { UserUpdate } from '@/types'
|
||||||
|
|
||||||
const inputClass = cn(
|
const inputClass = cn(
|
||||||
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
|
'mt-1 block w-full rounded-lg border border-[#1e2130] bg-[#14161d] px-3 py-2',
|
||||||
'text-foreground placeholder:text-muted-foreground',
|
'text-[#e2e5eb] placeholder:text-[#848b9b]',
|
||||||
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
'focus:border-primary/30 focus:outline-hidden focus:ring-1 focus:ring-primary/20'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,52 +67,52 @@ export function ProfileSettingsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<UserIcon className="h-8 w-8 text-muted-foreground" />
|
<UserIcon className="h-8 w-8 text-[#848b9b]" />
|
||||||
<h1 className="text-2xl font-bold font-heading text-foreground sm:text-3xl">Profile Settings</h1>
|
<h1 className="text-2xl font-bold font-heading text-[#e2e5eb] sm:text-3xl">Profile Settings</h1>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-muted-foreground">
|
<p className="mt-2 text-[#848b9b]">
|
||||||
Update your name, email, and personal details
|
Update your name, email, and personal details
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-xl">
|
<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 */}
|
{/* Name */}
|
||||||
<div>
|
<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} />
|
<input id="profile-name" type="text" value={name} onChange={(e) => setName(e.target.value)} required className={inputClass} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Email */}
|
{/* Email */}
|
||||||
<div>
|
<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} />
|
<input id="profile-email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required className={inputClass} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Password confirmation for email change */}
|
{/* Password confirmation for email change */}
|
||||||
{emailChanged && (
|
{emailChanged && (
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="profile-password" className="block text-sm font-medium text-foreground">Current Password</label>
|
<label htmlFor="profile-password" className="block text-sm font-medium text-[#e2e5eb]">Current Password</label>
|
||||||
<p className="text-xs text-muted-foreground">Required to change your email address</p>
|
<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} />
|
<input id="profile-password" type="password" value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} required className={inputClass} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Phone */}
|
{/* Phone */}
|
||||||
<div>
|
<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} />
|
<input id="profile-phone" type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="Optional" className={inputClass} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Job Title */}
|
{/* Job Title */}
|
||||||
<div>
|
<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} />
|
<input id="profile-job-title" type="text" value={jobTitle} onChange={(e) => setJobTitle(e.target.value)} placeholder="e.g. Network Engineer" className={inputClass} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Timezone */}
|
{/* Timezone */}
|
||||||
<div>
|
<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}>
|
<select id="profile-timezone" value={timezone} onChange={(e) => setTimezone(e.target.value)} className={inputClass}>
|
||||||
{COMMON_TIMEZONES.map((tz) => (
|
{COMMON_TIMEZONES.map((tz) => (
|
||||||
<option key={tz} value={tz}>{tz}</option>
|
<option key={tz} value={tz}>{tz}</option>
|
||||||
@@ -132,8 +132,8 @@ export function ProfileSettingsPage() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving || !hasChanges}
|
disabled={isSaving || !hasChanges}
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark',
|
'inline-flex items-center gap-2 rounded-lg bg-[#22d3ee] px-4 py-2 text-sm font-semibold text-brand-dark',
|
||||||
'shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97]',
|
'hover:brightness-110 active:scale-[0.98]',
|
||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -144,8 +144,8 @@ export function ProfileSettingsPage() {
|
|||||||
<Link
|
<Link
|
||||||
to="/change-password"
|
to="/change-password"
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-flex items-center rounded-[10px] px-4 py-2 text-sm font-medium',
|
'inline-flex items-center rounded-lg px-4 py-2 text-sm font-medium',
|
||||||
'bg-white/[0.04] border border-brand-border text-foreground',
|
'bg-white/[0.04] border border-brand-border text-[#e2e5eb]',
|
||||||
'hover:border-white/[0.12]'
|
'hover:border-white/[0.12]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export default function TargetListsPage() {
|
|||||||
action={(
|
action={(
|
||||||
<button
|
<button
|
||||||
onClick={() => openEditor()}
|
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" />
|
<Plus className="h-4 w-4" />
|
||||||
New List
|
New List
|
||||||
@@ -137,14 +137,14 @@ export default function TargetListsPage() {
|
|||||||
{lists.map(list => (
|
{lists.map(list => (
|
||||||
<div
|
<div
|
||||||
key={list.id}
|
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>
|
<div>
|
||||||
<p className="font-medium text-foreground">{list.name}</p>
|
<p className="font-medium text-[#e2e5eb]">{list.name}</p>
|
||||||
{list.description && (
|
{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} target{list.targets.length !== 1 ? 's' : ''}
|
||||||
{list.targets.length > 0 && (
|
{list.targets.length > 0 && (
|
||||||
<> · {list.targets.slice(0, 3).map(t => t.label).join(', ')}{list.targets.length > 3 ? '\u2026' : ''}</>
|
<> · {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">
|
<div className="flex items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => openEditor(list)}
|
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"
|
title="Edit"
|
||||||
>
|
>
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDeleteTarget(list)}
|
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"
|
title="Delete"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@@ -182,14 +182,14 @@ export default function TargetListsPage() {
|
|||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowEditor(false)}
|
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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
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'}
|
{isSaving ? 'Saving\u2026' : 'Save'}
|
||||||
</button>
|
</button>
|
||||||
@@ -198,38 +198,38 @@ export default function TargetListsPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<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]">
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={editorName}
|
value={editorName}
|
||||||
onChange={e => setEditorName(e.target.value)}
|
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"
|
placeholder="e.g. RDS Farm A"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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)
|
Description (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={editorDescription}
|
value={editorDescription}
|
||||||
onChange={e => setEditorDescription(e.target.value)}
|
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"
|
placeholder="e.g. Production RDS servers"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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 #)
|
Targets — one per line (add notes after #)
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={editorTargets}
|
value={editorTargets}
|
||||||
onChange={e => setEditorTargets(e.target.value)}
|
onChange={e => setEditorTargets(e.target.value)}
|
||||||
rows={6}
|
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"}
|
placeholder={"RDS-01 # 192.168.1.10\nRDS-02\nRDS-03 # Backup server"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -99,26 +99,26 @@ export function TeamCategoriesPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : categories.length === 0 ? (
|
) : categories.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center bg-card border border-border rounded-xl py-16">
|
<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-muted-foreground" />
|
<FolderTree className="h-12 w-12 text-[#848b9b]" />
|
||||||
<h3 className="mt-4 font-medium text-foreground">No team categories</h3>
|
<h3 className="mt-4 font-medium text-[#e2e5eb]">No team categories</h3>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">Create categories to organize your team's trees.</p>
|
<p className="mt-1 text-sm text-[#848b9b]">Create categories to organize your team's trees.</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{categories.map((cat) => (
|
{categories.map((cat) => (
|
||||||
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-border px-4 py-3">
|
<div key={cat.id} className="flex items-center justify-between rounded-lg border border-[#1e2130] px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-foreground">{cat.name}</span>
|
<span className="font-medium text-[#e2e5eb]">{cat.name}</span>
|
||||||
<span className="ml-3 text-sm text-muted-foreground">{cat.slug}</span>
|
<span className="ml-3 text-sm text-[#848b9b]">{cat.slug}</span>
|
||||||
{cat.description && <span className="ml-3 text-sm text-muted-foreground">- {cat.description}</span>}
|
{cat.description && <span className="ml-3 text-sm text-[#848b9b]">- {cat.description}</span>}
|
||||||
<span className="ml-3 text-xs text-muted-foreground">{cat.tree_count} trees</span>
|
<span className="ml-3 text-xs text-[#848b9b]">{cat.tree_count} trees</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground">
|
<button onClick={() => openEdit(cat)} className="rounded-md p-1.5 text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]">
|
||||||
<Pencil className="h-4 w-4" />
|
<Pencil className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleDelete(cat.id)} className="rounded-md p-1.5 text-muted-foreground hover:bg-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" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,15 +138,15 @@ export function TeamCategoriesPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<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>
|
||||||
<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 })} />
|
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||||
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,15 +163,15 @@ export function TeamCategoriesPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||||
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
|
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||||
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
<Input type="text" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} placeholder="Optional" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function AuditLogsPage() {
|
|||||||
render: (log) => (
|
render: (log) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => setExpandedId(expandedId === log.id ? null : log.id)}
|
onClick={() => setExpandedId(expandedId === log.id ? null : log.id)}
|
||||||
className="p-1 text-muted-foreground hover:text-foreground"
|
className="p-1 text-[#848b9b] hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
{expandedId === log.id ? (
|
{expandedId === log.id ? (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
@@ -78,14 +78,14 @@ export function AuditLogsPage() {
|
|||||||
key: 'action',
|
key: 'action',
|
||||||
header: 'Action',
|
header: 'Action',
|
||||||
render: (log) => (
|
render: (log) => (
|
||||||
<span className="text-sm font-medium text-foreground">{log.action}</span>
|
<span className="text-sm font-medium text-[#e2e5eb]">{log.action}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'resource',
|
key: 'resource',
|
||||||
header: 'Resource',
|
header: 'Resource',
|
||||||
render: (log) => (
|
render: (log) => (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
{log.resource_type}{log.resource_id ? ` (${log.resource_id.slice(0, 8)}...)` : ''}
|
{log.resource_type}{log.resource_id ? ` (${log.resource_id.slice(0, 8)}...)` : ''}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -94,14 +94,14 @@ export function AuditLogsPage() {
|
|||||||
key: 'user',
|
key: 'user',
|
||||||
header: 'User',
|
header: 'User',
|
||||||
render: (log) => (
|
render: (log) => (
|
||||||
<span className="text-sm text-muted-foreground">{log.user_email || 'System'}</span>
|
<span className="text-sm text-[#848b9b]">{log.user_email || 'System'}</span>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
header: 'Time',
|
header: 'Time',
|
||||||
render: (log) => (
|
render: (log) => (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
{new Date(log.created_at).toLocaleString()}
|
{new Date(log.created_at).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -117,8 +117,8 @@ export function AuditLogsPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium',
|
'flex items-center gap-2 rounded-md border border-[#1e2130] px-4 py-2 text-sm font-medium',
|
||||||
'text-muted-foreground hover:bg-accent hover:text-foreground'
|
'text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
@@ -134,8 +134,8 @@ export function AuditLogsPage() {
|
|||||||
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
|
onChange={(e) => { setActionFilter(e.target.value); setPage(1) }}
|
||||||
placeholder="Filter by action..."
|
placeholder="Filter by action..."
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 rounded-md border border-border bg-card px-3 text-sm text-foreground',
|
'h-9 rounded-md border border-[#1e2130] bg-[#14161d] px-3 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@@ -144,8 +144,8 @@ export function AuditLogsPage() {
|
|||||||
onChange={(e) => { setResourceFilter(e.target.value); setPage(1) }}
|
onChange={(e) => { setResourceFilter(e.target.value); setPage(1) }}
|
||||||
placeholder="Filter by resource type..."
|
placeholder="Filter by resource type..."
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 rounded-md border border-border bg-card px-3 text-sm text-foreground',
|
'h-9 rounded-md border border-[#1e2130] bg-[#14161d] px-3 text-sm text-[#e2e5eb]',
|
||||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,9 +166,9 @@ export function AuditLogsPage() {
|
|||||||
|
|
||||||
{/* Expanded details row */}
|
{/* Expanded details row */}
|
||||||
{expandedId && logs.find(l => l.id === expandedId)?.details && (
|
{expandedId && logs.find(l => l.id === expandedId)?.details && (
|
||||||
<div className="rounded-md border border-border bg-accent p-4">
|
<div className="rounded-md border border-[#1e2130] bg-accent p-4">
|
||||||
<h4 className="mb-2 text-sm font-medium text-foreground">Details</h4>
|
<h4 className="mb-2 text-sm font-medium text-[#e2e5eb]">Details</h4>
|
||||||
<pre className="overflow-x-auto rounded bg-card p-3 text-xs text-muted-foreground">
|
<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)}
|
{JSON.stringify(logs.find(l => l.id === expandedId)?.details, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ interface MetricCardProps {
|
|||||||
|
|
||||||
function MetricCard({ label, value, icon }: MetricCardProps) {
|
function MetricCard({ label, value, icon }: MetricCardProps) {
|
||||||
return (
|
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 className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">{label}</p>
|
<p className="text-sm text-[#848b9b]">{label}</p>
|
||||||
<p className="mt-1 text-3xl font-bold text-foreground">{value}</p>
|
<p className="mt-1 text-3xl font-bold text-[#e2e5eb]">{value}</p>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -71,18 +71,18 @@ export function DashboardPage() {
|
|||||||
{/* Recent Activity */}
|
{/* Recent Activity */}
|
||||||
{activity.length > 0 && (
|
{activity.length > 0 && (
|
||||||
<div>
|
<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">
|
<div className="mt-3 space-y-2">
|
||||||
{activity.slice(0, 10).map((entry) => (
|
{activity.slice(0, 10).map((entry) => (
|
||||||
<div key={entry.id} className="flex items-center justify-between rounded-md border border-border 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>
|
<div>
|
||||||
<span className="font-medium text-foreground">{entry.action}</span>
|
<span className="font-medium text-[#e2e5eb]">{entry.action}</span>
|
||||||
<span className="ml-2 text-muted-foreground">{entry.resource_type}</span>
|
<span className="ml-2 text-[#848b9b]">{entry.resource_type}</span>
|
||||||
{entry.user_email && (
|
{entry.user_email && (
|
||||||
<span className="ml-2 text-muted-foreground">by {entry.user_email}</span>
|
<span className="ml-2 text-[#848b9b]">by {entry.user_email}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-muted-foreground">
|
<span className="text-xs text-[#848b9b]">
|
||||||
{new Date(entry.created_at).toLocaleString()}
|
{new Date(entry.created_at).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,18 +93,18 @@ export function DashboardPage() {
|
|||||||
|
|
||||||
{/* Quick Links */}
|
{/* Quick Links */}
|
||||||
<div>
|
<div>
|
||||||
<h2 className="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">
|
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{quickLinks.map((link) => (
|
{quickLinks.map((link) => (
|
||||||
<Link
|
<Link
|
||||||
key={link.to}
|
key={link.to}
|
||||||
to={link.to}
|
to={link.to}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-3 bg-card border border-border rounded-xl p-4',
|
'flex items-center gap-3 bg-[#14161d] border border-[#1e2130] rounded-xl p-4',
|
||||||
'text-sm font-medium text-foreground transition-colors hover:bg-accent'
|
'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.label}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ export function FeatureFlagsPage() {
|
|||||||
const flagColumns: Column<FeatureFlagResponse>[] = [
|
const flagColumns: Column<FeatureFlagResponse>[] = [
|
||||||
{ key: 'name', header: 'Name', render: (f) => (
|
{ key: 'name', header: 'Name', render: (f) => (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-foreground">{f.display_name}</div>
|
<div className="font-medium text-[#e2e5eb]">{f.display_name}</div>
|
||||||
<div className="text-xs text-muted-foreground">{f.flag_key}</div>
|
<div className="text-xs text-[#848b9b]">{f.flag_key}</div>
|
||||||
</div>
|
</div>
|
||||||
)},
|
)},
|
||||||
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-muted-foreground">{f.description || '-'}</span> },
|
{ key: 'description', header: 'Description', render: (f) => <span className="text-sm text-[#848b9b]">{f.description || '-'}</span> },
|
||||||
...PLANS.map(plan => ({
|
...PLANS.map(plan => ({
|
||||||
key: plan,
|
key: plan,
|
||||||
header: plan.charAt(0).toUpperCase() + plan.slice(1),
|
header: plan.charAt(0).toUpperCase() + plan.slice(1),
|
||||||
@@ -133,10 +133,10 @@ export function FeatureFlagsPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const overrideColumns: Column<AccountFeatureOverrideResponse>[] = [
|
const overrideColumns: Column<AccountFeatureOverrideResponse>[] = [
|
||||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-[#e2e5eb]">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
||||||
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-muted-foreground">{o.flag_display_name || o.flag_key || o.flag_id.slice(0, 8)}</span> },
|
{ key: 'flag', header: 'Flag', render: (o) => <span className="text-sm text-[#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: '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',
|
key: 'actions', header: '', className: 'w-12',
|
||||||
render: (o) => (
|
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 (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -163,7 +163,7 @@ export function FeatureFlagsPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<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">
|
<div className="mt-3">
|
||||||
<DataTable columns={flagColumns} data={flags} keyExtractor={(f) => f.id} isLoading={loading}
|
<DataTable columns={flagColumns} data={flags} keyExtractor={(f) => f.id} isLoading={loading}
|
||||||
emptyState={<EmptyState icon={<ToggleLeft className="h-12 w-12" />} title="No feature flags" description="Create feature flags to control availability per plan." />}
|
emptyState={<EmptyState icon={<ToggleLeft className="h-12 w-12" />} title="No feature flags" description="Create feature flags to control availability per plan." />}
|
||||||
@@ -173,7 +173,7 @@ export function FeatureFlagsPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<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)}>
|
<Button onClick={() => setOverrideOpen(true)}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Override
|
Add Override
|
||||||
@@ -197,15 +197,15 @@ export function FeatureFlagsPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Flag Key</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<Input type="text" value={createForm.flag_key} onChange={(e) => setCreateForm({ ...createForm, flag_key: e.target.value })} placeholder="e.g. custom_branding" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Display Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<Input type="text" value={createForm.display_name} onChange={(e) => setCreateForm({ ...createForm, display_name: e.target.value })} placeholder="e.g. Custom Branding" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||||
<Input type="text" value={createForm.description ?? ''} onChange={(e) => setCreateForm({ ...createForm, description: e.target.value || null })} placeholder="Optional description" />
|
<Input type="text" value={createForm.description ?? ''} onChange={(e) => setCreateForm({ ...createForm, description: e.target.value || null })} placeholder="Optional description" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -222,22 +222,22 @@ export function FeatureFlagsPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<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>
|
||||||
<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}>
|
<select value={overrideForm.flag_id} onChange={(e) => setOverrideForm({ ...overrideForm, flag_id: e.target.value })} className={selectClass}>
|
||||||
<option value="">Select a flag...</option>
|
<option value="">Select a flag...</option>
|
||||||
{flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
|
{flags.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-border" />
|
<input type="checkbox" id="override-enabled" checked={overrideForm.enabled} onChange={(e) => setOverrideForm({ ...overrideForm, enabled: e.target.checked })} className="h-4 w-4 rounded border-[#1e2130]" />
|
||||||
<label htmlFor="override-enabled" className="text-sm font-medium text-foreground">Enabled</label>
|
<label htmlFor="override-enabled" className="text-sm font-medium text-[#e2e5eb]">Enabled</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Note</label>
|
||||||
<Input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason" />
|
<Input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ function SortOrderInput({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={cn(
|
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',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||||
disabled && 'cursor-not-allowed opacity-50',
|
disabled && 'cursor-not-allowed opacity-50',
|
||||||
)}
|
)}
|
||||||
@@ -117,19 +117,19 @@ function FilterBar({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="relative flex-1 min-w-[180px] max-w-xs">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search by name…"
|
placeholder="Search by name…"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => onSearchChange(e.target.value)}
|
onChange={e => onSearchChange(e.target.value)}
|
||||||
className={cn(
|
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',
|
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 => (
|
{(['all', 'featured', 'unfeatured'] as FilterMode[]).map(mode => (
|
||||||
<button
|
<button
|
||||||
key={mode}
|
key={mode}
|
||||||
@@ -137,8 +137,8 @@ function FilterBar({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'px-3 py-1.5 capitalize transition-colors',
|
'px-3 py-1.5 capitalize transition-colors',
|
||||||
filter === mode
|
filter === mode
|
||||||
? 'bg-primary text-[#101114] font-semibold'
|
? 'bg-primary text-white font-semibold'
|
||||||
: 'text-muted-foreground hover:text-foreground bg-card',
|
: 'text-[#848b9b] hover:text-[#e2e5eb] bg-[#14161d]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mode}
|
{mode}
|
||||||
@@ -166,7 +166,7 @@ function FlowsTable({
|
|||||||
}) {
|
}) {
|
||||||
if (flows.length === 0) {
|
if (flows.length === 0) {
|
||||||
return (
|
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">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border text-left">
|
<tr className="border-b border-[#1e2130] 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">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 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Featured</th>
|
||||||
<th className="pb-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Sort Order</th>
|
<th className="pb-3 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Sort Order</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border">
|
<tbody className="divide-y divide-border">
|
||||||
{flows.map(flow => (
|
{flows.map(flow => (
|
||||||
<tr key={flow.id} className="group hover:bg-[rgba(255,255,255,0.02)] transition-colors">
|
<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">
|
<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}
|
{flow.tree_type}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -234,7 +234,7 @@ function ScriptsTable({
|
|||||||
}) {
|
}) {
|
||||||
if (scripts.length === 0) {
|
if (scripts.length === 0) {
|
||||||
return (
|
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">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full text-sm">
|
<table className="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border text-left">
|
<tr className="border-b border-[#1e2130] 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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">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-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">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 pr-4 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Featured</th>
|
||||||
<th className="pb-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">Sort Order</th>
|
<th className="pb-3 font-sans text-xs text-[0.625rem] uppercase tracking-[0.1em] text-[#848b9b]">Sort Order</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-border">
|
<tbody className="divide-y divide-border">
|
||||||
{scripts.map(script => (
|
{scripts.map(script => (
|
||||||
<tr key={script.id} className="group hover:bg-[rgba(255,255,255,0.02)] transition-colors">
|
<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">
|
<td className="py-3 pr-4">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
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
|
script.is_active
|
||||||
? 'border-emerald-400/30 text-emerald-400 bg-emerald-400/10'
|
? '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'}
|
{script.is_active ? 'Active' : 'Inactive'}
|
||||||
@@ -417,23 +417,23 @@ export default function GalleryManagementPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{loading ? (
|
{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…
|
Loading gallery items…
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{/* Summary stats */}
|
{/* Summary stats */}
|
||||||
<div className="flex gap-4 flex-wrap">
|
<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" />
|
<Star className="h-4 w-4 text-amber-400 fill-amber-400" />
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
<span className="text-foreground font-semibold">{featuredFlowCount}</span> featured flow{featuredFlowCount !== 1 ? 's' : ''}
|
<span className="text-[#e2e5eb] font-semibold">{featuredFlowCount}</span> featured flow{featuredFlowCount !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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" />
|
<Star className="h-4 w-4 text-amber-400 fill-amber-400" />
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
<span className="text-foreground font-semibold">{featuredScriptCount}</span> featured script{featuredScriptCount !== 1 ? 's' : ''}
|
<span className="text-[#e2e5eb] font-semibold">{featuredScriptCount}</span> featured script{featuredScriptCount !== 1 ? 's' : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -442,14 +442,14 @@ export default function GalleryManagementPage() {
|
|||||||
<section>
|
<section>
|
||||||
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold text-foreground">Featured Flows</h2>
|
<h2 className="text-base font-semibold text-[#e2e5eb]">Featured Flows</h2>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">
|
<p className="text-xs text-[#848b9b] mt-0.5">
|
||||||
Toggle flows to show in the gallery. Lower sort order = shown first.
|
Toggle flows to show in the gallery. Lower sort order = shown first.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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
|
<FilterBar
|
||||||
search={flowSearch}
|
search={flowSearch}
|
||||||
onSearchChange={setFlowSearch}
|
onSearchChange={setFlowSearch}
|
||||||
@@ -469,14 +469,14 @@ export default function GalleryManagementPage() {
|
|||||||
<section>
|
<section>
|
||||||
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
<div className="mb-4 flex items-center justify-between gap-4 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-base font-semibold text-foreground">Featured Scripts</h2>
|
<h2 className="text-base font-semibold text-[#e2e5eb]">Featured Scripts</h2>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">
|
<p className="text-xs text-[#848b9b] mt-0.5">
|
||||||
Toggle scripts to show in the gallery. Lower sort order = shown first.
|
Toggle scripts to show in the gallery. Lower sort order = shown first.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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
|
<FilterBar
|
||||||
search={scriptSearch}
|
search={scriptSearch}
|
||||||
onSearchChange={setScriptSearch}
|
onSearchChange={setScriptSearch}
|
||||||
|
|||||||
@@ -73,10 +73,10 @@ export function GlobalCategoriesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: Column<AdminCategory>[] = [
|
const columns: Column<AdminCategory>[] = [
|
||||||
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-foreground">{c.name}</span> },
|
{ key: 'name', header: 'Name', render: (c) => <span className="font-medium text-[#e2e5eb]">{c.name}</span> },
|
||||||
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-muted-foreground">{c.slug}</span> },
|
{ key: 'slug', header: 'Slug', render: (c) => <span className="text-sm text-[#848b9b]">{c.slug}</span> },
|
||||||
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-muted-foreground">{c.description || '-'}</span> },
|
{ key: 'description', header: 'Description', render: (c) => <span className="text-sm text-[#848b9b]">{c.description || '-'}</span> },
|
||||||
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-muted-foreground">{c.tree_count}</span> },
|
{ key: 'tree_count', header: 'Trees', render: (c) => <span className="text-sm text-[#848b9b]">{c.tree_count}</span> },
|
||||||
{
|
{
|
||||||
key: 'actions', header: '', className: 'w-12',
|
key: 'actions', header: '', className: 'w-12',
|
||||||
render: (c) => (
|
render: (c) => (
|
||||||
@@ -124,15 +124,15 @@ export function GlobalCategoriesPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<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>
|
||||||
<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" />
|
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||||
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,15 +153,15 @@ export function GlobalCategoriesPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||||
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="e.g. Networking" />
|
<Input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} placeholder="e.g. Networking" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Slug</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Slug</label>
|
||||||
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" />
|
<Input type="text" value={form.slug} onChange={(e) => setForm({ ...form, slug: e.target.value })} placeholder="e.g. networking" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Description</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Description</label>
|
||||||
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
<Input type="text" value={form.description ?? ''} onChange={(e) => setForm({ ...form, description: e.target.value || null })} placeholder="Optional description" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ export function InviteCodesPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectClass = cn(
|
const selectClass = 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]',
|
||||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)
|
)
|
||||||
|
|
||||||
const columns: Column<InviteCodeResponse>[] = [
|
const columns: Column<InviteCodeResponse>[] = [
|
||||||
@@ -119,7 +119,7 @@ export function InviteCodesPage() {
|
|||||||
key: 'code',
|
key: 'code',
|
||||||
header: 'Code',
|
header: 'Code',
|
||||||
render: (c) => (
|
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 ? (
|
{c.email_sent ? (
|
||||||
<MailCheck className="h-3.5 w-3.5 text-emerald-400" />
|
<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>
|
</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',
|
key: 'trial',
|
||||||
header: 'Trial',
|
header: 'Trial',
|
||||||
render: (c) => c.has_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',
|
key: 'expires_at',
|
||||||
header: 'Expires',
|
header: 'Expires',
|
||||||
render: (c) => (
|
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'}
|
{c.expires_at ? new Date(c.expires_at).toLocaleDateString() : 'Never'}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -179,7 +179,7 @@ export function InviteCodesPage() {
|
|||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
header: 'Created',
|
header: 'Created',
|
||||||
render: (c) => (
|
render: (c) => (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
{new Date(c.created_at).toLocaleDateString()}
|
{new Date(c.created_at).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -254,7 +254,7 @@ export function InviteCodesPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Recipient Email</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Recipient Email</label>
|
||||||
<Input
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
@@ -264,7 +264,7 @@ export function InviteCodesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
<select
|
||||||
aria-label="Plan"
|
aria-label="Plan"
|
||||||
value={assignedPlan}
|
value={assignedPlan}
|
||||||
@@ -283,7 +283,7 @@ export function InviteCodesPage() {
|
|||||||
|
|
||||||
{assignedPlan !== 'free' && (
|
{assignedPlan !== 'free' && (
|
||||||
<div>
|
<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
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={trialDays}
|
value={trialDays}
|
||||||
@@ -292,12 +292,12 @@ export function InviteCodesPage() {
|
|||||||
min={1}
|
min={1}
|
||||||
max={90}
|
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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<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
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={expiresInDays}
|
value={expiresInDays}
|
||||||
@@ -307,7 +307,7 @@ export function InviteCodesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Note</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Note</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={note}
|
value={note}
|
||||||
|
|||||||
@@ -76,16 +76,16 @@ export function PlanLimitsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const planColumns: Column<PlanLimitConfig>[] = [
|
const planColumns: Column<PlanLimitConfig>[] = [
|
||||||
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-foreground capitalize">{p.plan}</span> },
|
{ key: 'plan', header: 'Plan', render: (p) => <span className="font-medium text-[#e2e5eb] capitalize">{p.plan}</span> },
|
||||||
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-muted-foreground">{p.max_trees ?? 'Unlimited'}</span> },
|
{ key: 'max_trees', header: 'Max Trees', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_trees ?? 'Unlimited'}</span> },
|
||||||
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-muted-foreground">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
|
{ key: 'max_sessions', header: 'Sessions/Month', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_sessions_per_month ?? 'Unlimited'}</span> },
|
||||||
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-muted-foreground">{p.max_users ?? 'Unlimited'}</span> },
|
{ key: 'max_users', header: 'Max Users', render: (p) => <span className="text-sm text-[#848b9b]">{p.max_users ?? 'Unlimited'}</span> },
|
||||||
{
|
{
|
||||||
key: 'actions', header: '', className: 'w-12',
|
key: 'actions', header: '', className: 'w-12',
|
||||||
render: (p) => (
|
render: (p) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => setEditPlan({ ...p })}
|
onClick={() => setEditPlan({ ...p })}
|
||||||
className="rounded-md px-3 py-1 text-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
className="rounded-md px-3 py-1 text-sm text-[#848b9b] hover:bg-accent hover:text-[#e2e5eb]"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -94,11 +94,11 @@ export function PlanLimitsPage() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const overrideColumns: Column<AccountOverrideResponse>[] = [
|
const overrideColumns: Column<AccountOverrideResponse>[] = [
|
||||||
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-foreground">{o.account_display_code || o.account_id.slice(0, 8)}</span> },
|
{ key: 'account', header: 'Account', render: (o) => <span className="text-sm font-medium text-[#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-muted-foreground">{o.override_max_trees ?? '-'}</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-muted-foreground">{o.override_max_sessions_per_month ?? '-'}</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-muted-foreground">{o.override_max_users ?? '-'}</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-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',
|
key: 'actions', header: '', className: 'w-12',
|
||||||
render: (o) => (
|
render: (o) => (
|
||||||
@@ -114,7 +114,7 @@ export function PlanLimitsPage() {
|
|||||||
<PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" />
|
<PageHeader title="Plan Limits" description="Configure plan tier limits and account-specific overrides" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-foreground">Plan Defaults</h2>
|
<h2 className="text-lg font-semibold text-[#e2e5eb]">Plan Defaults</h2>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} />
|
<DataTable columns={planColumns} data={plans} keyExtractor={(p) => p.plan} isLoading={loading} />
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +122,7 @@ export function PlanLimitsPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<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)}>
|
<Button onClick={() => setCreateOverride(true)}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
Add Override
|
Add Override
|
||||||
@@ -155,15 +155,15 @@ export function PlanLimitsPage() {
|
|||||||
{editPlan && (
|
{editPlan && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Trees (empty = unlimited)</label>
|
<label className="mb-1 block text-sm font-medium text-[#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 })} />
|
<Input type="number" value={editPlan.max_trees ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_trees: e.target.value ? parseInt(e.target.value) : null })} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Max Sessions/Month (empty = unlimited)</label>
|
<label className="mb-1 block text-sm font-medium text-[#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 })} />
|
<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>
|
||||||
<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 })} />
|
<Input type="number" value={editPlan.max_users ?? ''} onChange={(e) => setEditPlan({ ...editPlan, max_users: e.target.value ? parseInt(e.target.value) : null })} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,23 +185,23 @@ export function PlanLimitsPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
<label className="mb-1 block text-sm font-medium text-[#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" />
|
<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>
|
||||||
<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 })} />
|
<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>
|
||||||
<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 })} />
|
<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>
|
||||||
<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 })} />
|
<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>
|
||||||
<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" />
|
<Input type="text" value={overrideForm.note ?? ''} onChange={(e) => setOverrideForm({ ...overrideForm, note: e.target.value || null })} placeholder="Reason for override" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ export function SettingsPage() {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<PageHeader title="Platform Settings" description="Global platform configuration" />
|
<PageHeader title="Platform Settings" description="Global platform configuration" />
|
||||||
|
|
||||||
<div className="max-w-xl space-y-6 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 className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-foreground">Email Verification</h3>
|
<h3 className="font-medium text-[#e2e5eb]">Email Verification</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
When enabled, unverified users see a banner prompting them to verify their email.
|
When enabled, unverified users see a banner prompting them to verify their email.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +59,7 @@ export function SettingsPage() {
|
|||||||
onClick={() => setSettings({ ...settings, email_verification_enabled: !emailVerificationEnabled })}
|
onClick={() => setSettings({ ...settings, email_verification_enabled: !emailVerificationEnabled })}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-6 w-10 rounded-full transition-colors',
|
'h-6 w-10 rounded-full transition-colors',
|
||||||
emailVerificationEnabled ? 'bg-gradient-brand' : 'bg-accent'
|
emailVerificationEnabled ? 'bg-[#22d3ee]' : 'bg-accent'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
@@ -71,8 +71,8 @@ export function SettingsPage() {
|
|||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-foreground">Maintenance Mode</h3>
|
<h3 className="font-medium text-[#e2e5eb]">Maintenance Mode</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
When enabled, users will see a maintenance message instead of the app.
|
When enabled, users will see a maintenance message instead of the app.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +93,7 @@ export function SettingsPage() {
|
|||||||
|
|
||||||
{maintenanceMode && (
|
{maintenanceMode && (
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Maintenance Message</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Maintenance Message</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={maintenanceMessage}
|
value={maintenanceMessage}
|
||||||
onChange={(e) => setSettings({ ...settings, maintenance_message: e.target.value })}
|
onChange={(e) => setSettings({ ...settings, maintenance_message: e.target.value })}
|
||||||
@@ -103,13 +103,13 @@ export function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="border-t border-border pt-4">
|
<div className="border-t border-[#1e2130] pt-4">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md px-4 py-2 text-sm font-medium',
|
'rounded-md px-4 py-2 text-sm font-medium',
|
||||||
'bg-gradient-brand text-white shadow-lg shadow-primary/20 hover:opacity-90',
|
'bg-[#22d3ee] text-white hover:brightness-110',
|
||||||
'disabled:opacity-50'
|
'disabled:opacity-50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ export default function SurveyInvitesPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Create Invite Section */}
|
{/* Create Invite Section */}
|
||||||
<div className="glass-card-static p-6">
|
<div className="card-flat p-6">
|
||||||
<h3 className="font-heading text-sm font-semibold text-foreground mb-4">Create Invite</h3>
|
<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 flex-col gap-3 sm:flex-row sm:items-end">
|
||||||
<div className="flex-1">
|
<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
|
Recipient Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -82,11 +82,11 @@ export default function SurveyInvitesPage() {
|
|||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
placeholder="John Smith"
|
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>
|
||||||
<div className="flex-1">
|
<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)
|
Email (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -94,14 +94,14 @@ export default function SurveyInvitesPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={e => setEmail(e.target.value)}
|
onChange={e => setEmail(e.target.value)}
|
||||||
placeholder="john@example.com"
|
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>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCreate(false)}
|
onClick={() => handleCreate(false)}
|
||||||
disabled={creating || !name.trim()}
|
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" />}
|
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Link2 className="h-4 w-4" />}
|
||||||
Generate Link
|
Generate Link
|
||||||
@@ -109,7 +109,7 @@ export default function SurveyInvitesPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleCreate(true)}
|
onClick={() => handleCreate(true)}
|
||||||
disabled={creating || !name.trim() || !email.trim()}
|
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" />}
|
{creating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
||||||
Send Email
|
Send Email
|
||||||
@@ -122,17 +122,17 @@ export default function SurveyInvitesPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{lastCreated && (
|
{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="flex items-center justify-between gap-3">
|
||||||
<div className="min-w-0 flex-1">
|
<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 + ':'}
|
{lastCreated.email_sent ? 'Email sent to ' + lastCreated.recipient_email + '! Link:' : 'Share this link with ' + lastCreated.recipient_name + ':'}
|
||||||
</p>
|
</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>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopy(lastCreated.survey_url)}
|
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" />}
|
{copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
@@ -142,33 +142,33 @@ export default function SurveyInvitesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invites Table */}
|
{/* Invites Table */}
|
||||||
<div className="glass-card-static overflow-hidden">
|
<div className="card-flat overflow-hidden">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border">
|
<tr className="border-b border-[#1e2130]">
|
||||||
<th className="px-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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">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-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">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-center font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">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-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">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-left font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Completed</th>
|
||||||
<th className="px-4 py-3 text-center font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">Link</th>
|
<th className="px-4 py-3 text-center font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b]">Link</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{loading ? (
|
{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 ? (
|
) : 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 => (
|
invites.map(invite => (
|
||||||
<tr key={invite.id} className="border-b border-border/50 hover:bg-white/[0.02] transition-colors">
|
<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-foreground">{invite.recipient_name}</td>
|
<td className="px-4 py-3 text-sm text-[#e2e5eb]">{invite.recipient_name}</td>
|
||||||
<td className="px-4 py-3 text-sm text-muted-foreground">{invite.recipient_email || '—'}</td>
|
<td className="px-4 py-3 text-sm text-[#848b9b]">{invite.recipient_email || '—'}</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className={cn(
|
<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'
|
invite.status === 'completed'
|
||||||
? 'bg-emerald-400/10 text-emerald-400'
|
? 'bg-emerald-400/10 text-emerald-400'
|
||||||
: 'bg-amber-400/10 text-amber-400'
|
: 'bg-amber-400/10 text-amber-400'
|
||||||
@@ -178,17 +178,17 @@ export default function SurveyInvitesPage() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-center">
|
<td className="px-4 py-3 text-center">
|
||||||
{invite.email_sent ? (
|
{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>
|
||||||
<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-sans text-xs text-xs text-[#848b9b]">{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]">{invite.completed_at ? formatDate(invite.completed_at) : '—'}</td>
|
||||||
<td className="px-4 py-3 text-center">
|
<td className="px-4 py-3 text-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopy(invite.survey_url)}
|
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"
|
title="Copy survey link"
|
||||||
>
|
>
|
||||||
<Copy className="h-3.5 w-3.5" />
|
<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 }) {
|
function AnswerDisplay({ value, type }: { value: string | string[] | undefined; type: string }) {
|
||||||
if (!value || (Array.isArray(value) && value.length === 0)) {
|
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)) {
|
if (type === 'mc-multi' && Array.isArray(value)) {
|
||||||
@@ -50,7 +50,7 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
|||||||
{value.map((v, i) => (
|
{value.map((v, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
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}
|
{v}
|
||||||
</span>
|
</span>
|
||||||
@@ -63,8 +63,8 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
|||||||
return (
|
return (
|
||||||
<ol className="space-y-1">
|
<ol className="space-y-1">
|
||||||
{value.map((v, i) => (
|
{value.map((v, i) => (
|
||||||
<li key={i} className="flex items-start gap-2 text-sm text-foreground/90">
|
<li key={i} className="flex items-start gap-2 text-sm text-[#e2e5eb]/90">
|
||||||
<span className="font-label text-xs font-bold text-primary">{i + 1}.</span>
|
<span className="font-sans text-xs text-xs font-bold text-[#22d3ee]">{i + 1}.</span>
|
||||||
{v}
|
{v}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -75,12 +75,12 @@ function AnswerDisplay({ value, type }: { value: string | string[] | undefined;
|
|||||||
if (type === 'text') {
|
if (type === 'text') {
|
||||||
return (
|
return (
|
||||||
<div className="border-l-2 border-primary/30 pl-3">
|
<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>
|
</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 }) {
|
function ExpandedDetail({ response }: { response: SurveyResponseDetail }) {
|
||||||
@@ -98,16 +98,16 @@ function ExpandedDetail({ response }: { response: SurveyResponseDetail }) {
|
|||||||
{QUESTIONS.map((q) => (
|
{QUESTIONS.map((q) => (
|
||||||
<div
|
<div
|
||||||
key={q.id}
|
key={q.id}
|
||||||
className="rounded-[10px] p-4"
|
className="rounded-lg p-4"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(24, 26, 31, 0.6)',
|
background: '#14161d',
|
||||||
border: '1px solid var(--glass-border)',
|
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}
|
Q{q.num}
|
||||||
</p>
|
</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} />
|
<AnswerDisplay value={response.responses[q.id]} type={q.type} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -150,7 +150,7 @@ function ResponseRow({
|
|||||||
<>
|
<>
|
||||||
<tr
|
<tr
|
||||||
className={cn(
|
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',
|
!response.is_read && 'bg-primary/3',
|
||||||
'hover:bg-white/[0.02]'
|
'hover:bg-white/[0.02]'
|
||||||
)}
|
)}
|
||||||
@@ -158,16 +158,16 @@ function ResponseRow({
|
|||||||
{/* Checkbox */}
|
{/* Checkbox */}
|
||||||
<td className="px-2 py-3 w-8" onClick={e => { e.stopPropagation(); onSelect() }}>
|
<td className="px-2 py-3 w-8" onClick={e => { e.stopPropagation(); onSelect() }}>
|
||||||
{isSelected ? (
|
{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>
|
</td>
|
||||||
|
|
||||||
{/* Unread dot */}
|
{/* Unread dot */}
|
||||||
<td className="px-1 py-3 w-6" onClick={onToggle}>
|
<td className="px-1 py-3 w-6" onClick={onToggle}>
|
||||||
{!response.is_read && (
|
{!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>
|
</td>
|
||||||
|
|
||||||
@@ -175,46 +175,46 @@ function ResponseRow({
|
|||||||
<td className="px-2 py-3 w-8" onClick={onToggle}>
|
<td className="px-2 py-3 w-8" onClick={onToggle}>
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-4 w-4 text-muted-foreground transition-transform',
|
'h-4 w-4 text-[#848b9b] transition-transform',
|
||||||
isExpanded && 'rotate-180'
|
isExpanded && 'rotate-180'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 font-label text-xs text-muted-foreground" onClick={onToggle}>{index + 1}</td>
|
<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-foreground font-medium' : 'text-foreground')} onClick={onToggle}>
|
<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-muted-foreground italic">Anonymous</span>}
|
{response.respondent_name || <span className="text-[#848b9b] italic">Anonymous</span>}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3" onClick={onToggle}>
|
<td className="px-4 py-3" onClick={onToggle}>
|
||||||
{response.source === 'invite' ? (
|
{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" />
|
<User className="h-3 w-3" />
|
||||||
Invite
|
Invite
|
||||||
{response.invite_name && (
|
{response.invite_name && (
|
||||||
<span className="text-primary/70">({response.invite_name})</span>
|
<span className="text-[#22d3ee]/70">({response.invite_name})</span>
|
||||||
)}
|
)}
|
||||||
</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" />
|
<Link2 className="h-3 w-3" />
|
||||||
Direct
|
Direct
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</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', {
|
{new Date(response.created_at).toLocaleDateString('en-US', {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})}
|
})}
|
||||||
</td>
|
</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}
|
{answeredCount} / {QUESTIONS.length}
|
||||||
</td>
|
</td>
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<td className="px-3 py-3 w-10 relative">
|
<td className="px-3 py-3 w-10 relative">
|
||||||
<button
|
<button
|
||||||
onClick={e => { e.stopPropagation(); setShowMenu(!showMenu) }}
|
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" />
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
@@ -223,18 +223,18 @@ function ResponseRow({
|
|||||||
<div className="fixed inset-0 z-40" onClick={() => setShowMenu(false)} />
|
<div className="fixed inset-0 z-40" onClick={() => setShowMenu(false)} />
|
||||||
<div
|
<div
|
||||||
className="absolute right-3 top-full z-50 mt-1 w-44 rounded-xl py-1 shadow-xl"
|
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
|
<button
|
||||||
onClick={() => { onMarkRead(); setShowMenu(false) }}
|
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 ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
||||||
{response.is_read ? 'Mark Unread' : 'Mark Read'}
|
{response.is_read ? 'Mark Unread' : 'Mark Read'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => { onArchive(); setShowMenu(false) }}
|
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 ? <ArchiveRestore className="h-3.5 w-3.5" /> : <Archive className="h-3.5 w-3.5" />}
|
||||||
{response.archived_at ? 'Unarchive' : 'Archive'}
|
{response.archived_at ? 'Unarchive' : 'Archive'}
|
||||||
@@ -408,7 +408,7 @@ export default function SurveyResponsesPage() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center py-20">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -416,7 +416,7 @@ export default function SurveyResponsesPage() {
|
|||||||
if (error && !data) {
|
if (error && !data) {
|
||||||
return (
|
return (
|
||||||
<div className="px-6 py-8">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -434,10 +434,10 @@ export default function SurveyResponsesPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowArchived(!showArchived)}
|
onClick={() => setShowArchived(!showArchived)}
|
||||||
className={cn(
|
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
|
showArchived
|
||||||
? 'bg-primary/10 text-primary border-primary/20'
|
? 'bg-[rgba(34,211,238,0.10)] text-[#22d3ee] border-primary/20'
|
||||||
: 'bg-white/[0.04] text-muted-foreground border-brand-border hover:border-white/[0.12]'
|
: 'bg-white/[0.04] text-[#848b9b] border-brand-border hover:border-white/[0.12]'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Archive className="h-3.5 w-3.5" />
|
<Archive className="h-3.5 w-3.5" />
|
||||||
@@ -446,7 +446,7 @@ export default function SurveyResponsesPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
disabled={exporting || responses.length === 0}
|
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 ? (
|
{exporting ? (
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
@@ -460,36 +460,36 @@ export default function SurveyResponsesPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{error && (
|
{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}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Stat cards */}
|
{/* Stat cards */}
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="glass-card-static px-5 py-4 flex-1">
|
<div className="card-flat px-5 py-4 flex-1">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||||
Total Responses
|
Total Responses
|
||||||
</p>
|
</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}
|
{data?.total ?? 0}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card-static px-5 py-4 flex-1">
|
<div className="card-flat px-5 py-4 flex-1">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||||
This Week
|
This Week
|
||||||
</p>
|
</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}
|
{data?.this_week ?? 0}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="glass-card-static px-5 py-4 flex-1">
|
<div className="card-flat px-5 py-4 flex-1">
|
||||||
<p className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground mb-1">
|
<p className="font-sans text-xs text-[0.625rem] uppercase tracking-widest text-[#848b9b] mb-1">
|
||||||
Unread
|
Unread
|
||||||
</p>
|
</p>
|
||||||
<p className={cn(
|
<p className={cn(
|
||||||
'text-2xl font-heading font-bold',
|
'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}
|
{data?.unread ?? 0}
|
||||||
</p>
|
</p>
|
||||||
@@ -502,27 +502,27 @@ export default function SurveyResponsesPage() {
|
|||||||
className="flex items-center gap-3 rounded-xl px-4 py-2.5"
|
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)' }}
|
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
|
{selectedIds.size} selected
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBulkAction('mark_read')}
|
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" />
|
<Eye className="h-3.5 w-3.5" />
|
||||||
Mark Read
|
Mark Read
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBulkAction('mark_unread')}
|
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" />
|
<EyeOff className="h-3.5 w-3.5" />
|
||||||
Mark Unread
|
Mark Unread
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleBulkAction('archive')}
|
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 className="h-3.5 w-3.5" />
|
||||||
Archive
|
Archive
|
||||||
@@ -536,7 +536,7 @@ export default function SurveyResponsesPage() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedIds(new Set())}
|
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
|
Clear
|
||||||
</button>
|
</button>
|
||||||
@@ -544,14 +544,14 @@ export default function SurveyResponsesPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<div className="glass-card-static overflow-hidden">
|
<div className="card-flat overflow-hidden">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="border-b border-border/50">
|
<tr className="border-b border-[#1e2130]/50">
|
||||||
<th className="px-2 py-3 w-8">
|
<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 ? (
|
{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" />
|
<Square className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
@@ -559,19 +559,19 @@ export default function SurveyResponsesPage() {
|
|||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-3 w-6" />
|
<th className="px-1 py-3 w-6" />
|
||||||
<th className="px-2 py-3 w-8" />
|
<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>
|
||||||
<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
|
Respondent
|
||||||
</th>
|
</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
|
Source
|
||||||
</th>
|
</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
|
Date
|
||||||
</th>
|
</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
|
Answered
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-3 w-10" />
|
<th className="px-3 py-3 w-10" />
|
||||||
@@ -580,7 +580,7 @@ export default function SurveyResponsesPage() {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{responses.length === 0 ? (
|
{responses.length === 0 ? (
|
||||||
<tr>
|
<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.'}
|
{showArchived ? 'No archived responses.' : 'No survey responses yet.'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ export function UserDetailPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectClass = cn(
|
const selectClass = 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]',
|
||||||
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
'placeholder:text-[#848b9b] focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)
|
)
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -224,15 +224,15 @@ export function UserDetailPage() {
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/users')}
|
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" />
|
<ArrowLeft className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex-1">
|
<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}
|
{user.full_name || user.email}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-muted-foreground">{user.email}</p>
|
<p className="text-sm text-[#848b9b]">{user.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{user.is_super_admin && (
|
{user.is_super_admin && (
|
||||||
@@ -252,21 +252,21 @@ export function UserDetailPage() {
|
|||||||
|
|
||||||
{/* Account & Subscription */}
|
{/* Account & Subscription */}
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||||
Account & Subscription
|
Account & Subscription
|
||||||
</h2>
|
</h2>
|
||||||
<dl className="space-y-3">
|
<dl className="space-y-3">
|
||||||
{user.account && (
|
{user.account && (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Account</dt>
|
<dt className="text-sm text-[#848b9b]">Account</dt>
|
||||||
<dd className="text-sm text-foreground">{user.account.name}</dd>
|
<dd className="text-sm text-[#e2e5eb]">{user.account.name}</dd>
|
||||||
</div>
|
</div>
|
||||||
{user.account.display_code && (
|
{user.account.display_code && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Display Code</dt>
|
<dt className="text-sm text-[#848b9b]">Display Code</dt>
|
||||||
<dd className="text-sm font-mono text-muted-foreground">{user.account.display_code}</dd>
|
<dd className="text-sm font-mono text-[#848b9b]">{user.account.display_code}</dd>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -274,13 +274,13 @@ export function UserDetailPage() {
|
|||||||
{user.subscription ? (
|
{user.subscription ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Plan</dt>
|
<dt className="text-sm text-[#848b9b]">Plan</dt>
|
||||||
<dd className="text-sm font-semibold text-foreground">
|
<dd className="text-sm font-semibold text-[#e2e5eb]">
|
||||||
{user.subscription.plan.charAt(0).toUpperCase() + user.subscription.plan.slice(1)}
|
{user.subscription.plan.charAt(0).toUpperCase() + user.subscription.plan.slice(1)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Status</dt>
|
<dt className="text-sm text-[#848b9b]">Status</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<StatusBadge variant={user.subscription.status === 'trialing' ? 'warning' : 'success'}>
|
<StatusBadge variant={user.subscription.status === 'trialing' ? 'warning' : 'success'}>
|
||||||
{user.subscription.status}
|
{user.subscription.status}
|
||||||
@@ -289,24 +289,24 @@ export function UserDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
{user.subscription.current_period_end && (
|
{user.subscription.current_period_end && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Period End</dt>
|
<dt className="text-sm text-[#848b9b]">Period End</dt>
|
||||||
<dd className="text-sm text-muted-foreground">{fmt(user.subscription.current_period_end)}</dd>
|
<dd className="text-sm text-[#848b9b]">{fmt(user.subscription.current_period_end)}</dd>
|
||||||
</div>
|
</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">
|
<div className="flex justify-between">
|
||||||
<dt className="text-sm text-muted-foreground">Joined</dt>
|
<dt className="text-sm text-[#848b9b]">Joined</dt>
|
||||||
<dd className="text-sm text-muted-foreground">{fmt(user.created_at)}</dd>
|
<dd className="text-sm text-[#848b9b]">{fmt(user.created_at)}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Admin Actions */}
|
{/* Admin Actions */}
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||||
Admin Actions
|
Admin Actions
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -317,16 +317,16 @@ export function UserDetailPage() {
|
|||||||
setSelectedPlan(user.subscription?.plan || 'free')
|
setSelectedPlan(user.subscription?.plan || 'free')
|
||||||
setPlanModalOpen(true)
|
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
|
Change Plan
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setTrialModalOpen(true)}
|
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'}
|
{user.subscription?.status === 'trialing' ? 'Extend Trial' : 'Start Trial'}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -349,9 +349,9 @@ export function UserDetailPage() {
|
|||||||
setResetTempPassword(null)
|
setResetTempPassword(null)
|
||||||
setResetModalOpen(true)
|
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
|
Reset Password
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -402,42 +402,42 @@ export function UserDetailPage() {
|
|||||||
|
|
||||||
{/* Invite Code Used */}
|
{/* Invite Code Used */}
|
||||||
{user.invite_code_used && (
|
{user.invite_code_used && (
|
||||||
<div className="bg-card border border-border rounded-xl p-6">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl p-6">
|
||||||
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-[#848b9b]">
|
||||||
<Ticket className="mr-2 inline h-4 w-4" />
|
<Ticket className="mr-2 inline h-4 w-4" />
|
||||||
Invite Code Used
|
Invite Code Used
|
||||||
</h2>
|
</h2>
|
||||||
<dl className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
<dl className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-xs text-muted-foreground">Code</dt>
|
<dt className="text-xs text-[#848b9b]">Code</dt>
|
||||||
<dd className="mt-1 font-mono text-sm text-muted-foreground">{user.invite_code_used.code}</dd>
|
<dd className="mt-1 font-mono text-sm text-[#848b9b]">{user.invite_code_used.code}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-xs text-muted-foreground">Plan Assigned</dt>
|
<dt className="text-xs text-[#848b9b]">Plan Assigned</dt>
|
||||||
<dd className="mt-1 text-sm text-muted-foreground">
|
<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)}
|
{user.invite_code_used.assigned_plan.charAt(0).toUpperCase() + user.invite_code_used.assigned_plan.slice(1)}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-xs text-muted-foreground">Trial Days</dt>
|
<dt className="text-xs text-[#848b9b]">Trial Days</dt>
|
||||||
<dd className="mt-1 text-sm text-muted-foreground">{user.invite_code_used.trial_duration_days ?? '—'}</dd>
|
<dd className="mt-1 text-sm text-[#848b9b]">{user.invite_code_used.trial_duration_days ?? '—'}</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-xs text-muted-foreground">Created By</dt>
|
<dt className="text-xs text-[#848b9b]">Created By</dt>
|
||||||
<dd className="mt-1 text-sm text-muted-foreground">{user.invite_code_used.created_by_email ?? '—'}</dd>
|
<dd className="mt-1 text-sm text-[#848b9b]">{user.invite_code_used.created_by_email ?? '—'}</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Tabs: Sessions / Audit Logs */}
|
{/* Tabs: Sessions / Audit Logs */}
|
||||||
<div className="bg-card border border-border rounded-xl">
|
<div className="bg-[#14161d] border border-[#1e2130] rounded-xl">
|
||||||
<div className="flex border-b border-border">
|
<div className="flex border-b border-[#1e2130]">
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('sessions')}
|
onClick={() => setActiveTab('sessions')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-6 py-3 text-sm font-medium',
|
'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})
|
Sessions ({user.total_sessions})
|
||||||
@@ -446,7 +446,7 @@ export function UserDetailPage() {
|
|||||||
onClick={() => setActiveTab('audit')}
|
onClick={() => setActiveTab('audit')}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-6 py-3 text-sm font-medium',
|
'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})
|
Audit Logs ({user.total_audit_logs})
|
||||||
@@ -458,7 +458,7 @@ export function UserDetailPage() {
|
|||||||
user.recent_sessions.length > 0 ? (
|
user.recent_sessions.length > 0 ? (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<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">Tree</th>
|
||||||
<th className="pb-2 font-medium">Started</th>
|
<th className="pb-2 font-medium">Started</th>
|
||||||
<th className="pb-2 font-medium">Completed</th>
|
<th className="pb-2 font-medium">Completed</th>
|
||||||
@@ -467,17 +467,17 @@ export function UserDetailPage() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{user.recent_sessions.map(s => (
|
{user.recent_sessions.map(s => (
|
||||||
<tr key={s.id} className="border-b border-border">
|
<tr key={s.id} className="border-b border-[#1e2130]">
|
||||||
<td className="py-3 text-sm text-muted-foreground">{s.tree_name ?? '—'}</td>
|
<td className="py-3 text-sm text-[#848b9b]">{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-[#848b9b]">{fmtFull(s.started_at)}</td>
|
||||||
<td className="py-3 text-sm text-muted-foreground">{fmtFull(s.completed_at)}</td>
|
<td className="py-3 text-sm text-[#848b9b]">{fmtFull(s.completed_at)}</td>
|
||||||
<td className="py-3">
|
<td className="py-3">
|
||||||
{s.outcome ? (
|
{s.outcome ? (
|
||||||
<StatusBadge variant={s.outcome === 'resolved' ? 'success' : 'default'}>
|
<StatusBadge variant={s.outcome === 'resolved' ? 'success' : 'default'}>
|
||||||
{s.outcome}
|
{s.outcome}
|
||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-sm text-muted-foreground">—</span>
|
<span className="text-sm text-[#848b9b]">—</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -485,7 +485,7 @@ export function UserDetailPage() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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 ? (
|
user.recent_audit_logs.length > 0 ? (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead>
|
<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">Action</th>
|
||||||
<th className="pb-2 font-medium">Resource</th>
|
<th className="pb-2 font-medium">Resource</th>
|
||||||
<th className="pb-2 font-medium">Time</th>
|
<th className="pb-2 font-medium">Time</th>
|
||||||
@@ -501,16 +501,16 @@ export function UserDetailPage() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{user.recent_audit_logs.map(a => (
|
{user.recent_audit_logs.map(a => (
|
||||||
<tr key={a.id} className="border-b border-border">
|
<tr key={a.id} className="border-b border-[#1e2130]">
|
||||||
<td className="py-3 text-sm text-muted-foreground">{a.action}</td>
|
<td className="py-3 text-sm text-[#848b9b]">{a.action}</td>
|
||||||
<td className="py-3 text-sm text-muted-foreground">{a.resource_type ?? '—'}</td>
|
<td className="py-3 text-sm text-[#848b9b]">{a.resource_type ?? '—'}</td>
|
||||||
<td className="py-3 text-sm text-muted-foreground">{fmtFull(a.created_at)}</td>
|
<td className="py-3 text-sm text-[#848b9b]">{fmtFull(a.created_at)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
@@ -530,7 +530,7 @@ export function UserDetailPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<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
|
<select
|
||||||
aria-label="Subscription plan"
|
aria-label="Subscription plan"
|
||||||
value={selectedPlan}
|
value={selectedPlan}
|
||||||
@@ -560,11 +560,11 @@ export function UserDetailPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Choose how to reset the password for <span className="font-medium text-foreground">{user.full_name || user.email}</span>.
|
Choose how to reset the password for <span className="font-medium text-[#e2e5eb]">{user.full_name || user.email}</span>.
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-2">
|
<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
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="reset-mode"
|
name="reset-mode"
|
||||||
@@ -574,11 +574,11 @@ export function UserDetailPage() {
|
|||||||
className="mt-0.5"
|
className="mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-foreground">Send Reset Email</div>
|
<div className="text-sm font-medium text-[#e2e5eb]">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-xs text-[#848b9b]">User receives an email with a reset link (30 min expiry)</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</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
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="reset-mode"
|
name="reset-mode"
|
||||||
@@ -588,8 +588,8 @@ export function UserDetailPage() {
|
|||||||
className="mt-0.5"
|
className="mt-0.5"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-foreground">Generate Temp Password</div>
|
<div className="text-sm font-medium text-[#e2e5eb]">Generate Temp Password</div>
|
||||||
<div className="text-xs text-muted-foreground">A temporary password is generated. You share it manually.</div>
|
<div className="text-xs text-[#848b9b]">A temporary password is generated. You share it manually.</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -613,18 +613,18 @@ export function UserDetailPage() {
|
|||||||
This password will not be shown again. Copy it now.
|
This password will not be shown again. Copy it now.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<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}
|
{resetTempPassword}
|
||||||
</code>
|
</code>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyResetPassword}
|
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"
|
title="Copy password"
|
||||||
>
|
>
|
||||||
{resetCopied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
{resetCopied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
||||||
</button>
|
</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 next login.
|
The user will be required to change this password on next login.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -646,7 +646,7 @@ export function UserDetailPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<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
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={trialDays}
|
value={trialDays}
|
||||||
@@ -654,7 +654,7 @@ export function UserDetailPage() {
|
|||||||
min={1}
|
min={1}
|
||||||
max={90}
|
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>
|
</div>
|
||||||
</Modal>
|
</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">
|
<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:
|
This user cannot be deleted because they have dependencies:
|
||||||
</div>
|
</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]) => (
|
{Object.entries(hardDeleteBlockers).map(([key, count]) => (
|
||||||
<li key={key} className="flex justify-between">
|
<li key={key} className="flex justify-between">
|
||||||
<span>{key.replace(/_/g, ' ')}</span>
|
<span>{key.replace(/_/g, ' ')}</span>
|
||||||
<span className="font-mono text-muted-foreground">{count}</span>
|
<span className="font-mono text-[#848b9b]">{count}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -190,8 +190,8 @@ export function UsersPage() {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
render: (u) => (
|
render: (u) => (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium text-foreground">{u.name}</div>
|
<div className="font-medium text-[#e2e5eb]">{u.name}</div>
|
||||||
<div className="text-xs text-muted-foreground">{u.email}</div>
|
<div className="text-xs text-[#848b9b]">{u.email}</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -226,7 +226,7 @@ export function UsersPage() {
|
|||||||
header: 'Joined',
|
header: 'Joined',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (u) => (
|
render: (u) => (
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-[#848b9b]">
|
||||||
{new Date(u.created_at).toLocaleDateString()}
|
{new Date(u.created_at).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -286,12 +286,12 @@ export function UsersPage() {
|
|||||||
placeholder="Search by name or email..."
|
placeholder="Search by name or email..."
|
||||||
className="max-w-sm"
|
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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showArchived}
|
checked={showArchived}
|
||||||
onChange={(e) => { setShowArchived(e.target.checked); setPage(1) }}
|
onChange={(e) => { setShowArchived(e.target.checked); setPage(1) }}
|
||||||
className="rounded border-border bg-card"
|
className="rounded border-[#1e2130] bg-[#14161d]"
|
||||||
/>
|
/>
|
||||||
Show archived
|
Show archived
|
||||||
</label>
|
</label>
|
||||||
@@ -326,14 +326,14 @@ export function UsersPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Changing role for <span className="font-medium text-foreground">{roleModalUser?.name}</span>
|
Changing role for <span className="font-medium text-[#e2e5eb]">{roleModalUser?.name}</span>
|
||||||
</p>
|
</p>
|
||||||
<select
|
<select
|
||||||
value={newRole}
|
value={newRole}
|
||||||
onChange={(e) => setNewRole(e.target.value)}
|
onChange={(e) => setNewRole(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-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'
|
'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">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-[#848b9b]">
|
||||||
Moving <span className="font-medium text-foreground">{moveModalUser?.name}</span> to a new account.
|
Moving <span className="font-medium text-[#e2e5eb]">{moveModalUser?.name}</span> to a new account.
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Display Code</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Account Display Code</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayCode}
|
value={displayCode}
|
||||||
@@ -389,7 +389,7 @@ export function UsersPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-foreground">Name</label>
|
<label className="mb-1 block text-sm font-medium text-[#e2e5eb]">Name</label>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={createForm.name}
|
value={createForm.name}
|
||||||
@@ -398,7 +398,7 @@ export function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
value={createForm.email}
|
value={createForm.email}
|
||||||
@@ -407,12 +407,12 @@ export function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<select
|
||||||
value={createForm.account_mode}
|
value={createForm.account_mode}
|
||||||
onChange={(e) => setCreateForm(f => ({ ...f, account_mode: e.target.value as 'existing' | 'personal' }))}
|
onChange={(e) => setCreateForm(f => ({ ...f, account_mode: e.target.value as 'existing' | 'personal' }))}
|
||||||
className={cn(
|
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'
|
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -423,7 +423,7 @@ export function UsersPage() {
|
|||||||
{createForm.account_mode === 'existing' && (
|
{createForm.account_mode === 'existing' && (
|
||||||
<>
|
<>
|
||||||
<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
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={createForm.account_display_code}
|
value={createForm.account_display_code}
|
||||||
@@ -432,12 +432,12 @@ export function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<select
|
||||||
value={createForm.account_role}
|
value={createForm.account_role}
|
||||||
onChange={(e) => setCreateForm(f => ({ ...f, account_role: e.target.value as 'engineer' | 'viewer' }))}
|
onChange={(e) => setCreateForm(f => ({ ...f, account_role: e.target.value as 'engineer' | 'viewer' }))}
|
||||||
className={cn(
|
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'
|
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -453,9 +453,9 @@ export function UsersPage() {
|
|||||||
id="send-email"
|
id="send-email"
|
||||||
checked={createForm.send_email}
|
checked={createForm.send_email}
|
||||||
onChange={(e) => setCreateForm(f => ({ ...f, send_email: e.target.checked }))}
|
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
|
Send welcome email with temporary password
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -479,21 +479,21 @@ export function UsersPage() {
|
|||||||
This password will not be shown again. Copy it now.
|
This password will not be shown again. Copy it now.
|
||||||
</div>
|
</div>
|
||||||
<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">
|
<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}
|
{tempPassword}
|
||||||
</code>
|
</code>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopyPassword}
|
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"
|
title="Copy password"
|
||||||
>
|
>
|
||||||
{copied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
{copied ? <Check className="h-4 w-4 text-green-400" /> : <Copy className="h-4 w-4" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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.
|
The user will be required to change this password on first login.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,7 +516,7 @@ export function UsersPage() {
|
|||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<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
|
<Input
|
||||||
type="email"
|
type="email"
|
||||||
value={inviteForm.email}
|
value={inviteForm.email}
|
||||||
@@ -525,7 +525,7 @@ export function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={inviteForm.account_display_code}
|
value={inviteForm.account_display_code}
|
||||||
@@ -534,12 +534,12 @@ export function UsersPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<select
|
||||||
value={inviteForm.role}
|
value={inviteForm.role}
|
||||||
onChange={(e) => setInviteForm(f => ({ ...f, role: e.target.value as 'engineer' | 'viewer' }))}
|
onChange={(e) => setInviteForm(f => ({ ...f, role: e.target.value as 'engineer' | 'viewer' }))}
|
||||||
className={cn(
|
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'
|
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user