[TASK-12124] Prod Release Sprint 95#911
Conversation
- Add get-origin utility to reliably determine protocol and host - Update og route and layout to use dynamic origin for image URLs - Simplify metadata generation by using centralized origin handling
- Update error message for missing site URL to be more descriptive - Add host header validation to prevent injection attacks - Improve font loading error handling in OG image generation - Validate type parameter in OG route - Update alt text for logo image to be more specific
…/peanut-ui into feat/request-link-preview
…/peanut-ui into feat/request-link-preview
Feat/request link preview
fix: block direct requests on ens and addresses
Update recipient input validation to reject Peanut usernames for withdrawals and adjust placeholder text accordingly. This change ensures users can only enter valid addresses (banks) or ENS names when withdrawing funds.
add tailwind-colors utility for color management update GeneralRecipientInput label iconFillColor prop to AvatarWithBadge and related components
…ui into feat/withdraw-ens
…correctly also fixed other issues)
feat: setup flow minor ui changes
…ui into feat/withdraw-ens
…ut-ui into fix/setup-ui
fix: padding for setup flow
…ted utils clean up unused prop across multiple components and delete obsolete tailwind-colors utility
fix(withdraw): remove Peanut username support from recipient input
…children-fix fixed issue that was breaking next.js local development
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update introduces dynamic Open Graph image generation and metadata for payment links, adds new types and interfaces for payment links, and updates several UI components for improved validation, error handling, and event-specific messaging. It also enhances setup and withdrawal flows, adjusts Tailwind color palettes, and refines redirect and manifest configurations. Changes
Possibly related PRs
✨ Finishing Touches
🧪 Generate Unit Tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
🔭 Outside diff range comments (5)
src/components/Withdraw/views/Initial.withdraw.view.tsx (1)
83-90:⚠️ Potential issueInconsistent recipient type vs. new validation logic
PeanutActionDetailsCardis still hard-coded withrecipientType="USERNAME"(line 85) even though the surrounding UI now forbids usernames by:
- Updating the placeholder to “Enter an address or ENS”.
- Passing the new
isWithdrawalflag toGeneralRecipientInput, which rejects usernames.This creates a logical mismatch – the card says “USERNAME” while the form rejects them. Down-stream analytics/helpers that rely on
recipientTypemay also be mis-informed.- recipientType="USERNAME" + recipientType="ADDRESS"(or a more precise enum value used elsewhere).
Please align the constant (and any follow-on business logic) with the new withdrawal-only address/ENS rule.
Also applies to: 94-108
src/components/Global/PeanutActionDetailsCard/index.tsx (1)
70-75: 🛠️ Refactor suggestion
useCallbackmissing deps → stale closures
getAvatarIconreferencesviewType,recipientType, andtransactionTypebut the dependency array is empty. On rerenders with different props (e.g. after optimistic UI update) the memoised fn returns an outdated icon.- }, []) + }, [transactionType, recipientType, viewType])src/components/Global/GeneralRecipientInput/index.tsx (1)
45-85:⚠️ Potential issue
isWithdrawalmissing fromuseCallbackdeps – validation may use stale flag
IfisWithdrawalprop changes after mount,checkAddresswill still reference the old value, potentially allowing/disallowing usernames incorrectly.- const checkAddress = useCallback(async (recipient: string): Promise<boolean> => { + const checkAddress = useCallback(async (recipient: string): Promise<boolean> => { ... - }, []) + }, [isWithdrawal])src/components/Request/direct-request/views/Initial.direct.request.view.tsx (1)
86-92:⚠️ Potential issue
user!non-null assertion may throw
usercan benullwhen this callback runs (e.g. race condition after username change).
Guard before dereferencing to prevent a runtime crash.-if (!user) { - setErrorState({ showError: true, errorMessage: 'Recipient not loaded yet' }) - return -} -await usersApi.requestByUsername({ username: user.username, … }) +if (!user) { + setErrorState({ showError: true, errorMessage: 'Recipient not loaded yet' }) + return +} +await usersApi.requestByUsername({ username: user.username, … })src/components/Global/RewardsModal/index.tsx (1)
159-167:⚠️ Potential issueGuard non-null assertions
user!andactiveReward!may be undefined if the modal opens before data arrives.
Add early returns or disable the button until both are defined to avoid crashes.if (!user || !activeReward) return null
🧹 Nitpick comments (22)
src/components/Setup/components/SetupWrapper.tsx (1)
39-41: Use dynamic viewport height units
Switched container height fromvhtodvhfor improved mobile browser compatibility. Consider adding a fallback tovhor a CSS variable for browsers that don’t supportdvh.src/components/Global/WalletNavigation/index.tsx (1)
34-55: Avoid unnecessary extra DOM wrapper – keep the key on a fragment insteadReact has allowed
keyon<>fragments since 16.2 (<></>can be replaced with<React.Fragment key="">).
Switching to a<div>introduces an extra node that:
- May break flex/grid styling of parent containers.
- Slightly bloats the DOM for every nav item.
Refactor to:
- {paths.map(({ name, href, icon, size }, index) => ( - <div key={`${name}-${index}`}> - <Link ...> + {paths.map(({ name, href, icon, size }) => ( + <React.Fragment key={name}> + <Link ...>Keeps keys correct without altering layout.
src/components/Setup/Views/SetupPasskey.tsx (1)
61-83: Minor UX polish – clear stale error & always unset loading infinallyCurrent flow:
- Clicking twice after an error retains the previous error message.
dispatch(setLoading(false))is called in every catch branch, but not after a successfulhandleRegister, leaving the button in loading state until the separateuseEffectfires.Quick fix:
onClick={async () => { + setError(null) // clear old error dispatch(setupActions.setLoading(true)) try { await handleRegister(username) - } catch (e) { + } catch (e) { ... - dispatch(setupActions.setLoading(false)) + } finally { + // Always reset local loading in case address never resolves + dispatch(setupActions.setLoading(false)) } }}src/components/Withdraw/views/Initial.withdraw.view.tsx (1)
107-108: Explicit boolean prop – readabilityUsing the prop key alone implies
true, but explicitness helps future readers:- isWithdrawal + isWithdrawal={true}Optional, feel free to ignore.
src/components/Global/Modal/index.tsx (1)
3-4: Ref typing – tighten for better IntelliSense
dialogRefis currentlyMutableRefObject<null>, which loses type information and disables helpful hints onDialogmethods.- let dialogRef = useRef(null) + const dialogRef = useRef<HTMLDivElement | null>(null)No runtime change, but improves static analysis & DX.
Also applies to: 41-48
src/lib/og/build-og-metadata.ts (1)
26-30: Possible double ‘//’ in OG image URL
new URL(${siteUrl}/api/og)produceshttps://example.com//api/ogwhensiteUrlalready ends with/. Browsers tolerate this but CDN-level proxy rules sometimes do not.-const ogUrl = new URL(`${siteUrl}/api/og`) +const ogUrl = new URL('/api/og', siteUrl.endsWith('/') ? siteUrl : siteUrl + '/')src/components/Global/PeanutActionDetailsCard/index.tsx (2)
89-103: Condition now impossible after withdrawal username ban
Username recipients are explicitly blocked in the new withdrawal flow (GeneralRecipientInput). These additional avatar-color branches will never be hit, adding complexity without value.Consider removing the username-specific WITHDRAW paths or adding a comment explaining why you kept them.
113-115: Pluralisation logic leaks UI-text concerns into a presentational component
Embedding English plural rules (“Beer(s)”) here couples token-symbol semantics to the card. Prefer moving this to an i18n/util helper so UI copy is centralised and translatable.src/components/Global/GeneralRecipientInput/index.tsx (2)
64-68: User-visible error string is duplicated in multiple files
Hard-coded"Peanut usernames are not supported for withdrawals."appears here and inInitial.withdraw.view.tsx. Extract to a shared constants file to avoid divergence.
133-134: Label now hard-coded to “Wallet address” for all usages
The component is reused outside withdrawals (e.g. direct requests). The previous wording was neutral; now the label is misleading when the recipient can be ENS or IBAN. Consider passinglabelas a prop instead.src/components/Global/ValidatedInput/index.tsx (1)
205-213: Icons lack accessible labels
The new success / error icons are purely visual. Withoutaria-hidden/role="img"/titleassistive tech users receive no feedback.-<Icon size={20} className="text-secondary-2" name="error" /> +<Icon + size={20} + className="text-secondary-2" + name="error" + aria-label="invalid input" />Apply similarly for the success icon.
src/app/[...recipient]/layout.tsx (1)
7-29: Dead helpergetPreviewUrl()can be safely removedAfter the migration to the new
/api/ogendpoint the helper is no longer referenced anywhere in this file (or in the repo, according to a quickrgscan). Keeping it around adds cognitive noise.- function getPreviewUrl( … ) { - … - }src/components/Setup/Views/InstallPWA.tsx (1)
110-113: Serialising the entiredeferredPromptobject may crash the toast
BeforeInstallPromptEventcontains circular references →JSON.stringify()throws.
Log a safe subset instead.-captureException(error) -toast.error(JSON.stringify(deferredPrompt)) +captureException(error) +toast.error('Installation prompt failed – see console for details') +// optional: console.debug('Prompt:', { platforms: deferredPrompt?.platforms })src/app/api/og/route.tsx (1)
47-48:Number(searchParams.get('amount'))can yieldNaNWhen the query param is absent or invalid your OG card breaks (see PaymentCard comment).
Guard the conversion:-const amount = Number(searchParams.get('amount') ?? 0) +const amountParam = searchParams.get('amount') +const amount = amountParam && !isNaN(Number(amountParam)) ? Number(amountParam) : 0src/components/Request/direct-request/views/Initial.direct.request.view.tsx (5)
38-39: Avoid duplicate context reads
loadingStateContextis consumed twice (lines 38-39 and 76-77).
Reading the same context twice triggers two renders and is unnecessary.-const { setLoadingState, loadingState } = useContext(loadingStateContext) -… -const isButtonLoading = useContext(loadingStateContext).isLoading +const { setLoadingState, loadingState, isLoading: isButtonLoading } = + useContext(loadingStateContext)Also applies to: 76-77
69-73: Redundant double-negation
!!authUser?.user.userIdis already truthy/falsy; the cast is noise and flagged by Biome.-const isIdentityLogicMissing = !!authUser?.user.userId ? !address : !recipient.address +const isIdentityLogicMissing = + authUser?.user.userId ? !address : !recipient.address🧰 Tools
🪛 Biome (1.9.4)
[error] 72-72: Avoid redundant double-negation.
It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation(lint/complexity/noExtraBooleanCast)
79-83: Generic error message hides real causeThe disabled-button path always throws “Username or amount is missing”, even when the real blocker is a missing wallet/recipient address.
Build the message from the actual failing predicate to help the user correct it.
153-162: Minor readability: optional chaining & early return-if (response && response.username) { +if (response?.username) {Same check in the
catchcould share anabortControllerto cancel the request on unmount and avoid setting state on an unmounted component.
Not urgent, but worth considering.Also applies to: 171-176
🧰 Tools
🪛 Biome (1.9.4)
[error] 158-158: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
301-316: Button state race
disabled={isButtonDisabled || isButtonLoading}andloading={isButtonLoading}is fine, but combining both flags means the button is permanently disabled during the API call even ifisButtonDisabledbecomesfalse(e.g. user edits amount). Usuallyloadingimplies disabled; you can rely on it and drop the logical-OR.src/components/Global/RewardsModal/index.tsx (3)
58-60: Single-asset lookup silently ignores other rewards
getActiveRewardhard-codesPNT. If the backend later sends USDC-only rewards, the modal will never appear.
If that’s intentional for the event it’s fine; otherwise keep the previous fallback.-return rewardLinks.find((r) => r.assetCode === REWARD_ASSET_TYPE.PNT) +return ( + rewardLinks.find((r) => r.assetCode === REWARD_ASSET_TYPE.PNT) ?? + rewardLinks.find((r) => r.assetCode === REWARD_ASSET_TYPE.USDC) +)
118-124: DeriveshowModalinstead of mutating state
showModalcan be computed directly fromrewardLinks.length && activeReward—keeping it in state adds an extra render path and risks desync.-const [showModal, setShowModal] = useState(false) +const showModal = rewardLinks.length > 0 && !!activeRewardThen remove the effect that toggles it.
Simpler and easier to reason about.
76-84: Memoise modal copy
getModalContent()is pure; wrapping it inuseMemoprevents re-allocation on every render and clarifies dependencies.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (10)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpublic/arrows/bottom-left-arrow.svgis excluded by!**/*.svgpublic/arrows/bottom-right-arrow.svgis excluded by!**/*.svgpublic/arrows/top-left-arrows.svgis excluded by!**/*.svgpublic/arrows/top-right-arrow.svgis excluded by!**/*.svgpublic/icons/peanut-icon.svgis excluded by!**/*.svgpublic/logos/peanut-logo.svgis excluded by!**/*.svgpublic/scribble.svgis excluded by!**/*.svgsrc/assets/fonts/montserrat-medium.ttfis excluded by!**/*.ttfsrc/assets/fonts/montserrat-semibold.ttfis excluded by!**/*.ttf
📒 Files selected for processing (27)
package.json(1 hunks)redirects.json(1 hunks)src/app/[...recipient]/layout.tsx(4 hunks)src/app/api/og/route.tsx(1 hunks)src/app/manifest.ts(1 hunks)src/components/Claim/Claim.tsx(3 hunks)src/components/Claim/Link/Initial.view.tsx(6 hunks)src/components/Global/GeneralRecipientInput/index.tsx(5 hunks)src/components/Global/Icons/check-circle.tsx(1 hunks)src/components/Global/Modal/index.tsx(2 hunks)src/components/Global/PeanutActionDetailsCard/index.tsx(3 hunks)src/components/Global/RewardsModal/index.tsx(4 hunks)src/components/Global/ValidatedInput/index.tsx(4 hunks)src/components/Global/WalletNavigation/index.tsx(2 hunks)src/components/Request/direct-request/views/Initial.direct.request.view.tsx(9 hunks)src/components/Setup/Setup.consts.tsx(2 hunks)src/components/Setup/Views/InstallPWA.tsx(5 hunks)src/components/Setup/Views/SetupPasskey.tsx(2 hunks)src/components/Setup/Views/Signup.tsx(3 hunks)src/components/Setup/components/SetupWrapper.tsx(1 hunks)src/components/TransactionDetails/transactionTransformer.ts(1 hunks)src/components/Withdraw/views/Initial.withdraw.view.tsx(2 hunks)src/components/og/PaymentCardOG.tsx(1 hunks)src/interfaces/interfaces.ts(1 hunks)src/lib/hosting/get-origin.ts(1 hunks)src/lib/og/build-og-metadata.ts(1 hunks)tailwind.config.js(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
src/components/Withdraw/views/Initial.withdraw.view.tsx (1)
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#873
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:95-95
Timestamp: 2025-05-23T19:26:58.220Z
Learning: The GeneralRecipientInput component supports username validation and resolution through the validateAndResolveRecipient function in src/lib/validation/recipient.ts. The function automatically detects usernames (inputs that don't contain '.' for ENS and don't start with '0x' for addresses), validates them via API HEAD request, fetches user data, and resolves them to Ethereum addresses from the user's PEANUT_WALLET account.
src/components/Global/GeneralRecipientInput/index.tsx (1)
Learnt from: jjramirezn
PR: peanutprotocol/peanut-ui#873
File: src/components/Withdraw/views/Initial.withdraw.view.tsx:95-95
Timestamp: 2025-05-23T19:26:58.220Z
Learning: The GeneralRecipientInput component supports username validation and resolution through the validateAndResolveRecipient function in src/lib/validation/recipient.ts. The function automatically detects usernames (inputs that don't contain '.' for ENS and don't start with '0x' for addresses), validates them via API HEAD request, fetches user data, and resolves them to Ethereum addresses from the user's PEANUT_WALLET account.
🧬 Code Graph Analysis (8)
src/components/Claim/Claim.tsx (1)
src/components/TransactionDetails/transactionTransformer.ts (1)
REWARD_TOKENS(18-25)
src/components/Setup/Views/SetupPasskey.tsx (2)
src/components/0_Bruddle/Button.tsx (1)
Button(69-130)src/redux/slices/setup-slice.ts (1)
setupActions(50-50)
src/components/og/PaymentCardOG.tsx (1)
src/interfaces/interfaces.ts (1)
PaymentLink(293-298)
src/app/[...recipient]/layout.tsx (1)
src/lib/hosting/get-origin.ts (1)
getOrigin(3-16)
src/components/Global/PeanutActionDetailsCard/index.tsx (1)
src/constants/zerodev.consts.ts (1)
PEANUT_WALLET_TOKEN_SYMBOL(21-21)
src/components/Global/ValidatedInput/index.tsx (1)
src/components/Global/Icons/Icon.tsx (1)
Icon(155-164)
src/components/Setup/Views/InstallPWA.tsx (1)
src/context/authContext.tsx (1)
useAuth(202-208)
src/components/Global/RewardsModal/index.tsx (2)
src/services/services.types.ts (1)
RewardLink(262-269)src/context/authContext.tsx (1)
useAuth(202-208)
🪛 Biome (1.9.4)
src/components/Request/direct-request/views/Initial.direct.request.view.tsx
[error] 72-72: Avoid redundant double-negation.
It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation
(lint/complexity/noExtraBooleanCast)
[error] 158-158: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 206-206: Avoid redundant double-negation.
It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation
(lint/complexity/noExtraBooleanCast)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Deploy-Preview
🔇 Additional comments (15)
redirects.json (2)
39-42: Verify/pintsredirect
Destination changed to/setup. Ensure the/setuproute is correctly implemented and doesn’t introduce infinite loops or broken flows.
43-47: Verify/eventsredirect
Destination changed to/setup. Confirm this aligns with the intended event flow and that no legacy consumers expect the previous external URL.package.json (1)
42-42: Validate new WebAuthn dependency
Added@simplewebauthn/browser@^8.3.7. Confirm that this package provides TypeScript definitions (or add@types/...if needed) and that this version is compatible with the existing setup/passkey implementation.src/components/Global/Icons/check-circle.tsx (1)
7-7: Inherit current text color
Changingfill="currentColor"is a solid improvement—this lets the icon adapt to its parent’s text color for consistent theming.src/components/Setup/Setup.consts.tsx (2)
63-63: Center content in signup step
Replacedjustify-betweenwithjustify-centerfor the signup screen. This aligns the layout with the revised UX design.
74-74: Center content in passkey-permission step
Adjusted fromjustify-betweentojustify-centerto maintain consistency with the signup step’s centered layout.src/lib/hosting/get-origin.ts (1)
12-15: Handle multi-valuex-forwarded-protoheaderCDN / proxy setups sometimes send
"https, http".
Safer parsing:-const protocol = forwardedProto || (process.env.NODE_ENV === 'production' ? 'https' : 'http') +const protocol = + (forwardedProto?.split(',')[0].trim()) || + (process.env.NODE_ENV === 'production' ? 'https' : 'http')src/interfaces/interfaces.ts (1)
291-298: Great addition of explicitPaymentLinktypesTyped status enum and link payload will tighten runtime checks across the new OG-image flow.
No issues spotted.tailwind.config.js (1)
28-30: Color palette extension looks consistent
secondary.8fits the existing naming convention and doesn’t clash with other keys.src/components/Withdraw/views/Initial.withdraw.view.tsx (1)
95-96: Nice UX tweakThe updated placeholder accurately reflects the new restriction.
src/components/Claim/Claim.tsx (1)
115-116: 👍 Reward data plumbed throughConnecting
rewardDataintoextraDataForDrawerlooks correct and non-breaking.src/app/manifest.ts (1)
64-67: Missing fallback whenNEXT_PUBLIC_BASE_URLis undefinedIn non-prod environments the env var might not be set, producing
url: "undefined/manifest.webmanifest"– an invalid manifest that fails PWA install.Consider a safe default:
- url: `${process.env.NEXT_PUBLIC_BASE_URL}/manifest.webmanifest`, + url: `${process.env.NEXT_PUBLIC_BASE_URL ?? ''}/manifest.webmanifest`,or throw at build-time if the variable is absent to avoid shipping a bad manifest.
src/components/Global/Modal/index.tsx (1)
46-48: Good default-focus fallbackUsing the dialog itself when no
initialFocusis provided is a solid accessibility improvement.src/components/TransactionDetails/transactionTransformer.ts (1)
18-25: Export looks fineRe-exporting
REWARD_TOKENSfor reuse is straightforward and low-risk.src/components/Global/ValidatedInput/index.tsx (1)
154-158: Border classes duplicated & may be overridden by parent
ValidatedInputalways starts withborder-n-1; parents (e.g. Signup) also passborder-error/border-secondary-*.twMergekeeps the last duplicate, so internal error highlighting may be silently overridden. Document this precedence or expose anhasErrorprop instead of double class merging.
No description provided.