From 555f0d08a054f359b7876e01fb65b50f1be9e195 Mon Sep 17 00:00:00 2001 From: ocakciomursel-pixel Date: Sat, 17 Oct 2026 22:03:10 +0300 Subject: [PATCH 1/3] fix(claim): show loading spinner while claim data loads on network switch (#614) - Detect in-flight UBI/Identity multicall and entitlement resolution after switching networks (e.g., to XDC) - Render a page-loader Spinner instead of an unresponsive blue "CLAIM NOW" button while claim data is loading - Forward refresh={refreshRate} to ClaimButton so it polls everyBlock on network switch Fixes #614 --- src/pages/gd/Claim/OldClaim.tsx | 56 +++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/pages/gd/Claim/OldClaim.tsx b/src/pages/gd/Claim/OldClaim.tsx index 5f3689ad5..9923b0e21 100644 --- a/src/pages/gd/Claim/OldClaim.tsx +++ b/src/pages/gd/Claim/OldClaim.tsx @@ -11,7 +11,7 @@ import { useScreenSize, ClaimSuccessModal, } from '@gooddollar/good-design' -import { Box, Center, Text, useBreakpointValue } from 'native-base' +import { Box, Center, Spinner, Text, useBreakpointValue } from 'native-base' import { useConnectionInfo } from 'hooks/useConnectionInfo' import { useClaim, @@ -42,11 +42,13 @@ import { useIsSimpleApp } from 'state/simpleapp/simpleapp' import { useDisabledClaimingModal } from './useDisabledClaimModal' import { useGoodDappFeatures } from 'hooks/useFeaturesEnabled' +const INITIAL_PERIOD_EPOCH_MS = 86_400_000 + const OldClaim = memo(() => { const { i18n } = useLingui() const [refreshRate, setRefreshRate] = useState(12) const claimDetails = useClaim(refreshRate) - const { claimAmount } = claimDetails + const { claimAmount, isWhitelisted, nextClaimTime } = claimDetails const isMinipay = isMiniPay() const { resetState, state, send } = useContractFunctionWithDefaultGasFees( claimDetails.contract, @@ -152,7 +154,19 @@ const OldClaim = memo(() => { [sendData, network] ) + const isClaimDataLoading = Boolean( + address && + (activeChainFeatures['claimEnabled'] || !isProd || isMinipay) && + (!nextClaimTime || + nextClaimTime.getTime() <= INITIAL_PERIOD_EPOCH_MS || + (isWhitelisted && (claimAmount === undefined || claimed === undefined))) + ) + const handleClaim = useCallback(async () => { + if (isClaimDataLoading) { + return false + } + setRefreshRate('everyBlock') if (!claimEnabled) { @@ -186,7 +200,16 @@ const OldClaim = memo(() => { return false // eslint-disable-next-line react-hooks/exhaustive-deps - }, [send, network, sendData, activeChainFeatures, isSimpleApp, claimEnabled, showModal]) + }, [ + isClaimDataLoading, + send, + network, + sendData, + activeChainFeatures, + isSimpleApp, + claimEnabled, + showModal, + ]) const handleConnect = useCallback(async () => { if (!address) { @@ -411,16 +434,23 @@ const OldClaim = memo(() => { )} - + {isClaimDataLoading ? ( +
+ +
+ ) : ( + + )} {isHoliday ? (
Date: Sat, 17 Oct 2026 22:15:19 +0300 Subject: [PATCH 2/3] fix(claim): bind claim data to active chain, guard views, add retry on stall (#614) - Track dataChainId to prevent previous chain entitlement from rendering on chain switch - Keep both ClaimBalance and ClaimButton behind loading state until active chain resolves - Add timeout and Retry button for stalled or failed claim data requests - Remove refresh prop on ClaimButton to avoid duplicate every-block polling --- src/pages/gd/Claim/OldClaim.tsx | 99 +++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 23 deletions(-) diff --git a/src/pages/gd/Claim/OldClaim.tsx b/src/pages/gd/Claim/OldClaim.tsx index 9923b0e21..6ca3c6249 100644 --- a/src/pages/gd/Claim/OldClaim.tsx +++ b/src/pages/gd/Claim/OldClaim.tsx @@ -11,7 +11,7 @@ import { useScreenSize, ClaimSuccessModal, } from '@gooddollar/good-design' -import { Box, Center, Spinner, Text, useBreakpointValue } from 'native-base' +import { Box, Button, Center, Spinner, Text, useBreakpointValue } from 'native-base' import { useConnectionInfo } from 'hooks/useConnectionInfo' import { useClaim, @@ -60,6 +60,8 @@ const OldClaim = memo(() => { ) const [claimed, setClaimed] = useState(undefined) + const [dataChainId, setDataChainId] = useState(undefined) + const [claimLoadError, setClaimLoadError] = useState(false) const { address, chainId } = useConnectionInfo() const { open } = useAppKit() const network = SupportedV2Networks[chainId] @@ -109,24 +111,38 @@ const OldClaim = memo(() => { if (claimAmount?.isZero()) { setClaimed(true) + setDataChainId(chainId) setRefreshRate(12) resetState() return } else if (state.status === 'Success') { setClaimed(true) + setDataChainId(chainId) return } setClaimed(false) + setDataChainId(chainId) setRefreshRate('everyBlock') } - if (claimAmount) hasClaimed().catch(noop) + if (claimAmount && (!nextClaimTime || nextClaimTime.getTime() > INITIAL_PERIOD_EPOCH_MS)) { + hasClaimed().catch(noop) + } // eslint-disable-next-line react-hooks-addons/no-unused-deps, react-hooks/exhaustive-deps - }, [claimAmount, chainId, refreshRate]) + }, [claimAmount, chainId, nextClaimTime, resetState, state.status]) + + useEffect(() => { + if (!isWhitelisted && nextClaimTime && nextClaimTime.getTime() > INITIAL_PERIOD_EPOCH_MS) { + setDataChainId(chainId) + setClaimed(false) + } + }, [isWhitelisted, nextClaimTime, chainId]) - // upon switching chain we want temporarily to poll everyBlock up untill we have the latest data + // upon switching chain we want temporarily to poll everyBlock up until we have the latest data useEffect(() => { + setDataChainId(undefined) setClaimed(undefined) + setClaimLoadError(false) setRefreshRate('everyBlock') }, [/* used */ chainId]) @@ -157,11 +173,32 @@ const OldClaim = memo(() => { const isClaimDataLoading = Boolean( address && (activeChainFeatures['claimEnabled'] || !isProd || isMinipay) && - (!nextClaimTime || + !claimLoadError && + (dataChainId !== chainId || + !nextClaimTime || nextClaimTime.getTime() <= INITIAL_PERIOD_EPOCH_MS || (isWhitelisted && (claimAmount === undefined || claimed === undefined))) ) + useEffect(() => { + if (!isClaimDataLoading) { + return + } + + const timeoutId = setTimeout(() => { + setClaimLoadError(true) + }, 15000) + + return () => clearTimeout(timeoutId) + }, [isClaimDataLoading]) + + const handleRetry = useCallback(() => { + setClaimLoadError(false) + setDataChainId(undefined) + setClaimed(undefined) + setRefreshRate('everyBlock') + }, []) + const handleClaim = useCallback(async () => { if (isClaimDataLoading) { return false @@ -376,7 +413,30 @@ const OldClaim = memo(() => { - {claimed ? ( + {claimLoadError ? ( +
+ + {i18n._(t`Unable to load claim data. Please check your connection and try again.`)} + + +
+ ) : isClaimDataLoading ? ( +
+ +
+ ) : claimed ? ( @@ -434,23 +494,16 @@ const OldClaim = memo(() => { )} - {isClaimDataLoading ? ( -
- -
- ) : ( - - )} + {isHoliday ? (
Date: Sat, 17 Oct 2026 22:19:30 +0300 Subject: [PATCH 3/3] fix(claim): reset refreshRate to 12 once active chain claim state resolves (#614) --- src/pages/gd/Claim/OldClaim.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/gd/Claim/OldClaim.tsx b/src/pages/gd/Claim/OldClaim.tsx index 6ca3c6249..e2e1aab6b 100644 --- a/src/pages/gd/Claim/OldClaim.tsx +++ b/src/pages/gd/Claim/OldClaim.tsx @@ -118,12 +118,13 @@ const OldClaim = memo(() => { } else if (state.status === 'Success') { setClaimed(true) setDataChainId(chainId) + setRefreshRate(12) return } setClaimed(false) setDataChainId(chainId) - setRefreshRate('everyBlock') + setRefreshRate(12) } if (claimAmount && (!nextClaimTime || nextClaimTime.getTime() > INITIAL_PERIOD_EPOCH_MS)) { hasClaimed().catch(noop) @@ -135,6 +136,7 @@ const OldClaim = memo(() => { if (!isWhitelisted && nextClaimTime && nextClaimTime.getTime() > INITIAL_PERIOD_EPOCH_MS) { setDataChainId(chainId) setClaimed(false) + setRefreshRate(12) } }, [isWhitelisted, nextClaimTime, chainId]) @@ -187,6 +189,7 @@ const OldClaim = memo(() => { const timeoutId = setTimeout(() => { setClaimLoadError(true) + setRefreshRate(12) }, 15000) return () => clearTimeout(timeoutId)