Skip to content

feat: implement messaging block checks and notification preferences#62

Merged
evan-taylor merged 5 commits intodevfrom
evan/testflight-feedback
Apr 9, 2026
Merged

feat: implement messaging block checks and notification preferences#62
evan-taylor merged 5 commits intodevfrom
evan/testflight-feedback

Conversation

@evan-taylor
Copy link
Copy Markdown
Collaborator

@evan-taylor evan-taylor commented Mar 11, 2026

  • Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
  • Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
  • Updated the user schema to include a field for message notification settings.
  • Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
  • Added tests to ensure proper functionality of blocking and notification features.

Linked Issues

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

Summary

Briefly explain the change and why.

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

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Block and unblock users to prevent unwanted messages
    • Manage message notification preferences from settings
    • Delete your account and all associated data
    • Report inappropriate users and listings
  • Improvements

    • Streamlined conversation creation interface
    • Push notification integration during sign-up
    • Enhanced search with auto-focus
    • Better price formatting across the app
    • Updated settings with account management options

- Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
- Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
- Updated the user schema to include a field for message notification settings.
- Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
- Added tests to ensure proper functionality of blocking and notification features.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd2d0ee2-fa02-4676-8915-8f8562fbcac9

📥 Commits

Reviewing files that changed from the base of the PR and between 08a6f2e and e9df4c2.

📒 Files selected for processing (1)
  • frontend/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/package.json

📝 Walkthrough

Walkthrough

This PR implements user blocking, enhances push notifications with sender details and user preferences, adds comprehensive account deletion, refactors conversation creation with a dedicated flow, and applies consistent price formatting and modal presentation updates across the frontend.

Changes

Cohort / File(s) Summary
User Blocking System
backend/convex/blocks.ts, backend/convex/schema.ts
New userBlocks table with indices; blockUser, unblockUser mutations; isBlocking, isBlockedBy queries; and internal hasBlockBetween helper for checking blocks in either direction.
Messaging & Block Integration
backend/convex/messages.ts, backend/convex/__tests__/messages.test.ts
Added block checks to sendMessage flow; new createConversationAndSendFirstMessage action and internalGetOrCreateConversation helper; updated listUserConversations to filter out conversations without lastMessageId; expanded tests for blocking scenarios.
Test Utilities
backend/convex/__tests__/testUtils.ts
Renamed createTestConversation to createTestConversationEmpty; introduced new createTestConversation that creates a conversation with one initial message; wired blocks module for testing.
Push Notifications Infrastructure
backend/convex/pushNotifications.ts, frontend/hooks/usePushNotifications.ts
Enhanced sendNewMessageNotification with sender name, message preview, and messageNotificationsEnabled guard; added displayNameFromProfileAndUser helper; refactored token retrieval into getTokenIfPermissionGranted; new requestPermissionAndSyncToken public function.
User Management & Preferences
backend/convex/users.ts, backend/convex/schema.ts
Added messageNotificationsEnabled boolean field to users table; new getMessageNotificationsEnabled query, updateMessageNotificationsEnabled mutation; comprehensive deleteAccount mutation with cascading cleanup of user data, storage assets, and auth records.
Conversation Creation UI
frontend/app/conversations/new.tsx, frontend/app/listings/[id].tsx
New screen to create conversations about listings; removed getOrCreateConversation usage from listings detail in favor of direct navigation to new conversation screen with listingId parameter.
Conversation Blocking UI
frontend/app/conversations/[id].tsx
Added blockUser, unblockUser mutations and isBlocking, isBlockedBy query usage; conditional message input rendering based on blocking state; block/unblock button in header; prevented message sending when blocked.
Login & Settings Push Flow
frontend/app/auth/login.tsx, frontend/app/(tabs)/settings.tsx
Added 'push' step in login flow with enable/skip actions; new recordPushToken mutation in login; settings screen now includes message notification toggle, permission requests, token synchronization, and delete account flow with confirmation dialog.
Price Formatting
frontend/lib/formatPrice.ts, frontend/app/listings/...*, frontend/components/...
New formatPrice utility returning fixed two-decimal format; applied consistently across listing detail, listing card, filter bar, and form fields for display and validation.
Modal Presentation Updates
frontend/components/CategoryPicker.tsx, frontend/components/PriceRangePicker.tsx, frontend/components/TagPicker.tsx, frontend/components/MyListingActionsSheet.tsx
Added presentationStyle="overFullScreen" and statusBarTranslucent to modal components; changed overlay backgrounds from semi-transparent to transparent, removing visual dimming effects.
Listing & Profile UI
frontend/app/listings/[id]/edit.tsx, frontend/app/listings/new.tsx, frontend/app/(tabs)/my-listings.tsx, frontend/app/(tabs)/index.tsx, frontend/app/(tabs)/search.tsx, frontend/app/profile/[userId].tsx
Price input redesigned with dollar prefix and character sanitization; Create Listing chip conditionally rendered; search input auto-focus on mount; search terms added to recent searches on submit; added profile reporting UI; adjusted padding calculations.
Report Modal & Component
frontend/components/ReportModal.tsx, frontend/components/ListingCard.tsx, frontend/components/FilterBar.tsx
Dynamic title based on targetType (profile vs. listing); integrated theme colors for backdrop; applied formatPrice throughout components for consistent price display.
Dependencies
frontend/package.json, backend/convex/schema.ts
Updated Expo packages to patch versions (dev-client, image-manipulator, image-picker, notifications, router); updated CLI commands to use expo run:android/ios.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client/Mobile
    participant API as Backend API
    participant DB as Database

    rect rgba(100, 200, 150, 0.5)
    note over Client,DB: Block Check & Message Creation Flow
    Client->>API: sendMessage(senderId, recipientId, body)
    API->>DB: internalHasBlockBetween(senderId, recipientId)
    DB-->>API: hasBlock? boolean
    
    alt Blocked
        API-->>Client: ConvexError - Cannot send
    else Not Blocked
        API->>API: Moderate content
        API->>DB: Insert message
        API->>DB: Update conversation.lastMessageId
        API->>API: Enqueue push notification
        API-->>Client: Message sent
    end
    end

    rect rgba(100, 150, 200, 0.5)
    note over Client,DB: Conversation Creation with First Message
    Client->>API: createConversationAndSendFirstMessage(listingId, body)
    API->>DB: internalGetOrCreateConversation(listingId, buyerId, sellerId)
    DB-->>API: conversationId
    API->>DB: hasBlockBetween(buyerId, sellerId)
    DB-->>API: hasBlock? boolean
    
    alt Blocked
        API-->>Client: ConvexError - Blocked
    else Not Blocked
        API->>API: Moderate message body
        API->>DB: Insert message
        API->>DB: Patch lastMessageId
        API->>API: sendNewMessageNotification with senderName
        API-->>Client: { conversationId, messageId }
    end
    end
Loading
sequenceDiagram
    participant User as User/App
    participant Login as Login Screen
    participant Permissions as OS Permissions
    participant API as Backend API
    participant PushService as Push Service

    rect rgba(150, 150, 200, 0.5)
    note over User,PushService: Push Notification Onboarding Flow
    User->>Login: Complete profile creation
    Login->>Login: Transition to 'push' step
    User->>Login: Tap "Enable Push Notifications"
    Login->>Permissions: requestPermissionAndSyncToken()
    Permissions-->>Login: Permission granted/denied
    
    alt Permission Granted
        Login->>PushService: getTokenIfPermissionGranted()
        PushService-->>Login: pushToken
        Login->>API: recordPushToken({ token })
        API-->>Login: Token stored
        Login->>Login: Navigate to home
    else Permission Denied
        User->>Login: Tap "Skip"
        Login->>Login: Navigate to home
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

  • PR #26 — Modifies backend/convex/messages.ts conversation creation and send-message flows with related test helpers, directly overlapping with this PR's messaging changes.
  • PR #54 — Modifies the entire push-notifications stack (backend pushNotifications.ts, frontend hooks, Convex configs) with overlapping notification and token management logic.
  • PR #40 — Modifies backend/convex/messages.ts and test utilities for message flows and content checks, intersecting with this PR's messaging infrastructure updates.

Suggested reviewers

  • jaydonkc

🐰 Blocks and buttons, notifications bright,
Conversations flowing, messages take flight,
Delete with confidence, preferences align,
A messaging platform refined and divine!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers core features but lacks critical details like linked issues, summary reasoning, specific testing instructions, completed checklist items, and screenshots. Fill in the linked issues section (or explicitly state none), expand the summary with design rationale, provide step-by-step testing instructions, mark completed checklist items, and add screenshots of new UI features (push notification prompts, block UI).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title directly reflects the main changes: implementing messaging block checks and notification preferences management across the codebase.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch evan/testflight-feedback

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

🧹 Nitpick comments (3)
frontend/app/profile/[userId].tsx (1)

224-236: Increase Report button touch target for better accessibility.

Line 224–236 styles the CTA like inline text; consider enforcing a 44px-min tap area to improve usability on small screens.

