diff --git a/packages/app/src/components/DonorsList/DonorsList.tsx b/packages/app/src/components/DonorsList/DonorsList.tsx index 3ae7e416..8af41762 100644 --- a/packages/app/src/components/DonorsList/DonorsList.tsx +++ b/packages/app/src/components/DonorsList/DonorsList.tsx @@ -24,12 +24,13 @@ function DonorsList({ donors, listStyle }: DonorsListProps) { const userAddresses = useMemo(() => { return sortedDonors.map((donor) => donor.donor as `0x${string}`); }, [sortedDonors]); + const userFullNames = useFetchFullNames(userAddresses); return ( {sortedDonors.map((donor, index) => ( - + ))} ); diff --git a/packages/app/src/components/DonorsList/DonorsListItem.tsx b/packages/app/src/components/DonorsList/DonorsListItem.tsx index 752c8c53..d7eda602 100644 --- a/packages/app/src/components/DonorsList/DonorsListItem.tsx +++ b/packages/app/src/components/DonorsList/DonorsListItem.tsx @@ -7,6 +7,7 @@ import Decimal from 'decimal.js'; import { formatAddress } from '../../lib/formatAddress'; import { ethers } from 'ethers'; import { useEnsName } from 'wagmi'; +import { useFlowingBalance } from '../../hooks/useFlowingBalance'; interface DonorsListItemProps { donor: DonorCollective; @@ -14,13 +15,14 @@ interface DonorsListItemProps { userFullName?: string; } -export const DonorsListItem = (props: DonorsListItemProps) => { - const { donor, rank, userFullName } = props; +export const DonorsListItem = ({ donor, rank, userFullName }: DonorsListItemProps) => { const { navigate } = useCrossNavigate(); - const formattedDonations: string = new Decimal(ethers.utils.formatEther(donor.contribution) ?? 0).toFixed( - 2, - Decimal.ROUND_DOWN + const { formatted: formattedDonations } = useFlowingBalance( + donor.contribution, + donor.timestamp, + donor.flowRate, + undefined ); const { data: ensName } = useEnsName({ address: donor.donor as `0x${string}`, chainId: 1 }); diff --git a/packages/app/src/components/FlowingCurrentPoolRowItem.tsx b/packages/app/src/components/FlowingCurrentPoolRowItem.tsx index 61266dbf..4468acb5 100644 --- a/packages/app/src/components/FlowingCurrentPoolRowItem.tsx +++ b/packages/app/src/components/FlowingCurrentPoolRowItem.tsx @@ -6,6 +6,7 @@ import { useDonorCollectivesFlowingBalancesWithAltStaticBalance } from '../hooks import { DonorCollective } from '../models/models'; import { useGetTokenBalance } from '../hooks/useGetTokenBalance'; import { SupportedNetwork } from '../models/constants'; +import Decimal from 'decimal.js'; interface FlowingDonationsRowItemProps { rowInfo: string; @@ -14,6 +15,7 @@ interface FlowingDonationsRowItemProps { tokenPrice: number | undefined; currency?: string; imageUrl: string; + additionalBalance?: string; } function FlowingDonationsRowItem({ @@ -23,14 +25,18 @@ function FlowingDonationsRowItem({ tokenPrice, currency, imageUrl, + additionalBalance, }: FlowingDonationsRowItemProps) { const [isDesktopResolution] = useMediaQuery({ minWidth: 920, }); const currentBalance = useGetTokenBalance('G$', collective, SupportedNetwork.CELO); + const balanceUsed = additionalBalance + ? new Decimal(currentBalance).add(additionalBalance).toFixed(0, Decimal.ROUND_DOWN) + : currentBalance; const { formatted: formattedCurrentPool, usdValue: usdValueCurrentPool } = - useDonorCollectivesFlowingBalancesWithAltStaticBalance(currentBalance, donorCollectives, tokenPrice); + useDonorCollectivesFlowingBalancesWithAltStaticBalance(balanceUsed, donorCollectives, tokenPrice); return ( diff --git a/packages/app/src/components/Header/ConnectedAccountDisplay.tsx b/packages/app/src/components/Header/ConnectedAccountDisplay.tsx index 9584294b..9f65613d 100644 --- a/packages/app/src/components/Header/ConnectedAccountDisplay.tsx +++ b/packages/app/src/components/Header/ConnectedAccountDisplay.tsx @@ -1,11 +1,13 @@ import { Image, StyleSheet, Text, View } from 'react-native'; +import { useEnsName, useNetwork } from 'wagmi'; + import { InterRegular } from '../../utils/webFonts'; import { formatAddress } from '../../lib/formatAddress'; -import { useEnsName, useNetwork } from 'wagmi'; import { Colors } from '../../utils/colors'; import { PlaceholderAvatar } from '../../assets'; import { useGetTokenBalance } from '../../hooks/useGetTokenBalance'; import { formatNumberWithCommas } from '../../lib/formatFiatCurrency'; +import { SupportedNetwork } from '../../models/constants'; interface ConnectedAccountDisplayProps { isDesktopResolution: boolean; @@ -17,8 +19,9 @@ export const ConnectedAccountDisplay = (props: ConnectedAccountDisplayProps) => const { chain } = useNetwork(); let chainName = chain?.name.replace(/\d+|\s/g, ''); - if (chainName !== 'Celo') { - chainName = 'None'; + console.log('chainName', { chainName, chain: chain }); + if (!(chainName && chainName.toUpperCase() in SupportedNetwork)) { + chainName = 'Unsupported Network'; } const tokenBalance = useGetTokenBalance('G$', address, chain?.id, true); @@ -33,7 +36,7 @@ export const ConnectedAccountDisplay = (props: ConnectedAccountDisplayProps) => {chainName} diff --git a/packages/app/src/components/RowItem.tsx b/packages/app/src/components/RowItem.tsx index 91eb8758..b8516981 100644 --- a/packages/app/src/components/RowItem.tsx +++ b/packages/app/src/components/RowItem.tsx @@ -25,15 +25,15 @@ function RowItem({ rowInfo, rowData, balance, currency, imageUrl }: RowItemProps {rowInfo} - - + + {currency} {rowData} {isDesktopResolution && currency && = {usdBalance} USD} {!isDesktopResolution && currency && = {usdBalance} USD} - - + + ); } diff --git a/packages/app/src/components/StewardsList/StewardsList.tsx b/packages/app/src/components/StewardsList/StewardsList.tsx index 6f0cc632..a8f5aaca 100644 --- a/packages/app/src/components/StewardsList/StewardsList.tsx +++ b/packages/app/src/components/StewardsList/StewardsList.tsx @@ -41,7 +41,7 @@ function StewardList({ listType, stewards, titleStyle, listStyle }: StewardListP showActions={listType === 'viewStewards'} key={steward.steward} profileImage={profileImages[index % profileImages.length]} - userFullName={userFullNames[index]} + userFullName={userFullNames[steward.steward]} /> ))} diff --git a/packages/app/src/components/ViewCollective.tsx b/packages/app/src/components/ViewCollective.tsx index 7052813c..51fa598b 100644 --- a/packages/app/src/components/ViewCollective.tsx +++ b/packages/app/src/components/ViewCollective.tsx @@ -30,7 +30,6 @@ import { WebIcon, } from '../assets/'; import { calculateGoodDollarAmounts } from '../lib/calculateGoodDollarAmounts'; -import FlowingDonationsRowItem from './FlowingDonationsRowItem'; import { useDeleteFlow } from '../hooks/useContractCalls/useDeleteFlow'; import ErrorModal from './modals/ErrorModal'; import FlowingCurrentPoolRowItem from './FlowingCurrentPoolRowItem'; @@ -189,12 +188,14 @@ function ViewCollective({ collective }: ViewCollectiveProps) { - - | undefined => storage: new AsyncStorageWrapper(AsyncStorage), }); - const client = new ApolloClient({ - cache, - link: new HttpLink({ - uri: mongoDbUri, - fetch: async (uri, options) => { - const accessToken = await getValidAccessToken(); - if (!options) { - options = {}; - } - if (!options.headers) { - options.headers = {}; - } - (options.headers as Record).Authorization = `Bearer ${accessToken}`; - return fetch(uri, options); - }, - }), - defaultOptions: { - watchQuery: { - fetchPolicy: 'cache-and-network', - }, - query: { - fetchPolicy: 'cache-first', - }, + const httpLink = new HttpLink({ + uri: mongoDbUri, + fetch: async (uri, options) => { + const accessToken = await getValidAccessToken(); + if (!options) { + options = {}; + } + if (!options.headers) { + options.headers = {}; + } + (options.headers as Record).Authorization = `Bearer ${accessToken}`; + return fetch(uri, options); }, }); - setApolloClient(client); + + try { + const client = new ApolloClient({ + cache, + link: from([errorLink, retryLink, httpLink]), + defaultOptions: { + watchQuery: { + fetchPolicy: 'cache-and-network', + }, + query: { + fetchPolicy: 'cache-first', + }, + }, + }); + setApolloClient(client); + } catch (error) { + console.error(error); + } finally { + return; + } } initApollo().catch(console.error); diff --git a/packages/app/src/hooks/apollo/useCreateSubgraphApolloClient.ts b/packages/app/src/hooks/apollo/useCreateSubgraphApolloClient.ts index 30392758..a19904ec 100644 --- a/packages/app/src/hooks/apollo/useCreateSubgraphApolloClient.ts +++ b/packages/app/src/hooks/apollo/useCreateSubgraphApolloClient.ts @@ -1,22 +1,28 @@ import { useEffect, useState } from 'react'; -import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; +import { ApolloClient, from, HttpLink, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { AsyncStorageWrapper, persistCache } from 'apollo3-cache-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { errorLink, retryLink } from '../../utils/apolloLinkUtils'; + const subgraphUri = 'https://api.thegraph.com/subgraphs/name/gooddollar/goodcollective'; export const useCreateSubgraphApolloClient = (): ApolloClient | undefined => { const [apolloClient, setApolloClient] = useState | undefined>(); useEffect(() => { + const httpLink = new HttpLink({ + uri: subgraphUri, + }); async function initApollo() { const cache = new InMemoryCache(); await persistCache({ cache, storage: new AsyncStorageWrapper(AsyncStorage), }); + const client = new ApolloClient({ - uri: subgraphUri, + link: from([errorLink, retryLink, httpLink]), cache, defaultOptions: { watchQuery: { diff --git a/packages/app/src/hooks/useFetchFullName.ts b/packages/app/src/hooks/useFetchFullName.ts index 424ee79c..78df913c 100644 --- a/packages/app/src/hooks/useFetchFullName.ts +++ b/packages/app/src/hooks/useFetchFullName.ts @@ -5,6 +5,12 @@ import { useMongoDbQuery } from './apollo/useMongoDbQuery'; interface UserProfile { fullName?: { display?: string }; + index: { + walletAddress: { + hash: string; + display?: string; + }; + }; } interface UserProfilesResponse { @@ -17,6 +23,11 @@ const findProfiles = gql` fullName { display } + index { + walletAddress { + hash + } + } } } `; @@ -27,22 +38,34 @@ export function useFetchFullName(address?: string): string | undefined { return names[0]; } -export function useFetchFullNames(addresses: string[]): (string | undefined)[] { - const hashedAddresses = useMemo(() => { - return addresses.map((address: string) => ethers.utils.keccak256(address)); - }, [addresses]); +export function useFetchFullNames(addresses: string[]): any { + const addressToHashMapping = addresses.reduce((acc: any, address) => { + const hash = ethers.utils.keccak256(address); + acc[hash] = address; + return acc; + }, {}); + + const hashedAddresses = Object.keys(addressToHashMapping); const { data, error } = useMongoDbQuery(findProfiles, { - variables: { query: { index: { walletAddress: { hash_in: hashedAddresses } } } }, + variables: { + query: { + index: { walletAddress: { hash_in: hashedAddresses } }, + }, + }, }); return useMemo(() => { - if (error) { - console.error(error); - } if (!data || data.user_profiles.length === 0) { - return []; + return {}; } - return data.user_profiles.map((profile) => profile?.fullName?.display); - }, [data, error]); + return data.user_profiles.reduce((acc: Record, profile) => { + if (!profile) return {}; + const { hash } = profile.index.walletAddress; + const { display } = profile.fullName ?? {}; + const address = addressToHashMapping[hash]; + acc[address] = display ?? ''; + return acc; + }, {}); + }, [data, addressToHashMapping]); } diff --git a/packages/app/src/hooks/useSwapRoute.tsx b/packages/app/src/hooks/useSwapRoute.tsx index 104f4d4d..e488a6c7 100644 --- a/packages/app/src/hooks/useSwapRoute.tsx +++ b/packages/app/src/hooks/useSwapRoute.tsx @@ -1,7 +1,7 @@ import { AlphaRouter, SwapRoute, SwapType, V3Route } from '@uniswap/smart-order-router'; import { CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'; import { useAccount, useNetwork } from 'wagmi'; -import { GDToken } from '../models/constants'; +import { GDToken, SupportedNetwork } from '../models/constants'; import { useEthersSigner } from './useEthersSigner'; import { calculateRawTotalDonation } from '../lib/calculateRawTotalDonation'; import Decimal from 'decimal.js'; @@ -39,13 +39,13 @@ export function useSwapRoute( const [route, setRoute] = useState(undefined); useEffect(() => { - if (!address || !chain?.id || !signer?.provider || tokenIn.symbol === 'G$') { + if (!address || !chain?.id || chain.id !== SupportedNetwork.CELO || !signer?.provider || tokenIn.symbol === 'G$') { setRoute(undefined); return; } const router = new AlphaRouter({ - chainId: chain.id, + chainId: chain.id as number, provider: signer.provider, }); diff --git a/packages/app/src/utils/apolloLinkUtils.ts b/packages/app/src/utils/apolloLinkUtils.ts new file mode 100644 index 00000000..d64d8d90 --- /dev/null +++ b/packages/app/src/utils/apolloLinkUtils.ts @@ -0,0 +1,27 @@ +import { onError } from '@apollo/client/link/error'; +import { RetryLink } from '@apollo/client/link/retry'; + +// ref:https://www.apollographql.com/docs/react/data/error-handling/#advanced-error-handling-with-apollo-link +export const errorLink = onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) + graphQLErrors.forEach(({ message, locations, path }) => + console.error(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`) + ); + if (networkError) { + console.error(`[Network error]: ${networkError}`); + throw networkError; + } +}); + +// ref: https://www.apollographql.com/docs/react/api/link/apollo-link-retry/ +export const retryLink = new RetryLink({ + delay: { + initial: 500, + max: Infinity, + jitter: true, + }, + attempts: { + max: 3, + retryIf: (error, _operation) => !!error, + }, +});