From 32e58e303d682ed725409557a1bebd457cde7df1 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Tue, 31 Oct 2023 17:09:37 +0100 Subject: [PATCH 01/28] Send XP claim transactions one by one Wallet can't handle signing multiple transactions at once, we need to send them one by one and wait for the previous to either be signed or rejected. --- src/redux-state/thunks/island.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index 1afcd3876..62aa81381 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -375,14 +375,18 @@ export const claimXp = createDappAsyncThunk( return false } - await Promise.allSettled( - claims.map(async ({ distributorContractAddress, claim }) => { - await transactionService.send(id, claimXpTokens, { - distributorContractAddress, - claim, - }) + // eslint-disable-next-line no-restricted-syntax + for (const { claim, distributorContractAddress } of claims) { + // eslint-disable-next-line no-await-in-loop + const receipt = await transactionService.send(id, claimXpTokens, { + distributorContractAddress, + claim, }) - ) + + if (!receipt) { + break + } + } dispatch(fetchUnclaimedXp()) From e6f66443c37c354afc86eb697914371d3bf5fece Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 1 Nov 2023 14:01:41 +0100 Subject: [PATCH 02/28] Initial refactor of `BannerRewards` to move the claiming logic out --- .../RealmBanners/BannerRewards.tsx | 59 ++++------------ .../RealmDetails/XpClaim/XpClaimModal.tsx | 70 +++++++++++++++++++ 2 files changed, 82 insertions(+), 47 deletions(-) create mode 100644 src/ui/Island/RealmDetails/XpClaim/XpClaimModal.tsx diff --git a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx index aeb8b1f51..3079a4b8d 100644 --- a/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx +++ b/src/ui/Island/RealmDetails/RealmBanners/BannerRewards.tsx @@ -1,24 +1,17 @@ -import React, { useCallback, useEffect, useState } from "react" +import React, { useCallback, useState } from "react" import Button from "shared/components/Button" import RealmBanner from "shared/components/RealmModal/RealmBanner" import RealmIcon from "shared/components/RealmIcon" import { - claimXp, selectDisplayedRealmId, selectRealmById, - selectTransactionStatusById, - stopTrackingTransactionStatus, - useDappDispatch, useDappSelector, } from "redux-state" import ClaimCongratulations from "ui/Claim/modals/ClaimCongratulations" import Tooltip from "shared/components/Tooltip" -import { useTransactionSuccessCallback } from "shared/hooks" -import TransactionsModal from "shared/components/Transactions/TransactionsModal" import { bigIntToUserAmount, separateThousandsByComma } from "shared/utils" import { LINKS } from "shared/constants" - -const CLAIM_XP_TX_ID = "claim-xp" +import XpClaimModal from "../XpClaim/XpClaimModal" export default function BannerRewards({ amount, @@ -27,9 +20,9 @@ export default function BannerRewards({ amount: bigint setJustClaimed: (hasClaimed: boolean) => void }) { - const dispatch = useDappDispatch() const realmId = useDappSelector(selectDisplayedRealmId) const realm = useDappSelector((state) => selectRealmById(state, realmId)) + const parsedAmount = separateThousandsByComma(bigIntToUserAmount(amount)) const [savedAmount] = useState(() => parsedAmount) @@ -38,42 +31,14 @@ export default function BannerRewards({ const [isClaimTransactionModalOpen, setIsClaimTransactionModalOpen] = useState(false) - const claimXpTransactionStatus = useDappSelector((state) => - selectTransactionStatusById(state, CLAIM_XP_TX_ID) - ) - - const claimTransaction = () => { - if (realmId) { - dispatch(claimXp({ id: CLAIM_XP_TX_ID, realmId })) - setJustClaimed(true) // to keep the banner + congratulation modal open - } - } - - const claimTransactionData = { - id: CLAIM_XP_TX_ID, - title: "Sign transaction to claim XP", - buttonLabel: "Claim XP", - status: claimXpTransactionStatus, - onClick: claimTransaction, - } - - const claimTransactionSuccessCallback = useCallback(() => { - setIsClaimTransactionModalOpen(false) + const onClaim = useCallback(() => { + setJustClaimed(true) // to keep the banner + congratulation modal open setCongratulationsModalOpen(true) - dispatch(stopTrackingTransactionStatus(CLAIM_XP_TX_ID)) - }, [dispatch]) - - useTransactionSuccessCallback( - claimXpTransactionStatus, - claimTransactionSuccessCallback - ) + }, [setJustClaimed]) - useEffect( - () => () => { - dispatch(stopTrackingTransactionStatus(CLAIM_XP_TX_ID)) - }, - [dispatch] - ) + const onClose = useCallback(() => { + setIsClaimTransactionModalOpen(false) + }, []) if (!realmId || !realm) return null @@ -156,10 +121,10 @@ export default function BannerRewards({ `} - setIsClaimTransactionModalOpen(false)} - transactions={[claimTransactionData]} + onClaim={onClaim} + onClose={onClose} /> {congratulationsModalOpen && ( void + onClose: () => void +}) { + const dispatch = useDappDispatch() + const realmId = useDappSelector(selectDisplayedRealmId) + const claimXpTransactionStatus = useDappSelector((state) => + selectTransactionStatusById(state, CLAIM_XP_TX_ID) + ) + + const claimTransaction = () => { + if (realmId) { + dispatch(claimXp({ id: CLAIM_XP_TX_ID, realmId })) + } + } + + const claimTransactionSuccessCallback = useCallback(() => { + dispatch(stopTrackingTransactionStatus(CLAIM_XP_TX_ID)) + onClaim() + onClose() + }, [dispatch, onClose, onClaim]) + + useTransactionSuccessCallback( + claimXpTransactionStatus, + claimTransactionSuccessCallback + ) + + useEffect( + () => () => { + dispatch(stopTrackingTransactionStatus(CLAIM_XP_TX_ID)) + }, + [dispatch] + ) + + const claimTransactionData = { + id: CLAIM_XP_TX_ID, + title: "Sign transaction to claim XP", + buttonLabel: "Claim XP", + status: claimXpTransactionStatus, + onClick: claimTransaction, + } + + return ( + onClose()} + transactions={[claimTransactionData]} + /> + ) +} From e43050b02d5681a2f5387b0b514d5f054569ea3b Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 1 Nov 2023 16:53:04 +0100 Subject: [PATCH 03/28] Refactor claiming XP to be triggered by separate buttons --- src/redux-state/selectors/island.ts | 14 +++- src/redux-state/thunks/island.ts | 33 +++----- src/redux-state/thunks/wallet.ts | 18 +++++ .../Transactions/TransactionsModal.tsx | 3 +- .../RealmBanners/BannerRewards.tsx | 1 + .../RealmDetails/XpClaim/XpClaimModal.tsx | 80 ++++++++++++++----- 6 files changed, 103 insertions(+), 46 deletions(-) diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index 166a6156e..263fdd5dc 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -2,6 +2,7 @@ import { createSelector } from "@reduxjs/toolkit" import { RootState } from "redux-state/reducers" import { DAY } from "shared/constants" import { isSameAddress } from "shared/utils" +import { selectTransactionStatusById } from "./wallet" export const selectIslandMode = (state: RootState) => state.island.mode @@ -155,7 +156,7 @@ export const selectUserLeaderboardRankById = createSelector( export const selectUnclaimedXpById = createSelector( [(_, realmId: string) => realmId, selectUnclaimedXp], - (realmId, unclaimedXp) => unclaimedXp[realmId] + (realmId, unclaimedXp) => unclaimedXp[realmId] ?? [] ) export const selectUnclaimedXpSumById = createSelector( @@ -164,6 +165,17 @@ export const selectUnclaimedXpSumById = createSelector( unclaimedXp?.reduce((acc, item) => acc + BigInt(item.claim.amount), 0n) ?? 0n ) + +export const selectXpClaimTransactionStatuses = createSelector( + [selectUnclaimedXpById, (state) => state.wallet], + (unclaimedXp, walletState) => + Object.fromEntries( + unclaimedXp.map((data) => { + const id = `claim-xp-${data.merkleRoot}` + return [id, selectTransactionStatusById({ wallet: walletState }, id)] + }) + ) +) /* Population - selectors */ export const selectSortedPopulation = createSelector(selectRealms, (realms) => { const realmsData = Object.entries(realms).map(([id, data]) => ({ diff --git a/src/redux-state/thunks/island.ts b/src/redux-state/thunks/island.ts index 62aa81381..6028fb3e8 100644 --- a/src/redux-state/thunks/island.ts +++ b/src/redux-state/thunks/island.ts @@ -26,6 +26,7 @@ import { import { RealmContractDataWithId, TransactionProgressStatus, + UnclaimedXpData, } from "shared/types" import { updateTransactionStatus } from "redux-state/slices/wallet" import { bigIntToUserAmount, getAllowanceTransactionID } from "shared/utils" @@ -359,37 +360,25 @@ export const claimXp = createDappAsyncThunk( async ( { id, - realmId, + claimData, }: { id: string - realmId: string + claimData: UnclaimedXpData }, - { dispatch, getState, extra: { transactionService } } + { dispatch, extra: { transactionService } } ) => { - const { - island: { unclaimedXp }, - } = getState() - const claims = unclaimedXp[realmId] ?? [] - - if (!claims.length) { - return false - } + const { distributorContractAddress, claim } = claimData - // eslint-disable-next-line no-restricted-syntax - for (const { claim, distributorContractAddress } of claims) { - // eslint-disable-next-line no-await-in-loop - const receipt = await transactionService.send(id, claimXpTokens, { - distributorContractAddress, - claim, - }) + const receipt = await transactionService.send(id, claimXpTokens, { + distributorContractAddress, + claim, + }) - if (!receipt) { - break - } + if (!receipt) { + return false } dispatch(fetchUnclaimedXp()) - return true } ) diff --git a/src/redux-state/thunks/wallet.ts b/src/redux-state/thunks/wallet.ts index 56b2ec950..7df054a74 100644 --- a/src/redux-state/thunks/wallet.ts +++ b/src/redux-state/thunks/wallet.ts @@ -3,6 +3,7 @@ import { updateBalances, updateConnectedWallet, resetWalletState, + stopTrackingTransactionStatus, } from "redux-state/slices/wallet" import { resetClaiming, setClaimingUser } from "redux-state/slices/claim" import { getBalance, getStakeUnlockTime } from "shared/contracts" @@ -207,3 +208,20 @@ export const fetchWalletBalances = createDappAsyncThunk( return balances } ) + +export const stopTrackingClaimTransactions = createDappAsyncThunk( + "wallet/stopTrackingClaimTransactions", + async (_, { getState, dispatch }) => { + const { + wallet: { transactionStatus }, + } = getState() + + const claimTransactionIds = Object.keys(transactionStatus).filter((id) => + id.startsWith("claim-xp-") + ) + + claimTransactionIds.forEach((id) => { + dispatch(stopTrackingTransactionStatus(id)) + }) + } +) diff --git a/src/shared/components/Transactions/TransactionsModal.tsx b/src/shared/components/Transactions/TransactionsModal.tsx index 253bb7eca..5a7846ebe 100644 --- a/src/shared/components/Transactions/TransactionsModal.tsx +++ b/src/shared/components/Transactions/TransactionsModal.tsx @@ -6,7 +6,7 @@ import TransactionProgress, { } from "./TransactionProgress" type TransactionsModalProps = { - title?: string + title?: React.ReactNode isOpen: boolean close: () => void transactions?: ({ id: string } & TransactionProgressProps)[] @@ -62,6 +62,7 @@ export default function TransactionsModal({ + + ) +} diff --git a/src/shared/constants/external-links.ts b/src/shared/constants/external-links.ts index f217e1c17..86fcec8d9 100644 --- a/src/shared/constants/external-links.ts +++ b/src/shared/constants/external-links.ts @@ -8,4 +8,5 @@ export default { BRAVE_SUPPORT: "https://support.brave.com/hc/en-us/articles/360023646212-How-do-I-configure-global-and-site-specific-Shields-settings-", FEEDBACK: "https://tahowallet.typeform.com/subscapebeta", + PRIVACY_POLICY: "https://taho.xyz/privacy", } From 2c398e996d1ce521346c6e3f53e19b7d46f064c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Paczy=C5=84ski?= Date: Tue, 7 Nov 2023 14:08:34 +0100 Subject: [PATCH 20/28] Handle privacy policy popup in `localStorage` --- src/shared/components/PrivacyPolicy.tsx | 15 +++++++++++---- src/shared/constants/local-storage.ts | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/shared/components/PrivacyPolicy.tsx b/src/shared/components/PrivacyPolicy.tsx index f2219cc64..765212fe7 100644 --- a/src/shared/components/PrivacyPolicy.tsx +++ b/src/shared/components/PrivacyPolicy.tsx @@ -2,16 +2,23 @@ import React from "react" import Popup from "shared/components/Popup" import cookiesIcon from "shared/assets/icons/cookies.svg" import newTabIcon from "shared/assets/icons/s/new-tab.svg" -import { LINKS } from "shared/constants" +import { LINKS, LOCAL_STORAGE_COOKIES } from "shared/constants" +import { useLocalStorageChange } from "shared/hooks" import Icon from "./Icon" import Button from "./Button" export default function PrivacyPolicy() { + const { value, updateStorage } = useLocalStorageChange( + LOCAL_STORAGE_COOKIES + ) + + const closePopup = () => updateStorage(true) + return ( <> {}} + isVisible={value === null} + close={closePopup} bottomPosition={16} leftPosition={19} width={419} @@ -27,7 +34,7 @@ export default function PrivacyPolicy() { policy.

