Files
resolutionflow/frontend/src/components/session/HandoffModal.tsx
chihlasm 01836d6a2d fix: replace text-secondary with text-muted-foreground in branching components
In Tailwind v4, text-secondary resolves to --color-secondary (#2e3140),
a dark surface color — NOT --color-text-secondary (#848b9b). This made
all secondary text invisible on dark backgrounds.

The correct class is text-muted-foreground which maps to #848b9b.
This matches the pattern used by existing FlowPilot components.

Also reverts the unnecessary index.css variable bump from prior commit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:36:01 +00:00

168 lines
6.0 KiB
TypeScript

import { useState } from 'react'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { HandoffCreateRequest } from '@/types/branching'
interface HandoffModalProps {
onClose: () => void
onSubmit: (data: HandoffCreateRequest) => Promise<void>
}
type HandoffIntent = 'park' | 'escalate'
export function HandoffModal({ onClose, onSubmit }: HandoffModalProps) {
const [intent, setIntent] = useState<HandoffIntent>('park')
const [notes, setNotes] = useState('')
const [elevated, setElevated] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
async function handleSubmit() {
setIsSubmitting(true)
try {
const data: HandoffCreateRequest = {
intent,
engineer_notes: notes.trim() || undefined,
priority: intent === 'escalate' && elevated ? 'elevated' : 'normal',
}
await onSubmit(data)
onClose()
} finally {
setIsSubmitting(false)
}
}
return (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50"
onClick={onClose}
aria-hidden="true"
/>
{/* Dialog */}
<div
className={cn(
'relative z-10 w-full max-w-full sm:max-w-lg',
'bg-card border border-default rounded-lg',
'flex flex-col gap-0'
)}
role="dialog"
aria-modal="true"
aria-label="Hand off session"
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b border-default">
<h2 className="text-sm font-heading font-semibold text-heading">Hand Off Session</h2>
<button
type="button"
onClick={onClose}
className="text-muted hover:text-primary transition-colors"
aria-label="Close"
>
<X size={16} />
</button>
</div>
{/* Body */}
<div className="flex flex-col gap-4 px-4 py-4">
{/* Intent toggle */}
<div className="flex flex-col gap-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted">
Handoff Type
</span>
<div className="flex gap-2">
<button
type="button"
onClick={() => setIntent('park')}
className={cn(
'flex-1 rounded-[5px] border px-3 py-2 text-sm font-medium transition-colors',
intent === 'park'
? 'border-accent bg-accent-dim text-accent-text'
: 'border-default bg-transparent text-muted-foreground hover:bg-elevated hover:text-primary'
)}
>
Park
</button>
<button
type="button"
onClick={() => setIntent('escalate')}
className={cn(
'flex-1 rounded-[5px] border px-3 py-2 text-sm font-medium transition-colors',
intent === 'escalate'
? 'border-accent bg-accent-dim text-accent-text'
: 'border-default bg-transparent text-muted-foreground hover:bg-elevated hover:text-primary'
)}
>
Escalate
</button>
</div>
<p className="text-xs text-muted-foreground">
{intent === 'park'
? 'Park this session to resume later or hand to another engineer.'
: 'Escalate to a senior engineer with full context and branch history.'}
</p>
</div>
{/* Notes */}
<div className="flex flex-col gap-1.5">
<label
htmlFor="handoff-notes"
className="text-[10px] font-semibold uppercase tracking-wider text-muted"
>
Engineer Notes
<span className="ml-1 normal-case font-normal text-muted">(optional)</span>
</label>
<textarea
id="handoff-notes"
value={notes}
onChange={e => setNotes(e.target.value)}
rows={3}
placeholder="Add context for whoever picks this up…"
className={cn(
'w-full resize-none rounded-[5px] border border-default bg-input',
'px-3 py-2 text-sm text-primary placeholder:text-muted',
'focus:outline-none focus:border-accent focus:shadow-[0_0_0_2px_var(--color-accent-dim)]',
'transition-colors'
)}
/>
</div>
{/* Priority (escalate only) */}
{intent === 'escalate' && (
<label className="flex items-center gap-2 cursor-pointer select-none">
<input
type="checkbox"
checked={elevated}
onChange={e => setElevated(e.target.checked)}
className="accent-accent w-4 h-4"
/>
<span className="text-sm text-primary">Mark as elevated priority</span>
</label>
)}
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-2 px-4 py-3 border-t border-default">
<button
type="button"
onClick={onClose}
disabled={isSubmitting}
className="rounded-[5px] border border-default px-4 py-2 text-sm text-muted-foreground hover:bg-elevated hover:text-primary transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
type="button"
onClick={handleSubmit}
disabled={isSubmitting}
className="rounded-[5px] bg-accent px-4 py-2 text-sm font-medium text-white hover:bg-accent-hover transition-colors disabled:opacity-50"
>
{isSubmitting ? 'Submitting…' : intent === 'park' ? 'Park Session' : 'Escalate Session'}
</button>
</div>
</div>
</div>
)
}