diff --git a/src/redux-state/index.ts b/src/redux-state/index.ts index 41c6941c4..9c6f2344d 100644 --- a/src/redux-state/index.ts +++ b/src/redux-state/index.ts @@ -23,5 +23,12 @@ export * from "./slices/wallet" export * from "./slices/island" export * from "./selectors/claim" -export * from "./selectors/wallet" export * from "./selectors/island" +export * from "./selectors/leaderboard" +export * from "./selectors/population" +export * from "./selectors/realm" +export * from "./selectors/season" +export * from "./selectors/staking" +export * from "./selectors/token" +export * from "./selectors/wallet" +export * from "./selectors/xp" diff --git a/src/redux-state/selectors.ts b/src/redux-state/selectors.ts new file mode 100644 index 000000000..1cf798c2a --- /dev/null +++ b/src/redux-state/selectors.ts @@ -0,0 +1,20 @@ +import { createSelector } from "@reduxjs/toolkit" +import { + CreateIslandSelector, + CreateClaimSelector, + CreateWalletSelector, +} from "shared/types/selectors" +import { RootState } from "./reducers" + +export const selectIsland = (state: RootState) => state.island +export const selectClaim = (state: RootState) => state.claim +export const selectWallet = (state: RootState) => state.wallet + +export const createIslandSelector: CreateIslandSelector = (value) => + createSelector(selectIsland, (island) => island[value]) + +export const createClaimSelector: CreateClaimSelector = (value) => + createSelector(selectClaim, (claim) => claim[value]) + +export const createWalletSelector: CreateWalletSelector = (value) => + createSelector(selectWallet, (wallet) => wallet[value]) diff --git a/src/redux-state/selectors/claim.ts b/src/redux-state/selectors/claim.ts index f9dfe9b28..208267586 100644 --- a/src/redux-state/selectors/claim.ts +++ b/src/redux-state/selectors/claim.ts @@ -1,31 +1,22 @@ -import { createSelector } from "@reduxjs/toolkit" import { RootState } from "redux-state/reducers" import { truncateAddress } from "shared/utils" -import { RealmData } from "shared/types" -import { selectRealmById } from "./island" +import { createClaimSelector } from "redux-state/selectors" -export const selectClaimingUser = (state: RootState) => ({ - name: state.claim.name || truncateAddress(state.claim.address), - address: state.claim.address, -}) - -export const selectHasClaimed = (state: RootState) => state.claim.hasClaimed - -export const selectEligibility = (state: RootState) => state.claim.eligibility - -export const selectUseConnectedWalletToClaim = (state: RootState) => - state.claim.useConnectedWallet - -export const selectStakingData = createSelector( - (state: RootState) => selectRealmById(state, state.claim.selectedRealmId), - (state: RootState) => state.claim.stakeAmount, - (realmData: RealmData | null, stakeAmount) => ({ - realmContractAddress: realmData ? realmData.realmContractAddress : null, - stakeAmount, - }) +export const selectHasClaimed = createClaimSelector("hasClaimed") +export const selectEligibility = createClaimSelector("eligibility") +export const selectRealmId = createClaimSelector("selectedRealmId") +export const selectClaimingStakeAmount = createClaimSelector("stakeAmount") +export const selectClaimAdress = createClaimSelector("address") +export const selectClaimName = createClaimSelector("name") +export const selectUseConnectedWalletToClaim = + createClaimSelector("useConnectedWallet") +export const selectRepresentativeAddress = createClaimSelector( + "representativeAddress" ) +export const selectClaimingSelectedRealmId = + createClaimSelector("selectedRealmId") -export const selectRepresentativeAddress = (state: RootState) => - state.claim.representativeAddress - -export const selectRealmId = (state: RootState) => state.claim.selectedRealmId +export const selectClaimingUser = (state: RootState) => ({ + name: selectClaimName(state) || truncateAddress(selectClaimAdress(state)), + address: selectClaimAdress(state), +}) diff --git a/src/redux-state/selectors/island.ts b/src/redux-state/selectors/island.ts index 306ea30dc..3dd348a58 100644 --- a/src/redux-state/selectors/island.ts +++ b/src/redux-state/selectors/island.ts @@ -1,211 +1,13 @@ import { createSelector } from "@reduxjs/toolkit" -import { RootState } from "redux-state/reducers" -import { DAY } from "shared/constants" -import { isSameAddress } from "shared/utils" +import { createIslandSelector } from "redux-state/selectors" +import { IslandModeType } from "redux-state/slices/island" -export const selectIslandMode = (state: RootState) => state.island.mode +export const selectIslandMode = createIslandSelector("mode") +export const selectIslandOverlay = createIslandSelector("overlay") +export const selectIslandZoomLevel = createIslandSelector("zoomLevel") -export const selectIsDefaultIslandMode = (state: RootState) => - state.island.mode === "default" +const checkIslandMode = (value: IslandModeType) => + createSelector(selectIslandMode, (mode) => mode === value) -export const selectIsJoinRealmIslandMode = (state: RootState) => - state.island.mode === "join-realm" - -export const selectIslandOverlay = (state: RootState) => state.island.overlay - -export const selectRealms = (state: RootState) => state.island.realms - -export const selectRealmById = createSelector( - [selectRealms, (_, realmId: string | null) => realmId], - (realms, realmId) => (realmId ? realms[realmId] : null) -) - -export const selectRealmNameById = createSelector( - [selectRealms, (_, realmId: string | null) => realmId], - (realms, realmId) => (realmId ? realms[realmId].name : null) -) - -export const selectRealmWithIdByAddress = createSelector( - [selectRealms, (_, realmAddress: string) => realmAddress], - (realms, realmAddress) => - Object.entries(realms).find(([_, { realmContractAddress }]) => - isSameAddress(realmContractAddress, realmAddress) - ) -) - -export const selectHasLoadedRealmData = createSelector( - selectRealms, - (realms) => Object.keys(realms).length !== 0 -) - -export const selectHasLoadedSeasonInfo = createSelector( - (state: RootState) => state.island.seasonInfo, - (seasonInfo) => seasonInfo !== null -) - -/* Season info - selectors */ -export const selectSeasonStartTimestamp = (state: RootState) => - state.island.seasonInfo?.seasonStartTimestamp - -export const selectSeasonEndTimestamp = (state: RootState) => - state.island.seasonInfo?.seasonEndTimestamp - -export const selectSeasonDurationInWeeks = (state: RootState) => - state.island.seasonInfo?.durationInWeeks - -export const selectIsEndOfSeason = createSelector( - selectSeasonEndTimestamp, - (seasonEndTimestamp) => { - if (seasonEndTimestamp) { - return Date.now() > seasonEndTimestamp - } - return null - } -) - -export const selectSeasonWeek = createSelector( - selectSeasonStartTimestamp, - selectIsEndOfSeason, - selectSeasonDurationInWeeks, - (seasonStartTimestamp, isEndOfSeason, durationInWeeks) => { - if (isEndOfSeason) return durationInWeeks - - if (seasonStartTimestamp && durationInWeeks) { - const hasSeasonStarted = seasonStartTimestamp < Date.now() - if (!hasSeasonStarted) return 1 // if the start date is placed in the future, set season week to 1 - - return Math.trunc((Date.now() - seasonStartTimestamp) / (7 * DAY) + 1) - } - - return null - } -) - -export const selectWeekStartDate = createSelector( - selectSeasonStartTimestamp, - selectSeasonWeek, - (seasonStartTimestamp, seasonWeek) => { - if (seasonStartTimestamp && seasonWeek) { - const startDate = new Date(seasonStartTimestamp) - startDate.setDate(startDate.getDate() + (seasonWeek - 1) * 7) - return startDate - } - return null - } -) - -export const selectWeekEndDate = createSelector( - selectWeekStartDate, - (startDate) => { - if (!startDate) return null - - const endDate = new Date(startDate) - endDate.setDate(startDate.getDate() + 6) - return endDate - } -) - -/* Displayed Realm - selectors */ -export const selectDisplayedRealmId = (state: RootState) => - state.island.displayedRealmId - -export const selectDisplayedRealmAddress = createSelector( - selectRealms, - selectDisplayedRealmId, - (realms, realmId) => realmId && realms[realmId]?.realmContractAddress -) - -export const selectDisplayedRealmVeTokenAddress = createSelector( - selectRealms, - selectDisplayedRealmId, - (realms, realmId) => realmId && realms[realmId]?.veTokenContractAddress -) - -/* Staking Realm - selectors */ -export const selectStakingRealmId = (state: RootState) => - state.island.stakingRealmId - -export const selectStakingRealmAddress = createSelector( - selectRealms, - selectStakingRealmId, - (realms, stakingRealmId) => - stakingRealmId && realms[stakingRealmId]?.realmContractAddress -) - -/* Xp selectors */ -export const selectLeaderboards = (state: RootState) => - state.island.leaderboards - -export const selectUnclaimedXp = (state: RootState) => state.island.unclaimedXp - -const selectLeaderboardDataById = createSelector( - [(_, realmId: string) => realmId, selectLeaderboards], - (realmId, leaderboards) => leaderboards[realmId] -) - -export const selectLeaderboardById = createSelector( - selectLeaderboardDataById, - (leaderboardData) => leaderboardData?.leaderboard ?? [] -) - -export const selectUserLeaderboardRankById = createSelector( - selectLeaderboardDataById, - (leaderboardData) => leaderboardData?.currentUser ?? null -) - -export const selectUnclaimedXpById = createSelector( - [(_, realmId: string) => realmId, selectUnclaimedXp], - (realmId, unclaimedXp) => unclaimedXp[realmId] -) - -export const selectUnclaimedXpSumById = createSelector( - [selectUnclaimedXpById], - (unclaimedXp) => - unclaimedXp?.reduce((acc, item) => acc + BigInt(item.claim.amount), 0n) ?? - 0n -) -/* Population - selectors */ -export const selectSortedPopulation = createSelector(selectRealms, (realms) => { - const realmsData = Object.entries(realms).map(([id, data]) => ({ - id, - ...data, - })) - - const sortedRealms = realmsData.sort((a, b) => a.population - b.population) - return sortedRealms -}) - -export const selectPopulationById = createSelector( - selectRealmById, - (realm) => realm?.population ?? 0 -) - -export const selectTotalPopulation = createSelector( - selectSortedPopulation, - (realms) => - realms.length - ? realms.map((realm) => realm.population).reduce((a, b) => a + b) - : 0 -) - -export const selectMaxPopulation = createSelector( - selectSortedPopulation, - (realms) => - realms.length ? Math.max(...realms.map((realm) => realm.population)) : 0 -) - -/* Helpful selectors */ -export const selectIsStakingRealmDisplayed = createSelector( - selectStakingRealmAddress, - selectDisplayedRealmAddress, - (stakingAddress, displayedAddress) => - !!stakingAddress && - !!displayedAddress && - isSameAddress(stakingAddress, displayedAddress) -) - -export const selectStakeUnlockTime = (state: RootState) => - state.island.stakeUnlockTime - -export const selectIslandZoomLevel = (state: RootState) => - state.island.zoomLevel +export const selectIsDefaultIslandMode = checkIslandMode("default") +export const selectIsJoinRealmIslandMode = checkIslandMode("join-realm") diff --git a/src/redux-state/selectors/leaderboard.ts b/src/redux-state/selectors/leaderboard.ts new file mode 100644 index 000000000..0d2995d5d --- /dev/null +++ b/src/redux-state/selectors/leaderboard.ts @@ -0,0 +1,19 @@ +import { createSelector } from "@reduxjs/toolkit" +import { createIslandSelector } from "redux-state/selectors" + +export const selectLeaderboards = createIslandSelector("leaderboards") + +const selectLeaderboardDataById = createSelector( + [(_, realmId: string) => realmId, selectLeaderboards], + (realmId, leaderboards) => leaderboards[realmId] +) + +export const selectLeaderboardById = createSelector( + selectLeaderboardDataById, + (leaderboardData) => leaderboardData?.leaderboard ?? [] +) + +export const selectUserLeaderboardRankById = createSelector( + selectLeaderboardDataById, + (leaderboardData) => leaderboardData?.currentUser ?? null +) diff --git a/src/redux-state/selectors/population.ts b/src/redux-state/selectors/population.ts new file mode 100644 index 000000000..a13a8f625 --- /dev/null +++ b/src/redux-state/selectors/population.ts @@ -0,0 +1,31 @@ +import { createSelector } from "@reduxjs/toolkit" +import { RealmData } from "shared/types" +import { selectRealmById, selectRealms } from "./realm" + +const getPopulationOfRealms = (realms: RealmData[]) => + realms.map((realm) => realm.population) + +export const selectSortedPopulation = createSelector(selectRealms, (realms) => { + const realmsData = Object.entries(realms).map(([id, data]) => ({ + id, + ...data, + })) + + return realmsData.sort((a, b) => a.population - b.population) +}) + +export const selectPopulationById = createSelector( + selectRealmById, + (realm) => realm?.population ?? 0 +) + +export const selectTotalPopulation = createSelector( + selectSortedPopulation, + (realms) => + realms.length ? getPopulationOfRealms(realms).reduce((a, b) => a + b) : 0 +) + +export const selectMaxPopulation = createSelector( + selectSortedPopulation, + (realms) => (realms.length ? Math.max(...getPopulationOfRealms(realms)) : 0) +) diff --git a/src/redux-state/selectors/realm.ts b/src/redux-state/selectors/realm.ts new file mode 100644 index 000000000..1ae0eba20 --- /dev/null +++ b/src/redux-state/selectors/realm.ts @@ -0,0 +1,48 @@ +import { createSelector } from "@reduxjs/toolkit" +import { RootState } from "redux-state/reducers" +import { createIslandSelector } from "redux-state/selectors" +import { RealmData } from "shared/types" +import { isSameAddress } from "shared/utils" + +type DisplayedRealmProperty = ( + value: K +) => (state: RootState) => RealmData[K] | undefined + +export const selectRealms = createIslandSelector("realms") +export const selectDisplayedRealmId = createIslandSelector("displayedRealmId") + +const selectDisplayedRealmProperty: DisplayedRealmProperty = (value) => + createSelector(selectRealms, selectDisplayedRealmId, (realms, realmId) => + realmId && realms[realmId] ? realms[realmId][value] : undefined + ) + +export const selectRealmById = createSelector( + [selectRealms, (_, realmId: string | null) => realmId], + (realms, realmId) => (realmId ? realms[realmId] : null) +) + +export const selectRealmNameById = createSelector( + [selectRealms, (_, realmId: string | null) => realmId], + (realms, realmId) => (realmId ? realms[realmId].name : null) +) + +export const selectRealmWithIdByAddress = createSelector( + [selectRealms, (_, realmAddress: string) => realmAddress], + (realms, realmAddress) => + Object.entries(realms).find(([_, { realmContractAddress }]) => + isSameAddress(realmContractAddress, realmAddress) + ) +) + +export const selectHasLoadedRealmData = createSelector( + selectRealms, + (realms) => Object.keys(realms).length !== 0 +) + +export const selectDisplayedRealmAddress = selectDisplayedRealmProperty( + "realmContractAddress" +) + +export const selectDisplayedRealmVeTokenAddress = selectDisplayedRealmProperty( + "veTokenContractAddress" +) diff --git a/src/redux-state/selectors/season.ts b/src/redux-state/selectors/season.ts new file mode 100644 index 000000000..e7c203771 --- /dev/null +++ b/src/redux-state/selectors/season.ts @@ -0,0 +1,79 @@ +import { createSelector } from "@reduxjs/toolkit" +import { RootState } from "redux-state/reducers" +import { createIslandSelector } from "redux-state/selectors" +import { DAY } from "shared/constants" +import { SeasonInfo } from "shared/types" + +type SeasonInfoProperty = ( + value: K +) => (state: RootState) => SeasonInfo[K] | undefined + +export const selectSeasonInfo = createIslandSelector("seasonInfo") + +const selectSeasonInfoProperty: SeasonInfoProperty = (value) => + createSelector(selectSeasonInfo, (seasonInfo) => + seasonInfo ? seasonInfo[value] : undefined + ) + +export const selectHasLoadedSeasonInfo = createSelector( + selectSeasonInfo, + (seasonInfo) => seasonInfo !== null +) + +export const selectSeasonStartTimestamp = selectSeasonInfoProperty( + "seasonStartTimestamp" +) + +export const selectSeasonEndTimestamp = + selectSeasonInfoProperty("seasonEndTimestamp") + +export const selectSeasonDurationInWeeks = + selectSeasonInfoProperty("durationInWeeks") + +export const selectIsEndOfSeason = createSelector( + selectSeasonEndTimestamp, + (seasonEndTimestamp) => + seasonEndTimestamp ? Date.now() > seasonEndTimestamp : null +) + +export const selectSeasonWeek = createSelector( + selectSeasonStartTimestamp, + selectIsEndOfSeason, + selectSeasonDurationInWeeks, + (seasonStartTimestamp, isEndOfSeason, durationInWeeks) => { + if (isEndOfSeason) return durationInWeeks + + if (seasonStartTimestamp && durationInWeeks) { + const hasSeasonStarted = seasonStartTimestamp < Date.now() + if (!hasSeasonStarted) return 1 // if the start date is placed in the future, set season week to 1 + + return Math.trunc((Date.now() - seasonStartTimestamp) / (7 * DAY) + 1) + } + + return null + } +) + +export const selectWeekStartDate = createSelector( + selectSeasonStartTimestamp, + selectSeasonWeek, + (seasonStartTimestamp, seasonWeek) => { + if (seasonStartTimestamp && seasonWeek) { + const startDate = new Date(seasonStartTimestamp) + startDate.setDate(startDate.getDate() + (seasonWeek - 1) * 7) + return startDate + } + return null + } +) + +export const selectWeekEndDate = createSelector( + selectWeekStartDate, + (startDate) => { + if (!startDate) return null + + const endDate = new Date(startDate) + endDate.setDate(startDate.getDate() + 6) + return endDate + } +) diff --git a/src/redux-state/selectors/staking.ts b/src/redux-state/selectors/staking.ts new file mode 100644 index 000000000..940954719 --- /dev/null +++ b/src/redux-state/selectors/staking.ts @@ -0,0 +1,43 @@ +import { createSelector } from "@reduxjs/toolkit" +import { createIslandSelector } from "redux-state/selectors" +import { isSameAddress } from "shared/utils" +import { RootState } from "redux-state/reducers" +import { RealmData } from "shared/types" +import { + selectDisplayedRealmAddress, + selectRealmById, + selectRealms, +} from "./realm" +import { + selectClaimingSelectedRealmId, + selectClaimingStakeAmount, +} from "./claim" + +export const selectStakingRealmId = createIslandSelector("stakingRealmId") +export const selectStakeUnlockTime = createIslandSelector("stakeUnlockTime") + +export const selectStakingRealmAddress = createSelector( + selectRealms, + selectStakingRealmId, + (realms, stakingRealmId) => + stakingRealmId && realms[stakingRealmId]?.realmContractAddress +) + +export const selectIsStakingRealmDisplayed = createSelector( + selectStakingRealmAddress, + selectDisplayedRealmAddress, + (stakingAddress, displayedAddress) => + !!stakingAddress && + !!displayedAddress && + isSameAddress(stakingAddress, displayedAddress) +) + +export const selectStakingData = createSelector( + (state: RootState) => + selectRealmById(state, selectClaimingSelectedRealmId(state)), + selectClaimingStakeAmount, + (realmData: RealmData | null, stakeAmount) => ({ + realmContractAddress: realmData ? realmData.realmContractAddress : null, + stakeAmount, + }) +) diff --git a/src/redux-state/selectors/token.ts b/src/redux-state/selectors/token.ts new file mode 100644 index 000000000..c818a5dd0 --- /dev/null +++ b/src/redux-state/selectors/token.ts @@ -0,0 +1,39 @@ +import { createSelector } from "@reduxjs/toolkit" +import { createWalletSelector } from "redux-state/selectors" +import { TAHO_SYMBOL } from "shared/constants" + +export const selectTokenBalances = createWalletSelector("balances") + +export const selectTokenBalanceByAddress = createSelector( + [selectTokenBalances, (_, tokenAddress) => tokenAddress], + (balances, tokenAddress) => balances[tokenAddress]?.balance ?? 0n +) + +export const selectTokenSymbolByAddress = createSelector( + [selectTokenBalances, (_, tokenAddress) => tokenAddress], + (balances, tokenAddress) => balances[tokenAddress]?.symbol ?? "" +) + +export const selectTokenBalanceBySymbol = createSelector( + [selectTokenBalances, (_, tokenSymbol) => tokenSymbol], + (balances, tokenSymbol) => { + const tokenBalance = Object.values(balances).find( + (balance) => balance.symbol === tokenSymbol + ) + return tokenBalance?.balance ?? 0n + } +) + +export const selectHasRelevantTokens = createSelector( + selectTokenBalances, + (balances) => + Object.values(balances).some((balanceData) => { + // relevant tokens are any tokens with "TAHO" symbol + // because this group includes both TAHO and veTAHO tokens + if (balanceData.symbol === TAHO_SYMBOL && !!balanceData.balance) { + return true + } + + return false + }) +) diff --git a/src/redux-state/selectors/wallet.ts b/src/redux-state/selectors/wallet.ts index 88e527c2b..b73aa21c3 100644 --- a/src/redux-state/selectors/wallet.ts +++ b/src/redux-state/selectors/wallet.ts @@ -1,63 +1,28 @@ import { createSelector } from "@reduxjs/toolkit" -import { RootState } from "redux-state/reducers" -import { TAHO_SYMBOL } from "shared/constants" import { TransactionProgressStatus } from "shared/types" import { truncateAddress } from "shared/utils" - -export const selectWalletAddress = (state: RootState) => state.wallet.address - -export const selectWalletTruncatedAddress = (state: RootState) => - truncateAddress(state.wallet.address) - -export const selectWalletName = (state: RootState) => - state.wallet.name || truncateAddress(state.wallet.address) - -export const selectWalletAvatar = (state: RootState) => state.wallet.avatar - -export const selectIsWalletConnected = (state: RootState) => - state.wallet.isConnected - -export const selectTokenBalances = (state: RootState) => state.wallet.balances - -export const selectHasLoadedBalances = (state: RootState) => - state.wallet.hasLoadedBalances - -export const selectTokenBalanceByAddress = createSelector( - [selectTokenBalances, (_, tokenAddress) => tokenAddress], - (balances, tokenAddress) => balances[tokenAddress]?.balance ?? 0n -) - -export const selectTokenSymbolByAddress = createSelector( - [selectTokenBalances, (_, tokenAddress) => tokenAddress], - (balances, tokenAddress) => balances[tokenAddress]?.symbol ?? "" +import { createWalletSelector } from "redux-state/selectors" + +export const selectWalletAddress = createWalletSelector("address") +export const selectWalletAvatar = createWalletSelector("avatar") +export const selectWalletNameProperty = createWalletSelector("name") +export const selectIsWalletConnected = createWalletSelector("isConnected") +export const selectHasLoadedBalances = createWalletSelector("hasLoadedBalances") +export const selectWalletTransactionStatus = + createWalletSelector("transactionStatus") + +export const selectWalletTruncatedAddress = createSelector( + selectWalletAddress, + (address: string) => truncateAddress(address) ) -export const selectTokenBalanceBySymbol = createSelector( - [selectTokenBalances, (_, tokenSymbol) => tokenSymbol], - (balances, tokenSymbol) => { - const tokenBalance = Object.values(balances).find( - (balance) => balance.symbol === tokenSymbol - ) - return tokenBalance?.balance ?? 0n - } -) - -export const selectHasRelevantTokens = createSelector( - selectTokenBalances, - (balances) => - Object.values(balances).some((balanceData) => { - // relevant tokens are any tokens with "TAHO" symbol - // because this group includes both TAHO and veTAHO tokens - if (balanceData.symbol === TAHO_SYMBOL && !!balanceData.balance) { - return true - } - - return false - }) +export const selectWalletName = createSelector( + [selectWalletNameProperty, selectWalletTruncatedAddress], + (name, address) => name || address ) export const selectTransactionStatusById = createSelector( - [(_, id: string) => id, (state: RootState) => state.wallet.transactionStatus], + [(_, id: string) => id, selectWalletTransactionStatus], (id, transactionStatus) => transactionStatus[id] ?? TransactionProgressStatus.Idle ) diff --git a/src/redux-state/selectors/xp.ts b/src/redux-state/selectors/xp.ts new file mode 100644 index 000000000..59dfd9ef7 --- /dev/null +++ b/src/redux-state/selectors/xp.ts @@ -0,0 +1,16 @@ +import { createSelector } from "@reduxjs/toolkit" +import { createIslandSelector } from "redux-state/selectors" + +export const selectUnclaimedXp = createIslandSelector("unclaimedXp") + +export const selectUnclaimedXpById = createSelector( + [(_, realmId: string) => realmId, selectUnclaimedXp], + (realmId, unclaimedXp) => unclaimedXp[realmId] +) + +export const selectUnclaimedXpSumById = createSelector( + [selectUnclaimedXpById], + (unclaimedXp) => + unclaimedXp?.reduce((acc, item) => acc + BigInt(item.claim.amount), 0n) ?? + 0n +) diff --git a/src/redux-state/slices/island.ts b/src/redux-state/slices/island.ts index 3208b3ef3..0ef26e9ee 100644 --- a/src/redux-state/slices/island.ts +++ b/src/redux-state/slices/island.ts @@ -8,7 +8,7 @@ import { SeasonInfo, } from "shared/types" -type IslandModeType = "default" | "join-realm" +export type IslandModeType = "default" | "join-realm" export type IslandState = { mode: IslandModeType diff --git a/src/redux-state/thunks/wallet.ts b/src/redux-state/thunks/wallet.ts index 56b2ec950..ffce0570c 100644 --- a/src/redux-state/thunks/wallet.ts +++ b/src/redux-state/thunks/wallet.ts @@ -24,23 +24,23 @@ export const fetchWalletName = createDappAsyncThunk( claim: { useConnectedWallet }, } = getState() - const { name, avatar } = await resolveAddressToWalletData(address) + const data = await resolveAddressToWalletData(address) - if (name) { + if (data) { dispatch( updateConnectedWallet({ address, - name, - avatar, + ...(data.name ? { name: data.name } : {}), + ...(data.avatar ? { name: data.avatar } : {}), }) ) if (useConnectedWallet) { - dispatch(setClaimingUser({ name, address })) + dispatch(setClaimingUser({ name: data.name, address })) } } - return name + return data?.name } ) diff --git a/src/shared/types/selectors.ts b/src/shared/types/selectors.ts new file mode 100644 index 000000000..e914a93b5 --- /dev/null +++ b/src/shared/types/selectors.ts @@ -0,0 +1,22 @@ +import { IslandState } from "redux-state/slices/island" +import { ClaimState } from "redux-state/slices/claim" +import { WalletState } from "redux-state/slices/wallet" +import { RootState } from "redux-state/reducers" + +type Selector = (state: RootState) => T[K] + +type IslandSelector = Selector +type ClaimSelector = Selector +type WalletSelector = Selector + +export type CreateIslandSelector = ( + value: K +) => IslandSelector + +export type CreateClaimSelector = ( + value: K +) => ClaimSelector + +export type CreateWalletSelector = ( + value: K +) => WalletSelector diff --git a/src/shared/utils/names.ts b/src/shared/utils/names.ts index ec21305d3..2d4269fc5 100644 --- a/src/shared/utils/names.ts +++ b/src/shared/utils/names.ts @@ -16,7 +16,7 @@ const NAMES_CACHE_STRORAGE_KEY = "taho.cachedNames" const MAX_CACHE_AGE = 1000 * 60 * 60 * 24 * 7 // 1 week const resolveAddressPromiseCache: { - [address: string]: Promise + [address: string]: Promise } = {} const getCachedNames = () => { @@ -52,9 +52,11 @@ const resolveENSPromise = (address: string) => }) const resolveUNSPromise = (address: string) => - resolveAddressToUNS(address).then((name): string => { + resolveAddressToUNS(address).then((name): WalletData | null => { + if (!name) return null + addCachedName({ type: "uns", address, name }) - return name + return { name } }) const resolveUnknownNamePromise = () => @@ -70,17 +72,20 @@ const resolveAddressToWalletDataWithoutCache = async (address: string) => { resolveENSPromise(normalizedAddress), resolveUNSPromise(normalizedAddress), resolveUnknownNamePromise(), - ]) as Promise + ]) } - const { name, avatar } = await resolveAddressPromiseCache[normalizedAddress] + const cachedResult = + (await resolveAddressPromiseCache[normalizedAddress]) ?? {} + + const { name, avatar } = cachedResult - return { name, avatar } + return name ? { name, avatar } : null } export const resolveAddressToWalletData = async ( address: string -): Promise => { +): Promise => { const cachedNames = getCachedNames() const normalizedAddress = normalizeAddress(address) @@ -90,11 +95,9 @@ export const resolveAddressToWalletData = async ( return cachedItem.ens ?? cachedItem.uns } - const { name, avatar } = await resolveAddressToWalletDataWithoutCache( - normalizedAddress - ) + const data = await resolveAddressToWalletDataWithoutCache(normalizedAddress) - return { name, avatar } + return data ? { name: data.name, avatar: data.avatar } : null } export const resolveNameToAddress = async (addressOrName: string) => { diff --git a/src/shared/utils/uns.ts b/src/shared/utils/uns.ts index de4a6046f..95fb35800 100644 --- a/src/shared/utils/uns.ts +++ b/src/shared/utils/uns.ts @@ -50,23 +50,27 @@ export const resolveUNS = async (name: string) => { } export const resolveAddressToUNS = async (address: string) => { - const response: UNSReverseResponse = await fetchJson({ - url: `https://resolve.unstoppabledomains.com/domains/?owners=${address}&sortBy=id&sortDirection=ASC`, - headers: { - Authorization: `Bearer ${process.env.UNS_API_KEY}`, - }, - }) + try { + const response: UNSReverseResponse = await fetchJson({ + url: `https://resolve.unstoppabledomains.com/domains/?owners=${address}&sortBy=id&sortDirection=ASC`, + headers: { + Authorization: `Bearer ${process.env.UNS_API_KEY}`, + }, + }) - const { data }: UNSReverseResponse = response + const { data }: UNSReverseResponse = response - const name = data[0]?.id ?? null + const name = data[0]?.id ?? null - if (!name) { - throw new Error("Invalid UNS domain name") - } + if (!name) { + throw new Error("Invalid UNS domain name") + } - // FIXME: UNS tend to resolve faster than ENS, so to prefer ENS names let's wait a bit - await wait(10000) + // FIXME: UNS tend to resolve faster than ENS, so to prefer ENS names let's wait a bit + await wait(10000) - return name + return name + } catch { + return null + } } diff --git a/src/ui/Assistant/AssistantContent/AssistantWelcome.tsx b/src/ui/Assistant/AssistantContent/AssistantWelcome.tsx index 03fdbcd53..a0d63f1af 100644 --- a/src/ui/Assistant/AssistantContent/AssistantWelcome.tsx +++ b/src/ui/Assistant/AssistantContent/AssistantWelcome.tsx @@ -11,6 +11,7 @@ export default function AssistantWelcome() { <> updateAssistant({ visible: false, type: "default" })} >
Welcome to Subscape, Nomad
@@ -23,8 +24,7 @@ export default function AssistantWelcome() {

- Start exploring by hovering -
over our 5 Beta Realms + Start exploring by hovering over our 5 Beta Realms

diff --git a/src/ui/Assistant/AssistantContent/index.tsx b/src/ui/Assistant/AssistantContent/index.tsx index 91090caf4..e749b31fe 100644 --- a/src/ui/Assistant/AssistantContent/index.tsx +++ b/src/ui/Assistant/AssistantContent/index.tsx @@ -7,12 +7,14 @@ import closeIcon from "shared/assets/icons/s/close.svg" export type AssistantContentProps = { isVisible: boolean close: () => void + style?: React.CSSProperties } export default function AssistantContent({ children, isVisible, close, + style, }: AssistantContentProps & { children: ReactNode }) { const transition = useVisibilityTransition(isVisible) @@ -29,6 +31,7 @@ export default function AssistantContent({ padding: "24px 32px 32px", width: 375, pointerEvents: isVisible ? "all" : "none", + ...style, }} >