From 5b1c3a2fc41972de1ac4ece96a54750e13f1ec81 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 21:43:42 +0530 Subject: [PATCH 1/4] fix: functional bugs --- .../Global/Contributors/ContributorCard.tsx | 18 ++++++- .../Global/TokenAmountInput/index.tsx | 6 ++- src/components/Payment/PaymentForm/index.tsx | 50 ++++++++++--------- src/utils/general.utils.ts | 3 ++ 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/components/Global/Contributors/ContributorCard.tsx b/src/components/Global/Contributors/ContributorCard.tsx index d351b9e9f..e31207355 100644 --- a/src/components/Global/Contributors/ContributorCard.tsx +++ b/src/components/Global/Contributors/ContributorCard.tsx @@ -1,3 +1,4 @@ +'use client' import { type Payment } from '@/services/services.types' import Card, { type CardPosition } from '../Card' import AvatarWithBadge from '@/components/Profile/AvatarWithBadge' @@ -5,6 +6,8 @@ import { getColorForUsername } from '@/utils/color.utils' import { VerifiedUserLabel } from '@/components/UserHeader' import { formatTokenAmount } from '@/utils' import { isAddress } from 'viem' +import { useRouter } from 'next/navigation' +import { twMerge } from 'tailwind-merge' export type Contributor = { uuid: string @@ -13,15 +16,26 @@ export type Contributor = { username: string | undefined fulfillmentPayment: Payment | null isUserVerified: boolean + isPeanutUser: boolean } const ContributorCard = ({ contributor, position }: { contributor: Contributor; position: CardPosition }) => { const colors = getColorForUsername(contributor.username ?? '') const isEvmAddress = isAddress(contributor.username ?? '') + + const router = useRouter() + return ( - +
-
+
{ + if (contributor.isPeanutUser) { + router.push(`/${contributor.username}`) + } + }} + className={twMerge('flex items-center gap-2', contributor.isPeanutUser && 'cursor-pointer')} + > !!user && isPeanutWalletConnected, [user, isPeanutWalletConnected]) + const isRequestPotLink = !!chargeDetails?.requestLink + useEffect(() => { - if (initialSetupDone || showRequestPotInitialView) return + // skip this step for request pot payments + // Amount is set by the user so we dont need to manually update it + // chain and token are also USDC arb always, for cross-chain we use Daimo + if (initialSetupDone || showRequestPotInitialView || isRequestPotLink) return if (amount) { setInputTokenAmount(amount) @@ -192,7 +197,7 @@ export const PaymentForm = ({ } setInitialSetupDone(true) - }, [chain, token, amount, initialSetupDone, requestDetails, showRequestPotInitialView]) + }, [chain, token, amount, initialSetupDone, requestDetails, showRequestPotInitialView, isRequestPotLink]) // reset error when component mounts or recipient changes useEffect(() => { @@ -301,7 +306,16 @@ export const PaymentForm = ({ // Calculate USD value when requested token price is available useEffect(() => { - if (showRequestPotInitialView || !requestedTokenPriceData?.price || !requestDetails?.tokenAmount) return + // skip this step for request pot payments + // Amount is set by the user so we dont need to manually update it + // No usd conversion needed because amount will always be USDC + if ( + showRequestPotInitialView || + !requestedTokenPriceData?.price || + !requestDetails?.tokenAmount || + isRequestPotLink + ) + return const tokenAmount = parseFloat(requestDetails.tokenAmount) if (isNaN(tokenAmount) || tokenAmount <= 0) return @@ -309,9 +323,10 @@ export const PaymentForm = ({ if (isNaN(requestedTokenPriceData.price) || requestedTokenPriceData.price === 0) return const usdValue = formatAmount(tokenAmount * requestedTokenPriceData.price) + setInputTokenAmount(usdValue) setUsdValue(usdValue) - }, [requestedTokenPriceData?.price, requestDetails?.tokenAmount, showRequestPotInitialView]) + }, [requestedTokenPriceData?.price, requestDetails?.tokenAmount, showRequestPotInitialView, isRequestPotLink]) const canInitiatePayment = useMemo(() => { let amountIsSet = false @@ -581,10 +596,12 @@ export const PaymentForm = ({ // Initialize inputTokenAmount useEffect(() => { - if (amount && !inputTokenAmount && !initialSetupDone) { + // skip this step for request pot payments + // Amount is set by the user so we dont need to manually update it + if (amount && !inputTokenAmount && !initialSetupDone && !showRequestPotInitialView) { setInputTokenAmount(amount) } - }, [amount, inputTokenAmount, initialSetupDone]) + }, [amount, inputTokenAmount, initialSetupDone, showRequestPotInitialView]) useEffect(() => { const stepFromURL = searchParams.get('step') @@ -620,6 +637,7 @@ export const PaymentForm = ({ // ensure inputTokenAmount is a valid positive number before allowing payment const numericAmount = parseFloat(inputTokenAmount) + if (isNaN(numericAmount) || numericAmount <= 0) { if (!isExternalWalletFlow) return true } @@ -691,24 +709,8 @@ export const PaymentForm = ({ if (contributionAmounts.length === 0) return { percentage: 0, suggestedAmount: 0 } - const avgContribution = contributionAmounts.reduce((sum, amt) => sum + amt, 0) / contributionAmounts.length - - // Calculate remaining amount (could be negative if over-contributed) - const remaining = totalAmount - totalCollected - let suggestedAmount: number - - // If pot is already full or over-filled, suggest minimum contribution - if (remaining <= 0) { - // Pot is full/overfilled - suggest the smallest previous contribution or 10% of pot - const minContribution = Math.min(...contributionAmounts) - suggestedAmount = Math.min(minContribution, totalAmount * 0.1) - } else if (remaining < avgContribution) { - // If remaining is less than average, suggest the remaining amount - suggestedAmount = remaining - } else { - // Otherwise, suggest the average contribution (most common pattern) - suggestedAmount = avgContribution - } + // suggest the average contribution (most common pattern) + const suggestedAmount = contributionAmounts.reduce((sum, amt) => sum + amt, 0) / contributionAmounts.length // Convert amount to percentage of total pot const percentage = (suggestedAmount / totalAmount) * 100 diff --git a/src/utils/general.utils.ts b/src/utils/general.utils.ts index 15c57ac37..18ad49a2f 100644 --- a/src/utils/general.utils.ts +++ b/src/utils/general.utils.ts @@ -902,6 +902,8 @@ export const getContributorsFromCharge = (charges: ChargeEntry[]) => { username = successfulPayment.payerAccount.identifier } + const isPeanutUser = successfulPayment?.payerAccount?.type === AccountType.PEANUT_WALLET + return { uuid: charge.uuid, payments: charge.payments, @@ -909,6 +911,7 @@ export const getContributorsFromCharge = (charges: ChargeEntry[]) => { username, fulfillmentPayment: charge.fulfillmentPayment, isUserVerified: successfulPayment?.payerAccount?.user?.bridgeKycStatus === 'approved', + isPeanutUser, } }) } From 90b61e0276a5c96aeac584c5181381a6eb69b056 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 22:27:06 +0530 Subject: [PATCH 2/4] fix: overlapping numbers in progress bar --- src/components/Global/ProgressBar/index.tsx | 37 ++++++++++++++++++--- src/utils/general.utils.ts | 4 +-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/components/Global/ProgressBar/index.tsx b/src/components/Global/ProgressBar/index.tsx index 2c13b127f..7f1b1ea93 100644 --- a/src/components/Global/ProgressBar/index.tsx +++ b/src/components/Global/ProgressBar/index.tsx @@ -2,6 +2,7 @@ import { COIN_ICON } from '@/assets' import Image from 'next/image' import React from 'react' import { twMerge } from 'tailwind-merge' +import { formatExtendedNumber } from '@/utils/general.utils' interface ProgressBarProps { goal: number @@ -12,14 +13,42 @@ interface ProgressBarProps { const ProgressBar: React.FC = ({ goal, progress, isClosed }) => { const isOverGoal = progress > goal && goal > 0 const isGoalAchieved = progress >= goal && !isOverGoal && goal > 0 - const totalValue = isOverGoal ? progress : goal + + // Calculate actual ratio and enforce minimum visual distance when over goal + const MIN_VISUAL_DISTANCE = 30 // 30% minimum distance between markers + let totalValue = isOverGoal ? progress : goal + let visualGoalPercentage = 0 + let visualProgressPercentage = 0 + + if (isOverGoal && goal > 0) { + const actualRatio = (progress / goal) * 100 + if (actualRatio < 100 + MIN_VISUAL_DISTANCE) { + // Progress is too close to goal, enforce minimum distance + // Map goal to ~90% and progress to 100% + visualGoalPercentage = 100 - MIN_VISUAL_DISTANCE + visualProgressPercentage = 100 + } else { + // Progress is far enough, show actual ratio + totalValue = progress + visualGoalPercentage = (goal / totalValue) * 100 + visualProgressPercentage = 100 + } + } // Guard against division by zero and clamp percentages to valid ranges - const goalPercentage = totalValue > 0 ? Math.min(Math.max((goal / totalValue) * 100, 0), 100) : 0 - const progressPercentage = totalValue > 0 ? Math.min(Math.max((progress / totalValue) * 100, 0), 100) : 0 + const goalPercentage = isOverGoal + ? visualGoalPercentage + : totalValue > 0 + ? Math.min(Math.max((goal / totalValue) * 100, 0), 100) + : 0 + const progressPercentage = isOverGoal + ? visualProgressPercentage + : totalValue > 0 + ? Math.min(Math.max((progress / totalValue) * 100, 0), 100) + : 0 const percentage = goal > 0 ? Math.min(Math.max(Math.round((progress / goal) * 100), 0), 100) : 0 - const formatCurrency = (value: number) => `$${value.toFixed(2)}` + const formatCurrency = (value: number) => `$${formatExtendedNumber(value, 4)}` const getStatusText = () => { if (isOverGoal) return 'Goal exceeded!' diff --git a/src/utils/general.utils.ts b/src/utils/general.utils.ts index 18ad49a2f..49b842149 100644 --- a/src/utils/general.utils.ts +++ b/src/utils/general.utils.ts @@ -655,7 +655,7 @@ export const getHeaderTitle = (pathname: string) => { * @param amount - The number or string to format. * @returns A formatted string with appropriate suffix. */ -export const formatExtendedNumber = (amount: string | number): string => { +export const formatExtendedNumber = (amount: string | number, minDigitsForFomatting: number = 6): string => { // Handle null/undefined/invalid inputs if (!amount && amount !== 0) return '0' @@ -669,7 +669,7 @@ export const formatExtendedNumber = (amount: string | number): string => { const totalDigits = amount.toString().replace(/[.-]/g, '').length // If 6 or fewer digits, just use formatAmount - if (totalDigits <= 6) { + if (totalDigits <= minDigitsForFomatting) { return formatAmount(num) } From 8ce0e28914bea1f7e4b0c7042aa903ce0bd6a36a Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 22:39:36 +0530 Subject: [PATCH 3/4] fix: Disable bank fulfillments --- src/components/Common/ActionList.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Common/ActionList.tsx b/src/components/Common/ActionList.tsx index 331a81969..6ec704230 100644 --- a/src/components/Common/ActionList.tsx +++ b/src/components/Common/ActionList.tsx @@ -188,8 +188,14 @@ export default function ActionList({ break } } else if (flow === 'request' && requestLinkData) { - if (method.id === 'bank' && amountInUsd < 1) { - setShowMinAmountError(true) + // @dev TODO: Fix req fulfillment with bank properly post devconnect + if (method.id === 'bank') { + if (user?.user) { + router.push('/add-money') + } else { + const redirectUri = encodeURIComponent('/add-money') + router.push(`/setup?redirect_uri=${redirectUri}`) + } return } From bc9d49d38380be5dc6b62259658e06e00755bc71 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 22:42:40 +0530 Subject: [PATCH 4/4] fix: contributors scroll --- src/components/TransactionDetails/TransactionDetailsReceipt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx index 304c1542b..b144a1de9 100644 --- a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx +++ b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx @@ -1459,7 +1459,7 @@ export const TransactionDetailsReceipt = ({ {requestPotContributors.length > 0 && ( <>

Contributors ({requestPotContributors.length})

-
+
{requestPotContributors.map((contributor, index) => (