Skip to content

[TASK-15701] Fix: guest invite link flow#1311

Merged
Hugo0 merged 11 commits intopeanut-wallet-devfrom
fix/guest-invite-link
Oct 10, 2025
Merged

[TASK-15701] Fix: guest invite link flow#1311
Hugo0 merged 11 commits intopeanut-wallet-devfrom
fix/guest-invite-link

Conversation

@Zishan-7
Copy link
Contributor

No description provided.

@vercel
Copy link

vercel bot commented Oct 10, 2025

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

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Oct 10, 2025 5:33pm

@notion-workspace
Copy link

🪲 Bug: invites bugs

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds pre-claim and pre-payment invite-accept flows that refresh user state, tightens and centralizes redirect validation/handling (new useLogin hook and getValidRedirectUrl), hardens string trimming, narrows waitlist gating on public paths, and updates a few UI/modal behaviors and invite navigation.

Changes

Cohort / File(s) Summary
Waitlist guard
src/app/(mobile-ui)/layout.tsx
Require !isPublicPath when deciding to show JoinWaitlistPage, preventing waitlist render on public routes.
Claim link: pre-claim invite accept & user refresh
src/components/Claim/Link/Initial.view.tsx
When user lacks app access, call invitesApi.acceptInvite(EInviteType.PAYMENT_LINK) before claim processing; on success call fetchUser(); on failure log/capture and abort claim flow. Applied to main flow and internal handler.
Payment flow: invite acceptance + state sync
src/components/Payment/PaymentForm/index.tsx
Add isAcceptingInvite/inviteError; accept invite before add-money flow when recipient requires app access and balance is insufficient; call fetchUser() after accept; surface invite state in loading/error/routing.
Invite navigation & Invites page
src/components/Common/ActionList.tsx, src/components/Invites/InvitesPage.tsx
ActionList now builds /invite?code=...&redirect_uri=... and dispatches invite actions; InvitesPage reads redirect_uri, uses useLogin, and navigates to /setup?step=signup (with optional redirect_uri) while conditioning login UI on auth state.
New login hook with redirect handling
src/hooks/useLogin.tsx
New useLogin hook exposing handleLoginClick and isLoggingIn; after user becomes available performs sanitized redirect from redirect_uri, saved local redirect, or falls back to /home.
Redirect sanitization & utilities
src/utils/general.utils.ts, src/components/Setup/Views/SetupPasskey.tsx, src/components/Setup/Views/Welcome.tsx, src/components/Global/GuestLoginCta/index.tsx
sanitizeRedirectURL now returns `string
JoinWaitlist / login wiring
src/components/Setup/Views/JoinWaitlist.tsx
Replace ZeroDev-based login wiring with useLogin (handleLoginClick, isLoggingIn); remove component-level redirect effect and post-login routing.
Invite-code safety
src/hooks/useZeroDev.ts
Use optional chaining when checking userInviteCode (userInviteCode?.trim().length > 0) to avoid runtime errors when undefined/null.
Profile: KYC click behavior
src/components/Profile/index.tsx
Identity Verification menu item now always opens the initiate-KYC modal (removed conditional that showed an “already verified” modal).
UI/content tweaks
src/components/Common/ActionList.tsx, src/components/Global/EarlyUserModal/index.tsx
Adjusted invited-by banner alignment/gap and updated EarlyUserModal copy/structure; minor JSX/text changes only.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jjramirezn
  • kushagrasarathe
  • Hugo0

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request has no description provided, leaving no context for the changes and making it impossible to understand the intent or scope from the author’s notes. Please add a concise description summarizing the purpose of these changes, including key areas affected and the motivation behind the guest invite flow fixes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[TASK-15701] Fix: guest invite link flow" concisely and accurately reflects the main focus of the changeset, which centers on repairing the guest invite link flow across multiple components.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/guest-invite-link

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.

@coderabbitai coderabbitai bot added the enhancement New feature or request label Oct 10, 2025
Copy link
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: 0

🧹 Nitpick comments (1)
src/components/Claim/Link/Initial.view.tsx (1)

169-187: Consider extracting invite acceptance logic to reduce duplication.

The pre-claim invite acceptance logic is correctly implemented with proper error handling and background user refresh. However, this logic is very similar to the handleAcceptInvite function in src/components/Payment/PaymentForm/index.tsx (lines 322-337). Both construct the invite code, call invitesApi.acceptInvite with EInviteType.PAYMENT_LINK, trigger fetchUser() in the background, and handle errors similarly.

Consider extracting this logic into a shared utility function or custom hook (e.g., useInviteAcceptance) to eliminate duplication and ensure consistent invite handling across both flows:

// hooks/useInviteAcceptance.ts
export const useInviteAcceptance = () => {
  const { fetchUser } = useAuth()
  
  const acceptInvite = async (username: string): Promise<{ success: boolean; error?: string }> => {
    try {
      const inviteCode = `${username}INVITESYOU`
      await invitesApi.acceptInvite(inviteCode, EInviteType.PAYMENT_LINK)
      fetchUser() // Background refresh
      return { success: true }
    } catch (error) {
      Sentry.captureException(error)
      console.error('Failed to accept invite', error)
      return { 
        success: false, 
        error: 'Something went wrong. Please try again or contact support.' 
      }
    }
  }
  
  return { acceptInvite }
}

Then use it in both components:

const { acceptInvite } = useInviteAcceptance()

// In claim flow
if (!user?.user.hasAppAccess) {
  const result = await acceptInvite(claimLinkData.sender.username)
  if (!result.success) {
    setErrorState({ showError: true, errorMessage: result.error! })
    setLoadingState('Idle')
    return
  }
}

// In payment flow
const result = await acceptInvite(recipient?.identifier)
if (!result.success) {
  setInviteError(true)
  return
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2c49ba and 2848213.

📒 Files selected for processing (6)
  • src/app/(mobile-ui)/layout.tsx (1 hunks)
  • src/components/Claim/Link/Initial.view.tsx (3 hunks)
  • src/components/Common/ActionList.tsx (1 hunks)
  • src/components/Invites/InvitesPage.tsx (2 hunks)
  • src/components/Payment/PaymentForm/index.tsx (6 hunks)
  • src/hooks/useZeroDev.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-29T18:34:33.596Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1251
File: src/components/Invites/JoinWaitlistPage.tsx:41-55
Timestamp: 2025-09-29T18:34:33.596Z
Learning: In the JoinWaitlistPage component, after successfully accepting an invite via invitesApi.acceptInvite(), calling fetchUser() is sufficient to update the user state and automatically display the app. No manual navigation to /home or other pages is required since the user is already on the home page and the app will be displayed once user.hasAppAccess is updated.

Applied to files:

  • src/app/(mobile-ui)/layout.tsx
  • src/components/Claim/Link/Initial.view.tsx
🧬 Code graph analysis (2)
src/components/Claim/Link/Initial.view.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
src/redux/slices/send-flow-slice.ts (1)
  • setErrorState (89-91)
src/components/Payment/PaymentForm/index.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (6)
src/components/Invites/InvitesPage.tsx (1)

21-21: LGTM!

The redirect_uri extraction and conditional navigation logic is implemented correctly. The URI is properly encoded before being passed to the setup page, which prevents injection issues and ensures the redirect context is preserved through the signup flow.

Also applies to: 42-47

src/components/Common/ActionList.tsx (1)

207-207: LGTM!

The updated navigation correctly constructs the invite URL with both the invite code and the redirect_uri. This ensures that users who accept invites through payment links can return to the original flow context after signup.

src/hooks/useZeroDev.ts (1)

64-64: LGTM!

The added safety guard prevents potential runtime errors when userInviteCode is null or undefined. This defensive check ensures trim() is only called on valid string values.

src/app/(mobile-ui)/layout.tsx (1)

96-96: LGTM!

The added !isPublicPath condition correctly prevents the waitlist page from being shown on public paths like claim and invite pages. This ensures that users can access these public flows even without app access, which is essential for the guest invite functionality.

Based on learnings.

src/components/Payment/PaymentForm/index.tsx (2)

96-97: LGTM with a note on duplication.

The invite acceptance flow is correctly integrated into the payment process with proper error handling and state management. The logic accepts the invite before proceeding to add money when the user lacks app access, and fetchUser() is called in the background to refresh user state.

Note: As mentioned in the review for src/components/Claim/Link/Initial.view.tsx, consider extracting the invite acceptance logic into a shared utility to reduce duplication between this component and the claim flow component.

Also applies to: 322-337, 340-348


687-687: LGTM!

The button loading state correctly reflects both isAcceptingInvite and isProcessing, providing accurate feedback during invite acceptance and payment processing.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2848213 and 6533707.

📒 Files selected for processing (3)
  • src/components/Invites/InvitesPage.tsx (3 hunks)
  • src/components/Setup/Views/JoinWaitlist.tsx (3 hunks)
  • src/hooks/useLogin.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/Invites/InvitesPage.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/hooks/useLogin.tsx (1)
  • useLogin (7-40)
src/hooks/useLogin.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/hooks/useZeroDev.ts (1)
  • useZeroDev (36-187)
src/utils/general.utils.ts (2)
  • getFromLocalStorage (112-134)
  • sanitizeRedirectURL (1217-1229)
src/components/Setup/Views/JoinWaitlist.tsx (1)
src/hooks/useLogin.tsx (1)
  • useLogin (7-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview

Copy link
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)
src/components/Profile/index.tsx (1)

21-21: Unused state: isKycApprovedModalOpen is no longer set.

With the removal of the conditional logic at line 78, this state variable is never set to true, making it and the associated modal (lines 134-148) effectively dead code.

If the conditional logic is restored, this state will be used again. Otherwise, consider removing both the state and the unused modal.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6533707 and 471f1d9.

📒 Files selected for processing (2)
  • src/components/Common/ActionList.tsx (2 hunks)
  • src/components/Profile/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Common/ActionList.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 471f1d9 and fe8c9fc.

📒 Files selected for processing (5)
  • src/components/Global/GuestLoginCta/index.tsx (1 hunks)
  • src/components/Setup/Views/SetupPasskey.tsx (1 hunks)
  • src/components/Setup/Views/Welcome.tsx (1 hunks)
  • src/hooks/useLogin.tsx (1 hunks)
  • src/utils/general.utils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/Setup/Views/Welcome.tsx (1)
src/utils/general.utils.ts (1)
  • sanitizeRedirectURL (1217-1237)
src/hooks/useLogin.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/hooks/useZeroDev.ts (1)
  • useZeroDev (36-187)
src/utils/general.utils.ts (2)
  • getFromLocalStorage (112-134)
  • sanitizeRedirectURL (1217-1237)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (5)
src/hooks/useLogin.tsx (1)

18-30: Open redirect vulnerability has been addressed.

The guarded redirect handling now properly validates redirect_uri via sanitizeRedirectURL and only redirects when the result is truthy. This successfully blocks external redirects as previously flagged.

src/components/Setup/Views/Welcome.tsx (1)

27-33: LGTM!

The guarded redirect properly validates redirect_uri via sanitizeRedirectURL and only routes when the result is truthy, falling back to /home for invalid or external URLs.

src/components/Global/GuestLoginCta/index.tsx (1)

46-50: LGTM!

The guarded redirect correctly validates the redirect_uri via sanitizeRedirectURL and only navigates when the result is truthy. Invalid redirects keep the user on the current page, which is appropriate behavior for this context.

src/components/Setup/Views/SetupPasskey.tsx (1)

51-57: LGTM!

The guarded redirect with early return is correct. It validates redirect_uri via sanitizeRedirectURL, routes when valid, and falls through to the localStorage-based redirect logic when invalid. This pattern fits well with the subsequent redirect handling below.

src/utils/general.utils.ts (1)

1217-1237: LGTM! Secure redirect sanitization.

The updated sanitizeRedirectURL function properly guards against open redirects by:

  1. Returning string | null instead of always returning a string
  2. Rejecting off-origin URLs (line 1225)
  3. Rejecting protocol-relative URLs like //evil.com (line 1228)
  4. Rejecting any relative path containing a protocol (line 1230)

This implementation successfully addresses the security concerns around redirect handling across the application.

Copy link
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

♻️ Duplicate comments (1)
src/hooks/useLogin.tsx (1)

37-40: Move localStorage removal after successful validation.

Line 38 removes the stored redirect before validating it at line 39. If getValidRedirectUrl returns the fallback (meaning the stored URL was invalid), you've discarded the stored value without successfully using it.

Apply this diff to only remove the item when successfully using it:

             } else if (localStorageRedirect) {
-                localStorage.removeItem('redirect')
                 const validRedirectUrl = getValidRedirectUrl(String(localStorageRedirect), '/home')
-                router.push(validRedirectUrl)
+                if (validRedirectUrl !== '/home') {
+                    localStorage.removeItem('redirect')
+                    router.push(validRedirectUrl)
+                } else {
+                    // Invalid redirect, keep it and go to home
+                    router.push('/home')
+                }

Based on past review comments.

🧹 Nitpick comments (4)
src/utils/general.utils.ts (1)

1217-1238: Consider using console.warn for rejected URLs.

Line 1224 uses console.log to record off-origin URL rejections. For better visibility in production logs and to align with Hugo0's previous suggestion, consider using console.warn or Sentry.captureMessage instead.

Apply this diff:

-        console.log('Rejecting off-origin URL:', redirectUrl)
+        console.warn('Rejecting off-origin URL:', redirectUrl)
src/hooks/useLogin.tsx (2)

47-49: Add error handling to login flow.

The handleLoginClick function doesn't catch errors from handleLogin(). If passkey login fails (e.g., user cancels, no passkey found), the error will bubble up uncaught, potentially breaking the component.

Apply this diff to add error handling:

     const handleLoginClick = async () => {
-        await handleLogin()
+        try {
+            await handleLogin()
+        } catch (error) {
+            // Error already handled by useZeroDev, but we should re-throw
+            // so calling code can display appropriate UI feedback
+            throw error
+        }
     }

Or alternatively, if you want to handle errors locally:

const handleLoginClick = async () => {
    try {
        await handleLogin()
    } catch (error) {
        console.error('Login error:', error)
        // Optionally show user feedback here
    }
}

30-45: Limit useEffect dependencies to redirect_uri (avoid unintended re-triggers)

  • Extract redirect_uri outside the effect and list only it (rather than searchParams) in the dependency array
  • Or guard the redirect logic with a ref flag so it only runs once after login
src/components/Global/EarlyUserModal/index.tsx (1)

42-47: Remove commented-out code.

The commented block contains the old content structure and should be removed to reduce clutter, as the new consolidated message on lines 39-40 replaces it.

Apply this diff:

-                        {/* <span className="mt-2 block">
-                            <b>Friends you invite: </b> you earn a share of their fees.
-                        </span>
-                        <span className="block">
-                            <b> Their invites: </b> you earn a smaller share, too.
-                        </span> */}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe8c9fc and 142248c.

📒 Files selected for processing (7)
  • src/components/Global/EarlyUserModal/index.tsx (2 hunks)
  • src/components/Global/GuestLoginCta/index.tsx (1 hunks)
  • src/components/Payment/PaymentForm/index.tsx (6 hunks)
  • src/components/Setup/Views/SetupPasskey.tsx (3 hunks)
  • src/hooks/useLogin.tsx (1 hunks)
  • src/hooks/useZeroDev.ts (1 hunks)
  • src/utils/general.utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/useZeroDev.ts
  • src/components/Setup/Views/SetupPasskey.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-24T12:45:22.708Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#478
File: src/components/Request/Create/Views/Initial.view.tsx:169-176
Timestamp: 2024-10-24T12:45:22.708Z
Learning: When calling `handleOnNext` in `src/components/Request/Create/Views/Initial.view.tsx`, it's acceptable to duplicate parameter lists for readability instead of refactoring to avoid duplication.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
🧬 Code graph analysis (2)
src/hooks/useLogin.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/hooks/useZeroDev.ts (1)
  • useZeroDev (36-187)
src/utils/general.utils.ts (2)
  • getFromLocalStorage (112-134)
  • getValidRedirectUrl (1342-1358)
src/components/Payment/PaymentForm/index.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (2)
src/utils/general.utils.ts (1)

1342-1358: LGTM! Good abstraction for redirect validation.

The getValidRedirectUrl helper consolidates decoding, sanitization, and fallback logic into a single, reusable function. The error handling for URI decoding and the fallback pattern are appropriate.

src/hooks/useLogin.tsx (1)

7-21: LGTM! Clear documentation.

The JSDoc provides good context for the hook's purpose and behavior. This addresses Hugo0's previous request for file-level documentation.

Copy link
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.

Resolving as requested.

Copy link
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.

Marking discussion as resolved

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/components/Claim/Link/Initial.view.tsx (1)

161-168: Prevent stuck loading on early return

setLoadingState('Loading') is set on Line 161, but if recipient.address === '' you return without resetting it.

Apply:

-            if (recipient.address === '') return
+            if (recipient.address === '') {
+                setLoadingState('Idle')
+                return
+            }
♻️ Duplicate comments (4)
src/components/Global/GuestLoginCta/index.tsx (1)

51-51: Wrap the message in an Error object for proper Sentry stack traces.

While the Sentry.captureException call was correctly moved to the else block (addressing the false-positive issue from the previous review), the message is still passed as a string rather than an Error object. This degrades the quality of error reports in Sentry by omitting stack trace information.

Apply this diff to complete the fix:

-                    Sentry.captureException(`Invalid redirect URL ${redirect_uri}`)
+                    Sentry.captureException(new Error(`Invalid redirect URL: ${redirect_uri}`))
src/components/Claim/Link/Initial.view.tsx (1)

169-196: Avoid duplicating invite-accept logic across flows

This logic mirrors PaymentForm’s invite pre-accept. Extract a small shared helper/hook (e.g., useInviteAcceptance) to keep behavior consistent and reduce future fixes in multiple places.

src/components/Payment/PaymentForm/index.tsx (2)

322-322: DRY: centralize invite acceptance logic

This duplicates the pre-accept logic used in the claim flow. Extract a small helper/hook to keep behavior consistent and reduce future fixes in multiple places.


322-344: Return a boolean from handleAcceptInvite; reset state in finally; don’t block on fetchUser

  • Caller can’t know if acceptance failed; it always continues.
  • Clear inviteError on success; set isAcceptingInvite in finally.
  • Guard missing username.
  • Keep fetchUser non-blocking to avoid UI stall.

Apply:

-    const handleAcceptInvite = async () => {
-        try {
-            setIsAcceptingInvite(true)
-            const inviteCode = `${recipient?.identifier}INVITESYOU`
-            const result = await invitesApi.acceptInvite(inviteCode, EInviteType.PAYMENT_LINK)
-
-            if (!result.success) {
-                console.error('Failed to accept invite')
-                setInviteError(true)
-                return false
-            }
-
-            // fetch user so that we have the latest state and user can access the app.
-            // We dont need to wait for this, can happen in background.
-            await fetchUser()
-            setIsAcceptingInvite(false)
-        } catch (error) {
-            console.error('Failed to accept invite', error)
-            setInviteError(true)
-            setIsAcceptingInvite(false)
-            return
-        }
-    }
+    const handleAcceptInvite = async (): Promise<boolean> => {
+        setIsAcceptingInvite(true)
+        try {
+            if (recipient?.recipientType !== 'USERNAME' || !recipient?.identifier) {
+                return true
+            }
+            const inviteCode = `${recipient.identifier}INVITESYOU`
+            const { success } = await invitesApi.acceptInvite(inviteCode, EInviteType.PAYMENT_LINK)
+            if (!success) {
+                console.error('Failed to accept invite')
+                setInviteError(true)
+                return false
+            }
+            setInviteError(false)
+            void fetchUser()
+            return true
+        } catch (error) {
+            console.error('Failed to accept invite', error)
+            setInviteError(true)
+            return false
+        } finally {
+            setIsAcceptingInvite(false)
+        }
+    }

Based on learnings

🧹 Nitpick comments (2)
src/components/Global/GuestLoginCta/index.tsx (1)

46-52: Replace sanitizeRedirectURL with getValidRedirectUrl
Use const validRedirectUrl = getValidRedirectUrl(redirect_uri, '/home') and router.push(validRedirectUrl) to centralize validation and fallback. If remaining on-page is intentional, add a comment explaining why this flow differs.

src/components/Payment/PaymentForm/index.tsx (1)

439-458: Stabilize handleAcceptInvite to avoid stale closures (optional)

Wrap handleAcceptInvite in useCallback with [recipient?.identifier, recipient?.recipientType, fetchUser] and keep it in the dependency list. This avoids unnecessary re-creations and subtle stale state.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 142248c and d8bc95c.

📒 Files selected for processing (4)
  • src/components/Claim/Link/Initial.view.tsx (3 hunks)
  • src/components/Global/EarlyUserModal/index.tsx (1 hunks)
  • src/components/Global/GuestLoginCta/index.tsx (1 hunks)
  • src/components/Payment/PaymentForm/index.tsx (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/Global/EarlyUserModal/index.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-29T18:34:33.596Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1251
File: src/components/Invites/JoinWaitlistPage.tsx:41-55
Timestamp: 2025-09-29T18:34:33.596Z
Learning: In the JoinWaitlistPage component, after successfully accepting an invite via invitesApi.acceptInvite(), calling fetchUser() is sufficient to update the user state and automatically display the app. No manual navigation to /home or other pages is required since the user is already on the home page and the app will be displayed once user.hasAppAccess is updated.

Applied to files:

  • src/components/Claim/Link/Initial.view.tsx
📚 Learning: 2024-10-24T12:45:22.708Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#478
File: src/components/Request/Create/Views/Initial.view.tsx:169-176
Timestamp: 2024-10-24T12:45:22.708Z
Learning: When calling `handleOnNext` in `src/components/Request/Create/Views/Initial.view.tsx`, it's acceptable to duplicate parameter lists for readability instead of refactoring to avoid duplication.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-04-29T19:36:38.121Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#823
File: src/context/kernelClient.context.tsx:79-86
Timestamp: 2025-04-29T19:36:38.121Z
Learning: When Hugo0 asks to "resolve coderabbit comments", they want to acknowledge the comment without necessarily implementing the suggested changes, as the current implementation might be intentional for their specific use case.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
🧬 Code graph analysis (2)
src/components/Claim/Link/Initial.view.tsx (3)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
src/redux/slices/send-flow-slice.ts (1)
  • setErrorState (89-91)
src/components/Payment/PaymentForm/index.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview
🔇 Additional comments (2)
src/components/Global/GuestLoginCta/index.tsx (1)

37-39: Verify the guard clause behavior when user already has a passkey.

The guard clause prevents login attempts when passkeyAddress already exists, and the button is disabled in this state (line 69). However, the component continues to render the login CTA even when the user is already authenticated. The useEffect on lines 24-28 only logs this condition without taking further action.

Confirm this is the intended behavior. If users with an existing passkey should not see this component at all, consider either:

  1. Conditionally rendering the entire component based on authentication state in the parent, or
  2. Adding an auto-redirect in the useEffect to move authenticated users to an appropriate page (e.g., /home).

If showing a disabled login button to authenticated users is intentional (e.g., for consistent UI layout), document this design decision with a comment.

src/components/Payment/PaymentForm/index.tsx (1)

36-37: LGTM on imports, auth usage, and button loading

  • invitesApi/EInviteType imports correct.
  • useAuth now returns fetchUser; usage consistent.
  • Button shows loading during invite acceptance.

Also applies to: 71-71, 696-696

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/components/Claim/Link/Initial.view.tsx (1)

169-205: Don’t treat “missing user” as “no app access”

if (!user?.user.hasAppAccess) also matches when user is undefined, so guests try to accept the invite without credentials, hit the invite API with no JWT, and the flow aborts with the generic error. Align with the other call sites and gate on user?.user?.hasAppAccess === false so we only attempt acceptance once we actually have a user object.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8bc95c and beeb953.

📒 Files selected for processing (2)
  • src/components/Claim/Link/Initial.view.tsx (3 hunks)
  • src/components/Payment/PaymentForm/index.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-29T18:34:33.596Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1251
File: src/components/Invites/JoinWaitlistPage.tsx:41-55
Timestamp: 2025-09-29T18:34:33.596Z
Learning: In the JoinWaitlistPage component, after successfully accepting an invite via invitesApi.acceptInvite(), calling fetchUser() is sufficient to update the user state and automatically display the app. No manual navigation to /home or other pages is required since the user is already on the home page and the app will be displayed once user.hasAppAccess is updated.

Applied to files:

  • src/components/Claim/Link/Initial.view.tsx
📚 Learning: 2024-10-24T12:45:22.708Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#478
File: src/components/Request/Create/Views/Initial.view.tsx:169-176
Timestamp: 2024-10-24T12:45:22.708Z
Learning: When calling `handleOnNext` in `src/components/Request/Create/Views/Initial.view.tsx`, it's acceptable to duplicate parameter lists for readability instead of refactoring to avoid duplication.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-04-29T19:36:38.121Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#823
File: src/context/kernelClient.context.tsx:79-86
Timestamp: 2025-04-29T19:36:38.121Z
Learning: When Hugo0 asks to "resolve coderabbit comments", they want to acknowledge the comment without necessarily implementing the suggested changes, as the current implementation might be intentional for their specific use case.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
🧬 Code graph analysis (2)
src/components/Claim/Link/Initial.view.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
src/components/Payment/PaymentForm/index.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview

Copy link
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)
src/components/Claim/Link/Initial.view.tsx (1)

161-169: Prevent spinner lock when recipient is empty

You set Loading and then return early on empty recipient, leaving the view stuck. Reset loading (or guard before setting it).

Apply:

-            setLoadingState('Loading')
+            setLoadingState('Loading')
@@
-            if (recipient.address === '') return
+            if (recipient.address === '') {
+                setLoadingState('Idle')
+                return
+            }
♻️ Duplicate comments (2)
src/components/Payment/PaymentForm/index.tsx (1)

349-352: Let handleAcceptInvite own inviteError reset; drop manual clearing here

Redundant clearing risks masking state and isn’t needed if success path resets inviteError.

Apply:

-        // clear invite error
-        if (inviteError) {
-            setInviteError(false)
-        }
src/components/Claim/Link/Initial.view.tsx (1)

169-196: Harden invite-accept: strict guard, Sentry on failure, and non-blocking refresh

  • Use user?.user?.hasAppAccess === false (don’t treat undefined as false).
  • Capture unsuccessful accept to Sentry.
  • Explicitly background refresh via void fetchUser().

Apply:

-            // If the user doesn't have app access, accept the invite before claiming the link
-            if (!user?.user.hasAppAccess) {
+            // If the user doesn't have app access, accept the invite before claiming the link
+            if (user?.user?.hasAppAccess === false) {
@@
-                    const inviteCode = `${inviterUsername}INVITESYOU`
-                    const result = await invitesApi.acceptInvite(inviteCode, EInviteType.PAYMENT_LINK)
-                    if (!result.success) {
-                        console.error('Failed to accept invite')
+                    const inviteCode = `${inviterUsername}INVITESYOU`
+                    const { success } = await invitesApi.acceptInvite(inviteCode, EInviteType.PAYMENT_LINK)
+                    if (!success) {
+                        Sentry.captureMessage('Invite acceptance unsuccessful', { level: 'warning' })
                         setErrorState({
                             showError: true,
                             errorMessage: 'Something went wrong. Please try again or contact support.',
                         })
                         setLoadingState('Idle')
                         return
                     }
@@
-                    // fetch user so that we have the latest state and user can access the app.
-                    // We dont need to wait for this, can happen in background.
-                    fetchUser()
+                    // Refresh user in background
+                    void fetchUser()

Based on learnings

🧹 Nitpick comments (1)
src/components/Payment/PaymentForm/index.tsx (1)

355-358: Use strict hasAppAccess check to avoid unintended invite attempts

Avoid treating undefined as false.

Apply:

-            if (recipient.recipientType === 'USERNAME' && !user?.user.hasAppAccess) {
+            if (recipient.recipientType === 'USERNAME' && user?.user?.hasAppAccess === false) {

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between beeb953 and f6f4154.

📒 Files selected for processing (2)
  • src/components/Claim/Link/Initial.view.tsx (3 hunks)
  • src/components/Payment/PaymentForm/index.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-10-24T12:45:22.708Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#478
File: src/components/Request/Create/Views/Initial.view.tsx:169-176
Timestamp: 2024-10-24T12:45:22.708Z
Learning: When calling `handleOnNext` in `src/components/Request/Create/Views/Initial.view.tsx`, it's acceptable to duplicate parameter lists for readability instead of refactoring to avoid duplication.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-04-29T19:36:38.121Z
Learnt from: Hugo0
PR: peanutprotocol/peanut-ui#823
File: src/context/kernelClient.context.tsx:79-86
Timestamp: 2025-04-29T19:36:38.121Z
Learning: When Hugo0 asks to "resolve coderabbit comments", they want to acknowledge the comment without necessarily implementing the suggested changes, as the current implementation might be intentional for their specific use case.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2024-11-18T21:36:11.486Z
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#535
File: src/components/Claim/Claim.tsx:142-146
Timestamp: 2024-11-18T21:36:11.486Z
Learning: In `src/components/Claim/Claim.tsx`, external calls like token price fetching and cross-chain details retrieval are already encapsulated within existing `try...catch` blocks, so additional error handling may be unnecessary.

Applied to files:

  • src/components/Payment/PaymentForm/index.tsx
📚 Learning: 2025-09-29T18:34:33.596Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1251
File: src/components/Invites/JoinWaitlistPage.tsx:41-55
Timestamp: 2025-09-29T18:34:33.596Z
Learning: In the JoinWaitlistPage component, after successfully accepting an invite via invitesApi.acceptInvite(), calling fetchUser() is sufficient to update the user state and automatically display the app. No manual navigation to /home or other pages is required since the user is already on the home page and the app will be displayed once user.hasAppAccess is updated.

Applied to files:

  • src/components/Claim/Link/Initial.view.tsx
🧬 Code graph analysis (2)
src/components/Payment/PaymentForm/index.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
src/components/Claim/Link/Initial.view.tsx (2)
src/context/authContext.tsx (1)
  • useAuth (191-197)
src/services/invites.ts (1)
  • invitesApi (7-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview

@Hugo0 Hugo0 merged commit 51000ef into peanut-wallet-dev Oct 10, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants