-
Notifications
You must be signed in to change notification settings - Fork 619
feat: add support siwa feedback system for closed support tickets #7916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
WalkthroughAdds a client-side CSAT feedback flow into closed support tickets and a server-side feedback API module that validates input and proxies GET/POST requests to an external SIWA CSAT service. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant UI as SupportCaseDetails (Client)
participant API as Feedback server
participant SIWA as SIWA CSAT API
rect rgba(235,245,255,0.5)
U->>UI: Open closed ticket view
UI->>API: checkFeedbackStatus(ticketId)
API->>SIWA: GET /v1/csat/getCSATFeedback?ticket_id=...
SIWA-->>API: { has_feedback: true/false }
API-->>UI: { hasFeedback }
end
alt hasFeedback = false
U->>UI: Select stars, enter text, click Send
UI->>API: submitSupportFeedback({ ticketId, rating, feedback })
rect rgba(235,255,235,0.5)
API->>SIWA: POST /v1/csat/saveCSATFeedback (body + x-service-api-key)
alt Success
SIWA-->>API: 2xx
API-->>UI: { success: true }
UI->>UI: show toast, reset inputs, refetch checkFeedbackStatus
UI-->>U: Thank-you view
else Failure
SIWA-->>API: error
API-->>UI: { error }
UI-->>U: Error toast
end
end
else hasFeedback = true
UI-->>U: Show thank-you / already submitted
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7916 +/- ##
=======================================
Coverage 56.54% 56.54%
=======================================
Files 904 904
Lines 58605 58605
Branches 4142 4142
=======================================
Hits 33141 33141
Misses 25358 25358
Partials 106 106
🚀 New features to boost your workflow:
|
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (9)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (3)
1-2: Add server-only guard without breaking the server action directiveKeep "use server" as the very first statement, then import the server-only guard to prevent accidental client bundling.
"use server"; +import "server-only";
25-33: Add timeout and avoid leaking raw backend errors to users
- Add an AbortController with a sane timeout to avoid hung requests.
- Log detailed backend errors server-side; return a generic error to the client to avoid leaking internals.
- const response = await fetch(`${siwaUrl}/v1/csat/saveCSATFeedback`, { + const ac = new AbortController(); + const timeout = setTimeout(() => ac.abort(), 10_000); + const response = await fetch(`${siwaUrl}/v1/csat/saveCSATFeedback`, { method: "POST", headers: { "Content-Type": "application/json", "x-service-api-key": process.env.SERVICE_AUTH_KEY_SIWA || "", }, - body: JSON.stringify(payload), + body: JSON.stringify(payload), + signal: ac.signal, }); if (!response.ok) { const errorText = await response.text(); - return { error: `API Server error: ${response.status} - ${errorText}` }; + console.error("SIWA CSAT error:", response.status, errorText); + return { error: "Unable to submit feedback at this time. Please try again later." }; } return { success: true }; } catch (error) { console.error("Feedback submission error:", error); return { error: `Failed to submit feedback: ${error instanceof Error ? error.message : "Unknown error"}`, }; - } + } finally { + // @ts-ignore - timeout may be undefined if we refactor above; keep safe + if (typeof timeout !== "undefined") clearTimeout(timeout); + }Also applies to: 34-37, 39-46
3-11: Rename localFeedbackDatatoSupportFeedbackPayloadto avoid collisions
- In
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
• Replace theinterface FeedbackData { … }with• Update the function signature toexport type SupportFeedbackPayload = { rating: number; feedback: string; ticketId?: string; };-export async function submitSupportFeedback( - data: FeedbackData, -): Promise<{ success: true } | { error: string }> { +export async function submitSupportFeedback( + data: SupportFeedbackPayload, +): Promise<{ success: true } | { error: string }> {- No other
FeedbackDatadeclarations exist in the dashboard codebase; the only otherFeedbackDatais inpackages/nebula/src/client/types.gen.ts(a different shape).- All call sites (e.g. the literal passed in
SupportCaseDetails.tsxat line 66) continue to work without modification.This change prevents confusion with the generated type and follows our guideline to use descriptive type aliases for payloads.
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6)
41-54: Harden localStorage access and add in-flight state for feedback
- JSON.parse can throw; guard against corrupted storage.
- Add an isSubmittingFeedback flag now to prevent double submissions and to disable the send button.
// rating/feedback const [rating, setRating] = useState(0); const [feedback, setFeedback] = useState(""); + const [isSubmittingFeedback, setIsSubmittingFeedback] = useState(false); const [feedbackSubmitted, setFeedbackSubmitted] = useState(() => { // Check if feedback has already been submitted for this ticket if (typeof window !== "undefined") { - const submittedTickets = JSON.parse( - localStorage.getItem("feedbackSubmittedTickets") || "[]", - ); - return submittedTickets.includes(ticket.id); + try { + const raw = localStorage.getItem("feedbackSubmittedTickets") || "[]"; + const submittedTickets = JSON.parse(raw); + return Array.isArray(submittedTickets) && submittedTickets.includes(ticket.id); + } catch { + // Corrupted storage – treat as not submitted + return false; + } } return false; });
55-57: Simplify star click logic (use 1–5 directly)Avoid off-by-one mental overhead by passing the natural star value.
-const handleStarClick = (starIndex: number) => { - setRating(starIndex + 1); -}; +const handleStarClick = (starValue: number) => { + setRating(starValue); +}; @@ - onClick={() => handleStarClick(starValue - 1)} + onClick={() => handleStarClick(starValue)}Also applies to: 219-226
59-99: Prevent duplicate submissions; trim input before sendAdd an in-flight lock and trim the feedback to avoid sending whitespace-only content.
- const handleSendFeedback = async () => { + const handleSendFeedback = async () => { + if (isSubmittingFeedback) return; if (rating === 0) { toast.error("Please select a rating"); return; } try { + setIsSubmittingFeedback(true); const result = await submitSupportFeedback({ - rating, - feedback, + rating, + feedback: feedback.trim(), ticketId: ticket.id, }); if ("error" in result) { throw new Error(result.error); } toast.success("Thank you for your feedback!"); setRating(0); setFeedback(""); setFeedbackSubmitted(true); @@ - } catch (error) { + } catch (error) { console.error("Failed to submit feedback:", error); toast.error("Failed to submit feedback. Please try again."); - } + } finally { + setIsSubmittingFeedback(false); + } };
219-243: A11y and design tokens for the star rating
- Use a radiogroup/radio pattern with aria-checked for screen readers.
- Avoid inline hex colors; use Tailwind + design tokens with currentColor.
- Remove invalid svg attributes (rx on svg does nothing here).
- <div className="flex gap-2 mb-6 mt-4"> + <div className="flex gap-2 mb-6 mt-4" role="radiogroup" aria-label="Ticket satisfaction rating"> {[1, 2, 3, 4, 5].map((starValue) => ( <button key={`star-${starValue}`} type="button" - onClick={() => handleStarClick(starValue - 1)} - className="transition-colors" + role="radio" + aria-checked={starValue <= rating} + onClick={() => handleStarClick(starValue)} + className={cn( + "transition-colors", + starValue <= rating ? "text-primary" : "text-muted-foreground", + )} aria-label={`Rate ${starValue} out of 5 stars`} > <svg - width="32" - height="32" + width="32" + height="32" viewBox="0 0 24 24" - fill={starValue <= rating ? "#ff00aa" : "none"} - stroke={starValue <= rating ? "#ff00aa" : "#666"} - strokeWidth={starValue <= rating ? "2" : "1"} - className="hover:fill-pink-500 hover:stroke-pink-500 rounded-sm" - rx="2" + fill="currentColor" + stroke="currentColor" + strokeWidth={starValue <= rating ? "2" : "1"} + className="rounded-sm" aria-hidden="true" > <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" /> </svg> </button> ))} </div>
245-259: Use UI primitives and tokens for consistency; disable the button while submitting
- Replace raw textarea with AutoResizeTextarea and design tokens.
- Replace custom button with Button from the design system.
- <div className="relative"> - <textarea - value={feedback} - onChange={(e) => setFeedback(e.target.value)} - placeholder="Optional: Tell us how we can improve." - 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]" - /> - <button - type="button" - onClick={handleSendFeedback} - 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" - > - Send Feedback - </button> - </div> + <div className="relative"> + <AutoResizeTextarea + value={feedback} + onChange={(e) => setFeedback(e.target.value)} + placeholder="Optional: Tell us how we can improve." + className="text-sm w-full bg-card text-foreground rounded-lg pr-28 min-h-[100px] border border-border focus-visible:ring-0" + /> + <Button + type="button" + onClick={handleSendFeedback} + className="absolute mb-2 bottom-3 right-3 rounded-full text-sm" + size="sm" + disabled={isSubmittingFeedback} + > + {isSubmittingFeedback ? <Spinner className="size-4" /> : "Send Feedback"} + </Button> + </div>
81-93: Guard writes to localStorage the same way (try/catch; validate shape)Mirrors the read-guard to avoid breaking the store if it gets corrupted.
if (typeof window !== "undefined") { - const submittedTickets = JSON.parse( - localStorage.getItem("feedbackSubmittedTickets") || "[]", - ); - if (!submittedTickets.includes(ticket.id)) { - submittedTickets.push(ticket.id); - localStorage.setItem( - "feedbackSubmittedTickets", - JSON.stringify(submittedTickets), - ); - } + try { + const raw = localStorage.getItem("feedbackSubmittedTickets") || "[]"; + const submittedTickets = JSON.parse(raw); + const arr = Array.isArray(submittedTickets) ? submittedTickets : []; + if (!arr.includes(ticket.id)) { + arr.push(ticket.id); + localStorage.setItem("feedbackSubmittedTickets", JSON.stringify(arr)); + } + } catch { + // reset to a sane default + localStorage.setItem("feedbackSubmittedTickets", JSON.stringify([ticket.id])); + } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx(3 hunks)apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsxapps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsxapps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsxapps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
🧬 Code graph analysis (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (1)
submitSupportFeedback(9-46)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (1)
packages/nebula/src/client/types.gen.ts (1)
FeedbackData(3426-3431)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Size
- GitHub Check: Lint Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)
23-23: LGTM: importing the server action directly into a client componentThis is a valid Next pattern; the action remains server-executed. Keep secrets on the server side only (already true in the current wiring).
263-269: LGTM: post-submission acknowledgement copyClear, concise, and consistent with the flow.
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
13-19: Env var selection looks good; consider trimming trailing slashes to avoid // in URLsThis implements the earlier feedback to prefer a server-only var and fail fast if missing — nice.
- const siwaUrl = - process.env.SIWA_URL ?? process.env.NEXT_PUBLIC_SIWA_URL ?? ""; + const siwaUrl = (process.env.SIWA_URL ?? process.env.NEXT_PUBLIC_SIWA_URL ?? "") + .replace(/\/+$/, "");This prevents accidental double slashes when building
${siwaUrl}/v1/csat/saveCSATFeedback.
20-23: Fail-fast on missing API key is correctMatches earlier guidance to avoid sending empty secrets. Good.
🧹 Nitpick comments (9)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (9)
1-2: Add server-only guard import to harden against accidental client usageSince this module accesses server-only env vars, also import "server-only" (keep "use server" as the first statement). Referencing the retrieved learning about server-only secrets.
"use server"; +import "server-only";
3-7: Prefer a type alias and avoid name collision with existing FeedbackData in packages/nebulaGuidelines favor type aliases over interfaces. Also, there’s already a generated type named FeedbackData in packages/nebula/src/client/types.gen.ts with a different shape, which can cause confusion and accidental imports. Rename locally to a more specific alias.
-interface FeedbackData { - rating: number; - feedback: string; - ticketId?: string; -} +export type SupportFeedbackInput = { + rating: number; + feedback: string; + ticketId?: string; +};If you keep the current name, please confirm no collisions/ambiguous imports occur where both are in scope. The nebula one appears at packages/nebula/src/client/types.gen.ts:3425-3430.
9-12: Give the result a named alias and update the param type after the renameSmall readability win and easier reuse by consumers.
-export async function submitSupportFeedback( - data: FeedbackData, -): Promise<{ success: true } | { error: string }> { +export type SubmitSupportFeedbackResult = { success: true } | { error: string }; + +export async function submitSupportFeedback( + data: SupportFeedbackInput, +): Promise<SubmitSupportFeedbackResult> {
25-29: Align error message with validation (integer vs. rounding)Message says “integer,” but non-integers currently pass and are rounded later. Either enforce integers or relax the message. I recommend enforcing integers for a 1–5 star widget.
- if (!Number.isFinite(data.rating) || data.rating < 1 || data.rating > 5) { + if ( + !Number.isFinite(data.rating) || + data.rating < 1 || + data.rating > 5 || + !Number.isInteger(data.rating) + ) { return { error: "Rating must be an integer between 1 and 5." }; }
30-34: Input normalization is solid; minor text normalization options (optional)Optional: normalize line endings to “\n” and strip zero-width spaces to reduce noise upstream.
- const normalizedFeedback = (data.feedback ?? "") - .toString() - .trim() - .slice(0, 1000); // hard cap length + const normalizedFeedback = (data.feedback ?? "") + .toString() + .replace(/\r\n?/g, "\n") // normalize CRLF/CR + .replace(/[\u200B-\u200D\uFEFF]/g, "") // strip zero-widths + .trim() + .slice(0, 1000); // hard cap length
35-39: Narrow payload types and keep ticket_id trimmedTyping the payload prevents accidental shape drift and trims a possibly empty ticket id.
- const payload = { - rating: Math.round(data.rating), - feedback: normalizedFeedback, - ticket_id: data.ticketId || null, - }; + const ticketId = + typeof data.ticketId === "string" ? data.ticketId.trim() : null; + const payload: { rating: number; feedback: string; ticket_id: string | null } = { + rating: data.rating, // if you keep rounding, revert to Math.round(...) + feedback: normalizedFeedback, + ticket_id: ticketId || null, + };
41-48: Add a fetch timeout and disable caching for robustnessServer-side fetches to external services should be bounded. AbortSignal.timeout keeps the diff minimal; cache: "no-store" avoids any caching surprises.
- const response = await fetch(`${siwaUrl}/v1/csat/saveCSATFeedback`, { + const response = await fetch(`${siwaUrl}/v1/csat/saveCSATFeedback`, { method: "POST", headers: { "Content-Type": "application/json", "x-service-api-key": apiKey, }, + cache: "no-store", + signal: AbortSignal.timeout(8000), body: JSON.stringify(payload), });
50-53: Parse JSON error bodies when available and bound error lengthMore user-friendly and avoids dumping long HTML bodies.
- if (!response.ok) { - const errorText = await response.text(); - return { error: `API Server error: ${response.status} - ${errorText}` }; - } + if (!response.ok) { + const contentType = response.headers.get("content-type") || ""; + let errorText: string; + if (contentType.includes("application/json")) { + try { + const errJson: any = await response.json(); + errorText = (errJson?.message || errJson?.error || JSON.stringify(errJson)); + } catch { + errorText = await response.text(); + } + } else { + errorText = await response.text(); + } + const bounded = errorText.slice(0, 300); + return { error: `API Server error: ${response.status} - ${bounded}` }; + }
55-62: Consider a generic user-facing error while logging details server-sideCurrent message may surface internal fetch errors to users. Optional: return a generic message to the UI and keep details in logs.
- return { - error: `Failed to submit feedback: ${error instanceof Error ? error.message : "Unknown error"}`, - }; + return { error: "Failed to submit feedback. Please try again later." };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
🧠 Learnings (1)
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{ts,tsx} : Accessing server-only environment variables or secrets.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (1)
packages/nebula/src/client/types.gen.ts (1)
FeedbackData(3426-3431)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Lint Packages
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Analyze (javascript)
...dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
Outdated
Show resolved
Hide resolved
...dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
Outdated
Show resolved
Hide resolved
...dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (3)
24-25: Blocker: server-only APIs imported into a client component; proxy via internal API routesThis file is
"use client"but imports server-keyed APIs. This risks leaking secrets and will fail in the browser. Call internal API routes instead and keep service keys server-side.Apply these diffs:
- Remove server API imports
- import { checkFeedbackStatus, submitSupportFeedback } from "../apis/feedback"; - import { sendMessageToTicket } from "../apis/support"; + // Feedback and support calls are proxied via internal API routes to keep service keys server-side
- Query: use internal status route
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; + const res = await fetch( + `/api/support/feedback/status?ticketId=${encodeURIComponent(ticket.id)}`, + { method: "GET", cache: "no-store" }, + ); + if (!res.ok) { + throw new Error(await res.text()); + } + const data: { hasFeedback: boolean } = await res.json(); + return data.hasFeedback; }, enabled: ticket.status === "closed", staleTime: 60_000, gcTime: 5 * 60_000, });
- Mutation: post to internal feedback route and set cache on success
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; + const res = await fetch("/api/support/feedback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rating, feedback, ticketId: ticket.id }), + }); + if (!res.ok) { + throw new Error(await res.text()); + } + return (await res.json()) as { success: true }; }, onSuccess: () => { toast.success("Thank you for your feedback!"); setRating(0); setFeedback(""); // mark as submitted immediately queryClient.setQueryData(["feedbackStatus", ticket.id], true); },I can provide route handlers for /api/support/feedback and /api/support/feedback/status.
Also applies to: 46-59, 69-107
147-157: Don’t call a "use server" function from a client file; proxy sendMessage via an API routeReplace the direct
sendMessageToTicketcall with a POST to an internal route.- const result = await sendMessageToTicket({ - message: replyMessage, - teamSlug: team.slug, - teamId: team.id, - ticketId: ticket.id, - }); - - if ("error" in result) { - throw new Error(result.error); - } + const res = await fetch("/api/support/tickets/message", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + message: replyMessage, + teamSlug: team.slug, + teamId: team.id, + ticketId: ticket.id, + }), + }); + if (!res.ok) { + throw new Error(await res.text()); + } + await res.json();
259-268: Use design tokens; avoid raw Tailwind palette for star colorsSwap
text-pink-500to tokens so theming works consistently.- className={cn( - "transition-colors", - starValue <= rating - ? "text-pink-500 fill-current stroke-current" - : "text-muted-foreground fill-current stroke-current", - "hover:text-pink-500", - )} + className={cn( + "transition-colors fill-current stroke-current", + starValue <= rating ? "text-primary" : "text-muted-foreground", + "hover:text-primary", + )}
🧹 Nitpick comments (6)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6)
65-67: Simplify star click logic and add proper radiogroup semanticsCleaner handler and better accessibility with radiogroup/aria-checked.
- const handleStarClick = (starIndex: number) => { - setRating(starIndex + 1); - }; + const handleStarClick = (value: number) => { + setRating(value); + };- <div className="flex gap-2 mb-6 mt-4"> + <div + className="flex gap-2 mb-6 mt-4" + role="radiogroup" + aria-label="Ticket satisfaction rating" + >- onClick={() => handleStarClick(starValue - 1)} - className="transition-colors" - aria-label={`Rate ${starValue} out of 5 stars`} + onClick={() => handleStarClick(starValue)} + className="transition-colors" + role="radio" + aria-checked={rating === starValue} + aria-label={`Rate ${starValue} out of 5 stars`}Also applies to: 249-257
40-40: Type the messages state to avoid implicit never[] unionsMake the state explicitly
SupportMessage[].- const [localMessages, setLocalMessages] = useState(ticket.messages || []); + const [localMessages, setLocalMessages] = useState<SupportMessage[]>( + ticket.messages ?? [], + );
162-163: Fix user-facing copy capitalizationLowercase “message”.
- toast.error("Failed to send Message. Please try again."); + toast.error("Failed to send message. Please try again.");
32-35: Prefer type alias over interface per guidelinesSwap to a type alias.
-interface SupportCaseDetailsProps { - ticket: SupportTicket; - team: Team; -} +type SupportCaseDetailsProps = { + ticket: SupportTicket; + team: Team; +};
341-375: Split subordinate components into their own filesMove
TicketHeaderandTicketMessageinto separate modules to keep this file single-responsibility and lean.Also applies to: 377-458
428-433: Improve toggle button a11yExpose state via
aria-expandedand add a label.- <Button + <Button variant="ghost" className="size-10 rounded-full p-0 translate-x-2" - onClick={() => setIsExpanded(!isExpanded)} + onClick={() => setIsExpanded(!isExpanded)} + aria-expanded={isExpanded} + aria-label={isExpanded ? "Collapse message" : "Expand message"} >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 Learnings (14)
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Icons come from `lucide-react` or the project-specific `…/icons` exports – never embed raw SVG.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-07T21:21:47.488Z
Learnt from: saminacodes
PR: thirdweb-dev/js#7543
File: apps/portal/src/app/pay/page.mdx:4-4
Timestamp: 2025-07-07T21:21:47.488Z
Learning: In the thirdweb-dev/js repository, lucide-react icons must be imported with the "Icon" suffix (e.g., ExternalLinkIcon, RocketIcon) as required by the new linting rule, contrary to the typical lucide-react convention of importing without the suffix.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Stick to design-tokens: background (`bg-card`), borders (`border-border`), muted text (`text-muted-foreground`) etc.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Never hard-code colors – always go through Tailwind variables.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/api/**/*.{ts,tsx} : Prefix files with `import "server-only";` so they never end up in the client bundle.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Prefer API routes or server actions to keep tokens secret; the browser only sees relative paths.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Server Components (Node edge): Start files with `import "server-only";`
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Configure `staleTime`/`cacheTime` in React Query based on freshness (default ≥ 60s)
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Use React Query (`tanstack/react-query`) for all client data fetching.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Keep `queryKey` stable and descriptive for cache hits.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-28T19:32:54.690Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:62-76
Timestamp: 2025-08-28T19:32:54.690Z
Learning: In the StepCard component (apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx), all Next buttons should always use type="submit", even when nextButton.type is "click". The onClick handler for click-type buttons provides additional behavior on top of the submit functionality, not instead of it.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Prefer composable primitives over custom markup: `Button`, `Input`, `Select`, `Tabs`, `Card`, `Sidebar`, `Separator`, `Badge`.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/types/tickets.ts (2)
SupportTicket(21-30)SupportMessage(1-11)apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
checkFeedbackStatus(75-123)submitSupportFeedback(6-73)apps/portal/src/components/ui/textarea.tsx (1)
AutoResizeTextarea(26-48)apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/utils/ticket-status.ts (2)
getTicketStatusBadgeVariant(4-25)getTicketStatusLabel(27-46)apps/dashboard/src/app/(app)/components/ThirdwebMiniLogo.tsx (1)
ThirdwebMiniLogo(7-90)apps/dashboard/src/@/components/ui/DynamicHeight.tsx (1)
DynamicHeight(7-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: Unit Tests
- GitHub Check: Size
- GitHub Check: Build Packages
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)
236-247: Good fallback when status probe failsRendering the rating UI even if the status check errors, plus a concise inline hint, is the right UX.
57-59: No changes required:gcTimeis correct for React Query v5 (5.81.5)
Verified@tanstack/react-queryversion is 5.81.5 in apps/dashboard/package.json;gcTimereplacescacheTimein v5.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)
25-25: Do not import a "use server" function into a client component (duplicate of prior review)
sendMessageToTicketmust be called from server context; call a server action or internal API route from here.Example change inside the try-block:
- const result = await sendMessageToTicket({ - message: replyMessage, - teamSlug: team.slug, - teamId: team.id, - ticketId: ticket.id, - }); + const res = await fetch("/api/support/ticket-message", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + message: replyMessage, + teamSlug: team.slug, + teamId: team.id, + ticketId: ticket.id, + }), + }); + if (!res.ok) { + throw new Error(await res.text()); + }I can provide the server route (with auth and input validation) to proxy
sendMessageToTicket.
24-24: Critical: server-keyed SIWA API imported into a client componentThis pulls server-only logic (and potentially secrets) into the browser. Proxy these calls via internal API routes or server actions per dashboard guidelines.
Apply the following diffs:
- Remove the direct import.
- import { checkFeedbackStatus, submitSupportFeedback } from "../apis/feedback"; + // Feedback calls are proxied via internal API routes to keep service keys server-side
- Switch the status query to call an internal route.
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; - }, + queryFn: async () => { + const res = await fetch( + `/api/support/feedback/status?ticketId=${encodeURIComponent(ticket.id)}`, + { method: "GET", cache: "no-store" }, + ); + if (!res.ok) { + throw new Error(await res.text()); + } + const data: { hasFeedback: boolean } = await res.json(); + return data.hasFeedback; + }, enabled: ticket.status === "closed", staleTime: 60_000, gcTime: 5 * 60_000, });
- Switch the mutation to POST to the internal route.
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; + const res = await fetch("/api/support/feedback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rating, feedback, ticketId: ticket.id }), + }); + if (!res.ok) { + throw new Error(await res.text()); + } + return (await res.json()) as { success: true }; },I can scaffold the two API route handlers with validation and 10s timeouts if you want.
🧹 Nitpick comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)
65-67: Remove off-by-one dance in rating; set the selected value directlySimplifies mental model and avoids future fencepost errors.
- const handleStarClick = (starIndex: number) => { - setRating(starIndex + 1); - }; + const handleStarClick = (value: number) => { + setRating(value); + };- onClick={() => handleStarClick(starValue - 1)} + onClick={() => handleStarClick(starValue)} + aria-pressed={starValue <= rating}Also applies to: 251-255
261-268: Use design tokens instead of brand-specific Tailwind colors for starsFollow dashboard tokens; let currentColor drive fill/stroke.
<StarIcon size={32} className={cn( - "transition-colors", - starValue <= rating - ? "text-pink-500 fill-current stroke-current" - : "text-muted-foreground fill-current stroke-current", - "hover:text-pink-500", + "transition-colors fill-current stroke-current", + starValue <= rating + ? "text-primary" + : "text-muted-foreground", + "hover:text-primary", )} strokeWidth={starValue <= rating ? 2 : 1} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx(4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 Learnings (17)
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Icons come from `lucide-react` or the project-specific `…/icons` exports – never embed raw SVG.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-07T21:21:47.488Z
Learnt from: saminacodes
PR: thirdweb-dev/js#7543
File: apps/portal/src/app/pay/page.mdx:4-4
Timestamp: 2025-07-07T21:21:47.488Z
Learning: In the thirdweb-dev/js repository, lucide-react icons must be imported with the "Icon" suffix (e.g., ExternalLinkIcon, RocketIcon) as required by the new linting rule, contrary to the typical lucide-react convention of importing without the suffix.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Stick to design-tokens: background (`bg-card`), borders (`border-border`), muted text (`text-muted-foreground`) etc.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Never hard-code colors – always go through Tailwind variables.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Use design system tokens (e.g., `bg-card`, `border-border`, `text-muted-foreground`)
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/api/**/*.{ts,tsx} : Prefix files with `import "server-only";` so they never end up in the client bundle.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Prefer API routes or server actions to keep tokens secret; the browser only sees relative paths.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Server Components (Node edge): Start files with `import "server-only";`
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Interactive UI that relies on hooks (`useState`, `useEffect`, React Query, wallet hooks).
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Configure `staleTime`/`cacheTime` in React Query based on freshness (default ≥ 60s)
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Use React Query (`tanstack/react-query`) for all client data fetching.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Keep `queryKey` stable and descriptive for cache hits.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-28T19:32:54.690Z
Learnt from: MananTank
PR: thirdweb-dev/js#7939
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx:62-76
Timestamp: 2025-08-28T19:32:54.690Z
Learning: In the StepCard component (apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/_common/step-card.tsx), all Next buttons should always use type="submit", even when nextButton.type is "click". The onClick handler for click-type buttons provides additional behavior on top of the submit functionality, not instead of it.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*.{tsx,jsx} : Prefer composable primitives over custom markup: `Button`, `Input`, `Select`, `Tabs`, `Card`, `Sidebar`, `Separator`, `Badge`.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:20:32.530Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: .cursor/rules/dashboard.mdc:0-0
Timestamp: 2025-07-18T19:20:32.530Z
Learning: Applies to dashboard/**/*client.tsx : Anything that consumes hooks from `tanstack/react-query` or thirdweb SDKs.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to apps/{dashboard,playground-web}/**/*.{ts,tsx} : Wrap client-side data fetching calls in React Query (`tanstack/react-query`)
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-07-31T16:17:42.753Z
Learnt from: MananTank
PR: thirdweb-dev/js#7768
File: apps/playground-web/src/app/navLinks.ts:1-1
Timestamp: 2025-07-31T16:17:42.753Z
Learning: Configuration files that import and reference React components (like icon components from lucide-react) need the "use client" directive, even if they primarily export static data, because the referenced components need to be executed in a client context when used by other client components.
Applied to files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
checkFeedbackStatus(75-123)submitSupportFeedback(6-73)apps/portal/src/components/ui/textarea.tsx (1)
AutoResizeTextarea(26-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Size
- GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (3)
92-106: LGTM: robust error normalization and user-friendly toastsGood handling of unknown error shape and mapping to actionable messages.
225-234: LGTM: loading state for feedback status checkClear user feedback with spinner and muted copy.
273-299: LGTM: textarea + Button adhere to tokens and shared primitivesMax length enforced, proper disabled state tied to rating/pending.
) <!-- ## 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 introduces a feedback system for support tickets, allowing users to submit ratings and comments. It includes API calls for submitting feedback and checking its status, along with UI updates to handle user interactions and display feedback status. ### Detailed summary - Added `submitSupportFeedback` function to submit feedback. - Added `checkFeedbackStatus` function to verify if feedback has been submitted. - Integrated feedback submission UI in `SupportCaseDetails` component. - Implemented rating selection using stars. - Added input field for optional feedback comments. - Displayed loading and error states for feedback status checks. > ✨ 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** * 5‑star rating UI and optional comment for closed support cases, with Send Feedback button, “Checking feedback status…” and sending spinners, disabled send until a rating is chosen, and a thank‑you message after submission. * **Improvements** * Server-backed feedback status check and submission with input validation, 1–5 rating, 1000‑char comment limit, 10s request timeout, and contextual success/error toasts. * Accessible star controls with ARIA labels and filled‑star visuals. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
5b6179f to
d5ff9f2
Compare
PR-Codex overview
This PR introduces functionality for submitting and checking feedback on support tickets. It adds new API methods for handling feedback and integrates them into the
SupportCaseDetailscomponent, allowing users to rate their experience and provide comments.Detailed summary
submitSupportFeedbackandcheckFeedbackStatusfunctions infeedback.ts.SupportCaseDetails.tsx.Summary by CodeRabbit
New Features
Improvements