Skip to content

Conversation

@GiselleNessi
Copy link
Contributor

@GiselleNessi GiselleNessi commented Sep 9, 2025


PR-Codex overview

This PR focuses on enhancing input validation and error handling in the submitSupportFeedback and checkFeedbackStatus functions, as well as improving user experience in the SupportCaseDetails component with better feedback mechanisms.

Detailed summary

  • Added basic input validation for ticketId and rating in submitSupportFeedback.
  • Improved error handling for API responses and network issues.
  • Enhanced validation for response structure in both submitSupportFeedback and checkFeedbackStatus.
  • Introduced a non-blocking warning mechanism in SupportCaseDetails for status check failures.
  • Updated the feedback submission UI to provide clearer error messages and state management.
  • Refactored code to improve readability and maintainability.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

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.
    • Input validation (rating 1–5, ticket required) and feedback capped at 1000 chars.
    • Network/server timeouts and clearer, classified error messages (including timeout/network).
  • Bug Fixes

    • Feedback UI remains usable when status verification fails; malformed API responses handled gracefully.

@GiselleNessi GiselleNessi self-assigned this Sep 9, 2025
@GiselleNessi GiselleNessi requested review from a team as code owners September 9, 2025 14:36
@changeset-bot
Copy link

changeset-bot bot commented Sep 9, 2025

⚠️ No Changeset found

Latest commit: bd17c08

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

SupportCaseDetails was changed to a locally-declared function with a bottom-level export and gained non-fatal status-check handling, an interactive 5‑star rating, auto-resize textarea, refined submission/error classification, and adjusted reply error handling. apis/feedback.ts added strict input validation, JSON/shape checks, 10s AbortController timeouts, and granular error messages.

Changes

Cohort / File(s) Summary
Support UI component
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
Converted component to a non-exported function with export { SupportCaseDetails } at bottom; added statusCheckFailed state and handleStatusCheckError; checkFeedbackStatus now non-throwing with retry=1 and shows loader; replaced static rating with accessible interactive 5-star control and AutoResizeTextarea; integrated Send Feedback spinner; refined submit success/error handling (toasts, cache update, reset); reply catch switched to bare catch and removed console.error; conditional closed-ticket UI adjusted.
Feedback API functions
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
Added input validation for ticketId and rating; normalized/sliced feedback to max 1000 chars; used AbortController with 10s timeout for fetches; explicit JSON parsing and response shape validation; unified error return strings (Abort/Network/Server/validation/unknown); removed console logging; public function signatures unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as SupportCaseDetails UI
  participant Q as React-Query Cache
  participant API as Support Feedback API

  rect #EAF7FF
    UI->>API: GET /feedback/status?ticket_id=... (checkFeedbackStatus)
    Note right of API: AbortController (10s), parse JSON, validate shape
    API-->>UI: { has_feedback: boolean } or error string
  end

  alt loading
    UI->>UI: show "Checking feedback status..." loader
  else success & no feedback
    UI->>UI: render 5-star rating + AutoResizeTextarea + Send Feedback
  else status-check failed (non-fatal)
    UI->>UI: show warning + allow feedback submission
  end

  UI->>API: POST /feedback (submitSupportFeedback {ticketId,rating,feedback})
  Note right of API: validate inputs, normalize feedback, 10s timeout, parse JSON
  API-->>UI: { success: true } or error string

  alt success
    UI->>Q: update cache (mark feedback submitted)
    UI->>UI: show toast, reset inputs, render Thank you
  else error
    UI->>UI: classify error (timeout/network/server/validation) and show message
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description includes the commented repository template and a PR-Codex autogenerated overview that summarizes the changes, but it does not fill in the required template sections such as the formatted title header, "Notes for the reviewer", and "How to test", leaving those as placeholders. Because concrete reviewer notes and test instructions are missing, reviewers lack the guidance needed to validate behavior, exercise endpoints, or run tests. For those reasons the description does not meet the repository's template requirements. Please update the PR description to include a template-compliant title line (e.g., "[Dashboard] Fix: Feedback API input validation and status-check handling"), a "Notes for the reviewer" section summarizing key behavioral changes and risk areas, and a "How to test" section with concrete manual steps, endpoints to exercise, expected results, any required test data, and links to related issues or tests. Ensure any placeholders are replaced with real instructions and mention whether feature flags or rollouts are needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Fix/feedback api endpoints" references the core backend changes to feedback validation and API handling in the PR and is therefore related to the changeset, but it is terse, uses a slash and lowercase "api", and does not follow the repository's recommended title format. This makes it less discoverable and inconsistent with repo conventions. A clearer, template-compliant title would help reviewers and history scanning.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • TEAM-0000: Entity not found: Issue - Could not find referenced Issue.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/feedback-api-endpoints

Comment @coderabbitai help to get the list of available commands and usage tips.

@graphite-app
Copy link
Contributor

graphite-app bot commented Sep 9, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

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.

@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label Sep 9, 2025
@vercel
Copy link

vercel bot commented Sep 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
thirdweb-www Ready Ready Preview Comment Sep 11, 2025 9:11am
4 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
docs-v2 Skipped Skipped Sep 11, 2025 9:11am
nebula Skipped Skipped Sep 11, 2025 9:11am
thirdweb_playground Skipped Skipped Sep 11, 2025 9:11am
wallet-ui Skipped Skipped Sep 11, 2025 9:11am

@codecov
Copy link

codecov bot commented Sep 9, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.63%. Comparing base (79e3694) to head (bd17c08).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #8024   +/-   ##
=======================================
  Coverage   56.63%   56.63%           
=======================================
  Files         904      904           
  Lines       58684    58684           
  Branches     4162     4162           
=======================================
  Hits        33233    33233           
  Misses      25345    25345           
  Partials      106      106           
Flag Coverage Δ
packages 56.63% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 63.96 KB (0%) 1.3 s (0%) 324 ms (+49.74% 🔺) 1.7 s
thirdweb (cjs) 356.86 KB (0%) 7.2 s (0%) 1.8 s (-0.04% 🔽) 8.9 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 73 ms (+826.27% 🔺) 187 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 58 ms (+742.62% 🔺) 68 ms
thirdweb/react (minimal + tree-shaking) 19.15 KB (0%) 383 ms (0%) 101 ms (+747.06% 🔺) 484 ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (1)

