Skip to content

Production to staging 130#1680

Closed
jjramirezn wants to merge 15 commits intopeanut-wallet-devfrom
peanut-wallet
Closed

Production to staging 130#1680
jjramirezn wants to merge 15 commits intopeanut-wallet-devfrom
peanut-wallet

Conversation

@jjramirezn
Copy link
Contributor

No description provided.

jjramirezn and others added 15 commits February 4, 2026 12:25
the loading gate in the mobile-ui layout blocked on !hasToken, which
was set once on mount via hasValidJwtToken() and never updated. when
the jwt expired after 30 days, the backend returned 401 but the cookie
persisted (getJWTCookie refreshes its maxAge on every call), leaving
the app in a dead state: hasToken=false permanently, spinner forever.

- remove hasToken/hasValidJwtToken from the loading gate. the gate now
  blocks only on transient conditions (isFetchingUser, isCheckingAccount,
  needsRedirect) and !user (resolves once redirect navigates away)
- upgrade redirect to router.replace to avoid stacking history entries
- add 3s hard navigation fallback for pwa contexts where soft navigation
  can silently fail
…expired-jwt

🐛 fix: infinite spinner on /home when jwt expires
[TASK-18464] fix: show different error message for PIX
when backend returns 401 on /get-user, clear the jwt cookie and
send Clear-Site-Data to nuke the service worker cache. this lets
the client recover by redirecting to /setup for re-authentication
instead of spinning forever with a stale cookie.
…r-cookie

🐛 fix: clear expired jwt cookie and sw cache on 401
redux persists user data to localStorage, so on app boot the query
was skipping the backend check entirely (enabled: !authUser.userId).
this meant expired JWTs were never detected — the app rendered with
stale cached data while all API calls failed silently.

- remove redux gate from enabled, always fetch on mount
- use placeholderData instead of initialData (show cached data
  instantly but don't skip the fetch)
- clear redux on fetch failure so layout redirect triggers
…-on-auth-failure

🐛 fix: clear stale redux user on auth failure
feat: show appx. amt in local currency
@vercel
Copy link

vercel bot commented Feb 17, 2026

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Feb 17, 2026 10:53am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Walkthrough

Multiple independent feature updates and refactorings across the mobile UI, components, and API layer. Changes include authentication redirect mechanism updates, currency conversion display enhancements, payment error handling, API cookie management improvements, Redux data model expansion, and UI component simplifications.

Changes

Cohort / File(s) Summary
Authentication & Redirect Flow
src/app/(mobile-ui)/layout.tsx
Removed token validation on mount; introduced isRedirecting ref to guard one-time redirects with fallback to window.location.replace('/setup'); updated protected-path rendering check to rely on authentication/setup state rather than hasToken checks.
User Data & API Management
src/app/api/peanut/user/get-user-from-cookie/route.ts, src/hooks/query/user.ts, src/redux/slices/user-slice.ts
Dynamic header building for non-200 responses with 401 handling for Set-Cookie and Clear-Site-Data; simplified user query caching logic and removed keepPreviousData; expanded setUser reducer to accept null in addition to IUserProfile.
Currency Conversion & Display
src/components/ExchangeRate/index.tsx, src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
Added amountToConvert prop and currency symbol resolution to ExchangeRate component; computes and displays estimated local currency amount with conditional PaymentInfoRow rendering; withdraw page passes amountToWithdraw to ExchangeRate.
Payment & Transaction UX
src/app/(mobile-ui)/qr-pay/page.tsx, src/components/Payment/PaymentInfoRow.tsx
Added PIX QR type error handling branch in MANTECA payment flow; refactored PaymentInfoRow tooltip from manual state/DOM management to Tooltip component wrapper.
Content & Text Updates
src/app/page.tsx, src/components/Global/FAQs/index.tsx
Updated FAQ answer for account ownership messaging; introduced linkifyText helper to automatically convert URLs in FAQ answers into clickable anchor elements with proper rel attributes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Production to staging 130' is vague and generic, using non-descriptive terms that don't convey meaningful information about the actual changes in the pull request. Use a descriptive title summarizing the main change, such as 'Refactor authentication flow and improve QR payment error handling' or reference the actual feature/fix being addressed.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description that explains the purpose, scope, and key changes of this pull request for better context and review clarity.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ 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 peanut-wallet

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

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

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

41-54: Normalize the non‑Euro rate to a numeric value before reuse.

Right now rate is assigned from nonEruoExchangeRate directly, while the Euro path parses to a number. Aligning both avoids mixed types and makes the local-currency math more predictable.

♻️ Suggested normalization
-        rate = nonEruoExchangeRate
+        const parsedRate = Number(nonEruoExchangeRate)
+        rate = Number.isFinite(parsedRate) ? parsedRate : null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ExchangeRate/index.tsx` around lines 41 - 54, The branch that
handles nonEuroCurrency assigns rate directly from nonEruoExchangeRate, causing
mixed types with the USD path which uses parseFloat; update the nonEuroCurrency
branch to normalize nonEruoExchangeRate to a numeric value (e.g., via
parseFloat(nonEruoExchangeRate.toString()) or Number(...)) before assigning to
rate, and keep displayValue, isLoadingRate and moreInfoText behavior the same so
downstream math expecting a number uses a consistent numeric rate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/api/peanut/user/get-user-from-cookie/route.ts`:
- Around line 24-37: The Set-Cookie used to clear the jwt-token in the 401
branch omits the Secure flag, so deletion may fail in production; update the
headers assignment inside the response.status === 401 block (the headers object
and the Set-Cookie string) to include the Secure attribute only when
process.env.NODE_ENV === 'production' (i.e., append "; Secure" in production) so
it matches how the original cookie was set and ensures proper deletion; modify
the logic around headers['Set-Cookie'] in this route handler to conditionally
include Secure while keeping Path=/; Max-Age=0; SameSite=Lax.

In `@src/components/ExchangeRate/index.tsx`:
- Around line 57-63: The parsing of amountToConvert using parseFloat can
mis-handle comma-formatted values (e.g., "1,234.56"); update the logic around
localCurrencyAmount/amountToConvert to first sanitize the string by removing
thousands separators (commas) and trim whitespace, then parse to a number (e.g.,
Number(...) or parseFloat on the cleaned string) and validate with
Number.isFinite(amount) and amount > 0 before calculating (amount * rate).
Ensure the guard still checks rate && rate > 0 and preserve the toFixed(2)
assignment to localCurrencyAmount.

In `@src/hooks/query/user.ts`:
- Around line 31-36: The code currently clears Redux user state for any non-OK
fetch by calling dispatch(userActions.setUser(null)) when userResponse.status
!== 200; change this so only authentication errors trigger logout: detect 401
and 403 from userResponse.status and call dispatch(userActions.setUser(null))
only in that branch, while for other statuses (5xx, 429, etc.) just log a
warning and return null without clearing state; also ensure any fetch/network
exceptions caught by the enclosing function (e.g., in the same async function
that awaits the fetch) do not call userActions.setUser(null) on transient errors
— handle them by logging and returning null instead.

---

Nitpick comments:
In `@src/components/ExchangeRate/index.tsx`:
- Around line 41-54: The branch that handles nonEuroCurrency assigns rate
directly from nonEruoExchangeRate, causing mixed types with the USD path which
uses parseFloat; update the nonEuroCurrency branch to normalize
nonEruoExchangeRate to a numeric value (e.g., via
parseFloat(nonEruoExchangeRate.toString()) or Number(...)) before assigning to
rate, and keep displayValue, isLoadingRate and moreInfoText behavior the same so
downstream math expecting a number uses a consistent numeric rate.

Comment on lines +24 to +37
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}

