-
Notifications
You must be signed in to change notification settings - Fork 574
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
base: main
Are you sure you want to change the base?
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 end-user CSAT feedback to support tickets: a 5‑star rating with optional text input in SupportCaseDetails (shown for closed tickets, persisted per-ticket in localStorage). Introduces a server action submitSupportFeedback that posts validated feedback to an external SIWA CSAT endpoint using a service API key. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant UI as SupportCaseDetails (Client)
participant SA as submitSupportFeedback (Server)
participant SIWA as SIWA CSAT API
rect rgba(224,240,255,0.35)
U->>UI: Select stars, enter optional text, click "Send Feedback"
UI->>SA: submitSupportFeedback({ ticketId, rating, feedback })
end
alt SIWA configured
SA->>SIWA: POST /v1/csat/saveCSATFeedback<br/>headers: Content-Type, x-service-api-key<br/>body: { ticket_id, rating, feedback }
alt Success (2xx)
SIWA-->>SA: 200 OK
SA-->>UI: { success: true }
UI->>UI: Persist ticketId in localStorage
UI-->>U: Show success / thank-you
else Failure (!2xx)
SIWA-->>SA: Error status + text
SA-->>UI: { error }
UI-->>U: Show error toast
end
else Missing SIWA config
SA-->>UI: { error: "SIWA URL not configured" }
UI-->>U: Show error toast
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 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). (3)
✨ 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.53% 56.53%
=======================================
Files 904 904
Lines 58592 58592
Branches 4143 4143
=======================================
Hits 33126 33126
Misses 25360 25360
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 localFeedbackData
toSupportFeedbackPayload
to 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
FeedbackData
declarations exist in the dashboard codebase; the only otherFeedbackData
is inpackages/nebula/src/client/types.gen.ts
(a different shape).- All call sites (e.g. the literal passed in
SupportCaseDetails.tsx
at 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@/types
or localtypes.ts
barrels
Prefer type aliases over interface except for nominal shapes
Avoidany
andunknown
unless 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
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/_components/SupportCaseDetails.tsx
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
UseNavLink
for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()
from@/lib/utils
for 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: Bearer
header – 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, stablequeryKeys
for React Query cache hits
ConfigurestaleTime
/cacheTime
in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-js
in server components
Files:
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/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
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 (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@/types
or localtypes.ts
barrels
Prefer type aliases over interface except for nominal shapes
Avoidany
andunknown
unless 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
UseNavLink
for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()
from@/lib/utils
for 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: Bearer
header – 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, stablequeryKeys
for React Query cache hits
ConfigurestaleTime
/cacheTime
in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-js
in 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
Merge activity
|
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); | ||
} | ||
return false; | ||
}); |
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.
because this is stored in local storage only this means that if I open the support page on a second device suddenly every closed ticket will prompt me to report feedback, that's a problem
we might need a way to persistently store which ticket has feedback associated to it already, maybe SIWA can add the relevant API since it's storing it anyways?
localStorage.setItem( | ||
"feedbackSubmittedTickets", | ||
JSON.stringify(submittedTickets), | ||
); |
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.
as mentioned above, we just can't use localstorage for this
PR-Codex overview
This PR introduces a feedback submission feature for support tickets within the dashboard application. It allows users to rate their experience and provide comments when a ticket is closed.
Detailed summary
submitSupportFeedback
function infeedback.ts
for handling feedback submission.SupportCaseDetails.tsx
.localStorage
to prevent duplicate submissions.Summary by CodeRabbit
New Features
Improvements