44-48: Trim ticketId before sending to API

Ticket ID is validated with trim but the untrimmed value is sent. Send the normalized value to avoid subtle 404s.

   const payload = {
     rating: rating.toString(),
     feedback: normalizedFeedback,
-    ticket_id: ticketId,
+    ticket_id: ticketId.trim(),
   };
🧹 Nitpick comments (18)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (5)

65-68: Cap server-error text to prevent log/PII bloat

API error bodies can be large. Return a bounded excerpt.

-      const errorText = await response.text();
-      const error = `API Server error: ${response.status} - ${errorText}`;
+      const raw = await response.text();
+      const errorText = raw.length > 500 ? `${raw.slice(0, 500)}…` : raw;
+      const error = `API Server error: ${response.status} - ${errorText}`;
       return { error };

114-129: Drop unnecessary Content-Type on GET and prefer URLSearchParams

No body is sent; Content-Type is unnecessary. Using URL/URLSearchParams avoids manual encoding.

-    const response = await fetch(
-      `${siwaUrl}/v1/csat/getCSATFeedback?ticket_id=${encodeURIComponent(
-        ticketId.trim(),
-      )}`,
-      {
-        method: "GET",
-        cache: "no-store",
-        headers: {
-          "Content-Type": "application/json",
-          "x-service-api-key": apiKey,
-        },
-        signal: ac.signal,
-      },
-    ).finally(() => clearTimeout(t));
+    const url = new URL("/v1/csat/getCSATFeedback", siwaUrl);
+    url.search = new URLSearchParams({ ticket_id: ticketId.trim() }).toString();
+    const response = await fetch(url, {
+      method: "GET",
+      cache: "no-store",
+      headers: { "x-service-api-key": apiKey },
+      signal: ac.signal,
+    }).finally(() => clearTimeout(t));

132-134: Cap server-error text for status check as well

Mirror the submit path to bound error payload size.

-      const errorText = await response.text();
-      const error = `API Server error: ${response.status} - ${errorText}`;
+      const raw = await response.text();
+      const errorText = raw.length > 500 ? `${raw.slice(0, 500)}…` : raw;
+      const error = `API Server error: ${response.status} - ${errorText}`;
       return { error };

50-53: Optional: simplify timeouts with AbortSignal.timeout

If runtime is Node 18.17+/Next 14+, AbortSignal.timeout can replace the manual controller/clearTimeout.

-    const ac = new AbortController();
-    const t = setTimeout(() => ac.abort(), 10_000);
+    // Node 18.17+/Next 14+
+    const signal = AbortSignal.timeout(10_000);
@@
-      signal: ac.signal,
-    }).finally(() => clearTimeout(t));
+      signal,
+    });

Confirm your deployed runtime supports AbortSignal.timeout. If not, keep the current approach.


17-28: Consider avoiding NEXT_PUBLIC_ fallback on the server

Using NEXT_PUBLIC_* on server can mask missing server config and risks leaking base URLs to clients elsewhere.

Extract SIWA config once and fail fast on missing SIWA_URL; keep NEXT_PUBLIC_* only for client code paths.

Also applies to: 95-106

apps/dashboard/src/@/analytics/report.ts (4)

564-571: Add explicit return type : void

Keep consistency with “explicit return types” guideline.

