diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx index 8c1c66e7b50..645d8244aa4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx @@ -1,7 +1,8 @@ "use client"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { format } from "date-fns"; -import { ChevronDownIcon, UserIcon } from "lucide-react"; +import { ChevronDownIcon, StarIcon, UserIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; @@ -20,6 +21,7 @@ import { Spinner } from "@/components/ui/Spinner"; import { AutoResizeTextarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { ThirdwebMiniLogo } from "../../../../../../components/ThirdwebMiniLogo"; +import { checkFeedbackStatus, submitSupportFeedback } from "../apis/feedback"; import { sendMessageToTicket } from "../apis/support"; import type { SupportMessage, SupportTicket } from "../types/tickets"; import { @@ -37,6 +39,77 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) { const [isSubmittingReply, setIsSubmittingReply] = useState(false); const [localMessages, setLocalMessages] = useState(ticket.messages || []); + // rating/feedback + const [rating, setRating] = useState(0); + const [feedback, setFeedback] = useState(""); + + // Check if feedback has already been submitted for this ticket + const feedbackStatusQuery = useQuery({ + queryKey: ["feedbackStatus", ticket.id], + queryFn: async () => { + const result = await checkFeedbackStatus(ticket.id); + if ("error" in result) { + throw new Error(result.error); + } + return result.hasFeedback; + }, + enabled: ticket.status === "closed", + staleTime: 60_000, + gcTime: 5 * 60_000, + }); + + const feedbackSubmitted = feedbackStatusQuery.data ?? false; + const isLoading = feedbackStatusQuery.isLoading; + const hasError = feedbackStatusQuery.isError; + + const handleStarClick = (starIndex: number) => { + setRating(starIndex + 1); + }; + + const queryClient = useQueryClient(); + const submitFeedbackMutation = useMutation({ + mutationFn: async () => { + if (rating === 0) { + throw new Error("Please select a rating"); + } + const result = await submitSupportFeedback({ + rating, + feedback, + ticketId: ticket.id, + }); + if ("error" in result) { + throw new Error(result.error); + } + return result; + }, + onSuccess: () => { + toast.success("Thank you for your feedback!"); + setRating(0); + setFeedback(""); + // mark as submitted immediately + queryClient.setQueryData(["feedbackStatus", ticket.id], true); + }, + onError: (err) => { + console.error("Failed to submit feedback:", err); + const msg = err instanceof Error ? err.message : String(err ?? ""); + let message = "Failed to submit feedback. Please try again."; + if (/network|fetch/i.test(msg)) { + message = "Network error. Please check your connection and try again."; + } else if ( + /validation|Rating must be|Please select a rating/i.test(msg) + ) { + message = msg; // show precise user-facing validation error + } else if (/API Server error/i.test(msg)) { + message = "Server error. Please try again later."; + } + toast.error(message); + }, + }); + + const handleSendFeedback = () => { + submitFeedbackMutation.mutate(); + }; + const handleSendReply = async () => { if (!team.unthreadCustomerId) { toast.error("No unthread customer id found for this team"); @@ -149,11 +222,88 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) { )} - {ticket.status === "closed" && ( + {ticket.status === "closed" && isLoading && ( +
+ This ticket is closed. Give us a quick rating to let us know how + we did! +
+ {hasError && ( ++ Couldn't verify prior feedback right now — you can still submit + a rating. +
+ )} + +- This ticket is closed. If you need further assistance, please - create a new ticket. + Thank you for your feedback! We appreciate your input and will use + it to improve our service.