♿ Suggested style tweak
   reportButton: {
     alignSelf: 'flex-start',
+    minHeight: 44,
     paddingVertical: spacing.sm,
-    paddingHorizontal: 0,
+    paddingHorizontal: spacing.sm,
+    justifyContent: 'center',
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/profile/`[userId].tsx around lines 224 - 236, The report CTA
styles currently render like inline text and lack a large enough tap target;
update the style object for reportButton (and, if needed, reportButtonPressed)
to enforce a minimum touch area of 44px by adding minHeight: 44 and minWidth: 44
plus justifyContent: 'center' and alignItems: 'center' to keep the visual
centered, and preserve existing paddingVertical/paddingHorizontal; also ensure
the touchable component that uses reportButton applies hitSlop or the updated
style so the larger hit area is actually clickable.
frontend/app/auth/login.tsx (1)

287-304: Consider extracting the redirect logic to reduce duplication.

Both handlePushEnable and handlePushSkip contain identical redirect logic (setting step to 'success' and scheduling the router.replace). This is minor duplication that could be extracted into a shared helper.

♻️ Optional refactor to reduce duplication
+  const proceedToSuccess = () => {
+    setStep('success');
+    setTimeout(() => {
+      router.replace(postAuthRedirect);
+    }, 1500);
+  };
+
   const handlePushEnable = async () => {
     try {
       await requestPermissionAndSyncToken(recordPushToken);
     } catch {
       // Permission denied - continue anyway
     }
-    setStep('success');
-    setTimeout(() => {
-      router.replace(postAuthRedirect);
-    }, 1500);
+    proceedToSuccess();
   };

   const handlePushSkip = () => {
-    setStep('success');
-    setTimeout(() => {
-      router.replace(postAuthRedirect);
-    }, 1500);
+    proceedToSuccess();
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 287 - 304, Extract the shared "set
step to 'success' and schedule router.replace(postAuthRedirect)" logic from
handlePushEnable and handlePushSkip into a small helper (e.g.,
finishAndRedirect) and call that helper from both functions; update
handlePushEnable to only await requestPermissionAndSyncToken(recordPushToken)
inside try/catch and then call finishAndRedirect, and change handlePushSkip to
simply call finishAndRedirect so the duplicate setStep and setTimeout logic is
centralized.
frontend/app/conversations/[id].tsx (1)

377-410: Implementation is clean, but see earlier comment about bidirectional blocking.

The conditional rendering pattern is well-structured. The blocked state message clearly guides the user to unblock. However, this only handles the case where the current user initiated the block—see the earlier comment regarding the scenario where the other user blocked the current user.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/conversations/`[id].tsx around lines 377 - 410, The current JSX
only checks isBlockingOther and omits the scenario where the other party has
blocked the current user; add a second branch using a distinct flag (e.g.,
isBlockedByOther) so the UI shows an appropriate readonly message when the other
user blocked you (rather than suggesting "Tap Unblock"), and ensure the
TextInput, Pressable (onSend), and send button are not rendered or enabled in
that case; update the conditional around TextInput, messageBody, onSend,
Pressable, and styles.blockedComposer/blockedText to handle both isBlockingOther
(show unblock guidance) and isBlockedByOther (show informative blocked-by-other
message and disable sending).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/convex/__tests__/testUtils.ts`:
- Around line 134-137: Update the docstring above the test helper to remove the
stale reference to createTestConversationWithMessage and mention the correct
exported helper name createTestConversation; specifically change the second
sentence to direct users to use createTestConversation for listUserConversations
tests and ensure the comment references the function name
createTestConversation.

In `@backend/convex/blocks.ts`:
- Around line 31-55: In blockUser, validate that args.blockedId corresponds to
an existing user before inserting into userBlocks: after obtaining blockerId
(and after the self-block check) fetch the target user record (e.g.,
ctx.db.get('users', args.blockedId) or a query on the users table) and if it
does not exist throw a ConvexError like 'Target user not found'; only then
proceed to check existing userBlocks and insert the new block row so userBlocks
and hasBlockBetween remain trustworthy and no orphan rows are created.

In `@frontend/app/`(tabs)/settings.tsx:
- Around line 133-139: The current combined try/catch conflates
deleteAccount({}) and signOut() failures; change it so deleteAccount and signOut
are handled in separate try/catch blocks: first call deleteAccount({}) in its
own try and if it throws show the existing Alert.alert('Delete Account Failed',
...) and return early; only if deletion succeeds proceed to call signOut() in a
second try and handle signOut errors separately (e.g., log or show a
non-critical message like "Signed out failed; please sign out manually") so a
signOut failure does not report the deletion as failed; update the block
referencing deleteAccount and signOut accordingly.
- Around line 154-169: The code currently calls
updateMessageNotificationsEnabled({ enabled: value }) before attempting to
request permissions and sync the push token, causing enabled:true to be
persisted even if permission/token sync fails; change the flow so that when
enabling you first call requestPermissionAndSyncToken(recordPushToken) and only
if that resolves successfully call updateMessageNotificationsEnabled({ enabled:
true }), while keeping the existing disable path (removePushToken then
updateMessageNotificationsEnabled({ enabled: false })) and handling/rethrowing
or swallowing errors consistently around requestPermissionAndSyncToken and
removePushToken.

---

Nitpick comments:
In `@frontend/app/auth/login.tsx`:
- Around line 287-304: Extract the shared "set step to 'success' and schedule
router.replace(postAuthRedirect)" logic from handlePushEnable and handlePushSkip
into a small helper (e.g., finishAndRedirect) and call that helper from both
functions; update handlePushEnable to only await
requestPermissionAndSyncToken(recordPushToken) inside try/catch and then call
finishAndRedirect, and change handlePushSkip to simply call finishAndRedirect so
the duplicate setStep and setTimeout logic is centralized.

In `@frontend/app/conversations/`[id].tsx:
- Around line 377-410: The current JSX only checks isBlockingOther and omits the
scenario where the other party has blocked the current user; add a second branch
using a distinct flag (e.g., isBlockedByOther) so the UI shows an appropriate
readonly message when the other user blocked you (rather than suggesting "Tap
Unblock"), and ensure the TextInput, Pressable (onSend), and send button are not
rendered or enabled in that case; update the conditional around TextInput,
messageBody, onSend, Pressable, and styles.blockedComposer/blockedText to handle
both isBlockingOther (show unblock guidance) and isBlockedByOther (show
informative blocked-by-other message and disable sending).

In `@frontend/app/profile/`[userId].tsx:
- Around line 224-236: The report CTA styles currently render like inline text
and lack a large enough tap target; update the style object for reportButton
(and, if needed, reportButtonPressed) to enforce a minimum touch area of 44px by
adding minHeight: 44 and minWidth: 44 plus justifyContent: 'center' and
alignItems: 'center' to keep the visual centered, and preserve existing
paddingVertical/paddingHorizontal; also ensure the touchable component that uses
reportButton applies hitSlop or the updated style so the larger hit area is
actually clickable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c5eadf81-7536-4ad7-b1b4-5b83fcdd85e6

📥 Commits

Reviewing files that changed from the base of the PR and between ef8e00f and 822f10e.

⛔ Files ignored due to path filters (1)
  • backend/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (27)
  • backend/convex/__tests__/messages.test.ts
  • backend/convex/__tests__/testUtils.ts
  • backend/convex/blocks.ts
  • backend/convex/messages.ts
  • backend/convex/pushNotifications.ts
  • backend/convex/schema.ts
  • backend/convex/users.ts
  • frontend/app/(tabs)/index.tsx
  • frontend/app/(tabs)/my-listings.tsx
  • frontend/app/(tabs)/search.tsx
  • frontend/app/(tabs)/settings.tsx
  • frontend/app/auth/login.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/app/conversations/new.tsx
  • frontend/app/listings/[id].tsx
  • frontend/app/listings/[id]/edit.tsx
  • frontend/app/listings/new.tsx
  • frontend/app/profile/[userId].tsx
  • frontend/components/CategoryPicker.tsx
  • frontend/components/FilterBar.tsx
  • frontend/components/ListingCard.tsx
  • frontend/components/MyListingActionsSheet.tsx
  • frontend/components/PriceRangePicker.tsx
  • frontend/components/ReportModal.tsx
  • frontend/components/TagPicker.tsx
  • frontend/hooks/usePushNotifications.ts
  • frontend/lib/formatPrice.ts

Comment thread backend/convex/__tests__/testUtils.ts
Comment thread backend/convex/blocks.ts
Comment thread frontend/app/(tabs)/settings.tsx
Comment thread frontend/app/(tabs)/settings.tsx
- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.
- Implemented a new query to check if the current user is blocked by another user, improving user experience in conversations.
- Updated the blockUser mutation to ensure proper user ID normalization and error handling for blocking actions.
- Enhanced the Settings screen to provide better feedback during account deletion and sign-out processes.
- Refactored notification handling to ensure users are informed about permission status and potential issues when enabling notifications.
- Improved conversation detail screen to handle blocking states more effectively, preventing users from sending messages if blocked.
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

🧹 Nitpick comments (1)
backend/convex/blocks.ts (1)

74-90: Normalize block-query inputs the same way across this API.

blockUser now canonicalizes and validates the peer ID, but unblockUser, isBlocking, and isBlockedBy still read raw strings directly. That inconsistency is easy to drift on and makes malformed IDs behave differently depending on which endpoint the client calls. A small shared helper for user-ID normalization/lookup would keep these handlers aligned.

Also applies to: 97-125

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/blocks.ts` around lines 74 - 90, The handlers unblockUser,
isBlocking, and isBlockedBy are reading raw peer IDs while blockUser
canonicalizes/validates them; add a small shared helper (e.g.,
normalizePeerUserId or getValidatedPeerId) that canonicalizes and validates
args.blockedId and returns the normalized ID or throws, then call that helper at
the start of unblockUser, isBlocking, and isBlockedBy and use the returned
normalized ID in the ctx.db queries (the same way blockUser does) so all
endpoints use identical ID normalization/lookup logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`(tabs)/settings.tsx:
- Around line 187-200: When disabling notifications, don't unconditionally
revert the toggle on error; call removePushToken({}) first and record whether it
succeeded, then call updateMessageNotificationsEnabled({ enabled: false }) in a
separate try/catch. Use flags (from removePushToken success) to decide whether
to call setMessageNotificationsValue(previousValue) on failure: if
removePushToken succeeded but updateMessageNotificationsEnabled fails, keep the
switch off and show a partial-failure Alert.alert message; if removePushToken
failed and updateMessageNotificationsEnabled fails, revert the toggle with
setMessageNotificationsValue(previousValue) and show the full-failure alert.
Reference functions: removePushToken, updateMessageNotificationsEnabled,
setMessageNotificationsValue, and Alert.alert.

In `@frontend/app/auth/login.tsx`:
- Around line 207-208: Don't hardcode '2026' as the fallback when computing
yearInput/parsedYear; instead derive a safe fallback from the current date and
the profile bounds. Replace the line using year.trim() || '2026' with logic that
computes const currentYear = new Date().getFullYear() and uses
Math.min(Math.max(currentYear, PROFILE_BOUNDS.MIN_YEAR),
PROFILE_BOUNDS.MAX_YEAR) (or make the field required) so yearInput/parsedYear
always falls within PROFILE_BOUNDS.MIN_YEAR/PROFILE_BOUNDS.MAX_YEAR; update
references to yearInput and parsedYear accordingly.
- Around line 294-305: The handlers never persist the user's opt-in choice;
update handlePushEnable and handlePushSkip to set and persist
messageNotificationsEnabled (true for handlePushEnable, false for
handlePushSkip) before calling finishAndRedirect. In handlePushEnable, after
requestPermissionAndSyncToken(recordPushToken) succeeds (or if permission was
granted), await the existing persistence method (e.g., call the app's
user-settings updater or saveUserPreferences / updateUserSettings function) to
store messageNotificationsEnabled = true, handle/propagate any persistence
errors, then call finishAndRedirect; in handlePushSkip set and persist
messageNotificationsEnabled = false (await the same persistence API) and only
then call finishAndRedirect so the user's choice is saved. Ensure both handlers
reference messageNotificationsEnabled and reuse existing persistence helpers
rather than introducing new storage mechanisms.

In `@frontend/app/conversations/`[id].tsx:
- Around line 80-88: The composer is rendered before block status resolves
because isBlockingOther and isBlockedByOther can be undefined; update the
rendering logic to treat unresolved queries as loading: use the query result
objects (isBlockingOther, isBlockedByOther) rather than their .data shortcut and
gate the editable composer (the branch that enables Send) until both queries
have settled (e.g., check isLoading/isFetching or that .data is not undefined),
or include these queries in the screen-level loading gate so the editable input
is only shown when otherUserId, isBlockingOther.data, and isBlockedByOther.data
are known; update the conditional around the composer render accordingly to
reference otherUserId, isBlockingOther, and isBlockedByOther.

In `@frontend/app/profile/`[userId].tsx:
- Around line 34-35: Destructure user from useAuth() (in addition to
isAuthenticated) and use user.id to prevent self-reporting: update the Report
button / reportOpen logic in the component that currently uses resolvedUserId,
reportOpen, and setReportOpen so it is only shown/activatable when
resolvedUserId exists and does not equal user.id; remove the redundant
resolvedUserId truthiness guard at the later check (around resolvedUserId on
line 123) and consolidate the early-return/visibility logic to a single check
that hides or disables the Report UI when viewing your own profile.

---

Nitpick comments:
In `@backend/convex/blocks.ts`:
- Around line 74-90: The handlers unblockUser, isBlocking, and isBlockedBy are
reading raw peer IDs while blockUser canonicalizes/validates them; add a small
shared helper (e.g., normalizePeerUserId or getValidatedPeerId) that
canonicalizes and validates args.blockedId and returns the normalized ID or
throws, then call that helper at the start of unblockUser, isBlocking, and
isBlockedBy and use the returned normalized ID in the ctx.db queries (the same
way blockUser does) so all endpoints use identical ID normalization/lookup
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7d8daf7d-4f4a-47c3-8fd6-ce976aa30749

📥 Commits

Reviewing files that changed from the base of the PR and between 71d60b4 and 194f14c.

📒 Files selected for processing (6)
  • backend/convex/__tests__/testUtils.ts
  • backend/convex/blocks.ts
  • frontend/app/(tabs)/settings.tsx
  • frontend/app/auth/login.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/app/profile/[userId].tsx

Comment on lines +187 to +200
} else {
try {
await removePushToken({});
} catch {
// Best-effort token removal
}

await updateMessageNotificationsEnabled({ enabled: false });
}
} catch (error) {
setMessageNotificationsValue(previousValue);
const message =
error instanceof Error ? error.message : 'Failed to update notification preference';
Alert.alert('Notification Update Failed', message);
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 | 🟠 Major

Don't roll the switch back on after a partial disable.

If removePushToken({}) succeeds and updateMessageNotificationsEnabled({ enabled: false }) throws, Line 197 flips the toggle back to true even though this device's token is already gone. Split the disable-path error handling so a successful token removal keeps the switch off and reports a partial failure instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/settings.tsx around lines 187 - 200, When disabling
notifications, don't unconditionally revert the toggle on error; call
removePushToken({}) first and record whether it succeeded, then call
updateMessageNotificationsEnabled({ enabled: false }) in a separate try/catch.
Use flags (from removePushToken success) to decide whether to call
setMessageNotificationsValue(previousValue) on failure: if removePushToken
succeeded but updateMessageNotificationsEnabled fails, keep the switch off and
show a partial-failure Alert.alert message; if removePushToken failed and
updateMessageNotificationsEnabled fails, revert the toggle with
setMessageNotificationsValue(previousValue) and show the full-failure alert.
Reference functions: removePushToken, updateMessageNotificationsEnabled,
setMessageNotificationsValue, and Alert.alert.

Comment thread frontend/app/auth/login.tsx Outdated
Comment thread frontend/app/auth/login.tsx
Comment thread frontend/app/conversations/[id].tsx
Comment thread frontend/app/profile/[userId].tsx Outdated
- Introduced a new function to validate peer user IDs in blocking mutations, enhancing error handling for user blocking actions.
- Updated blockUser and unblockUser mutations to utilize the new validation function, ensuring proper user ID normalization.
- Enhanced Settings screen to provide detailed alerts for notification preference updates, improving user feedback during push token management.
- Improved conversation detail screen to handle blocking states more effectively, preventing message sending if users are blocked.
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: 3

♻️ Duplicate comments (1)
frontend/app/profile/[userId].tsx (1)

61-62: ⚠️ Potential issue | 🟡 Minor

Self-report guard still compares the wrong identifier.

resolvedUserId is the userId route/query key, but Line 62 compares it to user?._id. If those are different ID types, the Report button still shows on your own profile. Compare against the authenticated user's userId field instead so the self-report check actually closes the gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/profile/`[userId].tsx around lines 61 - 62, The self-report
guard in the canReportProfile expression is comparing resolvedUserId to
user?._id, which mixes route IDs with database IDs and can allow showing the
Report button on your own profile; change the check to compare resolvedUserId
against the authenticated user's userId field instead (i.e., update the
canReportProfile assignment that uses isAuthenticated and resolvedUserId so it
checks resolvedUserId !== <authenticatedUser>.userId rather than user?._id).
🧹 Nitpick comments (2)
frontend/app/(tabs)/settings.tsx (2)

309-317: Consider removing commented-out code or tracking re-enablement.

The "Share Profile" button is commented out with a "Temporarily hidden" note but no tracking reference. If this feature will be re-enabled soon, consider linking to an issue. Otherwise, remove the dead code—version control preserves the history if needed later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/settings.tsx around lines 309 - 317, The commented-out
"Share Profile" Pressable block (containing Pressable, handleShareProfile,
styles.shareButton, styles.buttonPressed, and styles.shareButtonText) should be
either removed or annotated with a TODO referencing an issue/ticket ID; if this
feature will be re-enabled soon, replace the comment with a one-line TODO that
includes the issue/link and estimated re-enable date, otherwise delete the
entire commented JSX to remove dead code while leaving source control history
intact.

2-14: Consolidate duplicate import from react-native.

Switch is imported separately on line 14, but there's already an import block from react-native on lines 2-12. Merge them into a single import statement.

Suggested fix
 import {
   Alert,
   Animated,
   Image,
   Pressable,
   ScrollView,
   StyleSheet,
+  Switch,
   Text,
   View,
   useWindowDimensions,
 } from 'react-native';
-import { Switch } from 'react-native';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/settings.tsx around lines 2 - 14, The file has a
duplicate import for Switch from 'react-native' (separate import on the line
with "Switch") while the block at the top already imports multiple symbols from
'react-native'; remove the separate "import { Switch } from 'react-native';" and
add Switch to the existing import list (the top import block that includes
Alert, Animated, Image, Pressable, ScrollView, StyleSheet, Text, View,
useWindowDimensions) so all react-native symbols are imported in one
consolidated statement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/auth/login.tsx`:
- Around line 340-364: The push-step handlers (handlePushEnable and
handlePushSkip) can be invoked concurrently which causes race conditions;
serialize these actions by introducing a loading flag (e.g., isPushSubmitting)
and use it to guard the handlers and disable the Pressable buttons while the
async mutation runs; update handlePushEnable and handlePushSkip to early-return
if isPushSubmitting, set the flag before awaiting the mutation and clear it
after, and bind the flag to the Pressable disabled state and disabled styling so
both taps and visual affordance are prevented during the async operation.
- Around line 306-320: The try/catch around requestPermissionAndSyncToken in
handlePushEnable is swallowing all errors (including missing Expo project ID or
recordPushToken failures) and proceeding as if permission was denied; change
requestPermissionAndSyncToken to return an explicit boolean (granted true/false)
or throw distinct error types, then in handlePushEnable check the boolean and
only treat false as "permission denied" while letting other errors bubble up or
displaying an Alert to the user; specifically, stop silently continuing to call
persistMessageNotificationsPreference when requestPermissionAndSyncToken failed
for non-permission reasons, and surface errors from recordPushToken or missing
config so the device registration state is not left inconsistent.

In `@frontend/app/conversations/`[id].tsx:
- Around line 190-217: The UI must be gated while block/unblock mutations run:
in handleBlockPress (and the similar handlers at 220-229, 299-309, 392-431) use
the blockUser/unblockUser mutation loading state (or set a local
isMutatingBlocking state when dispatching) to immediately disable the composer
send button and header action buttons; set isMutatingBlocking = true before
calling blockUser/unblockUser and reset it in finally (or rely on
mutation.isLoading) and pass that flag into the composer and header action props
so they are rendered disabled while the mutation is in-flight to prevent sending
messages before the updated block row exists.

---

Duplicate comments:
In `@frontend/app/profile/`[userId].tsx:
- Around line 61-62: The self-report guard in the canReportProfile expression is
comparing resolvedUserId to user?._id, which mixes route IDs with database IDs
and can allow showing the Report button on your own profile; change the check to
compare resolvedUserId against the authenticated user's userId field instead
(i.e., update the canReportProfile assignment that uses isAuthenticated and
resolvedUserId so it checks resolvedUserId !== <authenticatedUser>.userId rather
than user?._id).

---

Nitpick comments:
In `@frontend/app/`(tabs)/settings.tsx:
- Around line 309-317: The commented-out "Share Profile" Pressable block
(containing Pressable, handleShareProfile, styles.shareButton,
styles.buttonPressed, and styles.shareButtonText) should be either removed or
annotated with a TODO referencing an issue/ticket ID; if this feature will be
re-enabled soon, replace the comment with a one-line TODO that includes the
issue/link and estimated re-enable date, otherwise delete the entire commented
JSX to remove dead code while leaving source control history intact.
- Around line 2-14: The file has a duplicate import for Switch from
'react-native' (separate import on the line with "Switch") while the block at
the top already imports multiple symbols from 'react-native'; remove the
separate "import { Switch } from 'react-native';" and add Switch to the existing
import list (the top import block that includes Alert, Animated, Image,
Pressable, ScrollView, StyleSheet, Text, View, useWindowDimensions) so all
react-native symbols are imported in one consolidated statement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f896c45d-a691-4f6d-a701-abf33dd4269a

📥 Commits

Reviewing files that changed from the base of the PR and between 194f14c and 08a6f2e.

📒 Files selected for processing (5)
  • backend/convex/blocks.ts
  • frontend/app/(tabs)/settings.tsx
  • frontend/app/auth/login.tsx
  • frontend/app/conversations/[id].tsx
  • frontend/app/profile/[userId].tsx

Comment on lines +306 to +320
const handlePushEnable = async () => {
try {
await requestPermissionAndSyncToken(recordPushToken);
} catch {
// Permission denied - continue anyway
}

const messageNotificationsEnabled = true;
try {
await persistMessageNotificationsPreference(messageNotificationsEnabled);
} catch (error) {
const message =
error instanceof Error ? error.message : 'Failed to save notification preference.';
Alert.alert('Notification preference not saved', message);
return;
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

hook_file="$(fd 'usePushNotifications.ts$' frontend | head -n1)"
if [ -z "$hook_file" ]; then
  echo "frontend/hooks/usePushNotifications.ts not found" >&2
  exit 1
fi

echo "== requestPermissionAndSyncToken implementation =="
rg -nC8 'requestPermissionAndSyncToken|throw|catch|return' "$hook_file"

echo
echo "== related callers =="
rg -nC3 'requestPermissionAndSyncToken|recordPushToken' frontend backend

Repository: codebox-calpoly/PolyBuys

Length of output: 11094


The blank catch block treats all errors—including missing project ID and backend failures—as permission denial.

requestPermissionAndSyncToken throws an error when the Expo project ID is missing (not permission-related) and may also throw from the recordPushToken mutation. The catch block with comment "Permission denied - continue anyway" masks all these errors and unconditionally proceeds to save notifications as enabled, leaving the device potentially unregistered. Handle permission denial explicitly (via boolean return value) and surface other failures to the user; do not silently continue past throw errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 306 - 320, The try/catch around
requestPermissionAndSyncToken in handlePushEnable is swallowing all errors
(including missing Expo project ID or recordPushToken failures) and proceeding
as if permission was denied; change requestPermissionAndSyncToken to return an
explicit boolean (granted true/false) or throw distinct error types, then in
handlePushEnable check the boolean and only treat false as "permission denied"
while letting other errors bubble up or displaying an Alert to the user;
specifically, stop silently continuing to call
persistMessageNotificationsPreference when requestPermissionAndSyncToken failed
for non-permission reasons, and surface errors from recordPushToken or missing
config so the device registration state is not left inconsistent.

Comment on lines +340 to +364
if (isPushStep) {
return (
<View style={styles.container}>
<View style={styles.background}>
<View style={styles.orbTop} />
<View style={styles.orbBottom} />
<View style={styles.scrollContent}>
<Animated.View style={[styles.content, panelEntrance]}>
<Text style={styles.eyebrow}>Stay in the loop</Text>
<Text style={styles.title}>Enable notifications</Text>
<Text style={styles.subtitle}>
Get notified when someone messages you about a listing.
</Text>
<Pressable
style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
onPress={() => void handlePushEnable()}
>
<Text style={styles.buttonText}>Enable notifications</Text>
</Pressable>
<Pressable
style={({ pressed }) => [styles.skipButton, pressed && styles.buttonPressed]}
onPress={() => void handlePushSkip()}
>
<Text style={styles.skipButtonText}>Not now</Text>
</Pressable>
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 | 🟠 Major

Serialize the push-step actions.

Both buttons stay tappable while the async mutation path runs, so users can fire conflicting enable/skip writes and the saved value becomes timing-dependent.

🔒 Suggested fix
 const handlePushEnable = async () => {
+  if (isLoading) return;
+  setIsLoading(true);
   try {
     await requestPermissionAndSyncToken(recordPushToken);
   } catch {
     // Permission denied - continue anyway
   }

   const messageNotificationsEnabled = true;
   try {
     await persistMessageNotificationsPreference(messageNotificationsEnabled);
   } catch (error) {
     const message =
       error instanceof Error ? error.message : 'Failed to save notification preference.';
     Alert.alert('Notification preference not saved', message);
     return;
+  } finally {
+    setIsLoading(false);
   }

   finishAndRedirect();
 };

 const handlePushSkip = async () => {
+  if (isLoading) return;
+  setIsLoading(true);
   const messageNotificationsEnabled = false;
   try {
     await persistMessageNotificationsPreference(messageNotificationsEnabled);
   } catch (error) {
     const message =
       error instanceof Error ? error.message : 'Failed to save notification preference.';
     Alert.alert('Notification preference not saved', message);
     return;
+  } finally {
+    setIsLoading(false);
   }

   finishAndRedirect();
 };
 <Pressable
-  style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
+  style={({ pressed }) => [
+    styles.button,
+    pressed && styles.buttonPressed,
+    isLoading && styles.buttonDisabled,
+  ]}
   onPress={() => void handlePushEnable()}
+  disabled={isLoading}
 >
   <Text style={styles.buttonText}>Enable notifications</Text>
 </Pressable>
 <Pressable
-  style={({ pressed }) => [styles.skipButton, pressed && styles.buttonPressed]}
+  style={({ pressed }) => [
+    styles.skipButton,
+    pressed && styles.buttonPressed,
+    isLoading && styles.buttonDisabled,
+  ]}
   onPress={() => void handlePushSkip()}
+  disabled={isLoading}
 >
   <Text style={styles.skipButtonText}>Not now</Text>
 </Pressable>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 340 - 364, The push-step handlers
(handlePushEnable and handlePushSkip) can be invoked concurrently which causes
race conditions; serialize these actions by introducing a loading flag (e.g.,
isPushSubmitting) and use it to guard the handlers and disable the Pressable
buttons while the async mutation runs; update handlePushEnable and
handlePushSkip to early-return if isPushSubmitting, set the flag before awaiting
the mutation and clear it after, and bind the flag to the Pressable disabled
state and disabled styling so both taps and visual affordance are prevented
during the async operation.

Comment on lines +190 to +217
const handleBlockPress = () => {
if (!otherUserId) return;

if (isBlockingOther === true) {
unblockUser({ blockedId: otherUserId }).catch((err) => {
Alert.alert('Could not unblock', err instanceof Error ? err.message : 'Please try again.');
});
return;
}

const blockAction = () => {
blockUser({ blockedId: otherUserId })
.then(() => {
Alert.alert('User blocked', 'You will no longer receive messages from this user.');
})
.catch((err) => {
Alert.alert('Could not block', err instanceof Error ? err.message : 'Please try again.');
});
};

Alert.alert(
'Block user',
'You will no longer receive messages from this user. They will not be notified.',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Block', style: 'destructive', onPress: blockAction },
]
);
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 | 🟠 Major

Disable send/header actions while block state is being mutated.

After the user confirms Block, the composer and header action stay live until the mutation finishes and the queries refresh. A quick tap can still dispatch sendMessage before the new block row exists.

💡 One way to gate the UI immediately
   const [messageBody, setMessageBody] = useState('');
   const [isSending, setIsSending] = useState(false);
+  const [isUpdatingBlockState, setIsUpdatingBlockState] = useState(false);

   const handleBlockPress = () => {
-    if (!otherUserId) return;
+    if (!otherUserId || isUpdatingBlockState) return;

     if (isBlockingOther === true) {
-      unblockUser({ blockedId: otherUserId }).catch((err) => {
-        Alert.alert('Could not unblock', err instanceof Error ? err.message : 'Please try again.');
-      });
+      setIsUpdatingBlockState(true);
+      unblockUser({ blockedId: otherUserId })
+        .catch((err) => {
+          Alert.alert('Could not unblock', err instanceof Error ? err.message : 'Please try again.');
+        })
+        .finally(() => {
+          setIsUpdatingBlockState(false);
+        });
       return;
     }

     const blockAction = () => {
-      blockUser({ blockedId: otherUserId })
+      setIsUpdatingBlockState(true);
+      blockUser({ blockedId: otherUserId })
         .then(() => {
           Alert.alert('User blocked', 'You will no longer receive messages from this user.');
         })
         .catch((err) => {
           Alert.alert('Could not block', err instanceof Error ? err.message : 'Please try again.');
+        })
+        .finally(() => {
+          setIsUpdatingBlockState(false);
         });
     };
   };

   if (
     !trimmed ||
     !conversationId ||
     isSending ||
+    isUpdatingBlockState ||
     isBlockStatusLoading ||
     isBlockingOther === true ||
     isBlockedByOther === true
   ) {
     return;
   }

Also applies to: 220-229, 299-309, 392-431

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/conversations/`[id].tsx around lines 190 - 217, The UI must be
gated while block/unblock mutations run: in handleBlockPress (and the similar
handlers at 220-229, 299-309, 392-431) use the blockUser/unblockUser mutation
loading state (or set a local isMutatingBlocking state when dispatching) to
immediately disable the composer send button and header action buttons; set
isMutatingBlocking = true before calling blockUser/unblockUser and reset it in
finally (or rely on mutation.isLoading) and pass that flag into the composer and
header action props so they are rendered disabled while the mutation is
in-flight to prevent sending messages before the updated block row exists.

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.
@evan-taylor evan-taylor merged commit 7f8473f into dev Apr 9, 2026
3 checks passed
@evan-taylor evan-taylor deleted the evan/testflight-feedback branch April 9, 2026 04:47
evan-taylor added a commit that referenced this pull request Apr 9, 2026
* implemented CRUD operations in backend

* feat: implement Cal Poly email authentication with verification

- Add user authentication using @convex-dev/auth with Password provider
- Enforce @calpoly.edu email domain validation
- Implement email verification flow with SendGrid integration
- Create signup, login, and email verification screens
- Add user schema and auth-related queries/mutations
- Update shared types and utilities for email validation
- Add AuthProvider to app layout with auth routes

* docs: update environment variables documentation for auth

* fix: secure email verification with token validation

* feat: implemented profile viewing and editing

* feat: add search and filtering for listings
- Add condition field to listings (new, like_new, good, fair, poor)
- Add searchAndFilterListings query with full-text search
- Support category, price range, condition filters
- Add sort options and cursor-based pagination (20 items/page)
- Add compound indexes for performance

* refactor: switch from Password+SendGrid to OTP+Resend
- Replace Password provider with Resend OTP (8-digit code, 15 min expiry)
- Add Cal Poly email domain validation
- Simplify to single login screen (email → code)
- Remove separate signup and verify-email screens
- Switch from SendGrid to Resend for emails
- Use @convex-dev/auth OTP pattern per reviewer feedback

* Adding .env.examples back

* fix: address CodeRabbit review suggestions for auth implementation

* fix: configure ESM module resolution for backend typecheck

* Normalize email to lowercase and trim whitespace

