- Fix httpx version conflict: requirements-dev.txt now uses >=0.27.0 to match requirements.txt - Extract CSAT helper functions to csatUtils.ts to fix react-refresh/only-export-components - Remove default export from admin/EmptyState.tsx shim (same rule) - Fix empty catch block in Modal.tsx (no-empty) - Add eslint-disable comments for intentional setState-in-effect patterns in FlowAnalyticsPanel, QuickLaunch, NodeEditorPanel, useCachedQuota, MyAnalyticsPage, TeamAnalyticsPage - Add eslint-disable comments for intentional _children destructure in NodeEditorPanel - Fix _parentId unused var in useTreeLayout.ts - Rewrite usePaginationParams.ts to avoid reading refs during render Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { useState } from 'react'
|
|
import { Star } from 'lucide-react'
|
|
import { Modal } from '@/components/common/Modal'
|
|
import { analyticsApi } from '@/api'
|
|
import { cn } from '@/lib/utils'
|
|
import { markSessionRated } from './csatUtils'
|
|
|
|
interface CSATModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
sessionId: string
|
|
}
|
|
|
|
export function CSATModal({ isOpen, onClose, sessionId }: CSATModalProps) {
|
|
const [rating, setRating] = useState(0)
|
|
const [hoveredRating, setHoveredRating] = useState(0)
|
|
const [comment, setComment] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
|
|
const handleSubmit = async () => {
|
|
if (rating === 0 || submitting) return
|
|
setSubmitting(true)
|
|
try {
|
|
await analyticsApi.rateSession(sessionId, rating, comment || undefined)
|
|
markSessionRated(sessionId)
|
|
onClose()
|
|
} catch {
|
|
// Silently fail — still close
|
|
onClose()
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const handleSkip = () => {
|
|
markSessionRated(sessionId)
|
|
onClose()
|
|
}
|
|
|
|
return (
|
|
<Modal isOpen={isOpen} onClose={handleSkip} title="How was this flow?" size="sm">
|
|
<div className="space-y-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
Your feedback helps flow authors improve troubleshooting paths.
|
|
</p>
|
|
|
|
{/* Star rating */}
|
|
<div className="flex items-center justify-center gap-1">
|
|
{[1, 2, 3, 4, 5].map((value) => (
|
|
<button
|
|
key={value}
|
|
onClick={() => setRating(value)}
|
|
onMouseEnter={() => setHoveredRating(value)}
|
|
onMouseLeave={() => setHoveredRating(0)}
|
|
className="p-1 transition-colors"
|
|
>
|
|
<Star
|
|
size={28}
|
|
className={cn(
|
|
'transition-colors',
|
|
(hoveredRating || rating) >= value
|
|
? 'fill-yellow-400 text-yellow-400'
|
|
: 'fill-none text-muted-foreground'
|
|
)}
|
|
/>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Comment */}
|
|
<textarea
|
|
value={comment}
|
|
onChange={(e) => setComment(e.target.value)}
|
|
placeholder="Any comments? (optional)"
|
|
maxLength={500}
|
|
rows={3}
|
|
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 resize-none"
|
|
/>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center justify-between">
|
|
<button
|
|
onClick={handleSkip}
|
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
>
|
|
Skip
|
|
</button>
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={rating === 0 || submitting}
|
|
className="rounded-lg bg-gradient-brand px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-primary/20 hover:opacity-90 transition-opacity disabled:opacity-50"
|
|
>
|
|
{submitting ? 'Submitting...' : 'Submit'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
)
|
|
}
|