@@ -20,6 +20,7 @@ import { Spinner } from "@/components/ui/Spinner/Spinner";
2020import { AutoResizeTextarea } from "@/components/ui/textarea" ;
2121import { cn } from "@/lib/utils" ;
2222import { ThirdwebMiniLogo } from "../../../../../../components/ThirdwebMiniLogo" ;
23+ import { submitSupportFeedback } from "../apis/feedback" ;
2324import { sendMessageToTicket } from "../apis/support" ;
2425import type { SupportMessage , SupportTicket } from "../types/tickets" ;
2526import {
@@ -37,6 +38,65 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
3738 const [ isSubmittingReply , setIsSubmittingReply ] = useState ( false ) ;
3839 const [ localMessages , setLocalMessages ] = useState ( ticket . messages || [ ] ) ;
3940
41+ // rating/feedback
42+ const [ rating , setRating ] = useState ( 0 ) ;
43+ const [ feedback , setFeedback ] = useState ( "" ) ;
44+ const [ feedbackSubmitted , setFeedbackSubmitted ] = useState ( ( ) => {
45+ // Check if feedback has already been submitted for this ticket
46+ if ( typeof window !== "undefined" ) {
47+ const submittedTickets = JSON . parse (
48+ localStorage . getItem ( "feedbackSubmittedTickets" ) || "[]" ,
49+ ) ;
50+ return submittedTickets . includes ( ticket . id ) ;
51+ }
52+ return false ;
53+ } ) ;
54+
55+ const handleStarClick = ( starIndex : number ) => {
56+ setRating ( starIndex + 1 ) ;
57+ } ;
58+
59+ const handleSendFeedback = async ( ) => {
60+ if ( rating === 0 ) {
61+ toast . error ( "Please select a rating" ) ;
62+ return ;
63+ }
64+
65+ try {
66+ const result = await submitSupportFeedback ( {
67+ rating,
68+ feedback,
69+ ticketId : ticket . id ,
70+ } ) ;
71+
72+ if ( "error" in result ) {
73+ throw new Error ( result . error ) ;
74+ }
75+
76+ toast . success ( "Thank you for your feedback!" ) ;
77+ setRating ( 0 ) ;
78+ setFeedback ( "" ) ;
79+ setFeedbackSubmitted ( true ) ;
80+
81+ // Store the ticket ID in localStorage to prevent future submissions
82+ if ( typeof window !== "undefined" ) {
83+ const submittedTickets = JSON . parse (
84+ localStorage . getItem ( "feedbackSubmittedTickets" ) || "[]" ,
85+ ) ;
86+ if ( ! submittedTickets . includes ( ticket . id ) ) {
87+ submittedTickets . push ( ticket . id ) ;
88+ localStorage . setItem (
89+ "feedbackSubmittedTickets" ,
90+ JSON . stringify ( submittedTickets ) ,
91+ ) ;
92+ }
93+ }
94+ } catch ( error ) {
95+ console . error ( "Failed to submit feedback:" , error ) ;
96+ toast . error ( "Failed to submit feedback. Please try again." ) ;
97+ }
98+ } ;
99+
40100 const handleSendReply = async ( ) => {
41101 if ( ! team . unthreadCustomerId ) {
42102 toast . error ( "No unthread customer id found for this team" ) ;
@@ -149,11 +209,62 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
149209 ) }
150210 </ div >
151211
152- { ticket . status === "closed" && (
212+ { ticket . status === "closed" && ! feedbackSubmitted && (
213+ < div className = "border-t p-6" >
214+ < p className = "text-muted-foreground text-sm" >
215+ This ticket is closed. Give us a quick rating to let us know how
216+ we did!
217+ </ p >
218+
219+ < div className = "flex gap-2 mb-6 mt-4" >
220+ { [ 1 , 2 , 3 , 4 , 5 ] . map ( ( starValue ) => (
221+ < button
222+ key = { `star-${ starValue } ` }
223+ type = "button"
224+ onClick = { ( ) => handleStarClick ( starValue - 1 ) }
225+ className = "transition-colors"
226+ aria-label = { `Rate ${ starValue } out of 5 stars` }
227+ >
228+ < svg
229+ width = "32"
230+ height = "32"
231+ viewBox = "0 0 24 24"
232+ fill = { starValue <= rating ? "#ff00aa" : "none" }
233+ stroke = { starValue <= rating ? "#ff00aa" : "#666" }
234+ strokeWidth = { starValue <= rating ? "2" : "1" }
235+ className = "hover:fill-pink-500 hover:stroke-pink-500 rounded-sm"
236+ rx = "2"
237+ aria-hidden = "true"
238+ >
239+ < polygon points = "12,2 15.09,8.26 22,9.27 17,14.14 18.18,21.02 12,17.77 5.82,21.02 7,14.14 2,9.27 8.91,8.26" />
240+ </ svg >
241+ </ button >
242+ ) ) }
243+ </ div >
244+
245+ < div className = "relative" >
246+ < textarea
247+ value = { feedback }
248+ onChange = { ( e ) => setFeedback ( e . target . value ) }
249+ placeholder = "Optional: Tell us how we can improve."
250+ className = "text-muted-foreground text-sm w-full bg-black text-white rounded-lg p-4 pr-28 min-h-[100px] resize-none border border-[#262626] focus:border-[#262626] focus:outline-none placeholder-[#A1A1A1]"
251+ />
252+ < button
253+ type = "button"
254+ onClick = { handleSendFeedback }
255+ className = "absolute mb-2 bottom-3 right-3 bg-white text-black px-4 py-2 rounded-full text-sm font-medium hover:bg-gray-100 transition-colors"
256+ >
257+ Send Feedback
258+ </ button >
259+ </ div >
260+ </ div >
261+ ) }
262+
263+ { ticket . status === "closed" && feedbackSubmitted && (
153264 < div className = "border-t p-6" >
154265 < p className = "text-muted-foreground text-sm" >
155- This ticket is closed. If you need further assistance, please
156- create a new ticket .
266+ Thank you for your feedback! We appreciate your input and will use
267+ it to improve our service .
157268 </ p >
158269 </ div >
159270 ) }
0 commit comments