Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monthly only stream and donate in any currency #212

Merged
merged 7 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"preview:web": "vite preview -c web/vite.config.ts"
},
"dependencies": {
"@apollo/client": "^3.7.14",
"@apollo/client": "^3.10.8",
"@celo-tools/celo-ethers-wrapper": "^0.4.0",
"@ethersproject/shims": "^5.7.0",
"@gooddollar/good-design": "^0.1.31",
Expand All @@ -28,10 +28,10 @@
"@react-native-firebase/analytics": "16.7.0",
"@react-native-firebase/app": "16.7.0",
"@superfluid-finance/sdk-core": "^0.3.2",
"@uniswap/router-sdk": "^1.7.1",
"@uniswap/sdk-core": "^4.0.7",
"@uniswap/smart-order-router": "^3.20.0",
"@uniswap/v3-sdk": "^3.10.0",
"@uniswap/router-sdk": "^1.9.3",
"@uniswap/sdk-core": "^5.3.1",
"@uniswap/smart-order-router": "^3.35.12",
"@uniswap/v3-sdk": "^3.13.1",
"@usedapp/core": "^1.2.10",
"@wagmi/core": "^1.4.5",
"@walletconnect/modal-react-native": "^1.0.0-rc.9",
Expand All @@ -46,15 +46,15 @@
"decimal.js": "^10.4.3",
"ethers": "^5.6.2",
"fast-text-encoding": "^1.0.6",
"graphql": "^16.6.0",
"graphql": "^16.9.0",
"lodash": "^4.17.21",
"mixpanel-react-native": "^2.3.1",
"mobile-device-detect": "^0.4.3",
"moment": "^2.29.4",
"moment-duration-format": "^2.3.2",
"native-base": "^3.4.28",
"node-libs-react-native": "^1.2.1",
"qs": "^6.11.2",
"qs": "^6.12.2",
"react": "18",
"react-dom": "18",
"react-native": "^0.71.11",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { MongoDbApolloProvider } from './components/providers/MongoDbApolloProvi
function App(): JSX.Element {
const { publicClient, webSocketPublicClient } = configureChains(
[celo, mainnet],
[infuraProvider({ apiKey: '88284fbbacd3472ca3361d1317a48fa5' }), publicProvider()]
[publicProvider(), infuraProvider({ apiKey: '88284fbbacd3472ca3361d1317a48fa5' })]
);

const connectors = [
Expand Down
53 changes: 30 additions & 23 deletions packages/app/src/components/DonateComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,18 @@ function DonateComponent({ collective }: DonateComponentProps) {
}

const [currency, setCurrency] = useState<string>('G$');
const [frequency, setFrequency] = useState<Frequency>(Frequency.OneTime);
const [duration, setDuration] = useState(1);
const [frequency, setFrequency] = useState<Frequency>(Frequency.Monthly);
const [duration, setDuration] = useState(12);
const [decimalDonationAmount, setDecimalDonationAmount] = useState(0);

const tokenList = useTokenList();
const isOneTime = frequency === Frequency.OneTime;
const currencyOptions: { value: string; label: string }[] = useMemo(() => {
let options = Object.keys(tokenList).map((key) => ({
value: key,
label: key,
}));
if (isOneTime) {
options = [options.find((option) => option.value === 'G$')!];
}
return options;
}, [tokenList, isOneTime]);
}, [tokenList]);

const {
path: swapPath,
Expand All @@ -85,7 +81,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
);
const approvalNotReady = handleApproveToken === undefined && currency !== 'G$';

const { supportFlowWithSwap, supportFlow, supportSingleTransferAndCall } = useContractCalls(
const { supportFlowWithSwap, supportFlow, supportSingleTransferAndCall, supportSingleWithSwap } = useContractCalls(
collectiveId,
currency,
decimalDonationAmount,
Expand All @@ -101,7 +97,11 @@ function DonateComponent({ collective }: DonateComponentProps) {

const handleDonate = useCallback(async () => {
if (frequency === Frequency.OneTime) {
return await supportSingleTransferAndCall();
if (currency === 'G$') {
return await supportSingleTransferAndCall();
} else {
return await supportSingleWithSwap();
}
} else if (currency === 'G$') {
return await supportFlow();
}
Expand Down Expand Up @@ -140,6 +140,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
supportFlow,
supportFlowWithSwap,
supportSingleTransferAndCall,
supportSingleWithSwap,
]);

const currencyDecimals = useToken(currency).decimals;
Expand All @@ -151,7 +152,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
const isNonZeroDonation = totalDecimalDonation.gt(0);
const isInsufficientBalance =
isNonZeroDonation && (!donorCurrencyBalance || totalDecimalDonation.gt(donorCurrencyBalance));
const isInsufficientLiquidity = isNonZeroDonation && currency !== 'G$' && swapRouteStatus !== SwapRouteState.READY;
const isInsufficientLiquidity = isNonZeroDonation && currency !== 'G$' && swapRouteStatus === SwapRouteState.NO_ROUTE;
const isUnacceptablePriceImpact =
isNonZeroDonation && currency !== 'G$' && priceImpact ? priceImpact > acceptablePriceImpact : false;

Expand Down Expand Up @@ -184,16 +185,12 @@ function DonateComponent({ collective }: DonateComponentProps) {

const onChangeCurrency = (value: string) => setCurrency(value);
const onChangeAmount = (value: string) => setDecimalDonationAmount(formatDecimalStringInput(value));
const onChangeFrequency = useCallback(
(value: string) => {
if (currency !== 'G$' && value === Frequency.OneTime) {
setCurrency('G$');
setDecimalDonationAmount(0);
}
setFrequency(value as Frequency);
},
[currency]
);
const onChangeFrequency = useCallback((value: string) => {
if (value === Frequency.OneTime) {
setDuration(1);
}
setFrequency(value as Frequency);
}, []);
const onChangeDuration = (value: string) => setDuration(Number(value));
const onCloseErrorModal = () => setErrorMessage(undefined);

Expand Down Expand Up @@ -245,7 +242,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
{isDesktopResolution && (
<View style={styles.donationCurrencyHeader}>
<View style={styles.donationAction}>
<View>
<View style={styles.actionBox}>
<Text style={styles.title}>Donation Currency:</Text>
<Text style={styles.description}>You can donate using any cryptocurrency. </Text>
</View>
Expand Down Expand Up @@ -446,7 +443,10 @@ function DonateComponent({ collective }: DonateComponentProps) {
<Text style={styles.warningTitle}>Price impace warning!</Text>
<Text style={styles.warningLine}>
Due to low liquidity between your chosen currency and GoodDollar,
<Text style={{ ...InterSemiBold }}>your donation amount will reduce by 36% </Text>
<Text style={{ ...InterSemiBold }}>
{' '}
your donation amount will reduce by {priceImpact?.toFixed(2)}%{' '}
</Text>
when swapped.
</Text>
</View>
Expand Down Expand Up @@ -485,7 +485,9 @@ function DonateComponent({ collective }: DonateComponentProps) {
fontSize={18}
seeType={false}
onPress={handleDonate}
isLoading={swapRouteStatus === SwapRouteState.LOADING}
disabled={
(currency !== 'G$' && swapRouteStatus !== SwapRouteState.READY) ||
address === undefined ||
chain?.id === undefined ||
!(chain.id in SupportedNetwork) ||
Expand All @@ -497,7 +499,12 @@ function DonateComponent({ collective }: DonateComponentProps) {
<ErrorModal openModal={!!errorMessage} setOpenModal={onCloseErrorModal} message={errorMessage ?? ''} />
<ApproveSwapModal openModal={approveSwapModalVisible} setOpenModal={setApproveSwapModalVisible} />
<CompleteDonationModal openModal={completeDonationModalVisible} setOpenModal={setCompleteDonationModalVisible} />
<ThankYouModal openModal={thankYouModalVisible} address={address} collective={collective} />
<ThankYouModal
openModal={thankYouModalVisible}
address={address}
collective={collective}
isStream={frequency !== Frequency.OneTime}
/>
</View>
);
}
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/components/DonorsList/DonorsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { Colors } from '../../utils/colors';
import { InterRegular, InterSemiBold } from '../../utils/webFonts';
import { DonorCollective } from '../../models/models';
import useCrossNavigate from '../../routes/useCrossNavigate';
import Decimal from 'decimal.js';
import { formatAddress } from '../../lib/formatAddress';
import { ethers } from 'ethers';
import { useEnsName } from 'wagmi';
import { useFlowingBalance } from '../../hooks/useFlowingBalance';

Expand Down
7 changes: 5 additions & 2 deletions packages/app/src/components/RoundedButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Image, Text, TouchableOpacity, View, StyleSheet } from 'react-native';
import { Image, Text, TouchableOpacity, View, StyleSheet, ActivityIndicator } from 'react-native';
import { InterSemiBold } from '../utils/webFonts';
import { ForwardIcon } from '../assets';
import { Colors } from '../utils/colors';

interface RoundedButtonProps {
title: string;
Expand All @@ -11,6 +12,7 @@ interface RoundedButtonProps {
onPress?: () => void;
maxWidth?: number | string;
disabled?: boolean;
isLoading?: boolean;
}

function RoundedButton({
Expand All @@ -22,6 +24,7 @@ function RoundedButton({
onPress,
maxWidth,
disabled,
isLoading,
}: RoundedButtonProps) {
const dynamicTextStyle = {
color: color,
Expand All @@ -34,7 +37,7 @@ function RoundedButton({
disabled={disabled}
style={[styles.button, { backgroundColor, maxWidth: maxWidth ?? 'auto' }]}
onPress={onPress}>
<Text style={[styles.nonSeeTypeText, dynamicTextStyle]}>{title}</Text>
{isLoading ? (<ActivityIndicator size="large" color={Colors.blue[200]} />) : (<Text style={[styles.nonSeeTypeText, dynamicTextStyle]}>{title}</Text>)}
</TouchableOpacity>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Image, Text, View } from 'react-native';
import { Colors } from '../../utils/colors';
import { ReceiveIcon, SendIcon } from '../../assets';
import Decimal from 'decimal.js';
import { ethers } from 'ethers';
import { Link } from 'native-base';
import { styles } from './styles';
import { formatEther } from 'viem';

interface TransactionListItemProps {
userIdentifier: string;
Expand All @@ -15,7 +14,7 @@ interface TransactionListItemProps {
}

function TransactionListItem({ userIdentifier, isDonation, amount, txHash, rawNetworkFee }: TransactionListItemProps) {
const formattedFee: string = new Decimal(ethers.utils.formatEther(rawNetworkFee ?? 0)).toString();
const formattedFee: string = formatEther(BigInt(rawNetworkFee ?? 0)).toString();
const formattedHash = txHash.slice(0, 40) + '...';

return (
Expand Down
11 changes: 7 additions & 4 deletions packages/app/src/components/modals/ThankYouModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ interface ThankYouModalProps {
openModal: boolean;
address?: `0x${string}`;
collective: IpfsCollective;
isStream: boolean;
}

const ThankYouModal = ({ openModal, address, collective }: ThankYouModalProps) => {
const ThankYouModal = ({ openModal, address, collective, isStream }: ThankYouModalProps) => {
const { navigate } = useCrossNavigate();
const onClick = () => navigate(`/profile/${address}`);

Expand All @@ -21,9 +22,11 @@ const ThankYouModal = ({ openModal, address, collective }: ThankYouModalProps) =
<View style={styles.modalView}>
<Text style={styles.title}>THANK YOU!</Text>
<Text style={styles.paragraph}>{`You have just donated to ${collective.name} GoodCollective!`}</Text>
<Text style={styles.paragraph}>
{`To stop your donation, visit the ${collective.name} GoodCollective page.`}
</Text>
{isStream && (
<Text style={styles.paragraph}>
{`To stop your donation, visit the ${collective.name} GoodCollective page.`}
</Text>
)}
<Image source={ThankYouImg} alt="woman" style={styles.image} />
<TouchableOpacity style={styles.button} onPress={onClick}>
<Text style={styles.buttonText}>GO TO PROFILE</Text>
Expand Down
94 changes: 94 additions & 0 deletions packages/app/src/hooks/apollo/useCreateCoinGeckoApolloClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useEffect, useState } from 'react';
import { RestLink } from 'apollo-link-rest';
import { InvalidationPolicyCache, RenewalPolicy } from '@nerdwallet/apollo-cache-policies';
import { ApolloClient, ApolloError, from, NormalizedCacheObject, TypedDocumentNode } from '@apollo/client';
import { DocumentNode } from 'graphql/language';
import { AsyncStorageWrapper, persistCache } from 'apollo3-cache-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';

import { errorLink, retryLink } from '../../utils/apolloLinkUtils';

// use apolloclient for caching not to hit rate limits
export const useCreateCoinGeckoApolloClient = (): ApolloClient<any> | undefined => {
const [apolloClient, setApolloClient] = useState<ApolloClient<NormalizedCacheObject> | undefined>();

useEffect(() => {
async function initApollo() {
const cache = new InvalidationPolicyCache({
invalidationPolicies: {
timeToLive: 60 * 1000, // 1 minute
renewalPolicy: RenewalPolicy.AccessAndWrite,
types: {
currency: {
timeToLive: 60 * 1000,
},
},
},
});

await persistCache({
cache,
storage: new AsyncStorageWrapper(AsyncStorage),
});

const restLink = new RestLink({
uri: `https://api.coingecko.com/api/v3/simple`,
endpoints: {
byAddress: 'https://api.coingecko.com/api/v3/simple/token_price/celo',
bySymbol: 'https://api.coingecko.com/api/v3/simple/price',
},
responseTransformer: async (response) => {
const results = await response.json();
return Object.entries(results).map(([key, value]) => ({ address: key, ...(value as object) }));
},
});

try {
const client = new ApolloClient({
cache,
link: from([errorLink, retryLink, restLink]),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
query: {
fetchPolicy: 'cache-first',
},
},
});
setApolloClient(client);
} catch (error) {
console.error(error);
} finally {
return;
}
}

initApollo().catch(console.error);
}, []);

return apolloClient;
};

export function useCoinGeckoQuery<TData, TVariables extends Record<string, any> = Record<string, any>>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: Record<string, any>
): { data?: any; loading: boolean; error?: ApolloError } {
const client = useCreateCoinGeckoApolloClient();

const [data, setData] = useState<TData>();
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<ApolloError>();

useEffect(() => {
if (client && !options?.disabled) {
client.query({ query, ...options }).then((result) => {
setData(result.data);
setLoading(result.loading);
setError(result.error);
});
}
}, [client, options, query]);

return { data, loading, error };
}
4 changes: 2 additions & 2 deletions packages/app/src/hooks/apollo/useCreateMongoDbApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const useCreateMongoDbApolloClient = (): ApolloClient<any> | undefined =>

const httpLink = new HttpLink({
uri: mongoDbUri,
fetch: async (uri, options) => {
fetch: async (url, options) => {
const accessToken = await getValidAccessToken();
if (!options) {
options = {};
Expand All @@ -55,7 +55,7 @@ export const useCreateMongoDbApolloClient = (): ApolloClient<any> | undefined =>
options.headers = {};
}
(options.headers as Record<string, any>).Authorization = `Bearer ${accessToken}`;
return fetch(uri, options);
return fetch(url as string, options);
},
});

Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export * from './useStewardById';
export * from './useContractCalls';
export * from './useGetTokenPrice';
export * from './useIsStewardVerified';
export * from './useEthersSigner';
export * from './useEthers';
Loading
Loading