* fix: added constraints to profile creation

* POLY-10 add messaging and conversation schema and sendMessage mutation

* POLY-10 test function for sendMessage mutation

* POLY-10 feat: add retrieve conversation history query

* POLY-10 feat: add listUserConversations query

* POLY-10 fix: updated commenting and edited listingId in schema

* feat: associate conversations with listings, read functionality, real-time message delivery

* Sync Convex schema, generated files, and backend dependencies

* POLY-10: Fix functions for updated schema

* Add pagination to getProfiles query

Updated getProfiles query to support pagination options.

* Update package.json

* Update convex dependency version to 1.31.5

* Update package.json

* Update package.json

* Downgrade convex dependency version to 1.17.2

* this should fix it

* there we go

* feat: implement custom OTP authentication backend (POLY-29)
- Add otpCodes table with hashing and rate limiting
- Implement requestOTP action with Cal Poly email validation
- Implement verifyOTP mutation with attempt limiting
- Replace @convex-dev/auth with custom OTP system
- Add bcryptjs for secure OTP hashing
- Integrate Resend for email delivery
- Add emailVerified field to users table
BREAKING CHANGE: Replaces @convex-dev/auth authentication system

* chore: update package-lock.json for bcryptjs dependencies

* fix: implement CodeRabbit security improvements

- Make createAndStoreOTPInternal an internalMutation to prevent client access
- Implement atomic rate-limit check and OTP storage in single transaction
- Replace Math.random() with crypto.getRandomValues() for secure OTP generation
- Add emailVerified field to User type to match backend schema

Security improvements prevent race conditions and ensure cryptographically
secure random number generation for OTP codes.

* fix: make emailVerified optional for backward compatibility

Make emailVerified field optional in schema and User type to prevent
breaking existing user records that don't have this field. New users
created via OTP will have emailVerified set to true, while existing
users can be migrated gradually without validation errors.

* feat: add report schema and submission endpoint (POLY-19)

- Add reports table to Convex schema with indexes for target and reporter lookups
- Implement createReport mutation with authentication, validation, and duplicate prevention
- Add rate limiting (10 reports per 24 hours per user)
- Create comprehensive test suite with 9 tests covering all acceptance criteria
- Ensure reporterId is never exposed in responses

* Fix(POLY-19): Address test failures and code review feedback & Feat(POLY-20): Implement auto-hide logic

* fix: enforce auth in messaging queries and make debug mutation internal

* feat: added tags field with validation

* fix: fixed updateListing test cases

* fix: add AsyncStorage for React Native auth storage

* Fix(POLY-20): Prevent auto-hide from overwriting existing hidden context

* feat(POLY-15): Add home feed sorting and filtering

- Update getListings query with category, minPrice, maxPrice filters
- Use by_status_createdAt index for deterministic newest-first ordering
- Add price validation (min >= 0, max >= min)
- Add FilterBar component with filter chips
- Add CategoryPicker bottom sheet
- Add PriceRangePicker bottom sheet with presets
- Integrate filters into home screen with state management
- Add 'No results' state with clear filters option
- Add 7 tests for filter combinations

* fix(POLY-15): address code review feedback

- Refactor FilterBar to avoid nested TouchableOpacity
- Add negative max price validation to PriceRangePicker

* fix(POLY-15): add NaN/non-finite validation for price inputs

- Trim whitespace from inputs before parsing
- Treat empty/whitespace-only strings as undefined
- Validate parsed values are finite with Number.isFinite
- Show clear error message for invalid number input

* done

* done now

* other issues done too

* other issues done too

* fixed errors

* feat: add tag filters to getListings

* fix: pass empty args to getListings useQuery

* fix: modify listings.ts to pass local tests and ran npm install

* fixed final errors

* fixed final errors

* fixed new  errors

* fixed new  errors

* fixed

* Poly-16-Tags-Backend

* removed redundancy

* fix

* fix

* fix

* Fix2

* fix: integrate coderabbit changes and edit testcases

* fix: filter listings by tags in memory

* feat: exclude hidden listings from getListings

* Add validation for limit and price parameters in getListings query

* Fix duplicate reports table in schema and update dependencies

* Merge tag filtering with category/price filters

- Add tag filtering to getListings query (OR logic)
- Add category and price range filters
- Create unified FilterBar with all three filter types
- Add CategoryPicker and PriceRangePicker components
- Support URL params for tag deep linking
- Add comprehensive validation for price inputs

* refactor: address code review feedback

- Remove redundant arrayContaining assertion in schema test
- Extract shared filter types to frontend/types/filters.ts
- Remove duplicate CATEGORIES from CategoryPicker
- Fix PriceRangePicker useEffect to only reset on modal open
- Remove dead padding properties from chip style
- Fix URL param sync in handleTagsChange

* Fix duplicate useState import and unused styles in index.tsx

* fix: exclude hidden listings from getListings query

* fix: resolve merge conflicts and fix test configuration

- Merge tag filtering tests with basic filtering tests from dev
- Add @babel/preset-typescript to babel.config.js for proper TS support
- Configure jest to use ts-jest for .ts files and babel-jest for .js files
- Add price validation to getListings (minPrice/maxPrice checks)
- Add getCurrentUserSubject query from dev branch
- Fix test ordering by using _creationTime instead of createdAt
- All 55 tests now passing

* fix: remove duplicate functions from merge conflict

- Remove duplicate getListings export (kept complete version with tag filtering)
- Remove duplicate getCurrentUserSubject export
- Remove unused normalizeTags function
- All tests passing, linting clean

* fix: remove duplicate listings variable declaration

- Remove unfiltered listings query on line 13
- Keep filtered listings query that respects filter state
- Fixes TypeScript error: Cannot redeclare block-scoped variable

* fixed redundancy of validate/normalize

* chore: upgrade Expo SDK to 54 and fix dependencies

- Upgrade Expo from ~52.0.0 to ^54.0.33
- Upgrade React to 19.1.0 and React Native to 0.81.5
- Update all Expo packages to SDK 54 compatible versions
- Add @auth/core dependency to backend to fix convex dev
- Add .npmrc with legacy-peer-deps flag to handle peer dependency conflicts
- Fix npm audit vulnerabilities

* chore: add self-hosted Convex actions URL to environment configuration

* testing

* fixes

