Skip to content

Added loading and disabled states for async user actions across the app.#484

Merged
roshankumar0036singh merged 1 commit into
roshankumar0036singh:mainfrom
Richa-2005:feature/loading-states-async-operations
May 31, 2026
Merged

Added loading and disabled states for async user actions across the app.#484
roshankumar0036singh merged 1 commit into
roshankumar0036singh:mainfrom
Richa-2005:feature/loading-states-async-operations

Conversation

@Richa-2005
Copy link
Copy Markdown
Contributor

@Richa-2005 Richa-2005 commented May 31, 2026

Closes Issue #321

Description

Adds loading states and duplicate-submit protection for async operations across key app flows.

Changes

  • Added disabled/loading states for event creation and editing submissions.
  • Added loading feedback for RSVP, bookmark, reminder, share, certificate, LinkedIn, and buddy actions.
  • Added submission loading state in custom event registration forms.
  • Added feedback modal submit loading and disabled controls during submit.
  • Added loading/disabled state for event deletion in My Events.
  • Added loading state for club follow/unfollow.
  • Improved async operation feedback in Attendance Dashboard actions.

Testing

  • npm run lint
    • Passed with existing UserFeed.js warnings.
  • npm test -- --ci --passWithNoTests
    • Passed: 22 test suites, 129 tests.

Summary by CodeRabbit

  • New Features

    • Unified loading UI rows (spinner + status labels) for primary buttons and actions.
    • Soft-delete for events so deletions are reversible; delete buttons show per-item progress.
    • Follow/bookmark/register/certificate actions show inline loaders and disable relevant controls.
  • Bug Fixes

    • Prevented duplicate concurrent requests across many flows (buddy, follow, bookmark, share, RSVP, exports, sync, send certificates).
    • Inputs, switches, and rating controls are visually disabled and non-interactive while operations run.
    • Success/error alerts added for key operations and export alerts unified.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds per-action loading flags and early-return guards across components and screens, disables relevant UI during async work, shows inline ActivityIndicators and spinner+label loading content, and ensures flags are cleared in finally blocks.

Changes

Async Operation Guards and Loading UI Patterns

Layer / File(s) Summary
PremiumInput disabled prop support
app/src/components/PremiumInput.js
Adds disabled prop with conditional editable, accessibilityState, and styles.disabled opacity.
Form submission protection
app/src/components/FeedbackModal.js, app/src/screens/EventRegistrationFormScreen.js
Short-circuits duplicate submits via loading guards, disables inputs while submitting, makes Ratings readonly during submit, and replaces spinner-only submit UI with spinner + “Submitting...” content.
EventCard buddy toggle & register UI
app/src/components/EventCard.js
Adds buddyLoading to prevent concurrent buddy toggles, shows success/error Alert.alert, disables the buddy Switch while loading, and renders ActivityIndicator for register navigation state.
ClubProfile follow/unfollow
app/src/screens/ClubProfileScreen.js
Adds followLoading, optimistic UI snapshot, Firestore follow/unfollow branch guarded by followLoading, success alerts, error revert, and ActivityIndicator in follow button.
CreateEvent form disabling with Meet link exception
app/src/screens/CreateEvent.js
Disables most interactive controls while loading, adds a ref to prevent concurrent Meet auth/link generation, supports allowWhileSubmitting for meet-link generation during submit, and updates submit loading layout.
EventDetail multi-action loading
app/src/screens/EventDetail.js
Adds per-action loading flags (bookmark, reminder, buddy, share, rsvp, certificate, LinkedIn) with early returns, UI disabling/spinners, finally resets, and updated header/FAB/button loading UIs.
AttendanceDashboard guards & exports
app/src/screens/AttendanceDashboard.js
Adds isMountedRef, early-return guards for sending/syncing/exporting, refreshes offline pending counts safely, and unifies CSV completion alerts to “Export Ready”.
MyEvents per-item deletion
app/src/screens/MyEventsScreen.js
Adds deletingEventId, memoized delete handler with per-item guard, Firestore soft-delete via updateDoc, platform-specific confirmation, and per-row ActivityIndicator while deleting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

level:intermediate, quality:clean, type:refactor, type:design

Suggested reviewers

  • roshankumar0036singh

Poem

🐰 A rabbit hops through loading states so neat,

Buttons pause and spinners hum a steady beat,
Alerts chime softly when the async work is done,
Guards keep clicks single — no racing, only fun,
Now the UI breathes, and everything's complete.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added loading and disabled states for async user actions across the app' is fully related to the main changes in the PR, which add loading states and disabled UI controls for multiple async operations across components and screens.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/src/components/EventCard.js

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

app/src/components/FeedbackModal.js

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

app/src/components/PremiumInput.js

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 6 others

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.

@roshankumar0036singh
Copy link
Copy Markdown
Owner

fix the issue that is flagged by sonar

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

Caution

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

⚠️ Outside diff range comments (2)
app/src/components/PremiumInput.js (1)

53-64: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make disabled apply to the password toggle as well.

editable={false} stops typing, but password inputs still expose a live eye button below, so the control is only partially disabled.

Suggested fix
                 {secureTextEntry && (
                     <TouchableOpacity
                         onPress={() => setIsPasswordVisible(!isPasswordVisible)}
                         style={styles.eyeIcon}
+                        disabled={disabled}
                         accessibilityRole="button"
                         accessibilityLabel={isPasswordVisible ? 'Hide password' : 'Show password'}
                         accessibilityHint="Toggles password visibility"
-                        accessibilityState={{ checked: isPasswordVisible }}
+                        accessibilityState={{ checked: isPasswordVisible, disabled }}
                     >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/PremiumInput.js` around lines 53 - 64, The password
visibility toggle in PremiumInput.js is still interactive when the field is
disabled; update the password-toggle control (the eye button handler and its
Touchable/Pressable) to respect the disabled prop by either not rendering the
toggle or rendering it in a non-interactive state when disabled is true, and
ensure its accessibilityState includes disabled and its onPress is no-op (or
disabled={true}) so users cannot toggle visibility; update any references to
secureTextEntry/isPasswordVisible handling accordingly so the toggle only
functions when disabled is false.
app/src/screens/EventDetail.js (1)

904-915: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the certificate action locked until the web print call actually executes.

On web, printWindow.print() is deferred via setTimeout, but certificateDownloadLoading is cleared in finally right after scheduling (lines 940-941), so the FAB can re-enable before the print dialog opens and allow duplicate popup/print attempts.

Suggested fix
             if (Platform.OS === 'web') {
                 // Open separate window for reliable printing on mobile web
                 const printWindow = window.open('', '_blank');
                 if (printWindow) {
                     printWindow.document.write(html);
                     printWindow.document.close();

                     // Allow styles and fonts to load
-                    setTimeout(() =&gt; {
-                        printWindow.focus();
-                        printWindow.print();
-                    }, 500);
+                    await new Promise(resolve =&gt; {
+                        setTimeout(() =&gt; {
+                            printWindow.focus();
+                            printWindow.print();
+                            resolve();
+                        }, 500);
+                    });
                 } else {
                     Alert.alert('Blocked', 'Please allow pop-ups to download the certificate.');
                 }

Also, the “Send Certificates” tap guard (handleSendCertificates) relies on state (sendingCertificates), while the in-flight flag is set inside sendCertificates; rapid double-taps before re-render could still trigger duplicate sends—set the in-flight flag in the tap handler (or use a ref) before calling sendCertificates().

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

In `@app/src/screens/EventDetail.js` around lines 904 - 915, The web print flow
clears certificateDownloadLoading too early: keep the FAB locked until the print
actually runs by moving the certificateDownloadLoading state clear into the
printWindow.print() callback path — i.e., only call
setCertificateDownloadLoading(false) after printWindow.print() completes (or
after focus/print promise resolution in the setTimeout), and clear it on both
success and error paths (and when printWindow is null). Also prevent double-send
in the “Send Certificates” flow by setting the in-flight flag before invoking
sendCertificates: set sendingCertificates (or use a ref) in
handleSendCertificates immediately on tap and return early if already set, then
call sendCertificates(); ensure sendCertificates clears the flag on
completion/errors. Reference symbols: certificateDownloadLoading,
printWindow.print(), setTimeout callback, handleSendCertificates,
sendingCertificates, sendCertificates.
🧹 Nitpick comments (4)
app/src/screens/EventRegistrationFormScreen.js (1)

404-409: 💤 Low value

Consider standardizing loading content gap.

This submitLoadingContent style uses gap: 10, while FeedbackModal.js uses gap: 8 for the same layout pattern. Both work, but standardizing the value across the app would improve consistency.

This is cosmetic and can be deferred.

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

In `@app/src/screens/EventRegistrationFormScreen.js` around lines 404 - 409, The
submitLoadingContent style in EventRegistrationFormScreen.js uses gap: 10 which
differs from the gap: 8 used for the same loading layout in FeedbackModal.js;
update submitLoadingContent to use gap: 8 for consistent spacing (or
alternatively adjust both to a single chosen value) so the layout across
components like submitLoadingContent and the FeedbackModal loading style is
standardized.
app/src/components/FeedbackModal.js (2)

217-220: 💤 Low value

Callback guard is redundant with readonly prop.

Both rating inputs combine readonly={loading} with an if (!loading) guard inside onFinishRating. The readonly prop already prevents user interaction, making the callback guard defensive redundancy.

While not harmful, you can simplify by relying on readonly alone or keeping only the callback guard. The current implementation works correctly.

Also applies to: 246-249

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

In `@app/src/components/FeedbackModal.js` around lines 217 - 220, The
onFinishRating callbacks in FeedbackModal are redundantly checking loading
inside the handler while also passing readonly={loading} to the rating inputs;
remove the internal guard and rely on readonly instead (i.e., update the
onFinishRating handlers for the event rating and speaker rating—functions
referencing setEventRating and setSpeakerRating—to simply call
setEventRating(rating) / setSpeakerRating(rating) without the if (!loading)
check), or conversely remove readonly and keep the guard—pick one approach and
apply consistently to both occurrences.

82-89: ⚡ Quick win

Consider providing visual feedback that the modal is locked during submission.

The onRequestClose handler now blocks modal dismissal while loading is true, which prevents accidental cancellation. However, users attempting to close the modal via hardware back button (Android) or escape key will receive no feedback explaining why the action is blocked.

Consider adding a brief toast or alert when users attempt to close during loading, or show a persistent "Submitting..." indicator in the modal header.

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

In `@app/src/components/FeedbackModal.js` around lines 82 - 89, The onRequestClose
handler currently swallows close attempts when loading is true without feedback;
update the Modal behavior so that when loading is true the handler both prevents
closing and gives immediate user feedback—e.g., call a platform toast/alert
(ToastAndroid.show or Alert.alert / a cross-platform Toast component) inside the
onRequestClose arrow function when loading is true, and additionally ensure the
FeedbackModal UI (component FeedbackModal) shows a persistent "Submitting..."
indicator in the modal header while loading; reference the Modal prop
onRequestClose, the loading boolean, and the onClose callback to implement this
behavior.
app/src/screens/CreateEvent.js (1)

100-102: ⚖️ Poor tradeoff

Document the allowWhileSubmitting bypass and consider concurrent auth risks.

The allowWhileSubmitting option allows Meet link generation during form submission, but this creates two possible flows:

  1. User clicks "Auto-Generate" → blocked if loading
  2. Submit auto-generates → proceeds even if loading

While the intent is clear (prevent user-initiated concurrency but allow submit-driven generation), this could create confusing states if the Google auth flow (promptAsync) is invoked concurrently or if multiple token requests overlap.

Consider adding a comment explaining why this bypass exists and verify that the Google Calendar API handles concurrent or rapid-fire requests safely.

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

In `@app/src/screens/CreateEvent.js` around lines 100 - 102, The
handleGenerateMeetLink function's allowWhileSubmitting bypass permits meet-link
generation during form submission and risks concurrent Google auth/token
requests (e.g., promptAsync) causing confusing states; add an inline comment
above handleGenerateMeetLink explaining the intended bypass behavior and
rationale (prevent user-initiated generation while allowing submit-driven
generation), and then add a lightweight safety guard: ensure promptAsync/token
requests are serialized or deduplicated (e.g., short-lived in-memory
"authInProgress" flag) so concurrent calls from submit and user actions cannot
overlap; also add a TODO note to verify Google Calendar API idempotency/safety
for rapid requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/screens/AttendanceDashboard.js`:
- Around line 192-197: Consolidate the two separate try/finally blocks into a
single try/catch/finally around the offline sync work so errors from
getOfflineCheckInCount are caught and do not propagate; in the catch log or
handle the error and in the finally always call setSyncingOffline(false). Also
guard state updates (setPendingOfflineCount and setSyncingOffline) with a
mounted check (e.g., an isMounted ref or AbortController) so the flag is cleared
only when the component is still mounted; update the code paths around
getOfflineCheckInCount, setPendingOfflineCount, and setSyncingOffline
accordingly.

In `@app/src/screens/ClubProfileScreen.js`:
- Line 205: The optimistic update flips isFollowing directly then attempts to
revert by flipping again, which mis-restores state; capture the original value
before the optimistic change (e.g., const prevIsFollowing = isFollowing) in the
handler that performs the optimistic update (where setIsFollowing(!isFollowing)
is called), use setIsFollowing(!prevIsFollowing) for the optimistic change or
simply setIsFollowing(!prevIsFollowing) then, in the catch/error path where
setIsFollowing(!isFollowing) is currently used, call
setIsFollowing(prevIsFollowing) to reliably restore the original state; update
the code around the setIsFollowing and error handler references so the error
branch uses the captured prevIsFollowing rather than the stale isFollowing
closure.

In `@app/src/screens/EventDetail.js`:
- Around line 971-975: The handler handleSendCertificates has a race where it
reads sendingCertificates but setSendingCertificates(true) happens inside
sendCertificates, allowing rapid double-taps to start multiple flows; change the
latch to be acquired synchronously in the handler (either
setSendingCertificates(true) immediately before dispatching or use a ref like
sendingCertificatesRef.current = true in handleSendCertificates) and release it
in a finally block after awaiting sendCertificates (or ensure sendCertificates'
finally clears it). Update references to sendingCertificates,
setSendingCertificates, handleSendCertificates and sendCertificates so the
in-flight guard is set synchronously in the handler and cleared reliably to
prevent double-dispatch of sendBulkCertificates.

In `@app/src/screens/MyEventsScreen.js`:
- Around line 76-114: The current guard in handleDelete prevents any deletion
while any deletion is in progress, but the UI only disables the specific event's
delete button (renderItem uses deletingEventId === item.id), causing clicks on
other items to do nothing; change the guard in handleDelete from if
(deletingEventId) return; to if (deletingEventId === eventId) return; so only
duplicate deletes for the same event are blocked and the UI and logic remain
consistent (symbols: handleDelete, deletingEventId, eventId, renderItem,
item.id).

---

Outside diff comments:
In `@app/src/components/PremiumInput.js`:
- Around line 53-64: The password visibility toggle in PremiumInput.js is still
interactive when the field is disabled; update the password-toggle control (the
eye button handler and its Touchable/Pressable) to respect the disabled prop by
either not rendering the toggle or rendering it in a non-interactive state when
disabled is true, and ensure its accessibilityState includes disabled and its
onPress is no-op (or disabled={true}) so users cannot toggle visibility; update
any references to secureTextEntry/isPasswordVisible handling accordingly so the
toggle only functions when disabled is false.

In `@app/src/screens/EventDetail.js`:
- Around line 904-915: The web print flow clears certificateDownloadLoading too
early: keep the FAB locked until the print actually runs by moving the
certificateDownloadLoading state clear into the printWindow.print() callback
path — i.e., only call setCertificateDownloadLoading(false) after
printWindow.print() completes (or after focus/print promise resolution in the
setTimeout), and clear it on both success and error paths (and when printWindow
is null). Also prevent double-send in the “Send Certificates” flow by setting
the in-flight flag before invoking sendCertificates: set sendingCertificates (or
use a ref) in handleSendCertificates immediately on tap and return early if
already set, then call sendCertificates(); ensure sendCertificates clears the
flag on completion/errors. Reference symbols: certificateDownloadLoading,
printWindow.print(), setTimeout callback, handleSendCertificates,
sendingCertificates, sendCertificates.

---

Nitpick comments:
In `@app/src/components/FeedbackModal.js`:
- Around line 217-220: The onFinishRating callbacks in FeedbackModal are
redundantly checking loading inside the handler while also passing
readonly={loading} to the rating inputs; remove the internal guard and rely on
readonly instead (i.e., update the onFinishRating handlers for the event rating
and speaker rating—functions referencing setEventRating and setSpeakerRating—to
simply call setEventRating(rating) / setSpeakerRating(rating) without the if
(!loading) check), or conversely remove readonly and keep the guard—pick one
approach and apply consistently to both occurrences.
- Around line 82-89: The onRequestClose handler currently swallows close
attempts when loading is true without feedback; update the Modal behavior so
that when loading is true the handler both prevents closing and gives immediate
user feedback—e.g., call a platform toast/alert (ToastAndroid.show or
Alert.alert / a cross-platform Toast component) inside the onRequestClose arrow
function when loading is true, and additionally ensure the FeedbackModal UI
(component FeedbackModal) shows a persistent "Submitting..." indicator in the
modal header while loading; reference the Modal prop onRequestClose, the loading
boolean, and the onClose callback to implement this behavior.

In `@app/src/screens/CreateEvent.js`:
- Around line 100-102: The handleGenerateMeetLink function's
allowWhileSubmitting bypass permits meet-link generation during form submission
and risks concurrent Google auth/token requests (e.g., promptAsync) causing
confusing states; add an inline comment above handleGenerateMeetLink explaining
the intended bypass behavior and rationale (prevent user-initiated generation
while allowing submit-driven generation), and then add a lightweight safety
guard: ensure promptAsync/token requests are serialized or deduplicated (e.g.,
short-lived in-memory "authInProgress" flag) so concurrent calls from submit and
user actions cannot overlap; also add a TODO note to verify Google Calendar API
idempotency/safety for rapid requests.

In `@app/src/screens/EventRegistrationFormScreen.js`:
- Around line 404-409: The submitLoadingContent style in
EventRegistrationFormScreen.js uses gap: 10 which differs from the gap: 8 used
for the same loading layout in FeedbackModal.js; update submitLoadingContent to
use gap: 8 for consistent spacing (or alternatively adjust both to a single
chosen value) so the layout across components like submitLoadingContent and the
FeedbackModal loading style is standardized.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: eaf06a2a-9b22-471e-8fe8-fba54c0c80c3

📥 Commits

Reviewing files that changed from the base of the PR and between 12c11ad and dd0675d.

📒 Files selected for processing (9)
  • app/src/components/EventCard.js
  • app/src/components/FeedbackModal.js
  • app/src/components/PremiumInput.js
  • app/src/screens/AttendanceDashboard.js
  • app/src/screens/ClubProfileScreen.js
  • app/src/screens/CreateEvent.js
  • app/src/screens/EventDetail.js
  • app/src/screens/EventRegistrationFormScreen.js
  • app/src/screens/MyEventsScreen.js

Comment thread app/src/screens/AttendanceDashboard.js Outdated
Comment thread app/src/screens/ClubProfileScreen.js Outdated
Comment thread app/src/screens/EventDetail.js
Comment thread app/src/screens/MyEventsScreen.js
@Richa-2005 Richa-2005 force-pushed the feature/loading-states-async-operations branch from dd0675d to 8d6a670 Compare May 31, 2026 13:42
@roshankumar0036singh
Copy link
Copy Markdown
Owner

check the coderabbit sugestions

@Richa-2005 Richa-2005 force-pushed the feature/loading-states-async-operations branch 2 times, most recently from 533a34c to 569efe9 Compare May 31, 2026 15:21
@roshankumar0036singh
Copy link
Copy Markdown
Owner

@Richa-2005 resolev the conflcit carefullly for this one

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
app/src/screens/CreateEvent.js (1)

697-708: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Disable marker drag interaction while loading, not only the state write.

Line 706 blocks setCoordinates, but the marker is still draggable, which gives a false interactive affordance during submit. Set draggable={!loading} (and optionally disable map gestures) so UI behavior matches the loading lock.

Proposed fix
                                             <Marker
-                                                draggable
+                                                draggable={!loading}
                                                 coordinate={
                                                     coordinates || {
                                                         latitude: 28.7041,
                                                         longitude: 77.1025,
                                                     }
                                                 }
                                                 onDragEnd={e =>
                                                     !loading &&
                                                     setCoordinates(e.nativeEvent.coordinate)
                                                 }
                                             />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/screens/CreateEvent.js` around lines 697 - 708, The Marker currently
remains draggable even when submission is loading, causing a misleading
affordance; update the Marker props to set draggable={!loading} (instead of
hardcoded draggable) and ensure onDragEnd still guards setCoordinates with the
loading flag; also consider disabling map gestures on the surrounding MapView
(e.g., set scrollEnabled/zoomEnabled/rotateEnabled/pitchEnabled to !loading) so
the map and Marker both reflect the loading lock.
🧹 Nitpick comments (1)
app/src/screens/ClubProfileScreen.js (1)

177-242: Error-revert logic is now correct; consider an atomic write for backend consistency.

The optimistic update and rollback via previousFollowing correctly resolve the earlier double-flip bug, and finally reliably clears the loading flag.

One residual concern: the follow/unfollow path issues multiple sequential awaits (the two membership docs plus the count setDoc). If a later write fails, the UI reverts cleanly but Firestore can be left in an inconsistent state (e.g., following doc deleted while the followers doc and followersCount are stale). A writeBatch would make all three writes atomic and remove the partial-failure window.

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

In `@app/src/screens/ClubProfileScreen.js` around lines 177 - 242, In toggleFollow
replace the sequential await calls that touch myFollowingRef, clubFollowerRef
and clubRef with a single Firestore writeBatch so the three mutations are
atomic: build a writeBatch(), call batch.delete(myFollowingRef) /
batch.set(myFollowingRef, ...) and batch.set(clubFollowerRef, ...) as
appropriate, and update the club document via batch.set or batch.update using
FieldValue.increment(1) / increment(-1) for followersCount instead of reading
followersCount directly, then await batch.commit(); keep the optimistic UI and
rollback logic unchanged but remove the partial-update risk by committing all
three operations inside the batch before resolving success.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/screens/ClubProfileScreen.js`:
- Around line 35-36: The avatar URL currently injects club?.displayName raw
which breaks URLs and can produce name=undefined; update getClubAvatarUrl to
URL-encode the display name and avoid "undefined" by using a safe fallback (e.g.
const name = encodeURIComponent(club?.displayName ?? '') or similar) before
interpolating into the ui-avatars query string, while keeping the existing
club?.photoURL precedence; reference getClubAvatarUrl and club?.displayName when
making the change.

---

Outside diff comments:
In `@app/src/screens/CreateEvent.js`:
- Around line 697-708: The Marker currently remains draggable even when
submission is loading, causing a misleading affordance; update the Marker props
to set draggable={!loading} (instead of hardcoded draggable) and ensure
onDragEnd still guards setCoordinates with the loading flag; also consider
disabling map gestures on the surrounding MapView (e.g., set
scrollEnabled/zoomEnabled/rotateEnabled/pitchEnabled to !loading) so the map and
Marker both reflect the loading lock.

---

Nitpick comments:
In `@app/src/screens/ClubProfileScreen.js`:
- Around line 177-242: In toggleFollow replace the sequential await calls that
touch myFollowingRef, clubFollowerRef and clubRef with a single Firestore
writeBatch so the three mutations are atomic: build a writeBatch(), call
batch.delete(myFollowingRef) / batch.set(myFollowingRef, ...) and
batch.set(clubFollowerRef, ...) as appropriate, and update the club document via
batch.set or batch.update using FieldValue.increment(1) / increment(-1) for
followersCount instead of reading followersCount directly, then await
batch.commit(); keep the optimistic UI and rollback logic unchanged but remove
the partial-update risk by committing all three operations inside the batch
before resolving success.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aeb9779f-fed8-4b17-afaa-5083e7e2a3fe

📥 Commits

Reviewing files that changed from the base of the PR and between dd0675d and 569efe9.

📒 Files selected for processing (9)
  • app/src/components/EventCard.js
  • app/src/components/FeedbackModal.js
  • app/src/components/PremiumInput.js
  • app/src/screens/AttendanceDashboard.js
  • app/src/screens/ClubProfileScreen.js
  • app/src/screens/CreateEvent.js
  • app/src/screens/EventDetail.js
  • app/src/screens/EventRegistrationFormScreen.js
  • app/src/screens/MyEventsScreen.js
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/src/screens/EventRegistrationFormScreen.js
  • app/src/screens/AttendanceDashboard.js
  • app/src/components/EventCard.js
  • app/src/screens/MyEventsScreen.js
  • app/src/components/FeedbackModal.js

Comment thread app/src/screens/ClubProfileScreen.js Outdated
@Richa-2005 Richa-2005 force-pushed the feature/loading-states-async-operations branch from 569efe9 to 7ce23b4 Compare May 31, 2026 16:42
@sonarqubecloud
Copy link
Copy Markdown

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.

Caution

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

⚠️ Outside diff range comments (2)
app/src/components/EventCard.js (2)

93-121: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update lookingForBuddy locally to avoid stale switch state during async toggle.

Line 322 uses a controlled Switch, but Lines 93-121 only persist to Firestore and wait for onSnapshot to reflect the new value. Under latency, the toggle can visually revert/lag even though the action is in progress.

Proposed fix
 const handleToggleBuddy = async value => {
     if (buddyLoading) return;
     if (!user || !event?.id) return;
+    const previousValue = lookingForBuddy;
     try {
         setBuddyLoading(true);
+        setLookingForBuddy(value); // optimistic UI
         const participantRef = doc(db, 'events', event.id, 'participants', user.uid);
         await updateDoc(participantRef, {
             lookingForBuddy: value,
         });
@@
     } catch (error) {
         console.error('Error updating buddy preference:', error);
+        setLookingForBuddy(previousValue); // rollback on failure
         Alert.alert('Error', 'Failed to update buddy preference.');
     } finally {
         setBuddyLoading(false);
     }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/EventCard.js` around lines 93 - 121, The Switch visually
lags because handleToggleBuddy only persists the change to Firestore; update the
local state that the Switch is controlled by immediately (optimistic update)
before calling updateDoc so the UI reflects the new value, then call
setBuddyLoading(true), await updateDoc(participantRef, { lookingForBuddy: value
}), and in the catch block revert that local state if the update fails;
reference handleToggleBuddy, the controlled Switch state (e.g.,
localLookingForBuddy / setLocalLookingForBuddy), participantRef/updateDoc, and
ensure setBuddyLoading(false) remains in finally.

159-170: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always evict profileRequestCache after promise resolution, even when user doc is missing.

At Line 163, cache eviction only happens when snap.exists() is true. For missing docs, the resolved promise stays in profileRequestCache forever, which can accumulate stale misses and prevent future re-fetch if the profile is later created.

Proposed fix
 profileRequestCache
     .get(event.ownerId)
     .then(snap => {
+        profileRequestCache.delete(event.ownerId);
         if (snap.exists()) {
             const data = snap.data();
             profileCache.set(event.ownerId, { data, cachedAt: Date.now() });
-            profileRequestCache.delete(event.ownerId);
             if (!cancelled) {
                 setHostName(data.displayName || event.organization || 'Club Name');
             }
         }
     })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/EventCard.js` around lines 159 - 170, The promise
resolution path in EventCard.js only deletes profileRequestCache for
event.ownerId when snap.exists() is true, leaving misses cached forever; update
the .then handler (the block that handles snap => { ... }) so that
profileRequestCache.delete(event.ownerId) is always called regardless of whether
snap.exists() is true (or move the eviction into a finally-like path), and
ensure you still only call setHostName(data.displayName || event.organization ||
'Club Name') when !cancelled and data exists; keep existing updates to
profileCache and the catch block behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/src/components/EventCard.js`:
- Around line 93-121: The Switch visually lags because handleToggleBuddy only
persists the change to Firestore; update the local state that the Switch is
controlled by immediately (optimistic update) before calling updateDoc so the UI
reflects the new value, then call setBuddyLoading(true), await
updateDoc(participantRef, { lookingForBuddy: value }), and in the catch block
revert that local state if the update fails; reference handleToggleBuddy, the
controlled Switch state (e.g., localLookingForBuddy / setLocalLookingForBuddy),
participantRef/updateDoc, and ensure setBuddyLoading(false) remains in finally.
- Around line 159-170: The promise resolution path in EventCard.js only deletes
profileRequestCache for event.ownerId when snap.exists() is true, leaving misses
cached forever; update the .then handler (the block that handles snap => { ...
}) so that profileRequestCache.delete(event.ownerId) is always called regardless
of whether snap.exists() is true (or move the eviction into a finally-like
path), and ensure you still only call setHostName(data.displayName ||
event.organization || 'Club Name') when !cancelled and data exists; keep
existing updates to profileCache and the catch block behavior intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c8c6254-9f23-4e6f-96b2-09d63dc3e30c

📥 Commits

Reviewing files that changed from the base of the PR and between 569efe9 and 7ce23b4.

📒 Files selected for processing (9)
  • app/src/components/EventCard.js
  • app/src/components/FeedbackModal.js
  • app/src/components/PremiumInput.js
  • app/src/screens/AttendanceDashboard.js
  • app/src/screens/ClubProfileScreen.js
  • app/src/screens/CreateEvent.js
  • app/src/screens/EventDetail.js
  • app/src/screens/EventRegistrationFormScreen.js
  • app/src/screens/MyEventsScreen.js
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/screens/EventRegistrationFormScreen.js
  • app/src/screens/MyEventsScreen.js
  • app/src/screens/ClubProfileScreen.js
  • app/src/components/PremiumInput.js
  • app/src/screens/CreateEvent.js
  • app/src/components/FeedbackModal.js
  • app/src/screens/AttendanceDashboard.js
  • app/src/screens/EventDetail.js

@roshankumar0036singh roshankumar0036singh merged commit 3edd753 into roshankumar0036singh:main May 31, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants