Skip to content

Commit bd17c08

Browse files
committed
Fix/feedback api endpoints (#8024)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing input validation and error handling in the `submitSupportFeedback` and `checkFeedbackStatus` functions, as well as improving the `SupportCaseDetails` component for better user experience during feedback submission. ### Detailed summary - Improved input validation in `submitSupportFeedback` for `ticketId` and `rating`. - Enhanced error handling with more specific error messages. - Added comprehensive validation for API response structure. - Updated `SupportCaseDetails` to handle status check errors non-blockingly. - Adjusted feedback submission logic to improve user experience and error reporting. - Refactored button and feedback display logic for clarity and usability. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Interactive 5‑star rating, accessible controls, auto‑resizing feedback textarea, and integrated Send Feedback button with spinner. * “Checking feedback status…” loader and clear “Thank you” state after successful submission. * **Improvements** * Non‑blocking status check with a user‑facing warning if verification fails. * Faster, clearer error messages and timeouts for network/server issues; form validation prevents invalid submissions. * Feedback resets and success toasts on submission. * **Bug Fixes** * Feedback UI no longer fails when status verification errors occur; malformed API responses handled gracefully. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 79e3694 commit bd17c08

File tree

2 files changed

+190
-98
lines changed

2 files changed

+190
-98
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx

Lines changed: 97 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,49 @@ interface SupportCaseDetailsProps {
3434
team: Team;
3535
}
3636

37-
export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
37+
function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
3838
const [replyMessage, setReplyMessage] = useState("");
3939
const [isSubmittingReply, setIsSubmittingReply] = useState(false);
4040
const [localMessages, setLocalMessages] = useState(ticket.messages || []);
4141

4242
// rating/feedback
4343
const [rating, setRating] = useState(0);
4444
const [feedback, setFeedback] = useState("");
45+
// non-blocking warning when status check fails
46+
const [statusCheckFailed, setStatusCheckFailed] = useState(false);
47+
48+
// Helper function to handle status check errors consistently
49+
const handleStatusCheckError = (_error: unknown) => {
50+
// Set degraded state for warning banner
51+
setStatusCheckFailed(true);
52+
return;
53+
};
4554

4655
// Check if feedback has already been submitted for this ticket
4756
const feedbackStatusQuery = useQuery({
4857
queryKey: ["feedbackStatus", ticket.id],
4958
queryFn: async () => {
5059
const result = await checkFeedbackStatus(ticket.id);
5160
if ("error" in result) {
52-
throw new Error(result.error);
61+
handleStatusCheckError(result.error);
62+
return false; // Non-blocking: allow feedback submission despite status check failure
5363
}
64+
65+
// Clear degraded state on success
66+
if (statusCheckFailed) setStatusCheckFailed(false);
67+
5468
return result.hasFeedback;
5569
},
5670
enabled: ticket.status === "closed",
5771
staleTime: 60_000,
5872
gcTime: 5 * 60_000,
73+
retry: 1, // Reduce retries since we want non-blocking behavior
5974
});
6075

6176
const feedbackSubmitted = feedbackStatusQuery.data ?? false;
6277
const isLoading = feedbackStatusQuery.isLoading;
63-
const hasError = feedbackStatusQuery.isError;
78+
// query never throws; use local degraded flag for the inline warning
79+
const hasError = statusCheckFailed;
6480

6581
const handleStarClick = (starIndex: number) => {
6682
setRating(starIndex + 1);
@@ -70,29 +86,36 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
7086
const submitFeedbackMutation = useMutation({
7187
mutationFn: async () => {
7288
if (rating === 0) {
73-
throw new Error("Please select a rating");
89+
const error = "Please select a rating";
90+
throw new Error(error);
7491
}
7592
const result = await submitSupportFeedback({
7693
rating,
7794
feedback,
7895
ticketId: ticket.id,
7996
});
97+
8098
if ("error" in result) {
99+
// Add more specific error information
100+
81101
throw new Error(result.error);
82102
}
103+
83104
return result;
84105
},
85106
onSuccess: () => {
86107
toast.success("Thank you for your feedback!");
108+
87109
setRating(0);
88110
setFeedback("");
111+
89112
// mark as submitted immediately
90113
queryClient.setQueryData(["feedbackStatus", ticket.id], true);
91114
},
92115
onError: (err) => {
93-
console.error("Failed to submit feedback:", err);
94116
const msg = err instanceof Error ? err.message : String(err ?? "");
95117
let message = "Failed to submit feedback. Please try again.";
118+
96119
if (/network|fetch/i.test(msg)) {
97120
message = "Network error. Please check your connection and try again.";
98121
} else if (
@@ -102,6 +125,7 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
102125
} else if (/API Server error/i.test(msg)) {
103126
message = "Server error. Please try again later.";
104127
}
128+
105129
toast.error(message);
106130
},
107131
});
@@ -157,8 +181,7 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
157181
}
158182

159183
setReplyMessage("");
160-
} catch (error) {
161-
console.error("Failed to send reply:", error);
184+
} catch {
162185
toast.error("Failed to send Message. Please try again.");
163186

164187
// Remove the optimistic message on error
@@ -233,73 +256,77 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
233256
</div>
234257
)}
235258

