diff --git a/frontend/src/components/session/CSATModal.tsx b/frontend/src/components/session/CSATModal.tsx new file mode 100644 index 00000000..b99bcc74 --- /dev/null +++ b/frontend/src/components/session/CSATModal.tsx @@ -0,0 +1,118 @@ +import { useState } from 'react' +import { Star } from 'lucide-react' +import { Modal } from '@/components/common/Modal' +import { analyticsApi } from '@/api' +import { cn } from '@/lib/utils' + +interface CSATModalProps { + isOpen: boolean + onClose: () => void + sessionId: string +} + +const RATED_SESSIONS_KEY = 'rf-rated-sessions' + +function getRatedSessions(): string[] { + try { + return JSON.parse(localStorage.getItem(RATED_SESSIONS_KEY) || '[]') + } catch { + return [] + } +} + +function markSessionRated(sessionId: string) { + const rated = getRatedSessions() + rated.push(sessionId) + localStorage.setItem(RATED_SESSIONS_KEY, JSON.stringify(rated.slice(-100))) +} + +export function hasBeenRated(sessionId: string): boolean { + return getRatedSessions().includes(sessionId) +} + +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 ( + +
+

+ Your feedback helps flow authors improve troubleshooting paths. +

+ + {/* Star rating */} +
+ {[1, 2, 3, 4, 5].map((value) => ( + + ))} +
+ + {/* Comment */} +