Skip to content

Build report modal UI component#41

Merged
jaydonkc merged 2 commits intodevfrom
feature/POLY-21-modal-UI-component
Mar 2, 2026
Merged

Build report modal UI component#41
jaydonkc merged 2 commits intodevfrom
feature/POLY-21-modal-UI-component

Conversation

@mattphanm
Copy link
Copy Markdown
Collaborator

@mattphanm mattphanm commented Feb 16, 2026

Linked Issues

Closes #21
Linear: POLY-16 (e.g., POLY-123)

Summary

Added Report Modal and implementing functionality.

How to Test

Steps to verify locally:

  • npm run lint
  • npm run typecheck
  • npm test
  • Manual flow:
    1. npm run dev:backend (in terminal A)
    2. npm run dev (in terminal B)
    3. Verify the change: <describe expected behavior/screens>

Checklist

  • Tests added/updated (if applicable)
  • Lint/tests pass locally (npm run lint)
  • Docs updated (README/ADR/changelog if needed)
  • Follows conventional commit format
  • No merge conflicts with dev

Screenshots / Demos

Screenshot 2026-02-15 at 11 09 27 PM Screenshot 2026-02-15 at 11 09 36 PM

(if UI or visible behavior - attach images, videos, or GIFs)

Summary by CodeRabbit

Release Notes

  • New Features
    • Users can now report listings by selecting from categorized reasons: scam, inappropriate, or spam
    • New report modal supports optional notes with 500-character limit for additional context

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23dae36 and b4a7d91.

📒 Files selected for processing (2)
  • frontend/app/listings/[id].tsx
  • frontend/components/ReportModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/components/ReportModal.tsx

📝 Walkthrough

Walkthrough

This change introduces a user reporting feature by creating a new ReportModal component and integrating it into the listings detail page. The modal allows non-listing-owners to report listings with predefined reasons and optional notes, submitting reports via a Convex backend mutation.

Changes

Cohort / File(s) Summary
Report Modal Component
frontend/components/ReportModal.tsx
New React Native modal component for reporting listings or profiles. Includes reason selection (Scam, Inappropriate, Spam), character-limited notes input (500 char max), form validation, and Convex API integration for report submission.
Listing Page Integration
frontend/app/listings/[id].tsx
Added useState for report modal visibility state, "Report listing" button for non-owners, ReportModal component instantiation, and button styling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A rabbit reports with style so bright,
With reasons and notes, everything's right,
"Scam!" "Spam!" the buttons all gleam,
Modal pops open like part of a dream,
Convex receives our complaint with cheer! 📬

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR closes issue #21 but implements only the report modal UI component. Issue #21 requires full authentication implementation with Cal Poly email verification, user tables, backend auth setup, and frontend auth screens—none of which are present in this PR. Either split this into a separate UI-only PR (closing a new issue) or complete the authentication backend requirements from issue #21 in this PR.
Out of Scope Changes check ⚠️ Warning The PR changes (ReportModal component and report button integration) are unrelated to the authentication objectives in issue #21. These represent a different feature entirely. Create a separate issue for the report modal feature and link this PR to that instead, or remove the issue #21 reference if this PR should focus on reporting only.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Build report modal UI component' directly describes the main changes—adding a Report Modal component and its UI functionality.
Description check ✅ Passed The description provides linked issues, a summary, test instructions following the template, a checklist, and screenshots. However, the manual testing section placeholder '<describe expected behavior/screens>' was left unfilled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/POLY-21-modal-UI-component

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.

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: 5

Caution

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

⚠️ Outside diff range comments (1)
backend/convex/listings.ts (1)

19-35: ⚠️ Potential issue | 🔴 Critical

Critical: Inverted ownership check breaks updateListing, updateListingStatus, and deleteListing.

verifyOwnership is called by updateListing (Line 269), updateListingStatus (Line 341), and deleteListing (Line 351) — all of which require the caller to be the owner. The inverted condition (sellerId === userId → throw) now allows any authenticated user to update or delete anyone's listing, while blocking the actual owner from modifying their own listing.

You need two separate checks: one that enforces ownership (for CRUD operations) and one that enforces non-ownership (for reporting).

🐛 Proposed fix: separate ownership and non-ownership checks
 async function verifyOwnership(
   ctx: MutationCtx,
   listingId: Id<'listings'>
 ): Promise<Doc<'listings'>> {
   const userId = await getAuthUserId(ctx);
   if (userId === null) {
     throw new Error('You must be logged in to perform this action');
   }
   const listing = await ctx.db.get(listingId);
   if (!listing) {
     throw new Error('Listing not found');
   }
-  if (listing.sellerId === userId) {
-    throw new Error('Cannot Report Own Listing');
+  if (listing.sellerId !== userId) {
+    throw new Error('You do not have permission to modify this listing');
   }
   return listing;
 }
+
+async function verifyNotOwner(
+  ctx: MutationCtx,
+  listingId: Id<'listings'>
+): Promise<Doc<'listings'>> {
+  const userId = await getAuthUserId(ctx);
+  if (userId === null) {
+    throw new Error('You must be logged in to perform this action');
+  }
+  const listing = await ctx.db.get(listingId);
+  if (!listing) {
+    throw new Error('Listing not found');
+  }
+  if (listing.sellerId === userId) {
+    throw new Error('Cannot report your own listing');
+  }
+  return listing;
+}

Then use verifyNotOwner in the future report mutation, and keep verifyOwnership for updateListing, updateListingStatus, and deleteListing.

🤖 Fix all issues with AI agents
In `@frontend/app/listings/`[id].tsx:
- Line 37: The report menu is shown to unauthenticated users because
isOwnListing is false when currentUser is null; update the rendering and handler
to guard against unauthenticated access: either hide/skip rendering the report
menu entirely when currentUser is null (check currentUser before rendering the
menu component that shows "Report listing") or in the report flow (e.g., the
report button click handler / submit handler) add an explicit check for
currentUser and redirect to login or show a login prompt if missing; reference
the isOwnListing variable, currentUser, listing.sellerId and the report
modal/handler to locate where to add the guard.
- Around line 57-70: Summary: The menu renders an empty View for owners
(isOwnListing true), producing a confusing empty menu. Fix: update the menu
trigger and menu rendering so the "..." button and menuOpen UI only appear when
there are actual actions; either (A) hide the menu trigger when isOwnListing is
true and no owner actions exist by wrapping the trigger render with a condition
(e.g., render the Pressable that toggles menuOpen only when !isOwnListing or
actions.length > 0), or (B) add owner-specific actions inside the menu (e.g.,
include "Edit" and "Delete" entries that call appropriate handlers such as
setEditOpen or a deleteListing handler) so the View is never empty. Locate and
change the elements referencing isOwnListing, menuOpen, setMenuOpen,
setReportOpen and the Pressable that opens the menu to implement one of these
two options.
- Line 20: Remove the leftover debug placeholder comment "// HELP" in the
listings page file; locate the stray comment and delete it so the component /
page source (the file containing the listings [id] page) no longer contains that
unnecessary comment.

In `@frontend/components/ReportModal.tsx`:
- Around line 35-42: handleSubmit currently only awaits a 500ms timeout and
console.logs the report, so no backend call is made and the catch block with
rate-limit detection (and any error handling) is dead; either replace the
simulated sleep with a real API/mutation call (e.g., call the report creation
function or GraphQL mutation used elsewhere and await its promise, pass payload
{ targetId: props.targetId, targetType: props.targetType, reason, notes }, and
preserve the existing catch to handle rate-limit errors) or, if this is
intentionally stubbed, annotate handleSubmit with a clear TODO comment
indicating the missing backend call and why (so reviewers know it’s intentional)
and keep the simulated timeout only for UI feedback; reference handleSubmit, the
500ms Promise, and the existing catch/rate-limit logic when making the change.
- Around line 22-61: The modal preserves notes, reason, and error across
openings; add a local reset function (e.g., resetState) that calls setNotes(''),
setReason(null), setError(null) (and setSubmitting(false) if desired) and invoke
it after a successful submit in handleSubmit and whenever the modal is closed by
the consumer by wrapping props.onClose with a small onClose handler that calls
resetState then props.onClose (or call resetState inside any existing onClose
callback), referencing ReportModal, handleSubmit, notes, reason, error,
setNotes, setReason, and setError to locate the code to change.
🧹 Nitpick comments (3)
frontend/components/ReportModal.tsx (2)

72-73: Inner Pressable with empty handler is a fragile event-swallowing pattern.

onPress={() => {}} prevents the outer backdrop Pressable from closing the modal when the content area is tapped. This works but is non-obvious and fragile. A View with pointerEvents or explicit stopPropagation in onPress would communicate the intent more clearly. Also, the content area lacks a wrapping View with styling for the card/dialog appearance (background, border-radius, elevation), which is needed for a modal dialog to look distinct from the backdrop.


64-106: Accessibility: radio buttons and form elements lack semantic roles and labels.

The reason options are rendered as plain Pressable + Text with unicode radio glyphs (/). Screen readers won't identify these as selectable radio buttons. Consider adding accessibilityRole="radio", accessibilityState={{ checked: reason === r.label }}, and grouping them with accessibilityRole="radiogroup". Similarly, the Submit and Cancel buttons should have accessibilityRole="button".

package.json (1)

61-64: Move react-native-toast-message to the frontend workspace.

react-native-toast-message is only imported and used in frontend/components/ReportModal.tsx, so it should be in frontend/package.json rather than the root. While the dependency currently works at the root level, monorepo best practices place dependencies in the workspace that consumes them. This improves clarity of dependencies and maintains proper workspace boundaries.

Comment thread frontend/app/listings/[id].tsx Outdated
Comment thread frontend/app/listings/[id].tsx Outdated
Comment thread frontend/app/listings/[id].tsx Outdated
Comment thread frontend/components/ReportModal.tsx Outdated
Comment thread frontend/components/ReportModal.tsx Outdated
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: 2

🤖 Fix all issues with AI agents
In `@frontend/components/ReportModal.tsx`:
- Around line 56-62: In ReportModal.tsx the code lowercases msg into the
variable lower but then checks isUnauth using an uppercase literal ("You must be
logged in to perform this action"), so that branch can never match; update the
check in the isUnauth computation to compare lowercase text consistently (e.g.,
use lower.includes('you must be logged in to perform this action') or call
.toLowerCase() on the literal) and ensure the other includes checks still
operate on the lower variable (isUnauth and isRateLimit should all use
lower.includes(...)).
- Around line 82-84: Backdrop taps and Android back button currently call
props.onClose directly (via onRequestClose and the outer Pressable onPress) and
bypass resetState, leaving stale notes/reason/error; extract a shared close
handler (e.g., handleClose) inside the ReportModal component that calls
resetState() then props.onClose(), and replace both
onRequestClose={props.onClose} and the Pressable onPress={props.onClose} with
onRequestClose={handleClose} and onPress={handleClose} so all dismissal paths
mirror the Cancel button behavior.
🧹 Nitpick comments (2)
frontend/components/ReportModal.tsx (2)

126-131: Modal overlay has no dimming — appears as a plain white page.

The transparent prop on <Modal> makes the underlying view transparent, but the container style uses opaque #fff with no semi-transparent backdrop. This means users won't perceive it as a modal overlay. A typical pattern is a semi-transparent dark backdrop behind the content card.

♻️ Sketch: add an overlay + content card split
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: '#fff',
-    padding: 20,
+    backgroundColor: 'rgba(0,0,0,0.5)',
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  card: {
+    backgroundColor: '#fff',
+    borderRadius: 12,
+    padding: 20,
+    width: '90%',
   },
 });

Then wrap the inner content Pressable with style={styles.card}.


86-93: Radio options and buttons lack accessibility props.

The reason Pressable elements and action buttons have no accessibilityRole or accessibilityLabel. Screen-reader users won't be able to distinguish these as radio buttons or understand their purpose.

♻️ Example for reason items
 <Pressable
   key={r.label}
   onPress={() => setReason(r.label)}
+  accessibilityRole="radio"
+  accessibilityState={{ checked: reason === r.label }}
+  accessibilityLabel={`${r.label}: ${r.desc}`}
 >

Similarly, add accessibilityRole="button" to the Submit and Cancel Pressable elements.

Comment thread frontend/components/ReportModal.tsx Outdated
Comment thread frontend/components/ReportModal.tsx Outdated
@jaydonkc
Copy link
Copy Markdown
Collaborator

jaydonkc commented Mar 2, 2026

Created clean companion branch jaydon/clean-pr-41.

Feedback:

  • Modal UI should remain presentation-focused and avoid backend coupling in same PR.
  • Keep accessibility states (focus/keyboard, labels) in scope for web/mobile parity.
  • Prefer minimal, testable UI deltas.

@jaydonkc jaydonkc force-pushed the feature/POLY-21-modal-UI-component branch from 23dae36 to 165039b Compare March 2, 2026 21:50
@jaydonkc jaydonkc merged commit b0822f4 into dev Mar 2, 2026
3 checks passed
This was referenced Mar 11, 2026
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.

2 participants