236-
{ticket.status === "closed" && !isLoading && !feedbackSubmitted && (
237-
<div className="border-t p-6">
238-
<p className="text-muted-foreground text-sm">
239-
This ticket is closed. Give us a quick rating to let us know how
240-
we did!
241-
</p>
242-
{hasError && (
243-
<p className="text-destructive text-xs mt-2">
244-
Couldn't verify prior feedback right now — you can still submit
245-
a rating.
259+
{ticket.status === "closed" &&
260+
!isLoading &&
261+
(!feedbackSubmitted || hasError) && (
262+
<div className="border-t p-6">
263+
<p className="text-muted-foreground text-sm">
264+
This ticket is closed. Give us a quick rating to let us know how
265+
we did!
246266
</p>
247-
)}
267+
{hasError && (
268+
<div className="mt-2">
269+
<p className="text-destructive text-xs">
270+
Couldn't verify prior feedback right now — you can still
271+
submit a rating.
272+
</p>
273+
</div>
274+
)}
248275

249-
<div className="flex gap-2 mb-6 mt-4">
250-
{[1, 2, 3, 4, 5].map((starValue) => (
251-
<button
252-
key={`star-${starValue}`}
276+
<div className="flex gap-2 mb-6 mt-4">
277+
{[1, 2, 3, 4, 5].map((starValue) => (
278+
<button
279+
key={`star-${starValue}`}
280+
type="button"
281+
onClick={() => handleStarClick(starValue - 1)}
282+
className="transition-colors"
283+
aria-label={`Rate ${starValue} out of 5 stars`}
284+
>
285+
<StarIcon
286+
size={32}
287+
className={cn(
288+
"transition-colors",
289+
starValue <= rating
290+
? "text-pink-500 fill-current stroke-current"
291+
: "text-muted-foreground fill-current stroke-current",
292+
"hover:text-pink-500",
293+
)}
294+
strokeWidth={starValue <= rating ? 2 : 1}
295+
/>
296+
</button>
297+
))}
298+
</div>
299+
300+
<div className="relative">
301+
<AutoResizeTextarea
302+
value={feedback}
303+
onChange={(e) => setFeedback(e.target.value)}
304+
placeholder="Optional: Tell us how we can improve."
305+
maxLength={1000}
306+
className="text-sm w-full bg-card text-foreground rounded-lg p-4 pr-28 min-h-[100px] resize-none border border-border focus:outline-none placeholder:text-muted-foreground"
307+
/>
308+
<Button
253309
type="button"
254-
onClick={() => handleStarClick(starValue - 1)}
255-
className="transition-colors"
256-
aria-label={`Rate ${starValue} out of 5 stars`}
310+
onClick={handleSendFeedback}
311+
disabled={submitFeedbackMutation.isPending || rating === 0}
312+
className="absolute bottom-3 right-3 rounded-full h-auto py-2 px-4"
313+
variant="secondary"
314+
size="sm"
257315
>
258-
<StarIcon
259-
size={32}
260-
className={cn(
261-
"transition-colors",
262-
starValue <= rating
263-
? "text-pink-500 fill-current stroke-current"
264-
: "text-muted-foreground fill-current stroke-current",
265-
"hover:text-pink-500",
266-
)}
267-
strokeWidth={starValue <= rating ? 2 : 1}
268-
/>
269-
</button>
270-
))}
271-
</div>
272-
273-
<div className="relative">
274-
<AutoResizeTextarea
275-
value={feedback}
276-
onChange={(e) => setFeedback(e.target.value)}
277-
placeholder="Optional: Tell us how we can improve."
278-
maxLength={1000}
279-
className="text-sm w-full bg-card text-foreground rounded-lg p-4 pr-28 min-h-[100px] resize-none border border-border focus:outline-none placeholder:text-muted-foreground"
280-
/>
281-
<Button
282-
type="button"
283-
onClick={handleSendFeedback}
284-
disabled={submitFeedbackMutation.isPending || rating === 0}
285-
className="absolute bottom-3 right-3 rounded-full h-auto py-2 px-4"
286-
variant="secondary"
287-
size="sm"
288-
>
289-
{submitFeedbackMutation.isPending ? (
290-
<>
291-
<Spinner className="size-4 mr-2" />
292-
Sending...
293-
</>
294-
) : (
295-
"Send Feedback"
296-
)}
297-
</Button>
316+
{submitFeedbackMutation.isPending ? (
317+
<>
318+
<Spinner className="size-4 mr-2" />
319+
Sending...
320+
</>
321+
) : (
322+
"Send Feedback"
323+
)}
324+
</Button>
325+
</div>
298326
</div>
299-
</div>
300-
)}
327+
)}
301328

302-
{ticket.status === "closed" && feedbackSubmitted && (
329+
{ticket.status === "closed" && feedbackSubmitted && !hasError && (
303330
<div className="border-t p-6">
304331
<p className="text-muted-foreground text-sm">
305332
Thank you for your feedback! We appreciate your input and will use
@@ -455,3 +482,5 @@ function TicketMessage(props: { message: SupportMessage }) {
455482
</div>
456483
);
457484
}
485+
486+
export { SupportCaseDetails };

0 commit comments

Comments
 (0)