// on auth failure, clear the jwt cookie and sw cache so the client
// can recover even if running old cached code
if (response.status === 401) {
headers['Set-Cookie'] = 'jwt-token=; Path=/; Max-Age=0; SameSite=Lax'
headers['Clear-Site-Data'] = '"cache"'
}

return new NextResponse('Error in get-from-cookie', {
status: response.status,
headers: {
'Content-Type': 'application/json',
},
headers,
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "jwt-token" --type ts --type tsx --type js --type jsx -B 2 -A 2

Repository: peanutprotocol/peanut-ui

Length of output: 95


🏁 Script executed:

rg -n "Set-Cookie" --type ts --type tsx --type js --type jsx -B 2 -A 2

Repository: peanutprotocol/peanut-ui

Length of output: 95


🏁 Script executed:

fd -e "ts" -e "tsx" -e "js" -e "jsx" | xargs grep -l "cookie" -i | head -20

Repository: peanutprotocol/peanut-ui

Length of output: 849


🏁 Script executed:

rg "jwt-token" -n -B 2 -A 2

Repository: peanutprotocol/peanut-ui

Length of output: 20501


🏁 Script executed:

cat -n src/app/api/peanut/user/get-jwt-token/route.ts

Repository: peanutprotocol/peanut-ui

Length of output: 2448


🏁 Script executed:

cat -n src/app/api/peanut/user/logout-user/route.ts

Repository: peanutprotocol/peanut-ui

Length of output: 1092


🏁 Script executed:

cat -n src/app/api/peanut/user/get-user-from-cookie/route.ts | head -40

Repository: peanutprotocol/peanut-ui

Length of output: 1761


Add Secure flag to cookie-clear Set-Cookie header in production.

The original jwt-token cookie is set with secure: process.env.NODE_ENV === 'production', but the clear operation at line 31 omits the Secure flag. Include Secure in the Set-Cookie header to maintain consistency:

headers['Set-Cookie'] = 'jwt-token=; Path=/; Max-Age=0; SameSite=Lax; Secure'

This ensures proper deletion in production environments and aligns with the original cookie configuration.

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

In `@src/app/api/peanut/user/get-user-from-cookie/route.ts` around lines 24 - 37,
The Set-Cookie used to clear the jwt-token in the 401 branch omits the Secure
flag, so deletion may fail in production; update the headers assignment inside
the response.status === 401 block (the headers object and the Set-Cookie string)
to include the Secure attribute only when process.env.NODE_ENV === 'production'
(i.e., append "; Secure" in production) so it matches how the original cookie
was set and ensures proper deletion; modify the logic around
headers['Set-Cookie'] in this route handler to conditionally include Secure
while keeping Path=/; Max-Age=0; SameSite=Lax.

Comment on lines +57 to +63
// calculate local currency amount if provided
let localCurrencyAmount: string | null = null
if (amountToConvert && rate && rate > 0) {
const amount = parseFloat(amountToConvert)
if (!isNaN(amount) && amount > 0) {
localCurrencyAmount = (amount * rate).toFixed(2)
}
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 | 🟡 Minor

Guard against comma‑formatted inputs when parsing amountToConvert.

parseFloat("1,234.56") becomes 1, which will understate the local amount. Consider stripping commas and using Number.isFinite.

🛠️ Suggested guard
-        const amount = parseFloat(amountToConvert)
-        if (!isNaN(amount) && amount > 0) {
+        const normalized = amountToConvert.replace(/,/g, '')
+        const amount = Number(normalized)
+        if (Number.isFinite(amount) && amount > 0) {
             localCurrencyAmount = (amount * rate).toFixed(2)
         }
📝 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
// calculate local currency amount if provided
let localCurrencyAmount: string | null = null
if (amountToConvert && rate && rate > 0) {
const amount = parseFloat(amountToConvert)
if (!isNaN(amount) && amount > 0) {
localCurrencyAmount = (amount * rate).toFixed(2)
}
// calculate local currency amount if provided
let localCurrencyAmount: string | null = null
if (amountToConvert && rate && rate > 0) {
const normalized = amountToConvert.replace(/,/g, '')
const amount = Number(normalized)
if (Number.isFinite(amount) && amount > 0) {
localCurrencyAmount = (amount * rate).toFixed(2)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ExchangeRate/index.tsx` around lines 57 - 63, The parsing of
amountToConvert using parseFloat can mis-handle comma-formatted values (e.g.,
"1,234.56"); update the logic around localCurrencyAmount/amountToConvert to
first sanitize the string by removing thousands separators (commas) and trim
whitespace, then parse to a number (e.g., Number(...) or parseFloat on the
cleaned string) and validate with Number.isFinite(amount) and amount > 0 before
calculating (amount * rate). Ensure the guard still checks rate && rate > 0 and
preserve the toFixed(2) assignment to localCurrencyAmount.

Comment on lines 31 to 36
} else {
// RECOVERY FIX: Log error status for debugging
if (userResponse.status === 400 || userResponse.status === 500) {
console.error('Failed to fetch user with error status:', userResponse.status)
// This indicates a backend issue - user might be in broken state
// The KernelClientProvider recovery logic will handle cleanup
} else {
console.warn('Failed to fetch user. Probably not logged in.')
}
console.warn('Failed to fetch user, status:', userResponse.status)
// clear stale redux data so the app doesn't keep serving cached user
dispatch(userActions.setUser(null))
return null
}
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

Clearing user data on any fetch failure may cause unintended logouts.

The current implementation clears Redux user data for all non-OK responses, including temporary network issues (500, timeouts, connection errors). This could log users out during transient server issues or brief connectivity problems.

Consider differentiating between authentication failures (401/403) and server/network errors:

🛡️ Proposed fix to preserve user data on transient errors
         } else {
-            console.warn('Failed to fetch user, status:', userResponse.status)
-            // clear stale redux data so the app doesn't keep serving cached user
-            dispatch(userActions.setUser(null))
+            console.warn('Failed to fetch user, status:', userResponse.status)
+            // only clear user data on auth failures, not transient server errors
+            if (userResponse.status === 401 || userResponse.status === 403) {
+                dispatch(userActions.setUser(null))
+            }
             return null
         }
📝 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
} else {
// RECOVERY FIX: Log error status for debugging
if (userResponse.status === 400 || userResponse.status === 500) {
console.error('Failed to fetch user with error status:', userResponse.status)
// This indicates a backend issue - user might be in broken state
// The KernelClientProvider recovery logic will handle cleanup
} else {
console.warn('Failed to fetch user. Probably not logged in.')
}
console.warn('Failed to fetch user, status:', userResponse.status)
// clear stale redux data so the app doesn't keep serving cached user
dispatch(userActions.setUser(null))
return null
}
} else {
console.warn('Failed to fetch user, status:', userResponse.status)
// only clear user data on auth failures, not transient server errors
if (userResponse.status === 401 || userResponse.status === 403) {
dispatch(userActions.setUser(null))
}
return null
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/query/user.ts` around lines 31 - 36, The code currently clears
Redux user state for any non-OK fetch by calling
dispatch(userActions.setUser(null)) when userResponse.status !== 200; change
this so only authentication errors trigger logout: detect 401 and 403 from
userResponse.status and call dispatch(userActions.setUser(null)) only in that
branch, while for other statuses (5xx, 429, etc.) just log a warning and return
null without clearing state; also ensure any fetch/network exceptions caught by
the enclosing function (e.g., in the same async function that awaits the fetch)
do not call userActions.setUser(null) on transient errors — handle them by
logging and returning null instead.

@Hugo0 Hugo0 closed this Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants