1- import React , { useRef , useState } from 'react'
1+ import { Controller , useFormContext } from 'react-hook-form '
22import { FaRegThumbsDown , FaRegThumbsUp } from 'react-icons/fa'
33import {
44 Button ,
@@ -20,6 +20,14 @@ import {
2020} from '@chakra-ui/react'
2121import { useToast } from '@opengovsg/design-system-react'
2222
23+ import Form from '@/components/Form'
24+ import { SingleSelect } from '@/components/SingleSelect'
25+
26+ interface FeedbackFormData {
27+ 'feedback-dropdown' ?: string
28+ 'feedback-details' : string
29+ }
30+
2331interface ChatMessageToolbarProps {
2432 traceId : string
2533}
@@ -29,20 +37,97 @@ interface FeedbackButtonProps {
2937 traceId : string
3038}
3139
40+ const FEEDBACK_POPOVER_DETAILS = {
41+ positive : {
42+ dropdownLabel : null ,
43+ dropdownOptions : null ,
44+ textAreaLabel :
45+ 'Provide details on what was satisfying about this response:' ,
46+ textAreaPlaceholder : 'What was satisfying about this response?' ,
47+ score : 1 ,
48+ } ,
49+ negative : {
50+ dropdownLabel : 'What type of issue do you wish to report?' ,
51+ dropdownOptions : [
52+ 'Incorrect workflow generated' ,
53+ 'Incomplete response' ,
54+ 'UI bug' ,
55+ "I don't understand the response" ,
56+ 'Other' ,
57+ ] ,
58+ textAreaLabel : 'Provide details on what was wrong with this response:' ,
59+ textAreaPlaceholder : 'What was wrong with this response?' ,
60+ score : 0 ,
61+ } ,
62+ }
63+
64+ const FeedbackFormContent = ( {
65+ dropdownLabel,
66+ dropdownOptions,
67+ textAreaLabel,
68+ textAreaPlaceholder,
69+ autoFocus,
70+ } : {
71+ dropdownLabel : string | null
72+ dropdownOptions : string [ ] | null
73+ textAreaLabel : string
74+ textAreaPlaceholder : string
75+ autoFocus ?: boolean
76+ } ) => {
77+ const { control, register } = useFormContext < FeedbackFormData > ( )
78+
79+ return (
80+ < Flex direction = "column" >
81+ { dropdownLabel != null && dropdownOptions != null && (
82+ < >
83+ < FormLabel htmlFor = "feedback-dropdown" mt = { 2 } >
84+ { dropdownLabel }
85+ </ FormLabel >
86+ < Controller
87+ name = "feedback-dropdown"
88+ control = { control }
89+ defaultValue = ""
90+ render = { ( { field } ) => (
91+ < SingleSelect
92+ colorScheme = "secondary"
93+ name = "feedback-dropdown"
94+ items = { dropdownOptions }
95+ value = { field . value ?? '' }
96+ onChange = { field . onChange }
97+ isClearable = { false }
98+ />
99+ ) }
100+ />
101+ </ >
102+ ) }
103+ < FormLabel htmlFor = "feedback-details" mt = { 2 } >
104+ { textAreaLabel }
105+ </ FormLabel >
106+ < Textarea
107+ id = "feedback-details"
108+ rows = { 3 }
109+ resize = "none"
110+ autoFocus = { autoFocus }
111+ placeholder = { textAreaPlaceholder }
112+ { ...register ( 'feedback-details' ) }
113+ />
114+ </ Flex >
115+ )
116+ }
117+
32118const FeedbackButton = ( { feedbackType, traceId } : FeedbackButtonProps ) => {
33119 const { onOpen, onClose, isOpen } = useDisclosure ( )
34- const firstFieldRef = useRef ( null )
35120 const toast = useToast ( )
36121 const icon = feedbackType === 'positive' ? FaRegThumbsUp : FaRegThumbsDown
37- const formLabel =
38- feedbackType === 'positive'
39- ? 'What was helpful about this?'
40- : 'Why was this not helpful?'
41- const score = feedbackType === 'positive' ? 1 : 0
42-
43- const [ feedback , setFeedback ] = useState ( '' )
122+ const {
123+ dropdownLabel ,
124+ dropdownOptions ,
125+ textAreaLabel ,
126+ textAreaPlaceholder ,
127+ score ,
128+ } = FEEDBACK_POPOVER_DETAILS [ feedbackType ]
44129
45- const handleSubmitFeedback = async ( comment : string ) => {
130+ const handleSubmitFeedback = async ( data : FeedbackFormData ) => {
46131 try {
47132 if ( ! traceId ) {
48133 return
@@ -54,13 +139,20 @@ const FeedbackButton = ({ feedbackType, traceId }: FeedbackButtonProps) => {
54139 await fetch ( '/api/chat/feedback' , {
55140 method : 'POST' ,
56141 headers : { 'Content-Type' : 'application/json' } ,
57- body : JSON . stringify ( { traceId, feedback, score } ) ,
142+ body : JSON . stringify ( {
143+ traceId,
144+ feedback : {
145+ category : data [ 'feedback-dropdown' ] ,
146+ comment : data [ 'feedback-details' ] ,
147+ } ,
148+ score,
149+ } ) ,
58150 } )
59151 } catch {
60152 // don't throw error if feedback submission fails
61153 // as it is not critical to the user experience
62154 } finally {
63- // NOTE: do not reset comment here
155+ // NOTE: do not reset form here
64156 // so that user will see what they previously typed or submitted
65157 // if they attempt to submit again
66158 onClose ( )
@@ -77,9 +169,9 @@ const FeedbackButton = ({ feedbackType, traceId }: FeedbackButtonProps) => {
77169 return (
78170 < Popover
79171 isOpen = { isOpen }
80- initialFocusRef = { firstFieldRef }
81172 onOpen = { onOpen }
82173 onClose = { onClose }
174+ placement = "top-start"
83175 >
84176 < PopoverTrigger >
85177 < IconButton
@@ -94,31 +186,32 @@ const FeedbackButton = ({ feedbackType, traceId }: FeedbackButtonProps) => {
94186 < FocusLock persistentFocus = { false } >
95187 < PopoverArrow />
96188
97- < PopoverBody >
98- < Stack spacing = { 4 } >
99- < FormControl >
100- < FormLabel htmlFor = "feedback-details" > { formLabel } </ FormLabel >
101- < Textarea
102- ref = { firstFieldRef }
103- id = "feedback-details"
104- rows = { 3 }
105- resize = "none"
106- value = { feedback }
107- onChange = { ( e ) => setFeedback ( e . target . value ) }
108- />
109- </ FormControl >
110- < ButtonGroup display = "flex" justifyContent = "flex-end" >
111- < Button variant = "outline" onClick = { onClose } >
112- Cancel
113- </ Button >
114- < Button
115- isDisabled = { ! feedback }
116- onClick = { ( ) => handleSubmitFeedback ( feedback ) }
117- >
118- Submit
119- </ Button >
120- </ ButtonGroup >
121- </ Stack >
189+ < PopoverBody p = { 4 } >
190+ < Form
191+ onSubmit = { ( data ) =>
192+ handleSubmitFeedback ( data as FeedbackFormData )
193+ }
194+ >
195+ < Stack spacing = { 4 } >
196+ < FormControl >
197+ < FeedbackFormContent
198+ dropdownLabel = { dropdownLabel }
199+ dropdownOptions = { dropdownOptions }
200+ textAreaLabel = { textAreaLabel }
201+ textAreaPlaceholder = { textAreaPlaceholder }
202+ autoFocus = { feedbackType === 'positive' }
203+ />
204+ </ FormControl >
205+ < ButtonGroup display = "flex" justifyContent = "flex-end" >
206+ < Button variant = "clear" onClick = { onClose } size = "sm" >
207+ Cancel
208+ </ Button >
209+ < Button type = "submit" size = "sm" >
210+ Submit
211+ </ Button >
212+ </ ButtonGroup >
213+ </ Stack >
214+ </ Form >
122215 </ PopoverBody >
123216 </ FocusLock >
124217 </ PopoverContent >
0 commit comments