Skip to content

feat: add support problem reports#147

Open
Taye-Staats wants to merge 2 commits into
devfrom
feature/POLY-100-report-a-problem
Open

feat: add support problem reports#147
Taye-Staats wants to merge 2 commits into
devfrom
feature/POLY-100-report-a-problem

Conversation

@Taye-Staats
Copy link
Copy Markdown
Collaborator

@Taye-Staats Taye-Staats commented May 24, 2026

Linked Issues

Closes: N/A
Linear: POLY-100

Summary

Adds a signed-in Report a Problem flow from Account Settings for app bugs and support issues, separate from existing content moderation reports.

Support submissions are stored in a new Convex supportReports table with authenticated user context, app/device metadata, duplicate rapid-submission blocking, and rate limiting.

How to Test

  • npm run typecheck
  • npm test -- --runInBand
  • Manual flow:
    1. npm run dev:backend
    2. npm run dev
    3. Open mobile app Settings > Support > Report a Problem.
    4. Choose a category, enter a description, submit, and verify the success state.

Checklist

  • Tests added/updated
  • Lint/tests pass locally
  • Docs updated
  • Follows conventional commit format
  • No merge conflicts with dev

Screenshots / Demos

Add screenshots of Settings support entry and Report a Problem form.

Summary by CodeRabbit

  • New Features
    • Added "Report a Problem" feature accessible from account settings. Users can now submit support reports by selecting issue categories, entering descriptions, and optionally providing contextual details about their device and app environment to help our team better understand and resolve reported problems.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a complete support report submission feature. Shared constants define report categories and numeric limits. A new Convex backend mutation validates, deduplicates, rate-limits, and persists support reports. The frontend adds a Settings entry point, dedicated report form with category selection and bounded description input, error mapping, and success feedback.

Changes

Support Report Submission Feature

Layer / File(s) Summary
Shared Constants: Categories and Limits
packages/shared/constants/support.ts, packages/shared/constants/index.ts
SupportReportCategory union type and constants define category options (bug, account_login, messages, payments_offers, etc.), description max length (1200 chars), context value max (240 chars), and rate limits (3 per 10 min, 10 per day).
Backend Schema and Mutation
backend/convex/schema.ts, backend/convex/supportReports.ts
supportReports table stores reporterId, optional reporterEmail, category, required description, optional structured context, and createdAt timestamp, with indexes by reporter+time and creation time. submitSupportReport mutation enforces authentication, validates description (non-empty, ≤1200 chars), detects duplicate submissions within 10 minutes by matching category and description, rate-limits per reporter (3 per 10 min, 10 per day), fetches and trims reporter email, and inserts the report with computed timestamp.
Backend Mutation Tests
backend/convex/__tests__/testUtils.ts, backend/convex/__tests__/supportReports.test.ts
Test configuration registers supportReports module. Suite verifies durable report storage with context fields, authentication requirement, description validation (empty/max-length), duplicate detection with expected error message, and rate-limit enforcement after three rapid submissions.
Frontend Error Mapping
frontend/lib/user-flow-errors.ts, frontend/lib/__tests__/user-flow-errors.test.ts
Extends UserFlowErrorContext with 'submit-support-report' value. getUserFlowErrorMessage maps raw backend errors (duplicate submission, rate limit reached, invalid description, session/network issues) to user-friendly messages with retry guidance. Test verifies rate-limit error mapping.
Frontend Navigation and Entry Point
frontend/app/_layout.tsx, frontend/app/account-settings.tsx
Expo Router stack adds report-problem route with title "Report a Problem", iOS formSheet + Android modal presentation, and adjustable sheet detents. Account settings adds Support section header with "Report a Problem" button that navigates to the route, passing source: '/account-settings' for context. Associated styles added for button layout, text, and chevron icon.
Frontend Report Screen Implementation
frontend/app/report-problem.tsx
Complete form screen with: selectable category chips, multiline description input with live character counter enforcing max 1200 chars, optional context collection (platform/app/os/route/listing/conversation) via useMemo, web redirect to OpenInAppPrompt, native unauthenticated redirect to sign-in, form validation and submitSupportReport mutation submission with error display, success state rendering with back navigation button, and comprehensive stylesheet covering all UI states (inputs, errors, buttons, success card).

Sequence Diagram

sequenceDiagram
    participant User as User (Native)
    participant Settings as Account Settings
    participant ReportScreen as Report Screen
    participant Mutation as submitSupportReport
    participant DB as supportReports DB
    participant ErrorHandler as Error Handler

    User->>Settings: Open account settings
    Settings->>User: Display Support section + button
    User->>ReportScreen: Tap "Report a Problem"
    ReportScreen->>ReportScreen: Build reportContext (platform, app, OS, route)
    User->>ReportScreen: Select category, enter description
    User->>ReportScreen: Tap Submit
    ReportScreen->>Mutation: Call submitSupportReport(category, description, context)
    Mutation->>Mutation: Validate authentication
    Mutation->>Mutation: Validate description (non-empty, ≤1200 chars)
    Mutation->>DB: Query recent reports (last 10 min) for duplicates
    alt Duplicate found
        Mutation-->>ErrorHandler: "You already submitted this problem recently"
    else Recent reports > 3 per 10 min
        Mutation-->>ErrorHandler: "Support report limit reached. Please try again later"
    else Daily limit exceeded
        Mutation-->>ErrorHandler: "Support report limit reached. Please try again later"
    else Validation passed
        Mutation->>DB: Insert report with reporterId, category, description, context, createdAt
        Mutation-->>ReportScreen: Return reportId
        ReportScreen->>User: Show success card + "Done" button
    end
    User->>ReportScreen: Tap Done
    ReportScreen->>Settings: Navigate back to Settings
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • SamanSP1386
  • jaydonkc

Poem

🐰 Hop-hop, a new report is born,
From settings to form, all user concerns worn,
With categories, limits, and rate-limit care,
Support flows from backend to frontend so fair!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add support problem reports' clearly and concisely summarizes the main change: adding a new support problem reporting feature.
Description check ✅ Passed The PR description covers all key required sections: linked issues (Linear: POLY-100), clear summary of the feature, comprehensive how-to-test steps with proper commands, detailed checklist with most items checked, and appropriate notes about screenshots.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/POLY-100-report-a-problem

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vercel
Copy link
Copy Markdown

vercel Bot commented May 24, 2026

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

Project Deployment Actions Updated (UTC)
poly-buys Ready Ready Preview, Comment May 24, 2026 7:38pm

Copy link
Copy Markdown
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)
backend/convex/supportReports.ts (1)

96-101: ⚡ Quick win

Consider limiting query results for rate-limit checks.

The query fetches all recent reports to count them, but you only need to know if there are at least SUPPORT_REPORTS_PER_TEN_MINUTES reports. Consider using .take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1) to avoid fetching excessive rows if a user has many reports.

⚡ Proposed optimization
     const recentReports = await ctx.db
       .query('supportReports')
       .withIndex('by_reporter_createdAt', (q) =>
         q.eq('reporterId', reporterId).gt('createdAt', recentCutoff)
       )
+      .take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1)
       .collect();

Apply the same pattern to the daily limit query at lines 115-120:

     const dailyReports = await ctx.db
       .query('supportReports')
       .withIndex('by_reporter_createdAt', (q) =>
         q.eq('reporterId', reporterId).gt('createdAt', oneDayAgo)
       )
+      .take(SUPPORT_REPORTS_PER_DAY + 1)
       .collect();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/convex/supportReports.ts` around lines 96 - 101, The rate-limit
queries fetch all matching rows; limit them to only what you need by adding
.take(...) to each query: in the recent reports query (the
ctx.db.query('supportReports').withIndex('by_reporter_createdAt', ...) that
assigns recentReports) append .take(SUPPORT_REPORTS_PER_TEN_MINUTES + 1) so you
only fetch one more than the threshold, and do the same for the daily query that
collects dailyReports by using .take(SUPPORT_REPORTS_PER_DAY + 1); this reduces
work while preserving the ability to detect "over limit".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/app/report-problem.tsx`:
- Around line 62-70: The reportContext values (built in the useMemo assigned to
reportContext) currently forward sourceRoute, listingId, and conversationId raw;
clamp/sanitize these before submission by trimming and length-limiting
sourceRoute and converting listingId/conversationId to safe strings or numbers,
and remove any keys whose value is empty/undefined prior to invoking the report
mutation; update the useMemo to produce sanitized values (e.g., trim + substring
to a safe max length) and ensure the object passed into the mutation call (the
same reportContext used around the mutation invocation) has had empty fields
deleted so long/tampered deep links or falsy ids are omitted.

---