- Date: Tue, 7 Nov 2023 14:42:44 +0100 Subject: [PATCH 21/28] Fix type-o --- src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx | 6 +++--- src/ui/Assistant/AssistantContent/AssistantJoin.tsx | 6 +++--- src/ui/Assistant/AssistantContent/AssistantQuests.tsx | 6 +++--- src/ui/Assistant/AssistantContent/AssistantWelcome.tsx | 6 +++--- src/ui/Assistant/AssistantContent/index.tsx | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx b/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx index 38590931b..39c92c330 100644 --- a/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx +++ b/src/ui/Assistant/AssistantContent/AssistantFirstRealm.tsx @@ -1,7 +1,7 @@ import React from "react" import { useAssistant, useLocalStorageChange } from "shared/hooks" import { LOCAL_STORAGE_VISITED_REALM } from "shared/constants" -import AsisstantContent from "." +import AssistantContent from "." export default function AssistantFirstRealm() { const { updateAssistant, assistantVisible } = useAssistant() @@ -16,7 +16,7 @@ export default function AssistantFirstRealm() { return ( <> - @@ -32,7 +32,7 @@ export default function AssistantFirstRealm() { $TAHO you stake, the more $XP you earn. The more Citizens in each Realm, the more your weekly $XP reward gets diluted.

-
+