From 9fe34fe43d6bd7fd8fe448a9935f2af9a6d398ed Mon Sep 17 00:00:00 2001 From: TrivCodez Date: Sun, 17 May 2026 08:06:26 +1000 Subject: [PATCH 01/14] feat: add page satisfaction feedback banner component - Create PageSatisfactionBanner with thumbs up/down/comment icons - Add feedback modals for positive/negative ratings with reason selection - Implement API integration with feedbackService.submitFeedback - Display dynamic page name based on current route - Include dark mode support and responsive design Closes #3480 --- .../feedback/page-satisfaction-banner.tsx | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/vertex/components/features/feedback/page-satisfaction-banner.tsx diff --git a/src/vertex/components/features/feedback/page-satisfaction-banner.tsx b/src/vertex/components/features/feedback/page-satisfaction-banner.tsx new file mode 100644 index 0000000000..420f1c3c91 --- /dev/null +++ b/src/vertex/components/features/feedback/page-satisfaction-banner.tsx @@ -0,0 +1,260 @@ +'use client'; + +import React, { useState } from 'react'; +import { ThumbsUp, ThumbsDown, MessageSquare } from 'lucide-react'; +import { openFeedbackDialog } from './feedback-dialog'; +import { feedbackService } from '@/core/apis/feedback'; +import { useUserContext } from '@/core/hooks/useUserContext'; +import { toast } from '@/components/shared/toast/ReusableToast'; +import ReusableDialog from '@/components/shared/dialog/ReusableDialog'; +import { getApiErrorMessage } from '@/core/utils/getApiErrorMessage'; +import { usePathname } from 'next/navigation'; + +const NEGATIVE_REASONS = [ + 'Confusing', + 'Not helpful', + 'Missing features', + 'Missing or limited data', + 'I encountered technical issues', + 'Other', +]; + +const POSITIVE_REASONS = [ + 'Informative', + 'Actionable', + 'Makes my job easier', + 'Ease of use', + 'Other', +]; + +interface FeedbackModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (rating: number, reason: string, message: string) => Promise; + isSubmitting: boolean; + type: 'positive' | 'negative'; +} + +const FeedbackModal: React.FC = ({ + isOpen, + onClose, + onSubmit, + isSubmitting, + type, +}) => { + const [reason, setReason] = useState(''); + const [message, setMessage] = useState(''); + + const reasons = type === 'positive' ? POSITIVE_REASONS : NEGATIVE_REASONS; + + const handleSubmit = async () => { + if (!reason) { + toast.error('Please select a reason'); + return; + } + await onSubmit( + type === 'positive' ? 5 : 1, + reason, + message + ); + setReason(''); + setMessage(''); + onClose(); + }; + + return ( + { + setReason(''); + setMessage(''); + onClose(); + }} + title="Why did you choose this rating?" + size="md" + showFooter + primaryAction={{ + label: isSubmitting ? 'Sending...' : 'Submit', + onClick: handleSubmit, + disabled: isSubmitting || !reason, + }} + secondaryAction={{ + label: 'Cancel', + onClick: () => { + setReason(''); + setMessage(''); + onClose(); + }, + disabled: isSubmitting, + variant: 'outline', + }} + > +
+
+ +
+ {reasons.map((r) => ( + + ))} +
+
+ +
+ +