Nitpick comments:
In `@backend/convex/supportReports.ts`:
- Around line 96-101: The rate-limit queries fetch all matching rows; limit them
to only what you need by adding .take(...) to each query: in the recent reports
query (the ctx.db.query('supportReports').withIndex('by_reporter_createdAt',
...) that assigns recentReports) append .take(SUPPORT_REPORTS_PER_TEN_MINUTES +
1) so you only fetch one more than the threshold, and do the same for the daily
query that collects dailyReports by using .take(SUPPORT_REPORTS_PER_DAY + 1);
this reduces work while preserving the ability to detect "over limit".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74e2dabf-0af8-42f1-8eb1-db779546a08b

📥 Commits

Reviewing files that changed from the base of the PR and between fcdecc9 and 74369f7.

⛔ Files ignored due to path filters (2)
  • backend/convex/_generated/api.d.ts is excluded by !**/_generated/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • backend/convex/__tests__/supportReports.test.ts
  • backend/convex/__tests__/testUtils.ts
  • backend/convex/schema.ts
  • backend/convex/supportReports.ts
  • frontend/app/_layout.tsx
  • frontend/app/account-settings.tsx
  • frontend/app/report-problem.tsx
  • frontend/lib/__tests__/user-flow-errors.test.ts
  • frontend/lib/user-flow-errors.ts
  • packages/shared/constants/index.ts
  • packages/shared/constants/support.ts

Comment on lines +62 to +70
const reportContext = useMemo(
() => ({
platform: process.env.EXPO_OS ?? Platform.OS,
appVersion: Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined,
osVersion: Device.osVersion ?? undefined,
route: sourceRoute ?? '/report-problem',
listingId,
conversationId,
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Bound and sanitize context values before submit.

source, listingId, and conversationId are forwarded directly into context. A long/tampered deep link can exceed backend context limits and fail submission unnecessarily. Clamp these values client-side and omit empty fields before calling the mutation.

Suggested patch
-import { SUPPORT_REPORT_DESCRIPTION_MAX, type SupportReportCategory } from '`@polybuys/shared`';
+import {
+  SUPPORT_REPORT_CONTEXT_VALUE_MAX,
+  SUPPORT_REPORT_DESCRIPTION_MAX,
+  type SupportReportCategory,
+} from '`@polybuys/shared`';
@@
 function firstParam(value: string | string[] | undefined) {
   return Array.isArray(value) ? value[0] : value;
 }
+
+function sanitizeContextValue(value: string | undefined) {
+  if (!value) return undefined;
+  const trimmed = value.trim();
+  if (!trimmed) return undefined;
+  return trimmed.slice(0, SUPPORT_REPORT_CONTEXT_VALUE_MAX);
+}
@@
-  const reportContext = useMemo(
-    () => ({
-      platform: process.env.EXPO_OS ?? Platform.OS,
-      appVersion: Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined,
-      osVersion: Device.osVersion ?? undefined,
-      route: sourceRoute ?? '/report-problem',
-      listingId,
-      conversationId,
-    }),
-    [conversationId, listingId, sourceRoute]
-  );
+  const reportContext = useMemo(() => {
+    const context: Record<string, string> = {};
+    const platform = sanitizeContextValue(process.env.EXPO_OS ?? Platform.OS);
+    const appVersion = sanitizeContextValue(
+      Constants.expoConfig?.version ?? Constants.nativeApplicationVersion ?? undefined
+    );
+    const osVersion = sanitizeContextValue(Device.osVersion ?? undefined);
+    const route = sanitizeContextValue(sourceRoute ?? '/report-problem');
+    const sanitizedListingId = sanitizeContextValue(listingId);
+    const sanitizedConversationId = sanitizeContextValue(conversationId);
+
+    if (platform) context.platform = platform;
+    if (appVersion) context.appVersion = appVersion;
+    if (osVersion) context.osVersion = osVersion;
+    if (route) context.route = route;
+    if (sanitizedListingId) context.listingId = sanitizedListingId;
+    if (sanitizedConversationId) context.conversationId = sanitizedConversationId;
+
+    return context;
+  }, [conversationId, listingId, sourceRoute]);

Also applies to: 95-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/app/report-problem.tsx` around lines 62 - 70, The reportContext
values (built in the useMemo assigned to reportContext) currently forward
sourceRoute, listingId, and conversationId raw; clamp/sanitize these before
submission by trimming and length-limiting sourceRoute and converting
listingId/conversationId to safe strings or numbers, and remove any keys whose
value is empty/undefined prior to invoking the report mutation; update the
useMemo to produce sanitized values (e.g., trim + substring to a safe max
length) and ensure the object passed into the mutation call (the same
reportContext used around the mutation invocation) has had empty fields deleted
so long/tampered deep links or falsy ids are omitted.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant