'use client';
import { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { X, ThumbsUp, ThumbsDown, MessageSquare, Check, Loader2 } from 'lucide-react';
import { useLocale } from 'next-intl';
import type { FeedbackModalProps, FeedbackContext } from '@/lib/types/feedback';
export function FeedbackModal({
isOpen,
onClose,
feedbackType,
messageId,
conversationId,
messageContent,
onSuccess,
}: FeedbackModalProps) {
const locale = useLocale();
const [additionalComments, setAdditionalComments] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!isOpen || !mounted) return null;
const collectContext = (): FeedbackContext => {
return {
userAgent: typeof window !== 'undefined' ? navigator.userAgent : '',
pageUrl: typeof window !== 'undefined' ? window.location.href : '',
screenSize:
typeof window !== 'undefined'
? `${window.innerWidth}x${window.innerHeight}`
: '',
locale,
};
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
const context = collectContext();
const response = await fetch('/api/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
feedback_type: feedbackType,
message_id: messageId,
conversation_id: conversationId,
message_content: messageContent,
additional_comments: additionalComments || null,
context,
}),
});
const result = await response.json();
if (result.success) {
setIsSubmitted(true);
onSuccess?.();
// Auto-close after showing success message
setTimeout(() => {
handleClose();
}, 1500);
} else {
setError(result.error || 'Failed to submit feedback');
}
} catch (err) {
setError('An unexpected error occurred. Please try again.');
console.error('Feedback submission error:', err);
} finally {
setIsSubmitting(false);
}
};
const handleClose = () => {
if (!isSubmitting) {
setAdditionalComments('');
setError(null);
setIsSubmitted(false);
onClose();
}
};
const getIcon = () => {
if (isSubmitted) {
return <Check className="text-green-500" size={28} />;
}
switch (feedbackType) {
case 'positive':
return <ThumbsUp className="text-green-500" size={28} />;
case 'negative':
return <ThumbsDown className="text-red-500" size={28} />;
default:
return <MessageSquare className="text-accent-red" size={28} />;
}
};
const getTitle = () => {
if (isSubmitted) {
return 'Thank you!';
}
switch (feedbackType) {
case 'positive':
return 'Thanks for the positive feedback!';
case 'negative':
return 'Help us improve';
default:
return 'Send Feedback';
}
};
const getDescription = () => {
if (isSubmitted) {
return 'Your feedback has been submitted.';
}
switch (feedbackType) {
case 'positive':
return 'We\'re glad this response was helpful. Want to tell us more?';
case 'negative':
return 'We\'re sorry this response wasn\'t helpful. Please share what went wrong.';
default:
return 'Share your thoughts, suggestions, or report issues.';
}
};
return createPortal(
<>
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[9998]"
onClick={handleClose}
/>
{/* Modal */}
<div className="fixed inset-0 z-[9999] overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4">
<div
className="
bg-background-secondary border-2 border-border-primary rounded-lg
max-w-md w-full
shadow-2xl
"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-border-primary">
<div className="flex items-center gap-3">
{getIcon()}
<h2 className="text-xl font-bold text-text-primary">{getTitle()}</h2>
</div>
<button
onClick={handleClose}
disabled={isSubmitting}
className="text-text-tertiary hover:text-text-primary transition-colors disabled:opacity-50"
aria-label="Close"
>
<X size={24} />
</button>
</div>
{/* Content */}
{isSubmitted ? (
<div className="p-6 text-center">
<p className="text-text-primary dark:text-text-primary">{getDescription()}</p>
</div>
) : (
<form onSubmit={handleSubmit} className="p-6">
<p className="text-text-primary dark:text-text-primary mb-6">{getDescription()}</p>
{/* Additional comments textarea */}
<div className="mb-6">
<label
htmlFor="feedback-comments"
className="block text-sm font-medium text-text-primary dark:text-text-primary mb-2"
>
Additional comments{' '}
<span className="text-gray-700 dark:text-gray-300 font-normal">(optional)</span>
</label>
<textarea
id="feedback-comments"
value={additionalComments}
onChange={(e) => setAdditionalComments(e.target.value)}
placeholder={
feedbackType === 'negative'
? 'What was wrong with this response?'
: 'Tell us more about your experience...'
}
rows={4}
maxLength={2000}
disabled={isSubmitting}
className="
w-full px-4 py-3 rounded-lg
bg-background-primary border-2 border-border-primary
text-gray-900 dark:text-gray-100 placeholder-gray-500
focus:border-accent-red focus:outline-none
transition-colors resize-none
disabled:opacity-50
"
/>
<div className="text-xs text-gray-700 dark:text-gray-300 mt-1 text-right">
{additionalComments.length}/2000
</div>
</div>
{/* Error message */}
{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500 rounded-lg">
<p className="text-red-500 text-sm">{error}</p>
</div>
)}
{/* Actions */}
<div className="flex gap-3">
<button
type="button"
onClick={handleClose}
disabled={isSubmitting}
className="
flex-1 px-4 py-2 rounded-lg
bg-background-primary border-2 border-border-primary
text-text-primary font-medium
hover:border-border-hover
transition-all
disabled:opacity-50 disabled:cursor-not-allowed
"
>
Cancel
</button>
<button
type="submit"
disabled={isSubmitting}
className="
flex-1 px-4 py-2 rounded-lg
bg-accent-red text-white font-medium
hover:bg-red-700
transition-all
disabled:opacity-50 disabled:cursor-not-allowed
flex items-center justify-center gap-2
"
>
{isSubmitting ? (
<>
<Loader2 size={18} className="animate-spin" />
Submitting...
</>
) : (
'Submit Feedback'
)}
</button>
</div>
</form>
)}
</div>
</div>
</div>
</>,
document.body
);
}