[codex] Polish frontend launch readiness and login/profile flows#113
[codex] Polish frontend launch readiness and login/profile flows#113
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughEnforces profile email immutability on the backend, adds tests ensuring email cannot be changed, and introduces multiple frontend UI/keyboard utilities, pickers, centralized user-flow error messages, and minor deployment/env updates. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
backend/convex/profiles.ts (1)
165-303:⚠️ Potential issue | 🟡 MinorSilently-ignored
updateProfilestill acceptsupdateProfile({ email })on its own (e.g., an email-edit screen in an older build),updatestays empty and the call now throws'No valid fields to update'— the opposite of the stated "backwards compatibility" intent on Lines 203–204.Two options depending on the desired UX for legacy clients:
Option A — treat email-only updates as a successful no-op
- if (Object.keys(update).length === 0) { - throw new ConvexError('No valid fields to update'); - } - - await ctx.db.patch(profile._id, update); + if (Object.keys(update).length === 0) { + // Older clients may send only `email`, which is now immutable. Treat as no-op + // instead of surfacing a confusing error. + if (args.email !== undefined) return; + throw new ConvexError('No valid fields to update'); + } + + await ctx.db.patch(profile._id, update);Option B — drop
frontend/components/ui/KeyboardAwareScreen.tsx (1)
35-49:⚠️ Potential issue | 🟡 MinorDon’t let caller padding override the safe-area bottom padding.
contentContainerStylecomes last, so any caller withpaddingBottomreplacesbottomPadding. Add the caller’s bottom padding to the computed inset and apply the merged value last.🛠️ Proposed fix
const headerHeight = useHeaderHeight(); const keyboardHeight = useKeyboardHeight(); const bottomPadding = disableSafeAreaBottom ? 0 : insets.bottom + 8; + const flattenedContentContainerStyle = StyleSheet.flatten(contentContainerStyle); + const requestedPaddingBottom = + typeof flattenedContentContainerStyle?.paddingBottom === 'number' + ? flattenedContentContainerStyle.paddingBottom + : 0; return ( @@ <ScreenScrollView contentContainerStyle={[ styles.content, - { paddingBottom: bottomPadding }, contentContainerStyle, + { paddingBottom: bottomPadding + requestedPaddingBottom }, ]}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/ui/KeyboardAwareScreen.tsx` around lines 35 - 49, The caller can override the safe-area bottom padding because contentContainerStyle is spread last; update KeyboardAwareScreen to compute the caller's bottom padding from the incoming contentContainerStyle (handle object or array) and add it to the computed bottomPadding (bottomPadding + callerPaddingBottom) and then apply that merged paddingBottom as the final value in the ScreenScrollView's contentContainerStyle; reference the symbols bottomPadding, contentContainerStyle, and ScreenScrollView and ensure the resulting contentContainerStyle keeps styles.content first, the computed merged paddingBottom last so callers cannot replace it.frontend/app/(tabs)/settings.tsx (1)
429-447:⚠️ Potential issue | 🟡 MinorKeep pill touch targets at least 44px high.
The new
42min height makes these primary profile actions slightly undersized for mobile tapping. Bump them back to at least44.♿ Proposed fix
primaryPill: { flex: 1, - minHeight: 42, + minHeight: 44, @@ secondaryPill: { flex: 1, - minHeight: 42, + minHeight: 44,🤖 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 429 - 447, The pill touch targets are undersized: update the minHeight in the style objects for primaryPill (currently minHeight: 42) and secondaryPill to at least 44 so mobile tap targets meet accessibility guidelines; locate the minHeight properties in the primaryPill and secondaryPill style definitions and change their values from 42 to 44 (or greater) and ensure any related components relying on these styles still lay out correctly.
🧹 Nitpick comments (4)
backend/convex/__tests__/profiles.test.ts (1)
79-104: Good coverage for email immutability.Consider also asserting behavior when only
updateProfile— with the new logic, that call now throws'No valid fields to update', which is a subtle behavior change older clients may hit. A regression test would lock in whichever behavior you intend.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/convex/__tests__/profiles.test.ts` around lines 79 - 104, Add a regression test in the same file (profiles.test.ts) that creates a profile via asUser.mutation(api.profiles.createProfile, ...) and then calls asUser.mutation(api.profiles.updateProfile, { email: 'updated@calpoly.edu' }) expecting it to throw the 'No valid fields to update' error; reference the existing convexTest/withIdentity setup and the profileId lookup (ctx.db.get) to assert the profile's email remains unchanged if you instead decide to assert ignore behavior—this locks in the intended behavior for updateProfile when only email is supplied.frontend/app/conversations/[id].tsx (1)
281-289: Unblock action is fire-and-forget with no confirmation — inconsistent with block and withaccount-settings.Tapping the header "Unblock" immediately triggers
unblockUser(...)with no confirmation dialog, whileBlockuses anAlert.alertconfirm flow andaccount-settings.tsx/profile/[userId].tsxboth confirm unblock viaAlert.alert('Unblock', ...). Consider mirroring the existing confirmation pattern for consistency and to prevent accidental unblocks via the header chevron area.♻️ Proposed change
- if (isBlockingOther === true) { - unblockUser({ blockedId: otherUserId }).catch((err) => { - Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')); - }); - return; - } + if (isBlockingOther === true) { + Alert.alert('Unblock user', 'Allow this user to message you again?', [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Unblock', + onPress: () => { + unblockUser({ blockedId: otherUserId }).catch((err) => { + Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')); + }); + }, + }, + ]); + return; + }🤖 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 281 - 289, handleBlockPress currently calls unblockUser({ blockedId: otherUserId }) immediately when isBlockingOther is true; change it to show the same Alert.alert confirmation flow used for blocking and in account-settings/profile pages before calling unblockUser, and only call unblockUser inside the confirmation callback (preserving the existing .catch(err => Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')))). Update handleBlockPress to mirror the confirm dialog title/body/buttons and the error-handling behavior used by the Block flow so unblocking from the header requires explicit user confirmation.frontend/components/OpenInAppPrompt.tsx (1)
69-82: Nit: inconsistent async-handler pattern.The primary button uses
onPress={() => void handleOpenInApp()}(Line 50) while the download link'sonPressis directly assigned anasyncfunction on Line 70. With@typescript-eslint/no-misused-promisesor similar, the latter pattern can trigger warnings; consider extracting to a named handler and invoking via() => void handleDownload()for consistency.♻️ Proposed change
+ const handleDownloadApp = async () => { + try { + await ExpoLinking.openURL(APP_STORE_URL); + } catch (error) { + Alert.alert('Download Failed', getUserFlowErrorMessage(error, 'download-app')); + } + }; ... - <Pressable - onPress={async () => { - try { - await ExpoLinking.openURL(APP_STORE_URL); - } catch (error) { - Alert.alert('Download Failed', getUserFlowErrorMessage(error, 'download-app')); - } - }} + <Pressable + onPress={() => void handleDownloadApp()} accessibilityRole="link" accessibilityLabel="Download the PolyBuys app" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/OpenInAppPrompt.tsx` around lines 69 - 82, Extract the inline async onPress into a named async handler (e.g., handleDownload) and call it from the Pressable using () => void handleDownload() to match the primary button pattern; inside handleDownload await ExpoLinking.openURL(APP_STORE_URL) inside a try/catch and on error call Alert.alert('Download Failed', getUserFlowErrorMessage(error, 'download-app')) so the existing error message logic and accessibility props remain unchanged.frontend/components/MajorPicker.tsx (1)
66-77: Minor a11y: missing button role and selected state on major options.The option
TouchableOpacityhas noaccessibilityRole,accessibilityLabel, oraccessibilityState, so screen readers won't announce it as a selectable button or reflect the current selection. Consider:♻️ Proposed change
<TouchableOpacity style={[styles.option, isSelected && styles.optionSelected]} onPress={() => handleSelect(item)} + accessibilityRole="button" + accessibilityLabel={formatMajorLabel(item)} + accessibilityState={{ selected: isSelected }} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/components/MajorPicker.tsx` around lines 66 - 77, The TouchableOpacity option (in the MajorPicker component around handleSelect/formatMajorLabel usage) lacks accessibility props; update the TouchableOpacity to include accessibilityRole="button", accessibilityLabel derived from formatMajorLabel(item) (optionally include ordinal/index if available), accessibilityState={{ selected: isSelected }}, and accessible={true} so screen readers announce it as a selectable button and reflect the current selection; keep the existing visual styles (styles.option/styles.optionSelected) and existing onPress={ () => handleSelect(item) } intact.
🤖 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 378-380: The existing branch that handles
isExistingProfileError(err) should not setStep('checking') because
currentProfile may be stale and the checking effect will bounce the UI back to
'profile'; instead, treat this as a confirmed-post-auth state and redirect to
the post-auth/success flow directly. Update the error branch in login.tsx (where
isExistingProfileError(err) is handled) to invoke the same transition used for
successful auth (e.g., setStep('success') or call the post-auth handler) and
avoid reading or relying on currentProfile; keep the checking effect and flow
for other cases unchanged.
In `@frontend/app/listings/`[id].tsx:
- Around line 197-200: The effect that runs on listingId changes currently only
resets prevDisplayedSavedRef and bookmarkScale, but you must also clear the
optimistic bookmark state so displayedSaved doesn't show stale data; update the
useEffect (the one referencing prevDisplayedSavedRef, bookmarkScale and
listingId) to also reset the optimistic state (call the savedOptimistic setter,
e.g. setSavedOptimistic(null) or setSavedOptimistic(undefined)/false per your
state type) so displayedSaved (which prefers savedOptimistic) is recomputed from
fresh props when listingId changes.
In `@frontend/app/profile/edit.tsx`:
- Around line 249-270: The save flow is incorrectly validating a read-only
legacy field: remove the email validation that uses profileEmail/normalizedEmail
and the call to getEmailValidationError in the submit handler so saving
name/major/bio is not blocked for legacy/review accounts, and also remove the
now-unused getEmailValidationError import; verify there are no other references
to profileEmail validation in the createProfile/updateProfile submission code
paths.
In `@frontend/components/ListingCard.tsx`:
- Around line 311-341: The save and manage buttons (rendered when onToggleSave
and onManagePress are truthy using GlassIconButton with styles.saveButton and
styles.manageButton) currently overlap because they share the same absolute
position; update the component to render them in a small stacked/offset layout
(e.g., wrap both in a single container that positions them vertically with a
small gap or change styles.manageButton to apply a right/top offset when both
props exist) so both controls remain reachable and maintain accessibilityLabel
behavior; ensure the change also covers the other occurrence noted around lines
415-430 where the same pattern is used.
In `@frontend/constants/calPolyMajors.ts`:
- Around line 80-96: normalizeMajorSearchValue currently strips all
parentheticals which removes campus labels (e.g., "(Solano Campus)") so
majorMatchesQuery fails for queries like "solano"; update
normalizeMajorSearchValue to preserve parenthetical text that indicates campus
names by only removing parentheticals that match degree/type tokens (e.g., "BS",
"MS", "Minor") while keeping parentheticals containing the word "campus" or
known campus names; ensure majorMatchesQuery still normalizes both major and
query via normalizeMajorSearchValue and performs includes() as before.
In `@frontend/lib/env.ts`:
- Around line 1-20: readEnv currently reads process.env via bracket notation
(process.env[name]) which Metro can't inline; change to use dot-notation by
creating a local mapping object with dot-accessed properties (e.g. const PUBLIC
= { EXPO_PUBLIC_CONVEX_URL: process.env.EXPO_PUBLIC_CONVEX_URL }) and then have
readEnv/getRequiredExpoPublicEnv read from that mapping instead of
process.env[name]; update readEnv and getRequiredExpoPublicEnv to reference
PUBLIC.EXPO_PUBLIC_CONVEX_URL (or PUBLIC[name]) so Expo can inline the
EXPO_PUBLIC_* var while preserving type safety.
In `@frontend/lib/user-flow-errors.ts`:
- Around line 21-23: getRawErrorMessage currently only handles Error instances
so string rejections and error-like objects lose their messages; update
getRawErrorMessage to also handle string values (return trimmed lowercased
string), objects with a message property (stringified/trimmed/lowercased), and
fall back to empty string for other types; ensure you reference the function
name getRawErrorMessage and perform instanceof Error, typeof error === 'string',
and check for (error && typeof (error as any).message === 'string') to locate
and implement the checks.
In `@scripts/serve-static.mjs`:
- Around line 35-40: In resolveAssetPath, guard against malformed
percent-escapes by wrapping decodeURIComponent(urlPath) in a try/catch and fail
early on decode errors, and replace the insecure prefix check
candidatePath.startsWith(distDir) with a boundary-safe test using
path.relative(distDir, candidatePath) (reject when the relative path starts with
'..' or is an absolute escape); preserve the existing normalization logic
(safeRelativePath / candidatePath) but perform path.resolve/distDir computation
after successful decode and return a safe failure (null or throw) when the
boundary check fails. Ensure you reference resolveAssetPath, decodedPath,
candidatePath and distDir when applying these changes.
---
Outside diff comments:
In `@frontend/app/`(tabs)/settings.tsx:
- Around line 429-447: The pill touch targets are undersized: update the
minHeight in the style objects for primaryPill (currently minHeight: 42) and
secondaryPill to at least 44 so mobile tap targets meet accessibility
guidelines; locate the minHeight properties in the primaryPill and secondaryPill
style definitions and change their values from 42 to 44 (or greater) and ensure
any related components relying on these styles still lay out correctly.
In `@frontend/components/ui/KeyboardAwareScreen.tsx`:
- Around line 35-49: The caller can override the safe-area bottom padding
because contentContainerStyle is spread last; update KeyboardAwareScreen to
compute the caller's bottom padding from the incoming contentContainerStyle
(handle object or array) and add it to the computed bottomPadding (bottomPadding
+ callerPaddingBottom) and then apply that merged paddingBottom as the final
value in the ScreenScrollView's contentContainerStyle; reference the symbols
bottomPadding, contentContainerStyle, and ScreenScrollView and ensure the
resulting contentContainerStyle keeps styles.content first, the computed merged
paddingBottom last so callers cannot replace it.
---
Nitpick comments:
In `@backend/convex/__tests__/profiles.test.ts`:
- Around line 79-104: Add a regression test in the same file (profiles.test.ts)
that creates a profile via asUser.mutation(api.profiles.createProfile, ...) and
then calls asUser.mutation(api.profiles.updateProfile, { email:
'updated@calpoly.edu' }) expecting it to throw the 'No valid fields to update'
error; reference the existing convexTest/withIdentity setup and the profileId
lookup (ctx.db.get) to assert the profile's email remains unchanged if you
instead decide to assert ignore behavior—this locks in the intended behavior for
updateProfile when only email is supplied.
In `@frontend/app/conversations/`[id].tsx:
- Around line 281-289: handleBlockPress currently calls unblockUser({ blockedId:
otherUserId }) immediately when isBlockingOther is true; change it to show the
same Alert.alert confirmation flow used for blocking and in
account-settings/profile pages before calling unblockUser, and only call
unblockUser inside the confirmation callback (preserving the existing .catch(err
=> Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err,
'unblock-user')))). Update handleBlockPress to mirror the confirm dialog
title/body/buttons and the error-handling behavior used by the Block flow so
unblocking from the header requires explicit user confirmation.
In `@frontend/components/MajorPicker.tsx`:
- Around line 66-77: The TouchableOpacity option (in the MajorPicker component
around handleSelect/formatMajorLabel usage) lacks accessibility props; update
the TouchableOpacity to include accessibilityRole="button", accessibilityLabel
derived from formatMajorLabel(item) (optionally include ordinal/index if
available), accessibilityState={{ selected: isSelected }}, and accessible={true}
so screen readers announce it as a selectable button and reflect the current
selection; keep the existing visual styles (styles.option/styles.optionSelected)
and existing onPress={ () => handleSelect(item) } intact.
In `@frontend/components/OpenInAppPrompt.tsx`:
- Around line 69-82: Extract the inline async onPress into a named async handler
(e.g., handleDownload) and call it from the Pressable using () => void
handleDownload() to match the primary button pattern; inside handleDownload
await ExpoLinking.openURL(APP_STORE_URL) inside a try/catch and on error call
Alert.alert('Download Failed', getUserFlowErrorMessage(error, 'download-app'))
so the existing error message logic and accessibility props remain unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6f8d0a5d-5954-4049-b538-821e010d5234
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (41)
backend/convex/__tests__/profiles.test.tsbackend/convex/profiles.tsfrontend/.env.examplefrontend/app/(tabs)/home.tsxfrontend/app/(tabs)/settings.tsxfrontend/app/_layout.tsxfrontend/app/account-settings.tsxfrontend/app/auth/__tests__/loginRedirect.test.tsfrontend/app/auth/login.tsxfrontend/app/conversations/[id].tsxfrontend/app/conversations/new.tsxfrontend/app/listings/[id].tsxfrontend/app/listings/[id]/edit.tsxfrontend/app/listings/__tests__/newListingValidation.test.tsfrontend/app/listings/new.tsxfrontend/app/profile/[userId].tsxfrontend/app/profile/edit.tsxfrontend/components/ListingCard.tsxfrontend/components/MajorPicker.tsxfrontend/components/OpenInAppPrompt.tsxfrontend/components/PriceRangePicker.tsxfrontend/components/ReportModal.tsxfrontend/components/ui/GlassIconButton.tsxfrontend/components/ui/KeyboardAwareScreen.tsxfrontend/components/ui/KeyboardDockScreen.tsxfrontend/components/ui/KeyboardUnderlay.tsxfrontend/components/ui/ModalSheet.tsxfrontend/components/ui/ScreenScrollView.tsxfrontend/components/ui/index.tsfrontend/constants/__tests__/graduationYears.test.tsfrontend/constants/calPolyMajors.tsfrontend/constants/graduationYears.tsfrontend/hooks/useKeyboardHeight.tsfrontend/lib/auth/loginRedirect.tsfrontend/lib/env.tsfrontend/lib/listings/newListingValidation.tsfrontend/lib/user-flow-errors.tsfrontend/package.jsonpackage.jsonscripts/serve-static.mjsvercel.json
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/conversations/`[id].tsx:
- Around line 284-296: The unblock flow currently calls unblockAction which
fires unblockUser({ blockedId: otherUserId }) without awaiting and never shows
success feedback; update the isBlockingOther branch (functions/symbols:
isBlockingOther, unblockAction, unblockUser, getUserFlowErrorMessage,
Alert.alert) to await the unblockUser mutation inside unblockAction, handle
errors as done today, and on successful resolution call Alert.alert with a
success message (e.g., "User unblocked") so the UX mirrors the block flow and
surfaces success to the user.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 82eaf33b-a7e8-4d9b-b20f-66b660a8760a
📒 Files selected for processing (16)
backend/convex/__tests__/profiles.test.tsfrontend/app/(tabs)/settings.tsxfrontend/app/auth/login.tsxfrontend/app/conversations/[id].tsxfrontend/app/listings/[id].tsxfrontend/app/profile/edit.tsxfrontend/components/ListingCard.tsxfrontend/components/MajorPicker.tsxfrontend/components/OpenInAppPrompt.tsxfrontend/components/ui/KeyboardAwareScreen.tsxfrontend/constants/__tests__/calPolyMajors.test.tsfrontend/constants/calPolyMajors.tsfrontend/lib/__tests__/user-flow-errors.test.tsfrontend/lib/env.tsfrontend/lib/user-flow-errors.tsscripts/serve-static.mjs
✅ Files skipped from review due to trivial changes (4)
- backend/convex/tests/profiles.test.ts
- frontend/lib/tests/user-flow-errors.test.ts
- scripts/serve-static.mjs
- frontend/constants/tests/calPolyMajors.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- frontend/lib/env.ts
- frontend/app/(tabs)/settings.tsx
- frontend/components/MajorPicker.tsx
- frontend/app/profile/edit.tsx
- frontend/app/auth/login.tsx
- frontend/lib/user-flow-errors.ts
| if (isBlockingOther === true) { | ||
| unblockUser({ blockedId: otherUserId }).catch((err) => { | ||
| Alert.alert('Could not unblock', err instanceof Error ? err.message : 'Please try again.'); | ||
| }); | ||
| const unblockAction = () => { | ||
| unblockUser({ blockedId: otherUserId }).catch((err) => { | ||
| Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')); | ||
| }); | ||
| }; | ||
|
|
||
| Alert.alert('Unblock user', 'Allow this user to message you again?', [ | ||
| { text: 'Cancel', style: 'cancel' }, | ||
| { text: 'Unblock', onPress: () => void unblockAction() }, | ||
| ]); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Unblock flow lacks success feedback (UX inconsistency with block flow).
The block path surfaces a success Alert ("User blocked"), but the unblock path is silent on success — users get no confirmation that the unblock actually went through. Since unblockUser is fired and not awaited, a slow/failed mutation is easy to miss (error path is covered, success path is not). Recommend mirroring the block flow for consistency.
♻️ Suggested change
if (isBlockingOther === true) {
- const unblockAction = () => {
- unblockUser({ blockedId: otherUserId }).catch((err) => {
+ const unblockAction = async () => {
+ try {
+ await unblockUser({ blockedId: otherUserId });
+ Alert.alert('User unblocked', 'You can now exchange messages with this user again.');
+ } catch (err) {
Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user'));
- });
+ }
};
Alert.alert('Unblock user', 'Allow this user to message you again?', [
{ text: 'Cancel', style: 'cancel' },
{ text: 'Unblock', onPress: () => void unblockAction() },
]);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (isBlockingOther === true) { | |
| unblockUser({ blockedId: otherUserId }).catch((err) => { | |
| Alert.alert('Could not unblock', err instanceof Error ? err.message : 'Please try again.'); | |
| }); | |
| const unblockAction = () => { | |
| unblockUser({ blockedId: otherUserId }).catch((err) => { | |
| Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')); | |
| }); | |
| }; | |
| Alert.alert('Unblock user', 'Allow this user to message you again?', [ | |
| { text: 'Cancel', style: 'cancel' }, | |
| { text: 'Unblock', onPress: () => void unblockAction() }, | |
| ]); | |
| return; | |
| } | |
| if (isBlockingOther === true) { | |
| const unblockAction = async () => { | |
| try { | |
| await unblockUser({ blockedId: otherUserId }); | |
| Alert.alert('User unblocked', 'You can now exchange messages with this user again.'); | |
| } catch (err) { | |
| Alert.alert('Could Not Unblock', getUserFlowErrorMessage(err, 'unblock-user')); | |
| } | |
| }; | |
| Alert.alert('Unblock user', 'Allow this user to message you again?', [ | |
| { text: 'Cancel', style: 'cancel' }, | |
| { text: 'Unblock', onPress: () => void unblockAction() }, | |
| ]); | |
| return; | |
| } |
🤖 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 284 - 296, The unblock flow
currently calls unblockAction which fires unblockUser({ blockedId: otherUserId
}) without awaiting and never shows success feedback; update the isBlockingOther
branch (functions/symbols: isBlockingOther, unblockAction, unblockUser,
getUserFlowErrorMessage, Alert.alert) to await the unblockUser mutation inside
unblockAction, handle errors as done today, and on successful resolution call
Alert.alert with a success message (e.g., "User unblocked") so the UX mirrors
the block flow and surfaces success to the user.
What changed
Why
This branch consolidates a set of UI fixes and polish changes that were spread across login, profile, listings, and messaging surfaces. The goal is to make the launch path more consistent, reduce duplicated keyboard/layout behavior, and harden the user-facing flows before merge.
Impact
Validation
npm run lintnpm run typecheckSummary by CodeRabbit