-export function reportSupportFeedbackSubmitted(properties: {
+export function reportSupportFeedbackSubmitted(properties: {
   ticketId: string;
   rating: number;
   hasFeedback: boolean;
   feedbackLength?: number;
-}) {
+}): void {
   posthog.capture("support feedback submitted", properties);
 }

582-591: Add explicit return type : void

-export function reportSupportFeedbackFailed(properties: {
+export function reportSupportFeedbackFailed(properties: {
   ticketId: string;
   rating: number;
   hasFeedback: boolean;
   feedbackLength?: number;
   errorMessage: string;
   errorType: "validation" | "network" | "server" | "unknown";
-}) {
+}): void {
   posthog.capture("support feedback failed", properties);
 }

602-608: Add explicit return type : void

-export function reportSupportFeedbackStatusCheckFailed(properties: {
+export function reportSupportFeedbackStatusCheckFailed(properties: {
   ticketId: string;
   errorMessage: string;
   errorType: "network" | "server" | "unknown";
-}) {
+}): void {
   posthog.capture("support feedback status check failed", properties);
 }

551-609: Optional: unify error type(s) for feedback analytics

Create a shared type alias to avoid drift across events.

// near the SUPPORT FEEDBACK section header
type SupportFeedbackErrorType = "validation" | "network" | "server" | "unknown";

Then reference it in both helpers.

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx (1)

1-1: Optional: mark this page as server-only

Adds guardrail against accidental client import.

import "server-only";
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (8)

42-42: Add explicit return types to components

Align with our TS guidelines by annotating component return types.

-function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
+function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps): JSX.Element {
@@
-function TicketHeader(props: {
+function TicketHeader(props: {
   title: string;
   createdAt: string;
   updatedAt: string;
   status: SupportTicket["status"];
-}) {
+}): JSX.Element {
@@
-function TicketMessage(props: { message: SupportMessage }) {
+function TicketMessage(props: { message: SupportMessage }): JSX.Element {
@@
-export function SupportCaseDetailsWithErrorBoundary(
-  props: SupportCaseDetailsProps,
-) {
+export function SupportCaseDetailsWithErrorBoundary(
+  props: SupportCaseDetailsProps,
+): JSX.Element {

Also applies to: 618-623, 654-654, 790-799


37-40: Prefer type alias over interface for props

Project convention: use type for structural props.

-interface SupportCaseDetailsProps {
-  ticket: SupportTicket;
-  team: Team;
-}
+type SupportCaseDetailsProps = {
+  ticket: SupportTicket;
+  team: Team;
+};

188-200: Clarify log context naming: hasFeedback here means “has text comment”, not “already submitted”

This collides with the “feedback already submitted” concept from the status check. Rename in logs to avoid confusion while keeping analytics payloads unchanged.

-        hasFeedback: Boolean(feedback?.trim()),
+        hasTextFeedback: Boolean(feedback?.trim()),
@@
-        hasFeedback: Boolean(feedback?.trim()),
+        hasTextFeedback: Boolean(feedback?.trim()),
@@
-        hasFeedback: Boolean(feedback?.trim()),
+        hasTextFeedback: Boolean(feedback?.trim()),
@@
-      hasFeedback: Boolean(feedback?.trim()),
+      hasTextFeedback: Boolean(feedback?.trim()),

Also applies to: 267-274, 317-324, 366-379


78-86: Factor repeated error-type classification into a helper

Reduces duplication and drift across paths.

+function classifyErrorType(msg: string):
+  | "validation"
+  | "network"
+  | "server"
+  | "unknown" {
+  if (/validation|Rating must be|Please select a rating/i.test(msg)) return "validation";
+  if (/network|fetch/i.test(msg)) return "network";
+  if (/API Server error|Internal server error/i.test(msg)) return "server";
+  return "unknown";
+}
@@
-          const errorType = /network|fetch/i.test(result.error)
-            ? "network"
-            : /API Server error/i.test(result.error)
-              ? "server"
-              : /Internal server error/i.test(result.error)
-                ? "server"
-                : "unknown";
+          const errorType = classifyErrorType(result.error);
@@
-        const errorType = /network|fetch/i.test(
-          error instanceof Error ? error.message : String(error),
-        )
-          ? "network"
-          : /API Server error/i.test(
-                error instanceof Error ? error.message : String(error),
-              )
-            ? "server"
-            : "unknown";
+        const errorType = classifyErrorType(
+          error instanceof Error ? error.message : String(error),
+        );
@@
-      let errorType: "validation" | "network" | "server" | "unknown" =
-        "unknown";
+      let errorType: "validation" | "network" | "server" | "unknown" = "unknown";
@@
-      } else if (/API Server error/i.test(msg)) {
+      } else if (/API Server error/i.test(msg)) {
         message = "Server error. Please try again later.";
-        errorType = "server";
+        errorType = "server";
       }
@@
-    reportSupportFeedbackFailed({
+    reportSupportFeedbackFailed({
       ticketId: this.props.ticketId,
       rating: 0,
       hasFeedback: false,
       feedbackLength: 0,
-      errorMessage: `Error boundary: ${error.message}`,
-      errorType: "unknown",
+      errorMessage: `Error boundary: ${error.message}`,
+      errorType: classifyErrorType(error.message),
     });

Also applies to: 142-152, 339-350, 760-767


202-206: Gate verbose console logging behind a debug flag

Keep production console clean and reduce noise. Example pattern:

+const DEBUG_FEEDBACK = process.env.NEXT_PUBLIC_FEEDBACK_DEBUG === "true";
@@
-      console.log(
+      DEBUG_FEEDBACK && console.log(
         "[SUPPORT_FEEDBACK_CLIENT] Starting feedback submission",
         logContext,
       );
@@
-          console.error(
+          (DEBUG_FEEDBACK ? console.error : console.log)(
             "[SUPPORT_FEEDBACK_CLIENT] Feedback submission failed",
             {
               ...logContext,
               error: result.error,
               errorType: typeof result.error,
               errorLength: result.error?.length || 0,
             },
           );
@@
-        console.log(
+        DEBUG_FEEDBACK && console.log(
           "[SUPPORT_FEEDBACK_CLIENT] Feedback submission successful",
           logContext,
         );
@@
-      console.log(
+      DEBUG_FEEDBACK && console.log(
         "[SUPPORT_FEEDBACK_CLIENT] Feedback submission success callback",
         logContext,
       );
@@
-      console.log("[SUPPORT_FEEDBACK_CLIENT] Success state updates starting", {
+      DEBUG_FEEDBACK && console.log("[SUPPORT_FEEDBACK_CLIENT] Success state updates starting", {
         ...logContext,
         currentRating: rating,
         currentFeedback: feedback,
         queryDataBefore: queryClient.getQueryData([
           "feedbackStatus",
           ticket.id,
         ]),
       });
@@
-      console.log("[SUPPORT_FEEDBACK_CLIENT] Success state updates completed", {
+      DEBUG_FEEDBACK && console.log("[SUPPORT_FEEDBACK_CLIENT] Success state updates completed", {
         ...logContext,
         queryDataAfter: queryClient.getQueryData(["feedbackStatus", ticket.id]),
         ratingAfter: 0,
         feedbackAfter: "",
       });
@@
-      console.error(
+      (DEBUG_FEEDBACK ? console.error : console.log)(
         "[SUPPORT_FEEDBACK_CLIENT] Feedback submission error callback",
         {
           ...logContext,
           error: err instanceof Error ? err.message : String(err),
           errorName: err instanceof Error ? err.name : "Unknown",
         },
       );
@@
-    console.log(
+    DEBUG_FEEDBACK && console.log(
       "[SUPPORT_FEEDBACK_CLIENT] User initiated feedback submission",
       logContext,
     );
@@
-    console.error("[SUPPORT_FEEDBACK_CLIENT] Error boundary caught error:", {
+    (DEBUG_FEEDBACK ? console.error : console.log)("[SUPPORT_FEEDBACK_CLIENT] Error boundary caught error:", {
       ticketId: this.props.ticketId,
       error: error.message,
       errorStack: error.stack,
       errorInfo,
       timestamp: new Date().toISOString(),
     });

Also applies to: 225-233, 252-256, 275-279, 291-315, 325-333, 381-384, 750-758


459-466: Expose className on root for composability (dashboard guideline)

Add an optional className prop and merge via cn() on the root container.

-type SupportCaseDetailsProps = {
-  ticket: SupportTicket;
-  team: Team;
-};
+type SupportCaseDetailsProps = {
+  ticket: SupportTicket;
+  team: Team;
+  className?: string;
+};
@@
-function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps): JSX.Element {
+function SupportCaseDetails({ ticket, team, className }: SupportCaseDetailsProps): JSX.Element {
@@
-  return (
-    <div className="flex flex-col grow">
+  return (
+    <div className={cn("flex flex-col grow", className)}>

705-716: Type the keyboard event target

Use the textarea-specific event type for better intellisense and safety.

-const handleKeyDown = (e: React.KeyboardEvent) => {
+const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {

750-757: Use React’s ErrorInfo type for componentDidCatch

Minor typing polish.

-  componentDidCatch(error: Error, errorInfo: { componentStack: string }) {
+  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6f3a0 and 60fe778.

📒 Files selected for processing (4)
  • apps/dashboard/src/@/analytics/report.ts (1 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (3 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{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 local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown 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

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

Files:

  • apps/dashboard/src/@/analytics/report.ts
  • 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/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.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/@/analytics/report.ts
  • 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/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() 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 with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/@/analytics/report.ts
  • 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/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/@/analytics/report.ts
  • 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/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx
apps/{dashboard,playground}/src/@/analytics/report.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/src/@/analytics/report.ts: Check report.ts before adding a new analytics event to avoid duplicates
Analytics naming: event name as <subject> <verb>; helper function as report<Subject><Verb> (PascalCase)
Each analytics helper must include a JSDoc header (Why/Owner), accept a single typed properties object, and forward it unchanged to posthog.capture

Files:

  • apps/dashboard/src/@/analytics/report.ts
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx
🧠 Learnings (6)
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Analytics naming: event name as `<subject> <verb>`; helper function as `report<Subject><Verb>` (PascalCase)

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 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 src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 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/src/@/analytics/report.ts : Reporting helper: `report<Subject><Verb>` (PascalCase); all live in `src/@/analytics/report.ts`.

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Check `report.ts` before adding a new analytics event to avoid duplicates

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 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 src/@/analytics/report.ts : Analytics event name: human-readable `<subject> <verb>` (e.g., "contract deployed"); function: `report<Subject><Verb>` (PascalCase)

Applied to files:

  • apps/dashboard/src/@/analytics/report.ts
📚 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
🧬 Code graph analysis (2)
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 (91-170)
  • submitSupportFeedback (6-89)
apps/dashboard/src/@/analytics/report.ts (3)
  • reportSupportFeedbackStatusCheckFailed (602-608)
  • reportSupportFeedbackSubmitted (564-571)
  • reportSupportFeedbackFailed (582-591)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (1)
  • SupportCaseDetailsWithErrorBoundary (791-799)
⏰ 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)
  • GitHub Check: Lint Packages
  • GitHub Check: Size
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (8)
apps/dashboard/src/@/analytics/report.ts (2)

551-609: Events follow naming conventions and property pass-through

Event names and helper names match guidelines; JSDoc present; properties are forwarded unchanged.


1-609: All support feedback capture calls are unique
No duplicate event names found.

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/cases/[id]/page.tsx (3)

5-5: Good swap to error-boundary wrapper

Importing SupportCaseDetailsWithErrorBoundary aligns with the new public surface and isolates runtime errors.


37-42: Wrapper usage looks correct

Props preserved; server page rendering a client wrapper is fine.


5-5: No remaining SupportCaseDetails imports — confirmed no other files import the old SupportCaseDetails symbol.

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

172-175: Good use of React Query v5 options

staleTime and gcTime are set sensibly; key is stable. LGTM.


5-5: Icon source compliance

Using lucide-react and the project icon component aligns with our icon guideline. 👍

Also applies to: 684-690


280-287: Analytics field semantics: confirm intent of hasFeedback

You send hasFeedback: Boolean(feedback?.trim()) in analytics. Confirm downstream expects “has text comment” vs. “ticket already had CSAT”. If it’s the latter, consider sending an extra flag or renaming.

Do you want me to generate a quick PR to add a separate hadExistingFeedback boolean to the analytics payloads and reports?

Also applies to: 352-360

@vercel vercel bot temporarily deployed to Preview – docs-v2 September 9, 2025 15:01 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 9, 2025 15:01 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 9, 2025 15:01 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula September 9, 2025 15:01 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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/apis/feedback.ts (3)

16-24: Good: fail-fast input validation is now first

Moving ticketId/rating validation before config/network work addresses the earlier concern and reduces wasted work.


78-83: Correct network error detection

Using AbortError and TypeError branches is the right way to classify fetch failures.


170-175: Same here: error classification is correct

Mirrors the submit path; good consistency.

🧹 Nitpick comments (13)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (3)

39-48: Trim ticketId before sending; keep payload normalized

Minor consistency fix: you validate with trim but send the raw ticketId. Also, only stringify rating if the API requires it.

Apply:

   const payload = {
-    rating: rating.toString(),
+    // Keep as number unless the API strictly requires a string
+    rating: rating.toString(),
     feedback: normalizedFeedback,
-    ticket_id: ticketId,
+    ticket_id: ticketId.trim(),
   };

If SIWA accepts numeric rating, switch to rating (number) here and in analytics to avoid type drift. Do you want me to adjust both sides?


70-75: Guard JSON parsing for submit path (symmetry with status path)

If the API ever returns non-JSON (e.g., empty 204), this throws and returns a cryptic message. Mirror the status handler’s parse guard.

-    const data = await response.json();
+    let data: { success?: boolean } = {};
+    try {
+      data = await response.json();
+    } catch (_jsonErr) {
+      return { error: "Invalid JSON response from API" };
+    }

113-126: Nit: remove Content-Type for GET; add Accept on both calls

Content-Type on GET is unnecessary. Prefer advertising desired response via Accept.

       headers: {
-        "Content-Type": "application/json",
+        Accept: "application/json",
         "x-service-api-key": apiKey,
       },

And for the POST headers:

       headers: {
         "Content-Type": "application/json",
+        Accept: "application/json",
         "x-service-api-key": apiKey,
       },
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (10)

53-89: Unify error classification and remove no-op return from a throwing helper

  • Classify network via TypeError (not message regex) to match fetch semantics.
  • handleStatusCheckError always throws; returning its value is misleading.
   const handleStatusCheckError = (
     error: unknown,
     logContext: Record<string, string | number | boolean>,
   ) => {
-    const errorMessage = error instanceof Error ? error.message : String(error);
-    const errorType = /network|fetch/i.test(errorMessage)
-      ? "network"
-      : /API Server error/i.test(errorMessage)
-        ? "server"
-        : /Internal server error/i.test(errorMessage)
-          ? "server"
-          : "unknown";
+    const isTypeErr = error instanceof TypeError;
+    const errorMessage = error instanceof Error ? error.message : String(error);
+    const errorType = isTypeErr
+      ? "network"
+      : /API Server error|Internal server error/i.test(errorMessage)
+        ? "server"
+        : "unknown";
@@
-    // Throw a custom error that React Query will catch, but the UI will handle gracefully
-    // This prevents treating the error as "no feedback exists" which could cause duplicates
+    // Throw: React Query will surface isError; UI remains unblocked
     throw new Error(`Status check failed: ${errorMessage}`);
   };

And where you use it:

-          // Use shared error handler to maintain consistent behavior
-          return handleStatusCheckError(result.error, logContext);
+          // Use shared error handler to maintain consistent behavior
+          handleStatusCheckError(result.error, logContext);
-        // Use shared error handler to maintain consistent behavior
-        return handleStatusCheckError(error, logContext);
+        // Use shared error handler to maintain consistent behavior
+        handleStatusCheckError(error, logContext);

92-168: Avoid retries to prevent repeated analytics spam on status failures

React Query retries queries by default; since you intentionally throw to mark degraded state, disable retries.

   const feedbackStatusQuery = useQuery({
     queryKey: ["feedbackStatus", ticket.id],
@@
-    staleTime: 60_000,
-    gcTime: 5 * 60_000,
+    staleTime: 60_000,
+    gcTime: 5 * 60_000,
+    retry: false,
   });

170-174: Align comment with behavior; rely on isError

The query does throw on failures now. Use isError (optionally OR with the local flag if you keep it).

-// query never throws; use local degraded flag for the inline warning
-const hasError = statusCheckFailed;
+// query throws on failures; surface the inline warning
+const hasError = feedbackStatusQuery.isError || statusCheckFailed;

328-345: Classify network errors via TypeError, not message text

This keeps client-side classification consistent with the server helpers.

-  const msg = err instanceof Error ? err.message : String(err ?? "");
+  const msg = err instanceof Error ? err.message : String(err ?? "");
   let message = "Failed to submit feedback. Please try again.";
   let errorType: "validation" | "network" | "server" | "unknown" =
     "unknown";

-  if (/network|fetch/i.test(msg)) {
+  if (err instanceof TypeError || /network|fetch/i.test(msg)) {
     message = "Network error. Please check your connection and try again.";
     errorType = "network";
   } else if (

37-42: Prefer type alias and add a className prop (guideline: expose className in dashboard components)

Keeps to the repo style and improves composability.

-interface SupportCaseDetailsProps {
-  ticket: SupportTicket;
-  team: Team;
-}
+type SupportCaseDetailsProps = {
+  ticket: SupportTicket;
+  team: Team;
+  className?: string;
+};

42-42: Add explicit return type to component

Guideline: explicit function declarations and return types.

-function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
+function SupportCaseDetails({ ticket, team, className }: SupportCaseDetailsProps): JSX.Element {

455-456: Plumb className onto the root element

Expose styling control to callers.

-  return (
-    <div className="flex flex-col grow">
+  return (
+    <div className={cn("flex flex-col grow", className)}>

614-648: Add explicit return type on TicketHeader

-function TicketHeader(props: {
+function TicketHeader(props: {
   title: string;
   createdAt: string;
   updatedAt: string;
   status: SupportTicket["status"];
-}) {
+}): JSX.Element {

650-731: Add explicit return type on TicketMessage

-function TicketMessage(props: { message: SupportMessage }) {
+function TicketMessage(props: { message: SupportMessage }): JSX.Element {

787-795: Add explicit return type on the exported wrapper

-export function SupportCaseDetailsWithErrorBoundary(
-  props: SupportCaseDetailsProps,
-) {
+export function SupportCaseDetailsWithErrorBoundary(
+  props: SupportCaseDetailsProps,
+): JSX.Element {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 60fe778 and 33d3246.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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 local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown 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

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • 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/apis/feedback.ts
  • 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() 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 with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 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/**/*.{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
🧬 Code graph analysis (1)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (4)
packages/nebula/src/client/sdk.gen.ts (1)
  • feedback (377-398)
apps/dashboard/src/@/analytics/report.ts (3)
  • reportSupportFeedbackStatusCheckFailed (602-608)
  • reportSupportFeedbackSubmitted (564-571)
  • reportSupportFeedbackFailed (582-591)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
  • checkFeedbackStatus (87-177)
  • submitSupportFeedback (6-85)
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). (7)
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Size
  • GitHub Check: Lint Packages
  • GitHub Check: Unit Tests
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)

50-63: Timeout/Abort wiring looks solid

AbortController + finally clear is correct and prevents leaks.


151-166: Nice: comprehensive runtime shape validation

The explicit checks prevent false type safety from TS declarations.

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

505-574: UI flow reads well and meets design tokens; stars + textarea a11y is solid

Nice accessible labels, disabled states, and token usage.

@vercel vercel bot temporarily deployed to Preview – docs-v2 September 9, 2025 17:44 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 9, 2025 17:44 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula September 9, 2025 17:44 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 9, 2025 17:44 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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

541-584: Add better error recovery UI

The error boundary currently shows a generic error message. Consider providing recovery options or a way for users to retry the feedback submission.

 	render() {
 		if (this.state.hasError) {
 			return (
 				<div className="border-t p-6">
-					<div className="text-destructive text-sm">
-						<p className="font-medium">
-							Something went wrong with the feedback system.
-						</p>
-						<p className="text-xs mt-1">
-							Error: {this.state.error?.message || "Unknown error"}
-						</p>
-					</div>
+					<div className="text-destructive text-sm space-y-3">
+						<div>
+							<p className="font-medium">
+								Something went wrong with the feedback system.
+							</p>
+							<p className="text-xs mt-1">
+								Error: {this.state.error?.message || "Unknown error"}
+							</p>
+						</div>
+						<Button 
+							variant="outline" 
+							size="sm"
+							onClick={() => this.setState({ hasError: false, error: undefined })}
+						>
+							Try Again
+						</Button>
+					</div>
 				</div>
 			);
 		}

 		return this.props.children;
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 33d3246 and 7e498d3.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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 local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown 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

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() 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 with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates
📚 Learning: 2025-05-26T16:31:02.480Z
Learnt from: MananTank
PR: thirdweb-dev/js#7152
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/claim-conditions/shared-claim-conditions-page.tsx:43-49
Timestamp: 2025-05-26T16:31:02.480Z
Learning: In the thirdweb dashboard codebase, when `redirectToContractLandingPage()` is called, an explicit return statement is not required afterward because the function internally calls Next.js's `redirect()` which throws an error to halt execution.

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 : 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: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} : Use `NavLink` (`@/components/ui/NavLink`) for internal navigation so active states are handled automatically.

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/@/analytics/report.ts (3)
  • reportSupportFeedbackStatusCheckFailed (602-608)
  • reportSupportFeedbackSubmitted (564-571)
  • reportSupportFeedbackFailed (582-591)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
  • checkFeedbackStatus (87-177)
  • submitSupportFeedback (6-85)
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)
⏰ 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). (8)
  • GitHub Check: Lint Packages
  • GitHub Check: Size
  • GitHub Check: Unit Tests
  • GitHub Check: E2E Tests (pnpm, webpack)
  • GitHub Check: E2E Tests (pnpm, vite)
  • GitHub Check: Build Packages
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (3)

116-185: LGTM! Well-structured feedback submission with comprehensive error handling

The mutation includes proper validation, detailed error classification, analytics reporting, and user-friendly error messages. The optimistic cache update and toast notifications provide good UX.


587-595: LGTM! Clean wrapper implementation with error boundary

The wrapper properly preserves the original component API while adding error boundary protection. This maintains backward compatibility while improving error resilience.


313-381: UI provides clear feedback for degraded status check behavior

The conditional rendering correctly shows the warning banner when statusCheckFailed is true, and the star rating system with accessible controls is well-implemented. The feedback submission UI is clean and functional.

@vercel vercel bot temporarily deployed to Preview – nebula September 9, 2025 18:50 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 9, 2025 18:50 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 9, 2025 18:50 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 10, 2025 21:03 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 10, 2025 21:03 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula September 10, 2025 21:03 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 10, 2025 21:03 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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)

276-297: Make star rating accessible and switch to design tokens (no raw colors)

Add radiogroup semantics and replace hardcoded pink with tokens.

-              <div className="flex gap-2 mb-6 mt-4">
+              <div
+                className="flex gap-2 mb-6 mt-4"
+                role="radiogroup"
+                aria-label="Rate support experience"
+              >
                 {[1, 2, 3, 4, 5].map((starValue) => (
                   <button
                     key={`star-${starValue}`}
                     type="button"
                     onClick={() => handleStarClick(starValue - 1)}
-                    className="transition-colors"
+                    className="transition-colors"
+                    role="radio"
+                    aria-checked={starValue <= rating}
                     aria-label={`Rate ${starValue} out of 5 stars`}
                   >
                     <StarIcon
                       size={32}
                       className={cn(
                         "transition-colors",
                         starValue <= rating
-                          ? "text-pink-500 fill-current stroke-current"
+                          ? "text-primary fill-current stroke-current"
                           : "text-muted-foreground fill-current stroke-current",
-                        "hover:text-pink-500",
+                        "hover:text-primary",
                       )}
                       strokeWidth={starValue <= rating ? 2 : 1}
                     />
                   </button>
                 ))}
               </div>

288-293: Nit: replace raw color with token everywhere

There are two occurrences in this block; both updated in the larger diff above. Ensure no stray text-pink-500 remains.


259-263: Prevent duplicate submissions when status check fails (UI gate currently re-opens form)

The condition (!feedbackSubmitted || hasError) shows the form even after a successful submission if a later poll fails, enabling duplicates. Gate strictly on !feedbackSubmitted and surface the degraded banner separately.

- {ticket.status === "closed" &&
-   !isLoading &&
-   (!feedbackSubmitted || hasError) && (
+ {ticket.status === "closed" &&
+   !isLoading &&
+   !feedbackSubmitted && (

Optionally show the warning banner outside this gate when hasError is true. Do you want a patch for that placement?

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

32-37: Types and API surface: prefer type alias, expose className, and add explicit return type

Aligns with repo guidelines (type aliases, expose className on dashboard components, explicit return types).

-interface SupportCaseDetailsProps {
-  ticket: SupportTicket;
-  team: Team;
-}
-
-function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
+type SupportCaseDetailsProps = {
+  ticket: SupportTicket;
+  team: Team;
+  className?: string;
+};
+
+function SupportCaseDetails({ ticket, team, className }: SupportCaseDetailsProps): JSX.Element {

209-210: Plumb className through the root element

Let parents style/layout this component.

-    <div className="flex flex-col grow">
+    <div className={cn("flex flex-col grow", className)}>

368-373: Add explicit return types to helper components

Small clarity/consistency improvement.

-function TicketHeader(props: {
+function TicketHeader(props: {
   title: string;
   createdAt: string;
   updatedAt: string;
   status: SupportTicket["status"];
-}) {
+}): JSX.Element {
-function TicketMessage(props: { message: SupportMessage }) {
+function TicketMessage(props: { message: SupportMessage }): JSX.Element {

Also applies to: 404-405


214-216: Use NavLink for internal nav per dashboard guideline

Breadcrumb links are internal; prefer NavLink for active state consistency.

-import Link from "next/link";
+import Link from "next/link"; // if NavLink unavailable in breadcrumb, ignore
+// import { NavLink } from "@/components/ui/nav-link";
@@
-              <Link href={`/team/${team.slug}/~/support`}>Cases</Link>
+              {/* If BreadcrumbLink supports asChild with NavLink in your UI kit: */}
+              {/* <NavLink href={`/team/${team.slug}/~/support`}>Cases</NavLink> */}
+              <Link href={`/team/${team.slug}/~/support`}>Cases</Link>

If the project exposes a breadcrumb-aware NavLink, switch to it; otherwise, this is non-blocking.


184-191: Optional: keep the error for send-reply in logs/analytics

Bare catch {} hides details; consider logging/reporting while keeping the user-facing toast.

-    } catch {
-      toast.error("Failed to send Message. Please try again.");
+    } catch (err) {
+      // Consider reporting to analytics; keep UI non-noisy
+      console.error("[SUPPORT] Reply send failed", err);
+      toast.error("Failed to send message. Please try again.");

73-74: Minor: consider retry: 0 for faster UX

Given the non-blocking strategy, a single retry may still delay UI; retry: 0 is also reasonable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9789b05 and e6ed8bd.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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 local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown 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

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() 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 with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 Learnings (9)
📚 Learning: 2025-05-26T16:31:02.480Z
Learnt from: MananTank
PR: thirdweb-dev/js#7152
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/claim-conditions/shared-claim-conditions-page.tsx:43-49
Timestamp: 2025-05-26T16:31:02.480Z
Learning: In the thirdweb dashboard codebase, when `redirectToContractLandingPage()` is called, an explicit return statement is not required afterward because the function internally calls Next.js's `redirect()` which throws an error to halt execution.

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Client-side data fetching: wrap calls in React Query with descriptive, stable `queryKeys` and set sensible `staleTime/cacheTime` (≥ 60s default); keep tokens secret via internal routes or server actions

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-06-10T00:50:20.795Z
Learnt from: MananTank
PR: thirdweb-dev/js#7315
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx:153-226
Timestamp: 2025-06-10T00:50:20.795Z
Learning: In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx, the updateStatus function correctly expects a complete MultiStepState["status"] object. For pending states, { type: "pending" } is the entire status object. For error states, { type: "error", message: React.ReactNode } is the entire status object. The current code incorrectly spreads the entire step object instead of passing just the status object.

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 src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Check `report.ts` before adding a new analytics event to avoid duplicates

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-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Stick to design 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: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/**/*.{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 (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
  • checkFeedbackStatus (87-177)
  • submitSupportFeedback (6-85)
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). (5)
  • GitHub Check: E2E Tests (pnpm, esbuild)
  • GitHub Check: Size
  • GitHub Check: Unit Tests
  • 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)

45-53: LGTM: simplified non-throwing status error handler

The local degraded state avoids React Query error flow and matches the non-blocking UX.


24-25: Server Actions used—no secret exposure
The feedback.ts module is marked with "use server", so checkFeedbackStatus and submitSupportFeedback execute exclusively on the server via Next.js Server Actions and will not bundle SERVICE_AUTH_KEY_SIWA or x-service-api-key in the browser. (nextjs.org)

Likely an incorrect or invalid review comment.

@GiselleNessi GiselleNessi added the merge-queue Adds the pull request to Graphite's merge queue. label Sep 11, 2025
Copy link
Contributor Author

GiselleNessi commented Sep 11, 2025

Merge activity

<!--

## 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 -->
@graphite-app graphite-app bot force-pushed the fix/feedback-api-endpoints branch from 1c597dc to bd17c08 Compare September 11, 2025 09:03
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 11, 2025 09:03 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 11, 2025 09:03 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 11, 2025 09:03 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula September 11, 2025 09:03 Inactive
@graphite-app graphite-app bot merged commit bd17c08 into main Sep 11, 2025
23 of 24 checks passed
@graphite-app graphite-app bot deleted the fix/feedback-api-endpoints branch September 11, 2025 09:11
@graphite-app graphite-app bot removed the merge-queue Adds the pull request to Graphite's merge queue. label Sep 11, 2025
@vercel vercel bot temporarily deployed to Production – nebula September 11, 2025 09:11 Inactive
@vercel vercel bot temporarily deployed to Production – thirdweb_playground September 11, 2025 09:11 Inactive
@vercel vercel bot temporarily deployed to Production – wallet-ui September 11, 2025 09:11 Inactive
@vercel vercel bot temporarily deployed to Production – docs-v2 September 11, 2025 09:11 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (3)

16-24: Normalize and reuse trimmed ticketId

Trim once and use the canonical value in the payload to avoid storing/processing trailing spaces.

-    if (!ticketId?.trim()) {
+    const tid = ticketId?.trim();
+    if (!tid) {
       return { error: "ticketId is required." };
     }
@@
-    const payload = {
+    const payload = {
       rating: rating.toString(),
       feedback: normalizedFeedback,
-      ticket_id: ticketId,
+      ticket_id: tid,
     };

Also applies to: 44-48


65-68: Cap server error text to prevent oversized messages

Some backends return large HTML error pages. Consider truncating for UX and logs (keep status intact).

-      const errorText = await response.text();
+      const raw = await response.text();
+      const errorText = raw.length > 500 ? `${raw.slice(0, 500)}…` : raw;

122-136: Avoid sending Content-Type on GET; prefer Accept header

Setting Content-Type on GET is unnecessary and can confuse some intermediaries. Ask for JSON via Accept instead.

       {
         method: "GET",
         cache: "no-store",
-        headers: {
-          "Content-Type": "application/json",
-          "x-service-api-key": apiKey,
-        },
+        headers: {
+          Accept: "application/json",
+          "x-service-api-key": apiKey,
+        },
         signal: ac.signal,
       },
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (2)

119-127: Handle timeout errors explicitly in user message

API returns “Request timeout”; surface a clearer toast for that case too.

-      } else if (/API Server error/i.test(msg)) {
+      } else if (/API Server error/i.test(msg)) {
         message = "Server error. Please try again later.";
+      } else if (/timeout/i.test(msg)) {
+        message = "Request timed out. Please try again.";
       }

276-297: Make star rating accessible and use design tokens over raw color

Expose radiogroup semantics and replace text-pink-500 with text-primary per design tokens.

-              <div className="flex gap-2 mb-6 mt-4">
+              <div
+                className="flex gap-2 mb-6 mt-4"
+                role="radiogroup"
+                aria-label="Rate support experience"
+                onKeyDown={handleRatingKeyDown}
+              >
                 {[1, 2, 3, 4, 5].map((starValue) => (
                   <button
                     key={`star-${starValue}`}
                     type="button"
                     onClick={() => handleStarClick(starValue - 1)}
-                    className="transition-colors"
+                    className="transition-colors"
+                    role="radio"
+                    aria-checked={starValue === rating}
                     aria-label={`Rate ${starValue} out of 5 stars`}
                   >
                     <StarIcon
                       size={32}
                       className={cn(
                         "transition-colors",
                         starValue <= rating
-                          ? "text-pink-500 fill-current stroke-current"
+                          ? "text-primary fill-current stroke-current"
                           : "text-muted-foreground fill-current stroke-current",
-                        "hover:text-pink-500",
+                        "hover:text-primary",
                       )}
                       strokeWidth={starValue <= rating ? 2 : 1}
                     />
                   </button>
                 ))}
               </div>

Add this helper near other handlers:

function handleRatingKeyDown(e: React.KeyboardEvent) {
  if (e.key === "ArrowRight" || e.key === "ArrowUp") {
    e.preventDefault();
    setRating((r) => Math.min(5, Math.max(1, (r || 0) + 1)));
  } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
    e.preventDefault();
    setRating((r) => Math.min(5, Math.max(1, (r || 0) - 1)));
  } else if (e.key === "Home") {
    e.preventDefault();
    setRating(1);
  } else if (e.key === "End") {
    e.preventDefault();
    setRating(5);
  }
}

Also applies to: 287-293

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 074522f and bd17c08.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx (6 hunks)
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{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 local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown 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

**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • 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/apis/feedback.ts
  • 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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() 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 with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from @/components/ui/_ (e.g., Button, Input, Tabs, Card)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts
  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
apps/{dashboard,playground}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Expose a className prop on the root element of every component

Files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
🧠 Learnings (9)
📚 Learning: 2025-05-26T16:31:02.480Z
Learnt from: MananTank
PR: thirdweb-dev/js#7152
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/claim-conditions/shared-claim-conditions-page.tsx:43-49
Timestamp: 2025-05-26T16:31:02.480Z
Learning: In the thirdweb dashboard codebase, when `redirectToContractLandingPage()` is called, an explicit return statement is not required afterward because the function internally calls Next.js's `redirect()` which throws an error to halt execution.

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Client-side data fetching: wrap calls in React Query with descriptive, stable `queryKeys` and set sensible `staleTime/cacheTime` (≥ 60s default); keep tokens secret via internal routes or server actions

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-06-10T00:50:20.795Z
Learnt from: MananTank
PR: thirdweb-dev/js#7315
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx:153-226
Timestamp: 2025-06-10T00:50:20.795Z
Learning: In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/assets/create/nft/launch-nft.tsx, the updateStatus function correctly expects a complete MultiStepState["status"] object. For pending states, { type: "pending" } is the entire status object. For error states, { type: "error", message: React.ReactNode } is the entire status object. The current code incorrectly spreads the entire step object instead of passing just the status object.

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 src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates

Applied to files:

  • apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportCaseDetails.tsx
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Check `report.ts` before adding a new analytics event to avoid duplicates

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-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/**/*.{ts,tsx} : Stick to design 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: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/**/*.{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 (2)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)
  • checkFeedbackStatus (96-186)
  • submitSupportFeedback (6-94)
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). (3)
  • GitHub Check: Size
  • GitHub Check: Lint Packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/apis/feedback.ts (2)

70-85: Response parsing and shape checks look solid

Good JSON try/catch and minimal shape validation before trusting fields. LGTM.


87-93: Good differentiation of timeout vs. network vs. generic errors

This maps well to fetch semantics. LGTM.

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

45-53: Non-blocking degraded state handler looks good

Simple, predictable flag; prevents React Query error gating from hiding the warning. LGTM.


259-262: Confirm UX: form is shown when status check fails even if feedback exists

(!feedbackSubmitted || hasError) can allow duplicate submissions during transient API failures. If that’s intentional, ignore. If not, gate strictly on !feedbackSubmitted && !hasError or block submit when hasError is true.

Option A (strict):

-          (!feedbackSubmitted || hasError) && (
+          (!feedbackSubmitted && !hasError) && (

Option B (block on submit):

  • Keep UI visible, but in handleSendFeedback, bail out when hasError is true and ask the user to retry after a moment.

98-105: Nice: immediate optimistic UI update after successful submit

Resetting state and marking cache true avoids a second fetch. LGTM.

Also applies to: 109-114


486-487: Export pattern is clean

Local function + named export keeps tree-shaking friendly and aligns with the file’s client component directive. LGTM.

Comment on lines +143 to +152
let data: {
has_feedback: boolean;
feedback_data: {
id: string;
rating: number | null;
feedback: string | null;
ticket_id: string | null;
created_at: string;
} | null;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix: Missing object guard can throw TypeError and be misclassified as “Network error”

If the API returns null or a primitive, property access on data can throw and fall into the catch where TypeError is reported as network failure. Guard the JSON value before field access and avoid over-confident typing of data.

-    let data: {
-      has_feedback: boolean;
-      feedback_data: {
-        id: string;
-        rating: number | null;
-        feedback: string | null;
-        ticket_id: string | null;
-        created_at: string;
-      } | null;
-    };
+    let data: unknown;
@@
-      data = await response.json();
+      data = await response.json();
     } catch (_jsonError) {
       return { error: "Invalid JSON response from API" };
     }
 
-    // Comprehensive validation of the API response structure
-    if (
-      typeof data.has_feedback !== "boolean" ||
-      (data.feedback_data != null &&
-        (typeof data.feedback_data !== "object" ||
-          typeof data.feedback_data.id !== "string" ||
-          (data.feedback_data.rating != null &&
-            typeof data.feedback_data.rating !== "number") ||
-          (data.feedback_data.feedback != null &&
-            typeof data.feedback_data.feedback !== "string") ||
-          (data.feedback_data.ticket_id != null &&
-            typeof data.feedback_data.ticket_id !== "string") ||
-          typeof data.feedback_data.created_at !== "string"))
-    ) {
-      return { error: "Invalid response format from API" };
-    }
-
-    return { hasFeedback: data.has_feedback };
+    // Guard before accessing properties
+    if (typeof data !== "object" || data === null) {
+      return { error: "Invalid response format from API" };
+    }
+    const d = data as {
+      has_feedback?: unknown;
+      feedback_data?: {
+        id?: unknown;
+        rating?: unknown;
+        feedback?: unknown;
+        ticket_id?: unknown;
+        created_at?: unknown;
+      } | null;
+    };
+
+    if (
+      typeof d.has_feedback !== "boolean" ||
+      (d.feedback_data != null &&
+        (typeof d.feedback_data !== "object" ||
+          typeof d.feedback_data.id !== "string" ||
+          (d.feedback_data.rating != null &&
+            typeof d.feedback_data.rating !== "number") ||
+          (d.feedback_data.feedback != null &&
+            typeof d.feedback_data.feedback !== "string") ||
+          (d.feedback_data.ticket_id != null &&
+            typeof d.feedback_data.ticket_id !== "string") ||
+          typeof d.feedback_data.created_at !== "string"))
+    ) {
+      return { error: "Invalid response format from API" };
+    }
+
+    return { hasFeedback: d.has_feedback };

Follow-up: With this guard in place, genuine runtime shape errors won’t be misreported as network faults by the TypeError branch below.

Also applies to: 154-159, 160-175, 177-177, 179-185

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants