Skip to content

Hotfix/crisp ios issue#1403

Open
Hugo0 wants to merge 4 commits intomainfrom
hotfix/crisp-ios-issue
Open

Hotfix/crisp ios issue#1403
Hugo0 wants to merge 4 commits intomainfrom
hotfix/crisp-ios-issue

Conversation

@Hugo0
Copy link
Contributor

@Hugo0 Hugo0 commented Nov 5, 2025

Problem: Support drawer blank forever on iOS
Fix: Removed Suspense + manual URL params + device-specific timeouts
Status: Ready to test on iOS device

What Was Wrong

iOS Safari + Suspense + useSearchParams = streaming deadlock → blank screen forever

@vercel
Copy link

vercel bot commented Nov 5, 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 Nov 5, 2025 2:17pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Replaces local iframe readiness/polling logic with a shared CrispIframe component and useCrispIframeReady hook; refactors mobile support page and SupportDrawer to use the component; rewrites crisp-proxy page to use manual URL parsing, explicit polling + timeout, runtime config, and parent-ready notification with cleanup.

Changes

Cohort / File(s) Summary
New hook
src/hooks/useCrispIframeReady.ts
Adds exported CrispIframeState interface and useCrispIframeReady(enabled?: boolean) hook that manages CRISP_READY messages, origin validation, device-aware timeouts (8s iOS, 3s others), error state, retry semantics, logging, and cleanup.
New shared component
src/components/Global/CrispIframe/index.tsx
New CrispIframe component (crispProxyUrl, enabled?) using useCrispIframeReady; shows PeanutLoading while not ready, renders iframe when ready, displays an error overlay with retry and mailto support, and handles component-level errors.
Support UI integration
src/app/(mobile-ui)/support/page.tsx, src/components/Global/SupportDrawer/index.tsx
Replace local loading state, message listeners, and inline iframe rendering with CrispIframe; remove previous loading overlays, polling, and direct CRISP_READY handling from these components.
Crisp proxy page
src/app/crisp-proxy/page.tsx
Consolidates proxy into CrispProxyPage: manual URL param parsing (no useSearchParams/Suspense), notifyParentReady messaging, CRISP_RUNTIME_CONFIG, polling for Crisp availability with a 10s timeout, initialization via setAllData, and explicit cleanup of listeners/intervals/timeouts.
Debug/test page
src/app/(mobile-ui)/support-test/page.tsx
Adds a minimal client-side page for iOS debugging that displays user agent info and links to /support.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas needing closer review:
    • src/app/crisp-proxy/page.tsx — URL parsing, polling vs timeout interplay, notifyParentReady timing, and cleanup logic.
    • src/hooks/useCrispIframeReady.ts — origin validation, device timeout values, retry semantics, and event listener/timeout cleanup.
    • src/components/Global/CrispIframe/index.tsx — correct use of the hook, fallback/error UI, and safe handling of hook initialization errors.

Possibly related PRs

Suggested reviewers

  • kushagrasarathe

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title refers to an iOS-specific issue but lacks specificity about what the fix addresses; a developer scanning history would need to read the description to understand it concerns the support drawer blank-screen problem. Consider a more descriptive title like 'Fix iOS support drawer blank screen with Suspense removal and device-aware timeouts' to better communicate the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the problem (Suspense + useSearchParams deadlock on iOS), the solution approach (removing Suspense, manual URL parsing, device-specific timeouts), and current status, directly relating to the changeset.
✨ 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 hotfix/crisp-ios-issue

Comment @coderabbitai help to get the list of available commands and usage tips.

@Hugo0
Copy link
Contributor Author

Hugo0 commented Nov 5, 2025

@coderabbitai review in depth.

  1. Does this fix the iOS support chat not rendering issue?
  2. Are we breaking any functionality or bringing in any new bug risks?
  3. Evaluate code quality.

@coderabbitai coderabbitai bot added the enhancement New feature or request label Nov 5, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details
gh pr view 1403 --json title,body,files

# Get the diff for all changed files
gh pr diff 1403

Length of output: 15797


@Hugo0, here's my in-depth review:

1. ✅ Does this fix the iOS support chat not rendering issue?

Yes, this should fix the issue. The root cause is correctly identified and addressed:

  • Removed Suspense boundary: iOS Safari has known issues with Suspense causing streaming deadlocks when combined with useSearchParams()
  • Manual URL parsing: Switching to manual URLSearchParams avoids the hydration issues
  • iOS-specific timeout (8s vs 3s): Accounts for iOS's stricter security policies and slower script execution in iframe/PWA contexts
  • Error handling with retry: Provides fallback if loading fails

The fix targets the exact problem: iOS Safari streaming deadlock.

2. ⚠️ Breaking functionality & new bug risks

No breaking changes, but some risks to address:

Critical Issues:

  1. Inconsistent support email addresses:

    • Line 30 in support/page.tsx: support@peanut.me
    • Line 37 in SupportDrawer/index.tsx: hello@peanut.to
    • Which is the correct email?
  2. Confusing state combination (Line 62 in useCrispIframeReady.ts):

    setHasError(true)
    setIsReady(true) // Shows iframe anyway

    Both hasError and isReady can be true simultaneously. This is intentional (to show iframe for manual interaction) but the logic is confusing.

  3. Premature ready signal (Line 123 in crisp-proxy/page.tsx):

    setTimeout(() => {
        clearInterval(checkCrisp)
        // Notify parent even if Crisp failed to load to prevent infinite spinner
        notifyParentReady()
    }, 10000)

    Sends ready signal even if Crisp never loads. This could hide actual failures.

Minor Issues:

  1. Unused state: attemptCount triggers re-runs but isn't used for anything else (no max retry limit, no exponential backoff)

  2. Hook doesn't reset state: When enabled becomes false, the hook doesn't reset isReady or hasError

  3. No error boundary: No React error boundary around iframe components to catch rendering errors

3. 📊 Code quality evaluation

Strengths:

✅ Good separation of concerns with useCrispIframeReady hook
✅ Device-specific timeouts show platform awareness
✅ Proper cleanup of event listeners and timers
✅ Security check: event.origin !== window.location.origin
✅ Comprehensive error handling with user-friendly messages
✅ Good documentation in comments

Issues:

High Priority:

  • ❌ Email address inconsistency (user-facing)
  • ❌ Missing TypeScript types for Crisp SDK (window.$crisp)
  • ❌ Confusing state machine (hasError + isReady both true)

Medium Priority:

  • ⚠️ The retry mechanism doesn't have max attempts or exponential backoff
  • ⚠️ notifyParentReady is called even on failure
  • ⚠️ No error boundary for iframe failures

Low Priority:

  • 💡 Could use useReducer for more predictable state management
  • 💡 Could memoize crispProxyUrl if userData/prefilledMessage change frequently
  • 💡 The 10s timeout in crisp-proxy seems arbitrary (should match or exceed max hook timeout)

Recommendations:


🏁 Script executed:

#!/bin/bash
# Check if there are other places using these email addresses
rg -n "peanut\.to|peanut\.me" --type=tsx --type=ts -g '!node_modules' -C 2

# Check if useDeviceType hook exists and is properly implemented
fd -e ts -e tsx "useGetDeviceType" --exec cat {}

# Look for any other Crisp-related error handling or timeouts
rg -n "crisp|CRISP" --type=tsx --type=ts -g '!node_modules' -g '!*.test.*' | head -50

Length of output: 1122


🏁 Script executed:

#!/bin/bash
# Check email addresses - use correct type flags
rg -n "peanut\.to|peanut\.me" --type=typescript -C 2 2>/dev/null || rg -n "peanut\.to|peanut\.me" -g '*.ts' -g '*.tsx' -C 2

# Look for Crisp-related code and timeouts
rg -n "crisp|CRISP" -g '*.ts' -g '*.tsx' -g '!node_modules' -g '!*.test.*' | head -80

# Check if there are any existing error boundaries
rg -n "ErrorBoundary|componentDidCatch" -g '*.ts' -g '*.tsx' -g '!node_modules'

Length of output: 18581


@Hugo0, here's the complete in-depth review:

1. ✅ Does this fix the iOS support chat not rendering issue?

Yes, this should fix the issue. The implementation correctly addresses the root cause:

Removed Suspense boundary - Eliminates iOS Safari streaming deadlock
Manual URL parsing - Avoids useSearchParams() hydration issues
Device-specific timeouts - 8s for iOS (vs 3s desktop) accounts for stricter security
Proper iOS detection - Even handles iPadOS 13+ edge case (Macintosh UA + touch)
Error handling with retry - Provides fallback if loading fails

The fix is well-targeted and should resolve the blank screen issue.

2. ⚠️ Breaking functionality & new bug risks

No breaking changes, but 3 bugs found:

🔴 Critical: Email address inconsistency

// SupportDrawer/index.tsx:37-38
<a href="mailto:hello@peanut.to">hello@peanut.to</a>

// support/page.tsx:30
<a href="mailto:hello@peanut.to">support@peanut.me</a>

The href says hello@peanut.to but the display text says support@peanut.me! Which is correct?

🟡 Medium: Confusing state logic

In useCrispIframeReady.ts:62:

setHasError(true)
setIsReady(true) // Shows iframe anyway for manual interaction

Both hasError and isReady are true simultaneously. While intentional, this is confusing. Consider using a state enum instead:

enum LoadState { Loading, Ready, Error }

🟡 Medium: Silent failure signal

In crisp-proxy/page.tsx:123:

setTimeout(() => {
    clearInterval(checkCrisp)
    notifyParentReady() // ⚠️ Sends "ready" even if Crisp never loads
}, 10000)

This hides actual Crisp loading failures from the parent.

Minor issues:

  • ❌ No error boundary for iframe rendering failures
  • ⚠️ attemptCount triggers re-runs but has no max retry limit
  • ⚠️ Hook doesn't reset state when enabled becomes false

3. 📊 Code quality evaluation

Strengths:

  • Excellent separation of concerns with useCrispIframeReady hook
  • Platform-aware timeouts show good attention to detail
  • Proper cleanup of event listeners/timers
  • Security: Origin validation on postMessage
  • Good inline documentation
  • Comprehensive user-facing error messages

Issues:

High Priority:

  1. Email inconsistency (user-facing bug)
  2. Missing TypeScript types for window.$crisp (using any)
  3. Confusing state machine (hasError + isReady both true)

Medium Priority:
4. No max retry attempts or exponential backoff
5. Silent failure in timeout handler
6. No React error boundary

Code quality score: 7/10 (solid implementation marred by email bug and type safety issues)

📋 Recommendations:

Fix the email bug first:

// Use consistent email everywhere
<a href="mailto:hello@peanut.to">hello@peanut.to</a>

Optional improvements:

  1. Add TypeScript types for Crisp SDK
  2. Use state enum instead of boolean flags
  3. Add error boundary around iframe components
  4. Add max retry limit (e.g., 3 attempts)
  5. Don't send ready signal on failure - send error signal instead

Verdict:

The fix will work for iOS but has one critical bug to fix before merging (email inconsistency). Code quality is good overall but could benefit from better type safety and state management.


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.

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 d111a80 and 798963a.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/support/page.tsx (1 hunks)
  • src/app/crisp-proxy/page.tsx (4 hunks)
  • src/components/Global/SupportDrawer/index.tsx (1 hunks)
  • src/hooks/useCrispIframeReady.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.
📚 Learning: 2025-08-07T12:53:50.946Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1072
File: src/app/(setup)/setup/page.tsx:173-175
Timestamp: 2025-08-07T12:53:50.946Z
Learning: In the peanut-ui setup flow at `src/app/(setup)/setup/page.tsx`, when handling unsupported scenarios, both device not supported and browser not supported cases should show the same "Unsupported browser" message using the `UnsupportedBrowserModal` component, rather than having distinct messaging for each scenario.

Applied to files:

  • src/app/(mobile-ui)/support/page.tsx
  • src/components/Global/SupportDrawer/index.tsx
📚 Learning: 2024-10-23T09:38:27.670Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:27.670Z
Learning: In `src/app/request/pay/page.tsx`, if `linkRes` is not OK in the `generateMetadata` function, the desired behavior is to use the standard title and preview image without throwing an error.

Applied to files:

  • src/app/(mobile-ui)/support/page.tsx
📚 Learning: 2025-09-05T07:31:11.396Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1185
File: src/components/Claim/useClaimLink.tsx:14-0
Timestamp: 2025-09-05T07:31:11.396Z
Learning: In the peanut-ui codebase, `window.history.replaceState` is preferred over `router.replace` when immediate/synchronous URL parameter updates are required, as `router.replace` is asynchronous and doesn't guarantee instant URL changes that subsequent code can rely on. This pattern is used consistently across usePaymentInitiator.ts, Confirm.payment.view.tsx, and useClaimLink.tsx.

Applied to files:

  • src/app/crisp-proxy/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: Functions in Next.js that are not marked with "use server" and contain secrets are unsafe to import in client components, as they get bundled into the client JavaScript and can leak environment variables to the browser.

Applied to files:

  • src/app/crisp-proxy/page.tsx
📚 Learning: 2025-09-11T17:46:12.507Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1200
File: src/app/(mobile-ui)/recover-funds/page.tsx:9-9
Timestamp: 2025-09-11T17:46:12.507Z
Learning: In Next.js applications, functions marked with "use server" are server actions that are safe to import in client components. Next.js generates proxy stubs for these functions, ensuring the actual implementation and any secrets (like process.env variables) remain on the server and are not bundled into the client JavaScript.

Applied to files:

  • src/app/crisp-proxy/page.tsx
📚 Learning: 2025-09-15T17:25:12.198Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/hooks/useCreateOnramp.ts:47-49
Timestamp: 2025-09-15T17:25:12.198Z
Learning: Server Actions in Next.js are specifically designed to be called from Client Components. They are asynchronous functions marked with 'use server' that run on the server but can be imported and invoked directly from client-side code. This is a core feature of the Next.js App Router, eliminating the need to create separate API routes for server-side operations called from the client.

Applied to files:

  • src/app/crisp-proxy/page.tsx
📚 Learning: 2024-10-29T16:06:38.812Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 495
File: src/components/Create/useCreateLink.tsx:647-657
Timestamp: 2024-10-29T16:06:38.812Z
Learning: In the React code for `useCreateLink` in `src/components/Create/useCreateLink.tsx`, the `switchNetwork` function used within `useCallback` hooks is stable and does not need to be included in the dependency arrays.

Applied to files:

  • src/app/crisp-proxy/page.tsx
🧬 Code graph analysis (3)
src/app/(mobile-ui)/support/page.tsx (3)
src/hooks/useCrispProxyUrl.ts (1)
  • useCrispProxyUrl (15-69)
src/hooks/useCrispIframeReady.ts (1)
  • useCrispIframeReady (19-76)
src/components/0_Bruddle/Button.tsx (1)
  • Button (78-269)
src/components/Global/SupportDrawer/index.tsx (2)
src/hooks/useCrispIframeReady.ts (1)
  • useCrispIframeReady (19-76)
src/components/0_Bruddle/Button.tsx (1)
  • Button (78-269)
src/hooks/useCrispIframeReady.ts (1)
src/hooks/useGetDeviceType.ts (1)
  • useDeviceType (37-42)
⏰ 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

Comment on lines +27 to +31
<p className="text-sm text-grey-1">
Check your internet connection and try again. If the problem persists, you can email us at{' '}
<a href="mailto:hello@peanut.to" className="text-purple-1 underline">
support@peanut.me
</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix mismatched support email link.

The visible address says support@peanut.me while the link opens hello@peanut.to. Anyone copying the text will reach the wrong inbox. Please align the displayed text with the actual destination.

-                            <a href="mailto:hello@peanut.to" className="text-purple-1 underline">
-                                support@peanut.me
-                            </a>
+                            <a href="mailto:hello@peanut.to" className="text-purple-1 underline">
+                                hello@peanut.to
+                            </a>
📝 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.

Suggested change
<p className="text-sm text-grey-1">
Check your internet connection and try again. If the problem persists, you can email us at{' '}
<a href="mailto:hello@peanut.to" className="text-purple-1 underline">
support@peanut.me
</a>
<p className="text-sm text-grey-1">
Check your internet connection and try again. If the problem persists, you can email us at{' '}
<a href="mailto:hello@peanut.to" className="text-purple-1 underline">
hello@peanut.to
</a>
🤖 Prompt for AI Agents
In src/app/(mobile-ui)/support/page.tsx around lines 27 to 31, the anchor href
uses mailto:hello@peanut.to while the visible text shows support@peanut.me;
update the anchor so the mailto destination matches the displayed address
(change href to mailto:support@peanut.me) to ensure the link sends mail to the
same support address shown to users.

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 798963a and e72c7a6.

📒 Files selected for processing (3)
  • src/app/(mobile-ui)/support/page.tsx (1 hunks)
  • src/components/Global/CrispIframe/index.tsx (1 hunks)
  • src/components/Global/SupportDrawer/index.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:279-279
Timestamp: 2025-08-14T08:02:26.705Z
Learning: For hotfixes in the peanut-ui codebase, prefer generic error messages over specific validation error details until the copy can be reviewed with the team, even when the validation functions return detailed error messages.
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.
📚 Learning: 2025-08-07T12:53:50.946Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1072
File: src/app/(setup)/setup/page.tsx:173-175
Timestamp: 2025-08-07T12:53:50.946Z
Learning: In the peanut-ui setup flow at `src/app/(setup)/setup/page.tsx`, when handling unsupported scenarios, both device not supported and browser not supported cases should show the same "Unsupported browser" message using the `UnsupportedBrowserModal` component, rather than having distinct messaging for each scenario.

Applied to files:

  • src/app/(mobile-ui)/support/page.tsx
📚 Learning: 2025-07-24T13:26:10.290Z
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1014
File: src/components/Claim/Link/Initial.view.tsx:413-413
Timestamp: 2025-07-24T13:26:10.290Z
Learning: In the peanut-ui repository, the change from `${SQUID_API_URL}/route` to `${SQUID_API_URL}/v2/route` in src/components/Claim/Link/Initial.view.tsx was a typo fix, not an API migration, as the codebase was already using Squid API v2.

Applied to files:

  • src/app/(mobile-ui)/support/page.tsx
📚 Learning: 2025-08-14T08:02:26.705Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:279-279
Timestamp: 2025-08-14T08:02:26.705Z
Learning: For hotfixes in the peanut-ui codebase, prefer generic error messages over specific validation error details until the copy can be reviewed with the team, even when the validation functions return detailed error messages.

Applied to files:

  • src/app/(mobile-ui)/support/page.tsx
🧬 Code graph analysis (3)
src/app/(mobile-ui)/support/page.tsx (3)
src/hooks/useCrispUserData.ts (1)
  • useCrispUserData (23-56)
src/hooks/useCrispProxyUrl.ts (1)
  • useCrispProxyUrl (15-69)
src/components/Global/CrispIframe/index.tsx (1)
  • CrispIframe (16-54)
src/components/Global/CrispIframe/index.tsx (3)
src/hooks/useCrispIframeReady.ts (1)
  • useCrispIframeReady (19-76)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/0_Bruddle/Button.tsx (1)
  • Button (78-269)
src/components/Global/SupportDrawer/index.tsx (4)
src/context/SupportModalContext.tsx (1)
  • useSupportModalContext (39-45)
src/hooks/useCrispUserData.ts (1)
  • useCrispUserData (23-56)
src/hooks/useCrispProxyUrl.ts (1)
  • useCrispProxyUrl (15-69)
src/components/Global/CrispIframe/index.tsx (1)
  • CrispIframe (16-54)
⏰ 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/app/(mobile-ui)/support/page.tsx (1)

5-14: Shared CrispIframe integration looks good.

Import and usage align with the shared component; no issues spotted.

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

7-20: Drawer wiring to CrispIframe checks out.

Passing the proxy URL and gating readiness with the drawer’s open state matches the shared hook’s contract.

Comment on lines +27 to +51
<div className="absolute inset-0 z-20 flex flex-col items-center justify-center gap-4 bg-background p-6 text-center">
<div className="space-y-2">
<p className="text-base font-semibold">Having trouble loading support chat</p>
<p className="text-sm text-grey-1">
Check your internet connection and try again. If the problem persists, you can email us at{' '}
<a href="mailto:support@peanut.me" className="text-purple-1 underline">
support@peanut.me
</a>
</p>
</div>
<Button onClick={retry} shadowSize="4" variant="purple" icon="retry" iconSize={16}>
Try again
</Button>
</div>
)}
<iframe
src={crispProxyUrl}
className="h-full w-full"
style={{
height: '100%',
width: '100%',
minHeight: '-webkit-fill-available',
}}
title="Support Chat"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Retry button never reloads the Crisp iframe.

retry only resets hook state; the <iframe> keeps its original instance, so iOS users stuck on a blank load stay stuck even after pressing “Try again.” Please force a remount (or cache-busting source) when retrying so the Crisp proxy actually reloads.

 'use client'
 
+import { useCallback, useState } from 'react'
 import { useCrispIframeReady } from '@/hooks/useCrispIframeReady'
 import PeanutLoading from '../PeanutLoading'
 import { Button } from '@/components/0_Bruddle'
@@
-export const CrispIframe = ({ crispProxyUrl, enabled = true }: CrispIframeProps) => {
-    const { isReady, hasError, retry } = useCrispIframeReady(enabled)
+export const CrispIframe = ({ crispProxyUrl, enabled = true }: CrispIframeProps) => {
+    const [reloadNonce, setReloadNonce] = useState(0)
+    const { isReady, hasError, retry } = useCrispIframeReady(enabled)
+    const handleRetry = useCallback(() => {
+        setReloadNonce((prev) => prev + 1)
+        retry()
+    }, [retry])
@@
-                    <Button onClick={retry} shadowSize="4" variant="purple" icon="retry" iconSize={16}>
+                    <Button onClick={handleRetry} shadowSize="4" variant="purple" icon="retry" iconSize={16}>
                         Try again
                     </Button>
                 </div>
             )}
             <iframe
+                key={reloadNonce}
                 src={crispProxyUrl}
                 className="h-full w-full"
🤖 Prompt for AI Agents
In src/components/Global/CrispIframe/index.tsx around lines 27 to 51, the Retry
button only resets hook state but does not cause the iframe to reload, so on iOS
the original iframe instance remains blank; update the retry handler to force a
remount or cache-bust the iframe src: add a local key or stamp state (e.g.
reloadCounter or srcTimestamp) that is incremented/updated in retry and pass it
as the iframe key or append it as a query param to crispProxyUrl so React
unmounts/remounts the iframe (or the browser treats the URL as new) and the
Crisp proxy actually reloads.

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/Global/CrispIframe/index.tsx (1)

89-89: Retry button doesn't reload the iframe.

As noted in the previous review, the retry function only resets hook state but doesn't cause the iframe to remount or reload its source. Users experiencing a blank Crisp load will remain stuck even after clicking "Try again."

Add a key prop to force iframe remount on retry:

+'use client'
+
+import { useCallback, useState } from 'react'
 import { useCrispIframeReady } from '@/hooks/useCrispIframeReady'
 import PeanutLoading from '../PeanutLoading'
 import { Button } from '@/components/0_Bruddle'
 
 export const CrispIframe = ({ crispProxyUrl, enabled = true }: CrispIframeProps) => {
+    const [iframeKey, setIframeKey] = useState(0)
     const { isReady, hasError, retry } = useCrispIframeReady(enabled)
+    
+    const handleRetry = useCallback(() => {
+        setIframeKey((prev) => prev + 1)
+        retry()
+    }, [retry])
 
     // ... rest of component ...
     
-            <Button onClick={retry} shadowSize="4" variant="purple" icon="retry" iconSize={16}>
+            <Button onClick={handleRetry} shadowSize="4" variant="purple" icon="retry" iconSize={16}>
                 Try again
             </Button>
         </div>
     )}
     <iframe
+        key={iframeKey}
         src={crispProxyUrl}
🧹 Nitpick comments (1)
src/components/Global/CrispIframe/index.tsx (1)

32-42: Consider consolidating debug logs.

The two separate useEffect hooks for logging serve different purposes but could be combined for cleaner code. Given this is debug instrumentation for the iOS fix, the current approach is acceptable.

If you prefer, consolidate into one effect:

-    useEffect(() => {
-        console.log('[CrispIframe] Component mounted', {
-            enabled,
-            crispProxyUrl,
-            userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'SSR',
-        })
-    }, [enabled, crispProxyUrl])
-
-    useEffect(() => {
-        console.log('[CrispIframe] State:', { isReady, hasError })
-    }, [isReady, hasError])
+    useEffect(() => {
+        console.log('[CrispIframe] Mount/Update:', {
+            enabled,
+            crispProxyUrl,
+            isReady,
+            hasError,
+            userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'SSR',
+        })
+    }, [enabled, crispProxyUrl, isReady, hasError])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e72c7a6 and c5d3dea.

📒 Files selected for processing (3)
  • src/app/(mobile-ui)/support-test/page.tsx (1 hunks)
  • src/app/(mobile-ui)/support/page.tsx (1 hunks)
  • src/components/Global/CrispIframe/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(mobile-ui)/support/page.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1094
File: src/components/AddWithdraw/DynamicBankAccountForm.tsx:279-279
Timestamp: 2025-08-14T08:02:26.705Z
Learning: For hotfixes in the peanut-ui codebase, prefer generic error messages over specific validation error details until the copy can be reviewed with the team, even when the validation functions return detailed error messages.
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.
🧬 Code graph analysis (1)
src/components/Global/CrispIframe/index.tsx (3)
src/hooks/useCrispIframeReady.ts (1)
  • useCrispIframeReady (19-76)
src/components/0_Bruddle/Button.tsx (1)
  • Button (78-269)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
🪛 Biome (2.1.2)
src/components/Global/CrispIframe/index.tsx

[error] 23-23: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Actions: Tests
src/app/(mobile-ui)/support-test/page.tsx

[warning] 1-1: Code style issues found in the file. Run 'prettier --write' to fix.


[error] 1-1: Prettier formatting check failed. Command 'pnpm prettier --check .' exited with code 1. Run 'pnpm prettier --write .' to fix code style issues.

⏰ 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 (1)
src/components/Global/CrispIframe/index.tsx (1)

52-52: Confirm correct support email address with team.

The codebase currently uses support@peanut.me consistently in both lines 52 and 84 of src/components/Global/CrispIframe/index.tsx, with no instances of hello@peanut.to found. However, the review comment references inconsistencies between these two addresses.

Please verify with your team which email address is the correct support contact. If hello@peanut.to is the intended address, lines 52 and 84 need to be updated.

Comment on lines +1 to +33
'use client'

/**
* MINIMAL TEST PAGE for iOS debugging
* This bypasses all custom hooks to test if basic rendering works
*/
export default function SupportTestPage() {
return (
<div
style={{
width: '100%',
height: '100vh',
background: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
padding: '20px',
}}
>
<h1 style={{ fontSize: '24px', marginBottom: '20px' }}>iOS Test Page</h1>
<p style={{ marginBottom: '10px' }}>If you see this, page rendering works!</p>
<p style={{ fontSize: '12px', color: '#666' }}>
User agent: {typeof navigator !== 'undefined' ? navigator.userAgent : 'Loading...'}
</p>
<div style={{ marginTop: '20px' }}>
<a href="/support" style={{ color: 'blue', textDecoration: 'underline' }}>
Try /support page
</a>
</div>
</div>
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix formatting to pass CI checks.

The pipeline reports Prettier formatting issues that will block merge.

Run the following command to fix:

pnpm prettier --write .
🧰 Tools
🪛 GitHub Actions: Tests

[warning] 1-1: Code style issues found in the file. Run 'prettier --write' to fix.


[error] 1-1: Prettier formatting check failed. Command 'pnpm prettier --check .' exited with code 1. Run 'pnpm prettier --write .' to fix code style issues.

🤖 Prompt for AI Agents
In src/app/(mobile-ui)/support-test/page.tsx lines 1-33, Prettier formatting
errors are blocking CI; run the project's formatter to fix the file (execute:
pnpm prettier --write .), review the modified file to ensure 'use client' and
JSX remain unchanged, stage and commit the formatted changes, and push to the
branch so CI can re-run.

Comment on lines +20 to +27
// Defensive: Catch any hook errors
let hookState
try {
hookState = useCrispIframeReady(enabled)
} catch (error) {
console.error('[CrispIframe] Hook error:', error)
hookState = { isReady: true, hasError: true, retry: () => window.location.reload() }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix unconditional hook call violation.

Wrapping useCrispIframeReady in a try/catch creates a conditional hook call, which violates the Rules of Hooks. React requires hooks to be called unconditionally at the top level of every render. If the hook throws, subsequent renders will have mismatched hook order, causing React errors.

Move error handling into the hook itself, or wrap this entire component in an error boundary:

-    // Defensive: Catch any hook errors
-    let hookState
-    try {
-        hookState = useCrispIframeReady(enabled)
-    } catch (error) {
-        console.error('[CrispIframe] Hook error:', error)
-        hookState = { isReady: true, hasError: true, retry: () => window.location.reload() }
-    }
-
-    const { isReady, hasError, retry } = hookState
+    const { isReady, hasError, retry } = useCrispIframeReady(enabled)

If you need error boundaries for the entire component, add one in the parent that renders <CrispIframe>.

📝 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.

Suggested change
// Defensive: Catch any hook errors
let hookState
try {
hookState = useCrispIframeReady(enabled)
} catch (error) {
console.error('[CrispIframe] Hook error:', error)
hookState = { isReady: true, hasError: true, retry: () => window.location.reload() }
}
const { isReady, hasError, retry } = useCrispIframeReady(enabled)
🧰 Tools
🪛 Biome (2.1.2)

[error] 23-23: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🤖 Prompt for AI Agents
In src/components/Global/CrispIframe/index.tsx around lines 20 to 27, you
currently wrap the hook call in a try/catch which conditionally calls a hook and
violates the Rules of Hooks; instead, call useCrispIframeReady unconditionally
at the top of the component and move any try/catch/error handling into the hook
implementation (have the hook catch exceptions and return an error state like {
isReady:false, hasError:true, retry:() => ... }), or remove the try/catch here
and wrap the CrispIframe component in a React Error Boundary at the parent
level; update the component to use the error flags returned by the hook (or rely
on the boundary) and remove the local try/catch to ensure hooks are always
called in the same order.

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.

1 participant