* add docs for s3 convex config (#38)

* fixes

* delete

* fixes

* fixes

* fixes

* fixes

* test added

* fixes

* update

* tests

* feat: integrate OpenAI Moderation API for listings and messages

- Add moderationResults table to schema
- Create moderation.ts with moderateContent internalAction
- Refactor createListing/updateListing from mutations to actions
- Refactor sendMessage from mutation to action
- Add ConvexError responses for flagged content
- Update all test files for action-based API with fetch mocks
- Disable ts-jest diagnostics for convex-test ID serialization
- Document OPENAI_API_KEY in .env.example

Resolves: POLY-31, POLY-36, POLY-37

* fix: switch createListing/updateListing from useMutation to useAction in frontend

Frontend was still using useMutation for createListing and updateListing,
which are now actions after moderation integration. This caused TypeScript
errors: FunctionReference<"action"> not assignable to FunctionReference<"mutation">.

* fix: extract ConvexError.data for moderation rejection messages

ConvexError stores the custom error string in .data, not .message.
Without this fix, moderation rejection messages would not surface
to the user in the Alert dialog.

* feat: exclude hidden content from feed and handle hidden states

* fix: code rabbit changes

* WIP: messaging changes

* chore: make POLY-38 schema rollout backward-compatible with backfill

* feat: clean shareable links route and listing share action

* feat: add production-ready report modal flow for listing detail

* style: format ReportModal with Prettier

* feat: rebuild clean image uploader with upload URL flow and timeout safeguards

* feat: deep-linking listings rebased from nightly clean on latest dev

* feat: expo upgrade, fix config, enhance ui, add animations

- Deleted Babel configuration file as it is no longer needed.
- Updated Jest configuration to specify Babel config file path.
- Added environment variables for email handling in backend.
- Enhanced email validation and error handling in ResendOTP function.
- Improved frontend components with better loading states and animations.
- Updated dependencies in package-lock.json for compatibility with new Expo version.

* refactor: streamline auth configuration and improve search functionality

- Updated auth configuration to use a unified provider domain from environment variables.
- Simplified search component logic to enhance performance and maintain reactivity for first-page results.
- Removed unnecessary cursor handling for improved clarity in state management.

* chore: update expo-image-manipulator and expo-image-picker versions in package files

- Upgraded expo-image-manipulator to version 55.0.9 and expo-image-picker to version 55.0.10 in both package-lock.json and package.json for improved functionality and compatibility.

* fix: coderabbit feedback

- Added a function to dynamically determine the app origin for shareable links in the ListingDetailScreen.
- Updated the cancel button in both NewListingScreen and EditListingScreen to be disabled during submissions or pending uploads, with visual feedback for disabled state.

* feat: enhance login functionality with return redirection

- Integrated useLocalSearchParams to retrieve returnTo parameter for post-auth redirection.
- Updated router.replace to redirect users to the specified return path after successful login, improving user experience.

* feat: enhance login screen with verification step handling

- Introduced a new state variable to manage the verification step in the login process.
- Updated the rendering logic to display success messages conditionally based on the verification step, improving user feedback during authentication.

* chore: update dependencies and enhance TypeScript configuration

- Added expo-router version 55.0.3 to package.json and package-lock.json for improved routing capabilities.
- Updated TypeScript configuration in tsconfig.json to include additional file types and improve module resolution.

* feat: enhance listings and profiles management

- Updated the updateListing action to return specific moderation error responses instead of throwing exceptions.
- Added a new query to retrieve the current user's listings, including hidden and inactive items.
- Introduced a new query to fetch the current authenticated user's full profile, including non-public fields.
- Enhanced profile creation and update mutations to include additional fields and validation for email, year, and other profile attributes.
- Improved error handling in listing creation and editing to provide user-friendly feedback for moderation issues.
- Updated frontend components to reflect new profile and listing functionalities, including loading states and input validations.

* refactor: improve error messaging in profile creation tests

- Updated the test case for createProfile to clarify rejection conditions when no email is provided or available on identity.
- Enhanced error message to be more user-friendly, specifying that an email is required to create a profile.

* chore: update Node.js version in configuration files

* fix: ci script

* fix: ci checks

* fix: ci linux lightningcss binary

* fixed issue

* fixed issue

* potential redundent querires

* null request bug

* feat: implement push notifications and update dependencies

- Added support for push notifications using @convex-dev/expo-push-notifications.
- Updated the convex configuration to include push notification handling in message actions.
- Enhanced tests to verify push notification functionality upon message creation.
- Updated dependencies, including upgrading convex to version 1.32.0 and related Expo packages.
- Modified configuration files to reflect changes in app identifiers and notification settings.

* fix: improve push notification handling and enhance tests

- Updated the sendMessage action to use a more structured approach for push notifications based on the environment.
- Enhanced test setup for push notifications by refactoring the mock implementation for better clarity and maintainability.
- Added error handling for push notification failures during message delivery.
- Updated the useAuth hook to ensure push tokens are removed on sign-out, improving token management.

* fix: update app slug for consistency

* feat: add message seller button on listings

* feat: create inbox screen

* feat: create chat screen

* feat: implement read receipts for inbox in nav bar

* fix: remove errors when clicking inbox

* fix: prevent duplicate conversations

* feat: add read receipts to messages

* fix: show buyer name for listing creator

* fix: implemented code rabbit changes

* fix: add more code rabbit changes

* Development environment setup (#55)

* docs: add AGENTS.md with Cursor Cloud development instructions

* docs: update AGENTS.md with Convex cloud setup and login caveat

* docs: update AGENTS.md to reflect Convex cloud only (no self-hosted)

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* feat: UI overhaul (#57)

* feat: add saved listings functionality and enhance listings queries

- Implemented `savedListings` table with associated queries and mutations for saving, unsaving, and checking saved listings.
- Added `getListingsBySeller` query to fetch public active listings by seller.
- Updated `updateListingStatus` mutation to prevent changing status of sold listings.
- Enhanced tests for new saved listings features and updated existing tests for listing status changes.
- Improved UI components to support new functionalities and ensure better user experience.

* fix: agent bugs

* feat: integrate Sentry for error tracking and monitoring

- Added `@sentry/react-native` dependency to the project.
- Initialized Sentry in the application with the provided DSN for error tracking.
- Updated `app.json` to include Sentry configuration for Expo.
- Enhanced `_layout.tsx` to wrap the `RootLayout` component with Sentry for better error handling.

* refactor: update Metro configuration for Sentry integration

* feat: enhance authentication and profile management

- Introduced AppReviewOTP for Apple App Review email verification.
- Updated authentication flow to support specific email validation for the review process.
- Refactored profile bounds to a shared constants module for better maintainability.
- Improved error handling and user feedback in login and profile edit screens.
- Enhanced saved listings functionality to mark deleted and hidden listings as unavailable.
- Updated tests to cover new email validation logic and recent changes in saved listings.

* feat: add optional App Review OTP configuration

- Introduced optional App Review OTP functionality for Apple App Review login.
- Updated backend and frontend to support dynamic email and code configuration via environment variables.
- Enhanced email validation logic in the login process to accommodate the new OTP feature.
- Updated documentation to reflect the new configuration options for App Review.

* fix: icon and package mismatch (#59)

* refactor: unify authentication handling across backend modules (#60)

- Replaced direct calls to `getUserIdentity` with `requireAuthUserId` in various modules to ensure consistent user authentication checks.
- Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
- Enhanced error handling for unauthorized actions across multiple mutations and queries.
- Removed legacy participant ID handling in messages and conversations to streamline user identification.
- Improved overall code maintainability and clarity by consolidating authentication logic.

* feat: enhance My Listings screen with status filtering and action han… (#61)

* feat: enhance My Listings screen with status filtering and action handling

- Added status filter options ('all', 'active', 'inactive', 'sold') to the My Listings screen for better listing management.
- Implemented action handling for editing, marking as sold, setting status, and deleting listings through a new action sheet.
- Improved error handling with user-friendly alerts for various actions.
- Updated UI components to reflect changes in listing management and enhance user experience.

* feat: unify authentication redirection across screens

- Implemented consistent use of `router.replace` for authentication redirection in various screens including Inbox, Home, My Listings, Search, Settings, and Listing Details.
- Enhanced user experience by replacing sign-in prompts with loading indicators while redirecting to the login page.
- Improved accessibility labels in ListingCard for better screen reader support.

* feat: implement messaging block checks and notification preferences (#62)

* feat: implement messaging block checks and notification preferences

- Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
- Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
- Updated the user schema to include a field for message notification settings.
- Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
- Added tests to ensure proper functionality of blocking and notification features.

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* feat: enhance user blocking functionality and notification handling

- Implemented a new query to check if the current user is blocked by another user, improving user experience in conversations.
- Updated the blockUser mutation to ensure proper user ID normalization and error handling for blocking actions.
- Enhanced the Settings screen to provide better feedback during account deletion and sign-out processes.
- Refactored notification handling to ensure users are informed about permission status and potential issues when enabling notifications.
- Improved conversation detail screen to handle blocking states more effectively, preventing users from sending messages if blocked.

* feat: improve user blocking and notification preference handling

- Introduced a new function to validate peer user IDs in blocking mutations, enhancing error handling for user blocking actions.
- Updated blockUser and unblockUser mutations to utilize the new validation function, ensuring proper user ID normalization.
- Enhanced Settings screen to provide detailed alerts for notification preference updates, improving user feedback during push token management.
- Improved conversation detail screen to handle blocking states more effectively, preventing message sending if users are blocked.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: transition to Convex Cloud and update environment configurations (#53)

* feat: transition to Convex Cloud and update environment configurations

- Updated backend configuration to use Convex Cloud instead of self-hosted setup.
- Revised environment variable instructions in documentation for backend and frontend.
- Enhanced the My Listings screen with delete functionality and improved user feedback.
- Updated various documentation files to reflect the migration to Convex Cloud and removed references to the legacy self-hosted setup.

* docs: update README and contributing guide for local development setup

- Added instructions for local setup without a Convex account in README.md.
- Revised contributing guide to clarify environment variable configuration options for both Convex Cloud and local development.
- Removed outdated Convex Cloud migration documentation and legacy self-hosted references.

* fix: preserve settings route typing after rebase

* fix: address Convex migration and settings review notes

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>

* Improve messaging safety and polish the web experience (#63)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: manual GitHub Action for dev → main release PR (#64)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: add manual GitHub Action to open dev→main release PR

- workflow_dispatch only; idempotent open PR or report existing
- document usage in contributing releases section

---------

Co-authored-by: dfed25 <domfederico21@gmail.com>
Co-authored-by: Jaydon Chen <79879038+jaydonkc@users.noreply.github.com>
Co-authored-by: Cole <hackman@calpoly.edu>
Co-authored-by: Haixin <haixinhuang502@gmail.com>
Co-authored-by: BoB121isawesome <79879038+BoB121isawesome@users.noreply.github.com>
Co-authored-by: lheutchy <lheutchy@gmail.com>
Co-authored-by: dfed25 <150391626+dfed25@users.noreply.github.com>
Co-authored-by: MatthewPhan <Matthewminhphan@gmail.com>
Co-authored-by: SamanSP1386 <saman.sepehr86@gmail.com>
Co-authored-by: Jaydon Bot <jaydon-bot@local>
Co-authored-by: Taye-Staats <tayestaats@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
evan-taylor added a commit that referenced this pull request Apr 9, 2026
* implemented CRUD operations in backend

* feat: implement Cal Poly email authentication with verification

- Add user authentication using @convex-dev/auth with Password provider
- Enforce @calpoly.edu email domain validation
- Implement email verification flow with SendGrid integration
- Create signup, login, and email verification screens
- Add user schema and auth-related queries/mutations
- Update shared types and utilities for email validation
- Add AuthProvider to app layout with auth routes

* docs: update environment variables documentation for auth

* fix: secure email verification with token validation

* feat: implemented profile viewing and editing

* feat: add search and filtering for listings
- Add condition field to listings (new, like_new, good, fair, poor)
- Add searchAndFilterListings query with full-text search
- Support category, price range, condition filters
- Add sort options and cursor-based pagination (20 items/page)
- Add compound indexes for performance

* refactor: switch from Password+SendGrid to OTP+Resend
- Replace Password provider with Resend OTP (8-digit code, 15 min expiry)
- Add Cal Poly email domain validation
- Simplify to single login screen (email → code)
- Remove separate signup and verify-email screens
- Switch from SendGrid to Resend for emails
- Use @convex-dev/auth OTP pattern per reviewer feedback

* Adding .env.examples back

* fix: address CodeRabbit review suggestions for auth implementation

* fix: configure ESM module resolution for backend typecheck

* Normalize email to lowercase and trim whitespace

* fix: added constraints to profile creation

* POLY-10 add messaging and conversation schema and sendMessage mutation

* POLY-10 test function for sendMessage mutation

* POLY-10 feat: add retrieve conversation history query

* POLY-10 feat: add listUserConversations query

* POLY-10 fix: updated commenting and edited listingId in schema

* feat: associate conversations with listings, read functionality, real-time message delivery

* Sync Convex schema, generated files, and backend dependencies

* POLY-10: Fix functions for updated schema

* Add pagination to getProfiles query

Updated getProfiles query to support pagination options.

* Update package.json

* Update convex dependency version to 1.31.5

* Update package.json

* Update package.json

* Downgrade convex dependency version to 1.17.2

* this should fix it

* there we go

* feat: implement custom OTP authentication backend (POLY-29)
- Add otpCodes table with hashing and rate limiting
- Implement requestOTP action with Cal Poly email validation
- Implement verifyOTP mutation with attempt limiting
- Replace @convex-dev/auth with custom OTP system
- Add bcryptjs for secure OTP hashing
- Integrate Resend for email delivery
- Add emailVerified field to users table
BREAKING CHANGE: Replaces @convex-dev/auth authentication system

* chore: update package-lock.json for bcryptjs dependencies

* fix: implement CodeRabbit security improvements

- Make createAndStoreOTPInternal an internalMutation to prevent client access
- Implement atomic rate-limit check and OTP storage in single transaction
- Replace Math.random() with crypto.getRandomValues() for secure OTP generation
- Add emailVerified field to User type to match backend schema

Security improvements prevent race conditions and ensure cryptographically
secure random number generation for OTP codes.

* fix: make emailVerified optional for backward compatibility

Make emailVerified field optional in schema and User type to prevent
breaking existing user records that don't have this field. New users
created via OTP will have emailVerified set to true, while existing
users can be migrated gradually without validation errors.

* feat: add report schema and submission endpoint (POLY-19)

- Add reports table to Convex schema with indexes for target and reporter lookups
- Implement createReport mutation with authentication, validation, and duplicate prevention
- Add rate limiting (10 reports per 24 hours per user)
- Create comprehensive test suite with 9 tests covering all acceptance criteria
- Ensure reporterId is never exposed in responses

* Fix(POLY-19): Address test failures and code review feedback & Feat(POLY-20): Implement auto-hide logic

* fix: enforce auth in messaging queries and make debug mutation internal

* feat: added tags field with validation

* fix: fixed updateListing test cases

* fix: add AsyncStorage for React Native auth storage

* Fix(POLY-20): Prevent auto-hide from overwriting existing hidden context

* feat(POLY-15): Add home feed sorting and filtering

- Update getListings query with category, minPrice, maxPrice filters
- Use by_status_createdAt index for deterministic newest-first ordering
- Add price validation (min >= 0, max >= min)
- Add FilterBar component with filter chips
- Add CategoryPicker bottom sheet
- Add PriceRangePicker bottom sheet with presets
- Integrate filters into home screen with state management
- Add 'No results' state with clear filters option
- Add 7 tests for filter combinations

* fix(POLY-15): address code review feedback

- Refactor FilterBar to avoid nested TouchableOpacity
- Add negative max price validation to PriceRangePicker

* fix(POLY-15): add NaN/non-finite validation for price inputs

- Trim whitespace from inputs before parsing
- Treat empty/whitespace-only strings as undefined
- Validate parsed values are finite with Number.isFinite
- Show clear error message for invalid number input

* done

* done now

* other issues done too

* other issues done too

* fixed errors

* feat: add tag filters to getListings

* fix: pass empty args to getListings useQuery

* fix: modify listings.ts to pass local tests and ran npm install

* fixed final errors

* fixed final errors

* fixed new  errors

* fixed new  errors

* fixed

* Poly-16-Tags-Backend

* removed redundancy

* fix

* fix

* fix

* Fix2

* fix: integrate coderabbit changes and edit testcases

* fix: filter listings by tags in memory

* feat: exclude hidden listings from getListings

* Add validation for limit and price parameters in getListings query

* Fix duplicate reports table in schema and update dependencies

* Merge tag filtering with category/price filters

- Add tag filtering to getListings query (OR logic)
- Add category and price range filters
- Create unified FilterBar with all three filter types
- Add CategoryPicker and PriceRangePicker components
- Support URL params for tag deep linking
- Add comprehensive validation for price inputs

* refactor: address code review feedback

- Remove redundant arrayContaining assertion in schema test
- Extract shared filter types to frontend/types/filters.ts
- Remove duplicate CATEGORIES from CategoryPicker
- Fix PriceRangePicker useEffect to only reset on modal open
- Remove dead padding properties from chip style
- Fix URL param sync in handleTagsChange

* Fix duplicate useState import and unused styles in index.tsx

* fix: exclude hidden listings from getListings query

* fix: resolve merge conflicts and fix test configuration

- Merge tag filtering tests with basic filtering tests from dev
- Add @babel/preset-typescript to babel.config.js for proper TS support
- Configure jest to use ts-jest for .ts files and babel-jest for .js files
- Add price validation to getListings (minPrice/maxPrice checks)
- Add getCurrentUserSubject query from dev branch
- Fix test ordering by using _creationTime instead of createdAt
- All 55 tests now passing

* fix: remove duplicate functions from merge conflict

- Remove duplicate getListings export (kept complete version with tag filtering)
- Remove duplicate getCurrentUserSubject export
- Remove unused normalizeTags function
- All tests passing, linting clean

* fix: remove duplicate listings variable declaration

- Remove unfiltered listings query on line 13
- Keep filtered listings query that respects filter state
- Fixes TypeScript error: Cannot redeclare block-scoped variable

* fixed redundancy of validate/normalize

* chore: upgrade Expo SDK to 54 and fix dependencies

- Upgrade Expo from ~52.0.0 to ^54.0.33
- Upgrade React to 19.1.0 and React Native to 0.81.5
- Update all Expo packages to SDK 54 compatible versions
- Add @auth/core dependency to backend to fix convex dev
- Add .npmrc with legacy-peer-deps flag to handle peer dependency conflicts
- Fix npm audit vulnerabilities

* chore: add self-hosted Convex actions URL to environment configuration

* testing

* fixes

* add docs for s3 convex config (#38)

* fixes

* delete

* fixes

* fixes

* fixes

* fixes

* test added

* fixes

* update

* tests

* feat: integrate OpenAI Moderation API for listings and messages

- Add moderationResults table to schema
- Create moderation.ts with moderateContent internalAction
- Refactor createListing/updateListing from mutations to actions
- Refactor sendMessage from mutation to action
- Add ConvexError responses for flagged content
- Update all test files for action-based API with fetch mocks
- Disable ts-jest diagnostics for convex-test ID serialization
- Document OPENAI_API_KEY in .env.example

Resolves: POLY-31, POLY-36, POLY-37

* fix: switch createListing/updateListing from useMutation to useAction in frontend

Frontend was still using useMutation for createListing and updateListing,
which are now actions after moderation integration. This caused TypeScript
errors: FunctionReference<"action"> not assignable to FunctionReference<"mutation">.

* fix: extract ConvexError.data for moderation rejection messages

ConvexError stores the custom error string in .data, not .message.
Without this fix, moderation rejection messages would not surface
to the user in the Alert dialog.

* feat: exclude hidden content from feed and handle hidden states

* fix: code rabbit changes

* WIP: messaging changes

* chore: make POLY-38 schema rollout backward-compatible with backfill

* feat: clean shareable links route and listing share action

* feat: add production-ready report modal flow for listing detail

* style: format ReportModal with Prettier

* feat: rebuild clean image uploader with upload URL flow and timeout safeguards

* feat: deep-linking listings rebased from nightly clean on latest dev

* feat: expo upgrade, fix config, enhance ui, add animations

- Deleted Babel configuration file as it is no longer needed.
- Updated Jest configuration to specify Babel config file path.
- Added environment variables for email handling in backend.
- Enhanced email validation and error handling in ResendOTP function.
- Improved frontend components with better loading states and animations.
- Updated dependencies in package-lock.json for compatibility with new Expo version.

* refactor: streamline auth configuration and improve search functionality

- Updated auth configuration to use a unified provider domain from environment variables.
- Simplified search component logic to enhance performance and maintain reactivity for first-page results.
- Removed unnecessary cursor handling for improved clarity in state management.

* chore: update expo-image-manipulator and expo-image-picker versions in package files

- Upgraded expo-image-manipulator to version 55.0.9 and expo-image-picker to version 55.0.10 in both package-lock.json and package.json for improved functionality and compatibility.

* fix: coderabbit feedback

- Added a function to dynamically determine the app origin for shareable links in the ListingDetailScreen.
- Updated the cancel button in both NewListingScreen and EditListingScreen to be disabled during submissions or pending uploads, with visual feedback for disabled state.

* feat: enhance login functionality with return redirection

- Integrated useLocalSearchParams to retrieve returnTo parameter for post-auth redirection.
- Updated router.replace to redirect users to the specified return path after successful login, improving user experience.

* feat: enhance login screen with verification step handling

- Introduced a new state variable to manage the verification step in the login process.
- Updated the rendering logic to display success messages conditionally based on the verification step, improving user feedback during authentication.

* chore: update dependencies and enhance TypeScript configuration

- Added expo-router version 55.0.3 to package.json and package-lock.json for improved routing capabilities.
- Updated TypeScript configuration in tsconfig.json to include additional file types and improve module resolution.

* feat: enhance listings and profiles management

- Updated the updateListing action to return specific moderation error responses instead of throwing exceptions.
- Added a new query to retrieve the current user's listings, including hidden and inactive items.
- Introduced a new query to fetch the current authenticated user's full profile, including non-public fields.
- Enhanced profile creation and update mutations to include additional fields and validation for email, year, and other profile attributes.
- Improved error handling in listing creation and editing to provide user-friendly feedback for moderation issues.
- Updated frontend components to reflect new profile and listing functionalities, including loading states and input validations.

* refactor: improve error messaging in profile creation tests

- Updated the test case for createProfile to clarify rejection conditions when no email is provided or available on identity.
- Enhanced error message to be more user-friendly, specifying that an email is required to create a profile.

* chore: update Node.js version in configuration files

* fix: ci script

* fix: ci checks

* fix: ci linux lightningcss binary

* fixed issue

* fixed issue

* potential redundent querires

* null request bug

* feat: implement push notifications and update dependencies

- Added support for push notifications using @convex-dev/expo-push-notifications.
- Updated the convex configuration to include push notification handling in message actions.
- Enhanced tests to verify push notification functionality upon message creation.
- Updated dependencies, including upgrading convex to version 1.32.0 and related Expo packages.
- Modified configuration files to reflect changes in app identifiers and notification settings.

* fix: improve push notification handling and enhance tests

- Updated the sendMessage action to use a more structured approach for push notifications based on the environment.
- Enhanced test setup for push notifications by refactoring the mock implementation for better clarity and maintainability.
- Added error handling for push notification failures during message delivery.
- Updated the useAuth hook to ensure push tokens are removed on sign-out, improving token management.

* fix: update app slug for consistency

* feat: add message seller button on listings

* feat: create inbox screen

* feat: create chat screen

* feat: implement read receipts for inbox in nav bar

* fix: remove errors when clicking inbox

* fix: prevent duplicate conversations

* feat: add read receipts to messages

* fix: show buyer name for listing creator

* fix: implemented code rabbit changes

* fix: add more code rabbit changes

* Development environment setup (#55)

* docs: add AGENTS.md with Cursor Cloud development instructions

* docs: update AGENTS.md with Convex cloud setup and login caveat

* docs: update AGENTS.md to reflect Convex cloud only (no self-hosted)

---------



* feat: UI overhaul (#57)

* feat: add saved listings functionality and enhance listings queries

- Implemented `savedListings` table with associated queries and mutations for saving, unsaving, and checking saved listings.
- Added `getListingsBySeller` query to fetch public active listings by seller.
- Updated `updateListingStatus` mutation to prevent changing status of sold listings.
- Enhanced tests for new saved listings features and updated existing tests for listing status changes.
- Improved UI components to support new functionalities and ensure better user experience.

* fix: agent bugs

* feat: integrate Sentry for error tracking and monitoring

- Added `@sentry/react-native` dependency to the project.
- Initialized Sentry in the application with the provided DSN for error tracking.
- Updated `app.json` to include Sentry configuration for Expo.
- Enhanced `_layout.tsx` to wrap the `RootLayout` component with Sentry for better error handling.

* refactor: update Metro configuration for Sentry integration

* feat: enhance authentication and profile management

- Introduced AppReviewOTP for Apple App Review email verification.
- Updated authentication flow to support specific email validation for the review process.
- Refactored profile bounds to a shared constants module for better maintainability.
- Improved error handling and user feedback in login and profile edit screens.
- Enhanced saved listings functionality to mark deleted and hidden listings as unavailable.
- Updated tests to cover new email validation logic and recent changes in saved listings.

* feat: add optional App Review OTP configuration

- Introduced optional App Review OTP functionality for Apple App Review login.
- Updated backend and frontend to support dynamic email and code configuration via environment variables.
- Enhanced email validation logic in the login process to accommodate the new OTP feature.
- Updated documentation to reflect the new configuration options for App Review.

* fix: icon and package mismatch (#59)

* refactor: unify authentication handling across backend modules (#60)

- Replaced direct calls to `getUserIdentity` with `requireAuthUserId` in various modules to ensure consistent user authentication checks.
- Updated ownership and user ID retrieval logic in listings, messages, profiles, and saved listings to utilize stable user IDs.
- Enhanced error handling for unauthorized actions across multiple mutations and queries.
- Removed legacy participant ID handling in messages and conversations to streamline user identification.
- Improved overall code maintainability and clarity by consolidating authentication logic.

* feat: enhance My Listings screen with status filtering and action han… (#61)

* feat: enhance My Listings screen with status filtering and action handling

- Added status filter options ('all', 'active', 'inactive', 'sold') to the My Listings screen for better listing management.
- Implemented action handling for editing, marking as sold, setting status, and deleting listings through a new action sheet.
- Improved error handling with user-friendly alerts for various actions.
- Updated UI components to reflect changes in listing management and enhance user experience.

* feat: unify authentication redirection across screens

- Implemented consistent use of `router.replace` for authentication redirection in various screens including Inbox, Home, My Listings, Search, Settings, and Listing Details.
- Enhanced user experience by replacing sign-in prompts with loading indicators while redirecting to the login page.
- Improved accessibility labels in ListingCard for better screen reader support.

* feat: implement messaging block checks and notification preferences (#62)

* feat: implement messaging block checks and notification preferences

- Added functionality to prevent users from messaging each other if they have blocked one another, enhancing user experience and safety.
- Introduced a new mutation to manage message notification preferences, allowing users to enable or disable notifications for incoming messages.
- Updated the user schema to include a field for message notification settings.
- Enhanced the login flow to prompt users for notification permissions, improving engagement with the messaging feature.
- Added tests to ensure proper functionality of blocking and notification features.

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* feat: enhance user blocking functionality and notification handling

- Implemented a new query to check if the current user is blocked by another user, improving user experience in conversations.
- Updated the blockUser mutation to ensure proper user ID normalization and error handling for blocking actions.
- Enhanced the Settings screen to provide better feedback during account deletion and sign-out processes.
- Refactored notification handling to ensure users are informed about permission status and potential issues when enabling notifications.
- Improved conversation detail screen to handle blocking states more effectively, preventing users from sending messages if blocked.

* feat: improve user blocking and notification preference handling

- Introduced a new function to validate peer user IDs in blocking mutations, enhancing error handling for user blocking actions.
- Updated blockUser and unblockUser mutations to utilize the new validation function, ensuring proper user ID normalization.
- Enhanced Settings screen to provide detailed alerts for notification preference updates, improving user feedback during push token management.
- Improved conversation detail screen to handle blocking states more effectively, preventing message sending if users are blocked.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: transition to Convex Cloud and update environment configurations (#53)

* feat: transition to Convex Cloud and update environment configurations

- Updated backend configuration to use Convex Cloud instead of self-hosted setup.
- Revised environment variable instructions in documentation for backend and frontend.
- Enhanced the My Listings screen with delete functionality and improved user feedback.
- Updated various documentation files to reflect the migration to Convex Cloud and removed references to the legacy self-hosted setup.

* docs: update README and contributing guide for local development setup

- Added instructions for local setup without a Convex account in README.md.
- Revised contributing guide to clarify environment variable configuration options for both Convex Cloud and local development.
- Removed outdated Convex Cloud migration documentation and legacy self-hosted references.

* fix: preserve settings route typing after rebase

* fix: address Convex migration and settings review notes

---------



* Improve messaging safety and polish the web experience (#63)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: manual GitHub Action for dev → main release PR (#64)

* chore: update package dependencies and app configuration

- Updated various Expo package versions in package-lock.json and package.json for improved stability and features.
- Added iOS icon path in app.json to enhance app branding on iOS devices.

* chore: update app configuration and package scripts

- Removed iOS icon path from app.json to streamline configuration.
- Updated package.json scripts for running Android and iOS to use 'expo run' commands, enhancing build process consistency.

* feat: polish the Vercel web experience

Make the Expo web app feel intentional on desktop while steering app-only flows back to mobile.
Add Vercel-friendly config so preview and production builds resolve the right web origin.

* fix: remove duplicate layout import after rebase

* fix: clean up search list layout styles

* fix: preserve web search state

Keep the resolved app origin available at runtime and preserve active listing filters when web search updates or refreshes.

* chore: add manual GitHub Action to open dev→main release PR

- workflow_dispatch only; idempotent open PR or report existing
- document usage in contributing releases section

---------

Co-authored-by: dfed25 <domfederico21@gmail.com>
Co-authored-by: Jaydon Chen <79879038+jaydonkc@users.noreply.github.com>
Co-authored-by: Cole <hackman@calpoly.edu>
Co-authored-by: Haixin <haixinhuang502@gmail.com>
Co-authored-by: BoB121isawesome <79879038+BoB121isawesome@users.noreply.github.com>
Co-authored-by: lheutchy <lheutchy@gmail.com>
Co-authored-by: dfed25 <150391626+dfed25@users.noreply.github.com>
Co-authored-by: MatthewPhan <Matthewminhphan@gmail.com>
Co-authored-by: SamanSP1386 <saman.sepehr86@gmail.com>
Co-authored-by: Jaydon Bot <jaydon-bot@local>
Co-authored-by: Taye-Staats <tayestaats@outlook.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
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