diff --git a/packages/app/package.json b/packages/app/package.json
index 6156668f..add2b6aa 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -20,7 +20,7 @@
"@celo-tools/celo-ethers-wrapper": "^0.4.0",
"@ethersproject/shims": "^5.7.0",
"@gooddollar/good-design": "^0.1.57",
- "@gooddollar/goodcollective-sdk": "^1.1.2",
+ "@gooddollar/goodcollective-sdk": "^1.2.1",
"@gooddollar/web3sdk-v2": "^0.2.34",
"@nerdwallet/apollo-cache-policies": "^3.2.0",
"@react-native-aria/interactions": "0.2.3",
diff --git a/packages/app/src/assets/MetaMask_Fox.svg b/packages/app/src/assets/MetaMaskLogo.svg
similarity index 100%
rename from packages/app/src/assets/MetaMask_Fox.svg
rename to packages/app/src/assets/MetaMaskLogo.svg
diff --git a/packages/app/src/assets/Stream-warning.svg b/packages/app/src/assets/Stream-warning.svg
new file mode 100644
index 00000000..8571637c
--- /dev/null
+++ b/packages/app/src/assets/Stream-warning.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/app/src/assets/index.ts b/packages/app/src/assets/index.ts
index f40091a5..8d66a27b 100644
--- a/packages/app/src/assets/index.ts
+++ b/packages/app/src/assets/index.ts
@@ -5,7 +5,10 @@ export { default as BackIcon } from './BackIcon.svg';
export { default as BlackBrowedAlbatross } from './BlackBrowedAlbatross.svg';
export { default as CalendarIcon } from './CalendarIcon.svg';
export { default as CaliforniaCondor } from './CaliforniaCondor.svg';
+export { default as ClaimTX } from './ClaimTX.svg';
+export { default as ClaimTx } from './ClaimTx.svg';
export { default as CloseIcon } from './CloseIcon.svg';
+export { default as DonationTX } from './DonationTX.svg';
export { default as DonorBlue } from './Donor-blue.svg';
export { default as DonorGreenIcon } from './DonorGreenIcon.svg';
export { default as ErrorImg } from './ErrorImg.svg';
@@ -25,9 +28,11 @@ export { default as LightningIcon } from './LightningIcon.svg';
export { default as ListGreenIcon } from './List-greenIcon.svg';
export { default as Logout } from './Logout.svg';
export { default as MenuIcon } from './MenuIcon.svg';
+export { default as MetaMaskLogo } from './MetaMaskLogo.svg';
export { default as NoAvatar } from './NoAvatar.svg';
export { default as Ocean } from './Ocean.svg';
export { default as Paper } from './Paper.svg';
+export { default as PayoutTX } from './PayoutTX.svg';
export { default as PhoneImg } from './PhoneImg.svg';
export { default as PlaceholderAvatar } from './PlaceholderAvatar.svg';
export { default as QuestionImg } from './QuestionImg.svg';
@@ -40,23 +45,20 @@ export { default as SquaresIcon } from './SquaresIcon.svg';
export { default as StewardBlue } from './Steward-blue.svg';
export { default as StewardGreen } from './Steward-green.svg';
export { default as StewardOrange } from './Steward-orange.svg';
+export { default as StreamWarning } from './Stream-warning.svg';
+export { default as StreamStopTX } from './StreamStopTX.svg';
+export { default as StreamTX } from './StreamTX.svg';
+export { default as StreamUpdateTX } from './StreamUpdateTX.svg';
export { default as SupportImage } from './SupportImage.svg';
export { default as ThankYouImg } from './ThankYouImg.svg';
export { default as TransactionIcon } from './Transaction-Icon.svg';
export { default as TwitterIcon } from './TwitterIcon.svg';
export { default as VectorStroke } from './VectorStroke.svg';
export { default as VerifiedIcon } from './VerifiedIcon.svg';
+export { default as WalletConnectLogo } from './WalletConnectLogo.svg';
+export { default as WalletConnectLogoWhite } from './WalletConnectLogoWhite.svg';
export { default as WebIcon } from './WebIcon.svg';
export { default as Woman } from './Woman.svg';
export { default as chevronDown } from './chevron-down.svg';
export { default as chevronRight } from './chevron-right.svg';
export { default as empty } from './empty.svg';
-export { default as MetaMaskLogo } from './MetaMask_Fox.svg';
-export { default as WalletConnectLogo } from './WalletConnectLogo.svg';
-export { default as WalletConnectLogoWhite } from './WalletConnectLogoWhite.svg';
-export { default as StreamTX } from './StreamTX.svg';
-export { default as StreamStopTX } from './StreamStopTX.svg';
-export { default as StreamUpdateTX } from './StreamUpdateTX.svg';
-export { default as DonationTX } from './DonationTX.svg';
-export { default as ClaimTX } from './ClaimTX.svg';
-export { default as PayoutTX } from './PayoutTX.svg';
diff --git a/packages/app/src/components/ActionButton.tsx b/packages/app/src/components/ActionButton.tsx
index 84ecd2fb..0bb862db 100644
--- a/packages/app/src/components/ActionButton.tsx
+++ b/packages/app/src/components/ActionButton.tsx
@@ -17,9 +17,10 @@ export const buttonStyles = {
},
button: {
width: '100%',
- height: 47,
+ minHeight: 47,
flex: 1,
- justifyContent: 'space-between',
+ justifyContent: 'center',
+ textAlign: 'center',
alignItems: 'center',
paddingRight: 10,
paddingLeft: 10,
@@ -33,6 +34,7 @@ export const buttonStyles = {
buttonText: {
...InterSemiBold,
fontSize: 'md',
+ textAlign: 'center',
},
};
@@ -45,7 +47,7 @@ const ActionButton = ({ href, text, bg, textColor, onPress }: ActionButtonProps)
},
buttonText: {
...buttonStyles.buttonText,
- height: 47,
+ minHeight: 47,
display: 'flex',
alignItems: 'center',
},
diff --git a/packages/app/src/components/DonateComponent.tsx b/packages/app/src/components/DonateComponent.tsx
index 6ee177fc..e94f9adb 100644
--- a/packages/app/src/components/DonateComponent.tsx
+++ b/packages/app/src/components/DonateComponent.tsx
@@ -1,50 +1,170 @@
import { useCallback, useMemo, useState } from 'react';
-import { Image, StyleSheet, Text, TextInput, View } from 'react-native';
-import { Link } from 'native-base';
+import { Image, View } from 'react-native';
+import { Box, HStack, Link, Text, useBreakpointValue, VStack } from 'native-base';
import { useAccount, useNetwork } from 'wagmi';
import { useParams } from 'react-router-native';
import Decimal from 'decimal.js';
import { waitForTransaction } from '@wagmi/core';
import { TransactionReceipt } from 'viem';
+import { isEmpty } from 'lodash';
+import moment from 'moment';
-import { InterRegular, InterSemiBold, InterSmall } from '../utils/webFonts';
import RoundedButton from './RoundedButton';
-import CompleteDonationModal from './modals/CompleteDonationModal';
-import { Colors } from '../utils/colors';
import { useScreenSize } from '../theme/hooks';
-import Dropdown from './Dropdown';
-import { getDonateStyles, getFrequencyPlural } from '../utils';
+import BaseModal from './modals/BaseModal';
+import { getDonateStyles } from '../utils';
import { useContractCalls, useGetTokenPrice } from '../hooks';
import { Collective } from '../models/models';
import { useGetTokenBalance } from '../hooks/useGetTokenBalance';
-import { acceptablePriceImpact, Frequency, frequencyOptions, GDEnvTokens, SupportedNetwork } from '../models/constants';
+import { acceptablePriceImpact, Frequency, GDEnvTokens, SupportedNetwork } from '../models/constants';
import { InfoIconOrange } from '../assets';
-import { formatFiatCurrency } from '../lib/formatFiatCurrency';
-import ErrorModal from './modals/ErrorModal';
import { SwapRouteState, useSwapRoute } from '../hooks/useSwapRoute';
import { useApproveSwapTokenCallback } from '../hooks/useApproveSwapTokenCallback';
-import ApproveSwapModal from './modals/ApproveSwapModal';
+
import { useToken, useTokenList } from '../hooks/useTokenList';
import { formatDecimalStringInput } from '../lib/formatDecimalStringInput';
-import ThankYouModal from './modals/ThankYouModal';
import useCrossNavigate from '../routes/useCrossNavigate';
+import FrequencySelector from './DonateFrequency';
+import NumberInput from './NumberInput';
+import { ApproveTokenImg, PhoneImg, StreamWarning, ThankYouImg } from '../assets';
interface DonateComponentProps {
collective: Collective;
}
-function DonateComponent({ collective }: DonateComponentProps) {
- const { isDesktopView } = useScreenSize();
+const PriceImpact = ({ priceImpact }: any) => (
+
+ Due to low liquidity between your chosen currency and GoodDollar,
+
+ your donation amount will reduce by {priceImpact?.toFixed(2)}%{' '}
+
+ when swapped.
+
+);
+
+const WarningExplanation = ({ type }: any) => (
+
+
+ {type === 'liquidity'
+ ? 'There is not enough liquidity between your chosen currency and GoodDollar to proceed.'
+ : 'There is not enough balance in your wallet to proceed.'}
+
+
+);
+
+const warningProps = {
+ priceImpact: {
+ title: 'Price impact warning!',
+ Explanation: PriceImpact,
+ suggestion: ['Proceed, and accept the price impact', 'Select another donation currency above'],
+ href: 'https://gooddollar.notion.site/How-do-I-buy-GoodDollars-94e821e06f924f6ea739df7db02b5a2d',
+ },
+ liquidity: {
+ title: 'Insufficient liquidity!',
+ Explanation: WarningExplanation,
+ suggestion: ['Try with another currency', 'Reduce your donation amount'],
+ href: 'https://gooddollar.notion.site/How-do-I-buy-GoodDollars-94e821e06f924f6ea739df7db02b5a2d',
+ },
+ balance: {
+ title: 'Insufficient balance!',
+ Explanation: WarningExplanation,
+ suggestion: ['Reduce your donation amount', 'Try with another currency'],
+ href: 'https://gooddollar.notion.site/How-do-I-buy-GoodDollars-94e821e06f924f6ea739df7db02b5a2d',
+ },
+ noAmount: {
+ title: 'Enter an amount above',
+ },
+};
+
+const shouldWarning = (
+ currency: string,
+ donorCurrencyBalance: string,
+ priceImpact: number | undefined,
+ swapRouteStatus: SwapRouteState,
+ totalDecimalDonation: Decimal
+) => {
+ const isNonZeroDonation = totalDecimalDonation.gt(0);
+ const isInsufficientBalance =
+ isNonZeroDonation && (!donorCurrencyBalance || totalDecimalDonation.gt(donorCurrencyBalance));
+ const isInsufficientLiquidity =
+ isNonZeroDonation && currency.startsWith('G$') === false && swapRouteStatus === SwapRouteState.NO_ROUTE;
+ const isUnacceptablePriceImpact =
+ isNonZeroDonation && currency.startsWith('G$') === false && priceImpact
+ ? priceImpact > acceptablePriceImpact
+ : false;
+
+ return { isNonZeroDonation, isInsufficientBalance, isInsufficientLiquidity, isUnacceptablePriceImpact };
+};
+
+const SwapValue = ({ swapValue }: { swapValue: number }) => (
+
+ =
+
+ {' '}
+ G${' '}
+
+ {swapValue.toFixed(4)}
+
+);
+
+const WarningBox = ({ content, explanationProps = {} }: any) => {
+ const Explanation = content.Explanation;
+
+ return (
+
+
+
+
+
+ {content.title}
+
+
+ {!isEmpty(explanationProps) ? : null}
+
+ {content.suggestion ? (
+
+
+ You may:
+
+
+
+ {content.suggestion.map((suggestion: string, index: number) => (
+
+ {index + 1}. {suggestion}
+
+ ))}
+
+ 3.
+ Purchase and use GoodDollar
+
+
+
+
+ ) : null}
+
+
+ );
+};
+
+const DonateComponent = ({ collective }: DonateComponentProps) => {
+ const { isDesktopView, isMobileView } = useScreenSize();
const { id: collectiveId = '0x' } = useParams();
- const { address, isConnected } = useAccount();
+ const { address } = useAccount();
const { chain } = useNetwork();
const [completeDonationModalVisible, setCompleteDonationModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState(undefined);
const [approveSwapModalVisible, setApproveSwapModalVisible] = useState(false);
const [thankYouModalVisible, setThankYouModalVisible] = useState(false);
+ const [startStreamingVisible, setStartStreamingVisible] = useState(false);
+ const [estimatedDuration, setEstimatedDuration] = useState<{ duration: number; endDate: string }>({
+ duration: 0,
+ endDate: '',
+ });
+ const { price: tokenPrice = 0 } = useGetTokenPrice('G$');
+ const [swapValue, setSwapValue] = useState(undefined);
const [isDonationComplete, setIsDonationComplete] = useState(false);
const { navigate } = useCrossNavigate();
@@ -52,9 +172,46 @@ function DonateComponent({ collective }: DonateComponentProps) {
navigate(`/profile/${address}`);
}
- const [frequency, setFrequency] = useState(Frequency.Monthly);
+ const [frequency, setFrequency] = useState(Frequency.OneTime);
+ const [streamRate, setRate] = useState(1);
const [duration, setDuration] = useState(12);
- const [decimalDonationAmount, setDecimalDonationAmount] = useState(0);
+ const [decimalDonationAmount, setDecimalDonationAmount] = useState(0);
+
+ const [inputAmount, setInputAmount] = useState(undefined);
+
+ const container = useBreakpointValue({
+ base: {
+ width: '343',
+ paddingLeft: 2,
+ paddingRight: 2,
+ },
+ sm: {
+ minWidth: '100%',
+ paddingLeft: 2,
+ paddingRight: 2,
+ },
+ md: {
+ maxWidth: 800,
+ width: '100%',
+ paddingLeft: 4,
+ paddingRight: 4,
+ },
+ xl: {
+ maxWidth: '100%',
+ paddingLeft: 4,
+ paddingRight: 4,
+ },
+ });
+
+ const direction = useBreakpointValue({
+ base: {
+ flexDirection: 'column',
+ },
+ lg: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ },
+ });
const tokenList = useTokenList();
const gdEnvSymbol =
@@ -94,6 +251,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
collectiveId as `0x${string}`
);
const approvalNotReady = handleApproveToken === undefined && currency.startsWith('G$') === false;
+ // const approvalNotReady = false;
const { supportFlowWithSwap, supportFlow, supportSingleTransferAndCall, supportSingleWithSwap } = useContractCalls(
collectiveId,
@@ -109,7 +267,37 @@ function DonateComponent({ collective }: DonateComponentProps) {
swapPath
);
+ const [confirmNoAmount, setConfirmNoAmount] = useState(false);
+
+ const token = useToken(currency);
+ // const currencyDecimals = token.decimals;
+ const donorCurrencyBalance = useGetTokenBalance(token.address, address, chain?.id, true);
+
+ const totalDecimalDonation = new Decimal(duration * decimalDonationAmount);
+ // const totalDonationFormatted = totalDecimalDonation.toDecimalPlaces(currencyDecimals, Decimal.ROUND_DOWN).toString();
+
+ const { isNonZeroDonation, isInsufficientBalance, isInsufficientLiquidity, isUnacceptablePriceImpact } =
+ shouldWarning(currency, donorCurrencyBalance, priceImpact, swapRouteStatus, totalDecimalDonation);
+
+ const handleStreamingWarning = useCallback(async () => {
+ if (startStreamingVisible) {
+ setStartStreamingVisible(false);
+ if (currency.startsWith('G$')) {
+ return await supportFlow();
+ } else {
+ return await supportFlowWithSwap();
+ }
+ }
+
+ setStartStreamingVisible(true);
+ }, [currency, startStreamingVisible, supportFlow, supportFlowWithSwap]);
+
const handleDonate = useCallback(async () => {
+ if (!isNonZeroDonation) {
+ setConfirmNoAmount(true);
+ return;
+ }
+
if (frequency === Frequency.OneTime) {
if (currency.startsWith('G$')) {
return await supportSingleTransferAndCall();
@@ -117,7 +305,7 @@ function DonateComponent({ collective }: DonateComponentProps) {
return await supportSingleWithSwap();
}
} else if (currency.startsWith('G$')) {
- return await supportFlow();
+ handleStreamingWarning();
}
let isApproveSuccess = isRequireApprove === false;
@@ -143,7 +331,8 @@ function DonateComponent({ collective }: DonateComponentProps) {
}
}
if (isApproveSuccess) {
- await supportFlowWithSwap();
+ // await supportFlowWithSwap();
+ handleStreamingWarning();
}
}, [
chain?.id,
@@ -151,33 +340,14 @@ function DonateComponent({ collective }: DonateComponentProps) {
frequency,
isRequireApprove,
handleApproveToken,
- supportFlow,
- supportFlowWithSwap,
+ handleStreamingWarning,
+ // supportFlow,
+ // supportFlowWithSwap,
supportSingleTransferAndCall,
supportSingleWithSwap,
+ isNonZeroDonation,
]);
- const token = useToken(currency);
- const currencyDecimals = token.decimals;
- const donorCurrencyBalance = useGetTokenBalance(token.address, address, chain?.id, true);
-
- const totalDecimalDonation = new Decimal(duration * decimalDonationAmount);
- const totalDonationFormatted = totalDecimalDonation.toDecimalPlaces(currencyDecimals, Decimal.ROUND_DOWN).toString();
-
- const isNonZeroDonation = totalDecimalDonation.gt(0);
- const isInsufficientBalance =
- isNonZeroDonation && (!donorCurrencyBalance || totalDecimalDonation.gt(donorCurrencyBalance));
- const isInsufficientLiquidity =
- isNonZeroDonation && currency.startsWith('G$') === false && swapRouteStatus === SwapRouteState.NO_ROUTE;
- const isUnacceptablePriceImpact =
- isNonZeroDonation && currency.startsWith('G$') === false && priceImpact
- ? priceImpact > acceptablePriceImpact
- : false;
-
- const { price } = useGetTokenPrice(currency);
- const donationAmountUsdValue = price ? formatFiatCurrency(decimalDonationAmount * price) : undefined;
- const totalDonationUsdValue = price ? formatFiatCurrency(totalDecimalDonation.mul(price).toNumber()) : undefined;
-
const donateStyles = useMemo(() => {
return getDonateStyles({
noAddress: !address,
@@ -201,521 +371,297 @@ function DonateComponent({ collective }: DonateComponentProps) {
const { buttonCopy, buttonBgColor, buttonTextColor } = donateStyles;
- const onChangeCurrency = (value: string) => setCurrency(value);
- const onChangeAmount = (value: string) => setDecimalDonationAmount(formatDecimalStringInput(value));
- const onChangeFrequency = useCallback((value: string) => {
+ const onReset = () => {
+ setInputAmount(undefined);
+ setEstimatedDuration({ duration: 0, endDate: '' });
+ setSwapValue(undefined);
+ };
+
+ const onChangeCurrency = (value: string) => {
+ setCurrency(value);
+ onReset();
+ };
+
+ const estimateDuration = useCallback(
+ (v: string, altDuration?: number) => {
+ const calculateEstDuration = (value: number, rate: number) => value / (rate === 0 ? 1 : rate);
+
+ let estDuration = altDuration ?? 0;
+
+ if (frequency === Frequency.Monthly && !altDuration) {
+ if (currency.includes('G$')) {
+ estDuration = parseFloat(donorCurrencyBalance) / parseFloat(v);
+ } else {
+ estDuration = calculateEstDuration(parseFloat(v), parseFloat(streamRate.toString()));
+ }
+ }
+
+ if (!altDuration && !currency.includes('G$')) {
+ const gdValue = parseFloat(v) / tokenPrice;
+ setSwapValue(gdValue);
+ }
+
+ const estimatedEndDate = moment().add(estDuration, 'months').format('DD.MM.YY HH:mm');
+
+ setEstimatedDuration({ duration: estDuration, endDate: estimatedEndDate });
+ setDuration(Math.max(1, estDuration));
+ },
+ [currency, donorCurrencyBalance, streamRate, frequency, tokenPrice]
+ );
+
+ const onChangeRate = (value: string) => {
+ setRate(value.endsWith('.') ? value : Number(value));
+
+ if (!Number(value)) return;
+ const estDuration = parseFloat(decimalDonationAmount) / Math.max(parseFloat(value), 1);
+ estimateDuration(value, estDuration);
+ };
+
+ const onChangeAmount = useCallback(
+ (v: string) => {
+ setInputAmount(v);
+
+ if (![''].includes(v)) setConfirmNoAmount(false);
+ if (v.endsWith('.') || ['0', ''].includes(v)) return;
+
+ setDecimalDonationAmount(formatDecimalStringInput(v));
+
+ estimateDuration(v);
+ },
+ [estimateDuration]
+ );
+
+ const onChangeFrequency = (value: string) => {
if (value === Frequency.OneTime) {
setDuration(1);
}
setFrequency(value as Frequency);
- }, []);
- const onChangeDuration = (value: string) => setDuration(Number(value));
+ onReset();
+ };
+
const onCloseErrorModal = () => setErrorMessage(undefined);
+ const onCloseThankYouModal = () => {
+ setThankYouModalVisible(false);
+ navigate(`/profile/${address}`);
+ };
+
+ const isWarning = isInsufficientBalance || isInsufficientLiquidity || isUnacceptablePriceImpact || confirmNoAmount;
return (
-
-
- Donate
-
- Support {collective.ipfs.name}{' '}
- {isDesktopView && (
- <>
-
- >
- )}
- by donating any amount you want either one time or on a recurring monthly basis.
-
-
-
-
- {!isDesktopView && (
- <>
-
- Donation Currency:
- You can donate using any cryptocurrency.
-
-
-
-
-
-
- {currency}
-
-
-
- {donationAmountUsdValue} USD
-
-
-
- >
- )}
-
- {isDesktopView && (
-
-
-
- Donation Currency:
- You can donate using any cryptocurrency.
-
-
-
-
-
-
- {currency}
-
-
-
- {donationAmountUsdValue} USD
-
-
-
-
-
-
-
- Donation Frequency
-
- How often do you want to donate this {!isDesktopView &&
} amount?
-
-
-
-
-
- {frequency !== 'One-Time' && (
-
- For How Long:
-
-
+
+ {/* todo: find simpler solution to render different modals */}
+
+
+
+
+ setStartStreamingVisible(false)}
+ title="STREAMS CONTINUE UNLESS YOU STOP THEM!"
+ paragraphs={[
+ 'The stream will end if your GoodDollar wallet balance is depleted.',
+ 'You may cancel the stream at any time by visiting the GoodCollective page or Superfluid dApp.',
+ ]}
+ onConfirm={handleStreamingWarning}
+ confirmButtonText="OK, Continue"
+ image={StreamWarning}
+ />
- {getFrequencyPlural(frequency as Frequency)}
-
-
- )}
-
-
- )}
-
-
- <>
- {!isDesktopView && (
- <>
-
- {frequency !== 'One-Time' && (
-
- For How Long:
-
-
-
- {getFrequencyPlural(frequency)}
-
-
- )}
- >
- )}
- >
-
- {isConnected && (
-
-
-
- Review Your Donation
-
- Your donation will be made in GoodDollars, the currency in use by this GoodCollective.{'\n'}
- If recurrent, your donation will be streamed using Superfluid.{'\n'}
- Pressing “Confirm” will trigger your donation.{'\n'}
-
-
+
+
+
+ Donate
+
+
+ Support {collective.ipfs.name}
+ by donating any amount you want either one time or streaming at a monthly rate.
+
+
+
+
+ {/* Donation frequency */}
+
+
+
+ Donation Frequency
+
+ How do you want to donate
+
+
+
+ {frequency === Frequency.Monthly ? (
+
+ Your donation will be streamed using Superfluid.
+
+
How does Superfluid work?
-
-
- Donation Amount:
-
-
- {currency} {decimalDonationAmount}
-
- {donationAmountUsdValue} USD
-
-
-
- {frequency !== 'One-Time' && (
-
- Donation Duration:
-
-
- {duration} {getFrequencyPlural(frequency)}
-
-
-
+ ) : (
+
)}
- {frequency !== Frequency.OneTime && (
-
- Total Amount:
-
-
- {currency} {totalDonationFormatted}
+
+ {/* Amount and token */}
+
+
+
+ How much?
+
+ You can donate using any token on Celo.
+
+
+ {frequency === 'One-Time' && currency === 'CELO' && isNonZeroDonation && swapValue ? (
+
+ ) : null}
+
+
+
+ {frequency !== 'One-Time' && !currency.includes('G$') ? (
+ <>
+
+
+ At what streaming rate?
- {totalDonationUsdValue} USD
-
-
- )}
-
-
-
- {isInsufficientLiquidity && (
-
-
-
-
- Insufficient liquidity!
-
- There is not enough liquidity between your chosen currency and GoodDollar to proceed.
-
-
-
- You may:
-
- 1. Try with another currency {'\n'}
- 2. Reduce your donation amount {'\n'}
-
- 3. Purchase and use GoodDollar {'\n'}
-
-
-
-
-
- )}
-
- {isInsufficientBalance && (
-
-
-
-
- Insufficient balance!
- There is not enough balance in your wallet to proceed.
-
-
- You may:
-
- 1. Reduce your donation amount {'\n'}
- 2. Try with another currency {'\n'}
-
- 3. Purchase and use GoodDollar {'\n'}
-
-
-
-
-
- )}
-
- {isUnacceptablePriceImpact && (
-
-
-
-
- Price impace warning!
-
- Due to low liquidity between your chosen currency and GoodDollar,
-
- {' '}
- your donation amount will reduce by {priceImpact?.toFixed(2)}%{' '}
-
- when swapped.
-
-
-
- You may:
-
- 1. Proceed and accept the price slip {'\n'}
- 2. Select another Donation Currency above {'\n'}
-
- 3. Purchase and use GoodDollar {'\n'}
-
-
-
-
-
- )}
-
-
+
+
+ {' '}
+ >
+ ) : null}
+ {frequency !== 'One-Time' && isNonZeroDonation && !isEmpty(estimatedDuration.endDate) ? (
+
+
+
+ Estimated duration:
+
+
+ {parseFloat(estimatedDuration.duration.toFixed(2))} months
+
+
+
+
+ Estimated End Date:
+
+
+ {estimatedDuration.endDate}
+
+
+
+ ) : null}
+
+
+ {frequency !== 'One-Time' && currency === 'CELO' && isNonZeroDonation && swapValue ? (
+
+
+ Total Donation Swap Amount:
+
+
+
+ CELO {decimalDonationAmount}
+
+
+
+
+ ) : null}
+
+
+
+ {isWarning
+ ? Object.keys(warningProps).map((key) => {
+ const whichWarning =
+ key === 'priceImpact'
+ ? isUnacceptablePriceImpact
+ : key === 'balance'
+ ? isInsufficientBalance
+ : key === 'noAmount'
+ ? !isNonZeroDonation && confirmNoAmount
+ : isInsufficientLiquidity;
+ return whichWarning ? (
+
+ ) : null;
+ })
+ : null}
+ {isNonZeroDonation ? (
+
+ You are about to begin a donation stream
+
Pressing “Confirm” will begin the donation streaming process. You will need to confirm using your
connected wallet. You may be asked to sign multiple transactions.
-
-
- )}
-
-
-
-
-
-
-
-
+
+ ) : null}
+
+
+
+
+
);
-}
-
-const styles = StyleSheet.create({
- body: {
- gap: 24,
- paddingBottom: 32,
- paddingTop: 32,
- paddingHorizontal: 16,
- backgroundColor: Colors.white,
- },
- bodyDesktop: {
- borderRadius: 30,
- marginTop: 12,
- },
- title: {
- lineHeight: 25,
- fontSize: 20,
- textAlign: 'left',
- ...InterSemiBold,
- marginBottom: 16,
- },
- description: {
- color: Colors.gray[200],
- fontSize: 16,
- lineHeight: 24,
- textAlign: 'left',
- ...InterSmall,
- },
- divider: {
- width: '100%',
- height: 1,
- backgroundColor: Colors.gray[600],
- },
- form: {
- alignItems: 'center',
- flexGrow: 1,
- justifyContent: 'center',
- },
- upperForm: {
- flexDirection: 'row',
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- width: '50%',
- paddingLeft: '25%',
- },
- actionContent: {
- gap: 8,
- },
- actionBox: {
- gap: 16,
- flex: 1,
- zIndex: -1,
- },
- row: {
- flexDirection: 'row',
- gap: 8,
- },
- desktopActionBox: {
- flex: 1,
- flexDirection: 'column',
- justifyContent: 'space-between',
- },
- headerLabel: {
- fontSize: 18,
- lineHeight: 27,
- color: Colors.gray[100],
- ...InterSmall,
- },
- subHeading: {
- fontSize: 20,
- lineHeight: 27,
- ...InterSemiBold,
- color: Colors.gray[100],
- paddingLeft: 10,
- width: 159,
- },
- lowerText: {
- fontSize: 12,
- lineHeight: 18,
- textAlign: 'center',
- color: Colors.gray[900],
-
- ...InterRegular,
- },
- frequencyDetails: {
- height: 32,
- gap: 8,
- backgroundColor: Colors.purple[100],
- justifyContent: 'center',
- alignItems: 'center',
- borderColor: Colors.gray[600],
- borderBottomWidth: 1,
- flex: 1,
- flexDirection: 'row',
- },
- desktopFrequencyDetails: {
- maxHeight: 59,
- },
- durationInput: {
- fontSize: 18,
- lineHeight: 27,
- ...InterSemiBold,
- width: '20%',
- color: Colors.purple[400],
- textAlign: 'center',
- },
- durationLabel: {
- ...InterSmall,
- fontSize: 18,
- lineHeight: 27,
- color: Colors.purple[400],
- textAlignVertical: 'bottom',
- },
- downIcon: {
- width: 24,
- height: 24,
- },
- descriptionLabel: {
- fontSize: 12,
- lineHeight: 18,
- textAlign: 'right',
- color: Colors.gray[200],
- ...InterSmall,
- },
- lastDesc: {
- color: Colors.gray[200],
- fontSize: 16,
- lineHeight: 24,
- textAlign: 'center',
- ...InterSemiBold,
- },
- warningView: {
- width: '100%',
- borderRadius: 4,
- paddingHorizontal: 12,
- paddingVertical: 8,
- gap: 8,
- backgroundColor: Colors.orange[100],
- flex: 1,
- flexDirection: 'row',
- flexWrap: 'wrap',
- },
- infoIcon: {
- width: 16,
- height: 16,
- },
- actionHeader: {
- gap: 4,
- width: '100%',
- },
- warningTitle: {
- ...InterSemiBold,
- color: Colors.orange[300],
- fontSize: 14,
- lineHeight: 21,
- },
- warningLine: {
- ...InterSmall,
- color: Colors.orange[300],
- fontSize: 14,
- lineHeight: 21,
- },
- reviewContainer: {
- width: '100%',
- gap: 24,
- padding: 8,
- borderRadius: 4,
- backgroundColor: Colors.green[400],
- },
- reviewSubtitle: {
- fontSize: 18,
- lineHeight: 27,
- color: Colors.green[300],
- ...InterSemiBold,
- },
- reviewRow: {
- flex: 1,
- flexDirection: 'row',
- justifyContent: 'space-between',
- },
- reviewDesc: {
- fontSize: 14,
- lineHeight: 21,
- color: Colors.gray[100],
- ...InterSmall,
- },
- italic: { fontStyle: 'italic' },
- frequencyWrapper: { gap: 17, zIndex: -1 },
- donationAction: { width: 'auto', flexGrow: 1 },
- donationCurrencyHeader: { flexDirection: 'row', width: 'auto', gap: 20 },
-});
+};
export default DonateComponent;
diff --git a/packages/app/src/components/DonateFrequency.tsx b/packages/app/src/components/DonateFrequency.tsx
new file mode 100644
index 00000000..82a92ce3
--- /dev/null
+++ b/packages/app/src/components/DonateFrequency.tsx
@@ -0,0 +1,47 @@
+import { useState } from 'react';
+import { Box, HStack, Radio } from 'native-base';
+
+import { useScreenSize } from '../theme/hooks';
+
+interface DropdownProps {
+ onSelect: (value: string) => void;
+ options?: string[];
+}
+
+const WhiteDot = () => ;
+
+const FrequencySelector = ({ onSelect, options = ['One-Time', 'Monthly'] }: DropdownProps) => {
+ const [value, setValue] = useState('One-Time');
+ const { isTabletView } = useScreenSize();
+
+ return (
+
+ {
+ setValue(v);
+ onSelect(v);
+ }}
+ style={{ flexDirection: 'row' }}
+ width="100%"
+ flexDir="row"
+ justifyContent="space-between">
+ {options.map((option) => (
+ }
+ value={option}>
+ {option === 'Monthly' ? 'Streaming' : option}
+
+ ))}
+
+
+ );
+};
+
+export default FrequencySelector;
diff --git a/packages/app/src/components/Dropdown.tsx b/packages/app/src/components/Dropdown.tsx
index c9c9cf22..ae57425c 100644
--- a/packages/app/src/components/Dropdown.tsx
+++ b/packages/app/src/components/Dropdown.tsx
@@ -1,24 +1,18 @@
import { Image, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useState } from 'react';
+import { HStack, Pressable } from 'native-base';
+
import { Colors } from '../utils/colors';
import { InterSemiBold, InterSmall } from '../utils/webFonts';
import { chevronDown } from '../assets';
-function renderDropdownItemText(current: string, selection: string) {
+const renderDropdownItemText = (current: string, selection: string) => {
if (current === selection) {
return {selection};
} else {
return {selection};
}
-}
-
-function getDropdownBGC(openModal: boolean) {
- if (openModal) {
- return Colors.blue[100];
- } else {
- return Colors.purple[100];
- }
-}
+};
interface DropdownProps {
onSelect: (value: string) => void;
@@ -26,27 +20,27 @@ interface DropdownProps {
options: { value: string; label: string }[];
}
-function Dropdown({ onSelect, value, options }: DropdownProps) {
+const Dropdown = ({ onSelect, value, options }: DropdownProps) => {
const [open, setOpen] = useState(false);
return (
-
-
+ {
setOpen(!open);
}}>
{value}
-
- {open && (
+
+ {open ? (
{options.map((option) => (
))}
- )}
-
+ ) : null}
+
);
-}
+};
const styles = StyleSheet.create({
- body: {
- gap: 24,
- paddingBottom: 32,
- paddingTop: 32,
- paddingHorizontal: 16,
- backgroundColor: Colors.white,
- },
- title: {
- lineHeight: 25,
- fontSize: 20,
- textAlign: 'left',
- ...InterSemiBold,
- },
- form: {
- alignItems: 'center',
- width: '70%',
- justifyContent: 'center',
- },
- row: {
- flexDirection: 'row',
- gap: 8,
- },
- button: {
- gap: 2,
- borderRadius: 12,
- padding: 16,
- minWidth: 105,
- width: '100%',
- height: 59,
- justifyContent: 'space-between',
- },
buttonText: {
color: Colors.purple[400],
fontSize: 18,
@@ -121,7 +84,7 @@ const styles = StyleSheet.create({
paddingBottom: 10,
position: 'absolute',
zIndex: 2,
- top: 60,
+ top: 50,
left: 0,
borderRadius: 12,
shadowColor: Colors.black,
@@ -141,13 +104,6 @@ const styles = StyleSheet.create({
minHeight: 60,
alignItems: 'center',
},
- dropdownSeparator: {
- width: '100%',
- height: 2,
- backgroundColor: Colors.gray[600],
- marginTop: 5,
- marginBottom: 5,
- },
dropdownMyProfileText: {
fontSize: 18,
marginLeft: 15,
@@ -159,7 +115,6 @@ const styles = StyleSheet.create({
color: Colors.purple[400],
textAlign: 'center',
},
- dropdown: { position: 'relative' },
});
export default Dropdown;
diff --git a/packages/app/src/components/Layout/Layout.tsx b/packages/app/src/components/Layout/Layout.tsx
index 83aacf52..dc12b005 100644
--- a/packages/app/src/components/Layout/Layout.tsx
+++ b/packages/app/src/components/Layout/Layout.tsx
@@ -24,7 +24,7 @@ function Layout({ children, breadcrumbPath }: LayoutProps) {
const scrollViewHeight = safeAreaHeight - 105;
const { address } = useAccount();
- const { isDesktopView } = useScreenSize();
+ const { isDesktopView, isMobileView, isTabletView } = useScreenSize();
const location = useLocation();
const { navigate } = useCrossNavigate();
@@ -39,8 +39,9 @@ function Layout({ children, breadcrumbPath }: LayoutProps) {
const scrollViewStyles = [
styles.scrollView,
- { maxHeight: scrollViewHeight, minHeight: scrollViewHeight },
+ { ...(!isMobileView && { maxHeight: scrollViewHeight, minHeight: scrollViewHeight }) },
{ paddingBottom: isCollectivePage ? 61 : 0 },
+ { paddingHorizontal: isTabletView ? 48 : isMobileView ? 8 : 24 },
];
return (
diff --git a/packages/app/src/components/NumberInput.tsx b/packages/app/src/components/NumberInput.tsx
new file mode 100644
index 00000000..b684ea34
--- /dev/null
+++ b/packages/app/src/components/NumberInput.tsx
@@ -0,0 +1,91 @@
+import { useCallback } from 'react';
+import { Box, HStack, Input, Text, VStack } from 'native-base';
+import { noop } from 'lodash';
+
+import Dropdown from './Dropdown';
+
+const NumberInput = ({
+ type,
+ dropdownValue,
+ onSelect = noop,
+ onChangeAmount,
+ options = [],
+ isWarning = false,
+ inputValue = undefined,
+}: {
+ type: string;
+ dropdownValue: string;
+ onSelect?: (v: string) => void;
+ onChangeAmount: (v: string) => void;
+ options?: { value: string; label: string }[];
+ isWarning?: boolean;
+ inputValue?: string | undefined;
+}) => {
+ const onChange = useCallback(
+ (v: string) => {
+ if (!/^\d+(\.\d{0,18})?$/.test(v)) {
+ console.error('Invalid input', v);
+ if (v !== '') {
+ return;
+ }
+ }
+
+ onChangeAmount(v);
+ },
+ [onChangeAmount]
+ );
+ return (
+
+
+ {type === 'token' ? : }
+
+
+
+
+
+ {type === 'duration' ? (
+
+
+ / Month
+
+
+ ) : (
+ <>>
+ )}
+
+
+ );
+};
+
+export default NumberInput;
diff --git a/packages/app/src/components/StopDonationActionButton.tsx b/packages/app/src/components/StopDonationActionButton.tsx
index ced4af7f..d12526e8 100644
--- a/packages/app/src/components/StopDonationActionButton.tsx
+++ b/packages/app/src/components/StopDonationActionButton.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react';
-import ErrorModal from './modals/ErrorModal';
-import StopDonationModal from './modals/StopDonationModal';
+import { QuestionImg } from '../assets';
+import BaseModal from './modals/BaseModal';
import ProcessingModal from './modals/ProcessingModal';
import { useDeleteFlow } from '../hooks/useContractCalls/useDeleteFlow';
import RoundedButton from './RoundedButton';
@@ -30,12 +30,23 @@ export const StopDonationActionButton = ({ donorCollective }: { donorCollective:
color={Colors.orange[300]}
onPress={handleStopDonation}
/>
- setErrorMessage(undefined)}
- message={errorMessage ?? ''}
+ errorMessage={errorMessage ?? ''}
+ onConfirm={() => setErrorMessage(undefined)}
+ />
+
-
);
diff --git a/packages/app/src/components/modals/ApproveSwapModal.tsx b/packages/app/src/components/modals/ApproveSwapModal.tsx
deleted file mode 100644
index 50e08544..00000000
--- a/packages/app/src/components/modals/ApproveSwapModal.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import { Modal, StyleSheet, Text, View, Image, TouchableOpacity, Platform } from 'react-native';
-import { InterRegular, InterSemiBold } from '../../utils/webFonts';
-import { Colors } from '../../utils/colors';
-import { modalStyles } from '../shared';
-import { CloseIcon, ApproveTokenImg } from '../../assets';
-
-interface AproveSwapModalProps {
- openModal: boolean;
- setOpenModal: any;
-}
-
-const ApproveSwapModal = ({ openModal, setOpenModal }: AproveSwapModalProps) => {
- return (
-
-
-
-
- setOpenModal(false)}>
-
-
-
- APPROVE TOKEN SWAP
-
- To approve the exchange from your donation currency to this GoodCollective's currency, sign with your
- wallet.
-
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- centeredView: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 22,
- },
- modalView: {
- maxWidth: '90%',
- maxHeight: '90%',
- // @ts-ignore
- overflowY: Platform.select({
- native: 'scroll',
- default: 'auto',
- }),
- margin: 20,
- backgroundColor: Colors.blue[100],
- borderRadius: 20,
- paddingTop: 24,
- paddingHorizontal: 24,
- paddingBottom: 40,
- gap: 24,
- alignItems: 'center',
- shadowColor: Colors.black,
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 4,
- elevation: 5,
- },
- title: {
- fontSize: 30,
- textAlign: 'center',
- marginHorizontal: 0,
- ...InterSemiBold,
- },
- paragraph: {
- ...InterRegular,
- fontSize: 18,
- textAlign: 'center',
- width: '100%',
- lineHeight: 27,
- },
- image: {
- alignSelf: 'center',
- width: 295,
- height: 224,
- },
- closeIcon: {
- width: 24,
- height: 24,
- alignSelf: 'flex-end',
- },
-
- button: {
- backgroundColor: Colors.orange[100],
- width: '100%',
- borderRadius: 30,
- paddingTop: 12,
- paddingRight: 22,
- paddingBottom: 12,
- paddingLeft: 20,
- gap: 8,
- alignContent: 'center',
- },
- buttonText: {
- ...InterSemiBold,
- fontSize: 18,
- textAlign: 'center',
- alignSelf: 'center',
- color: Colors.orange[300],
- },
-
- textStyle: {
- color: 'white',
- fontWeight: 'bold',
- textAlign: 'center',
- },
- modalText: {
- marginBottom: 15,
- textAlign: 'center',
- },
-});
-
-export default ApproveSwapModal;
diff --git a/packages/app/src/components/modals/BaseModal.tsx b/packages/app/src/components/modals/BaseModal.tsx
new file mode 100644
index 00000000..7bcfed7e
--- /dev/null
+++ b/packages/app/src/components/modals/BaseModal.tsx
@@ -0,0 +1,135 @@
+import { Modal, Platform } from 'react-native';
+import { Image, Pressable, Text, VStack } from 'native-base';
+
+import ActionButton from '../ActionButton';
+import { CloseIcon, ThankYouImg } from '../../assets';
+
+const modalView = {
+ maxHeight: '90%',
+ // @ts-ignore
+ overflowY: Platform.select({
+ native: 'scroll',
+ default: 'auto',
+ }) as any,
+ margin: 20,
+ backgroundColor: 'goodPurple.300',
+ borderRadius: 20,
+ paddingTop: 8,
+ paddingX: 8,
+ paddingBottom: 10,
+ ...Platform.select({
+ web: {
+ gap: 24,
+ maxWidth: '420',
+ },
+ native: {
+ maxWidth: '90%',
+ },
+ }),
+ alignItems: 'center',
+};
+
+const defaultModalProps = {
+ error: {
+ dTitle: 'Something went wrong',
+ dParagraphs: (errorMessage: string) => ['Please try again later.', 'Reason: ' + (errorMessage ?? 'unknown')],
+ dConfirmButtonText: 'OK',
+ dImage: ThankYouImg,
+ },
+};
+
+type BaseModalProps = {
+ type?: 'error';
+ openModal: boolean;
+ setOpenModal: (open: boolean) => void;
+ onConfirm?: () => void;
+ title?: string;
+ paragraphs?: any[] | undefined;
+ confirmButtonText?: string;
+ image?: any;
+ errorMessage?: string;
+ withClose?: boolean;
+};
+
+export const BaseModal = ({
+ type,
+ openModal,
+ setOpenModal,
+ onConfirm,
+ title,
+ paragraphs,
+ confirmButtonText,
+ image,
+ errorMessage = '',
+ withClose = true,
+}: BaseModalProps) => {
+ const onClose = () => setOpenModal(false);
+ const { dTitle, dParagraphs, dConfirmButtonText, dImage } =
+ type === 'error'
+ ? defaultModalProps[type as keyof typeof defaultModalProps]
+ : { dTitle: title, dParagraphs: paragraphs, dConfirmButtonText: confirmButtonText, dImage: image };
+
+ const paragraph =
+ type === 'error' && typeof dParagraphs === 'function'
+ ? dParagraphs?.(errorMessage)
+ : (dParagraphs as any[] | undefined);
+
+ return (
+
+
+
+ {withClose ? (
+
+
+
+
+
+ ) : null}
+
+
+ {dTitle?.toUpperCase()}
+
+
+ {paragraph
+ ? // eslint-disable-next-line @typescript-eslint/no-shadow
+ paragraph.map((paragraph: string, index: number) =>
+ paragraph ? (
+
+ {paragraph}
+
+ ) : null
+ )
+ : null}
+
+
+ {dConfirmButtonText ? (
+
+ ) : null}
+
+
+
+
+ );
+};
+
+export default BaseModal;
diff --git a/packages/app/src/components/modals/CompleteDonationModal.tsx b/packages/app/src/components/modals/CompleteDonationModal.tsx
deleted file mode 100644
index 9be5312b..00000000
--- a/packages/app/src/components/modals/CompleteDonationModal.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Modal, StyleSheet, Text, View, Image, TouchableOpacity, Platform } from 'react-native';
-import { InterRegular, InterSemiBold } from '../../utils/webFonts';
-import { Colors } from '../../utils/colors';
-import { modalStyles } from '../shared';
-import { CloseIcon, PhoneImg } from '../../assets';
-
-interface CompleteDonationModalProps {
- openModal: boolean;
- setOpenModal: any;
-}
-
-const CompleteDonationModal = ({ openModal, setOpenModal }: CompleteDonationModalProps) => {
- const onClickClose = () => setOpenModal(false);
-
- return (
-
-
-
-
-
-
-
-
- COMPLETE YOUR DONATION
- To complete your donation, sign with your wallet.
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- centeredView: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 22,
- },
- modalView: {
- maxWidth: '90%',
- maxHeight: '90%',
- overflowY: Platform.select({
- native: 'scroll',
- default: 'auto',
- }),
- margin: 20,
- backgroundColor: Colors.blue[100],
- borderRadius: 20,
- paddingTop: 24,
- paddingHorizontal: 24,
- paddingBottom: 40,
- gap: 24,
- alignItems: 'center',
- shadowColor: Colors.black,
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 4,
- elevation: 5,
- },
- title: {
- fontSize: 30,
- textAlign: 'center',
- marginHorizontal: 0,
- ...InterSemiBold,
- },
- paragraph: {
- ...InterRegular,
- fontSize: 18,
- textAlign: 'center',
- width: '100%',
- lineHeight: 27,
- },
- image: {
- alignSelf: 'center',
- width: 190,
- height: 224,
- },
- closeIcon: {
- width: 24,
- height: 24,
- alignSelf: 'flex-end',
- },
-
- button: {
- backgroundColor: Colors.orange[100],
- width: '100%',
- borderRadius: 30,
- paddingTop: 12,
- paddingRight: 22,
- paddingBottom: 12,
- paddingLeft: 20,
- gap: 8,
- alignContent: 'center',
- },
- buttonText: {
- ...InterSemiBold,
- fontSize: 18,
- textAlign: 'center',
- alignSelf: 'center',
- color: Colors.orange[300],
- },
-
- textStyle: {
- color: 'white',
- fontWeight: 'bold',
- textAlign: 'center',
- },
- modalText: {
- marginBottom: 15,
- textAlign: 'center',
- },
-});
-
-export default CompleteDonationModal;
diff --git a/packages/app/src/components/modals/ErrorModal.tsx b/packages/app/src/components/modals/ErrorModal.tsx
deleted file mode 100644
index 0aebe43c..00000000
--- a/packages/app/src/components/modals/ErrorModal.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-import { Modal, StyleSheet, Text, View, Image, TouchableOpacity, Platform } from 'react-native';
-import { InterRegular, InterSemiBold } from '../../utils/webFonts';
-import { Colors } from '../../utils/colors';
-import { CloseIcon, ThankYouImg } from '../../assets';
-
-interface ErrorModalProps {
- openModal: boolean;
- setOpenModal: any;
- message: string;
-}
-
-const ErrorModal = ({ openModal, setOpenModal, message }: ErrorModalProps) => {
- const onCloseClicked = () => setOpenModal(false);
- return (
-
-
-
-
-
-
-
-
-
- SOMETHING WENT WRONG
- Please try again later.
- Reason: {message}
-
-
- OK
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- centeredView: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 22,
- },
- modalView: {
- maxWidth: '90%',
- maxHeight: '90%',
- // @ts-ignore
- overflowY: Platform.select({
- native: 'scroll',
- default: 'auto',
- }),
- margin: 20,
- backgroundColor: Colors.blue[100],
- borderRadius: 20,
- paddingTop: 24,
- paddingHorizontal: 24,
- paddingBottom: 40,
- gap: 24,
- alignItems: 'center',
- shadowColor: Colors.black,
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 4,
- elevation: 5,
- },
- title: {
- fontSize: 30,
- textAlign: 'center',
- marginHorizontal: 0,
- ...InterSemiBold,
- },
- paragraph: {
- ...InterRegular,
- fontSize: 18,
- textAlign: 'center',
- width: '100%',
- lineHeight: 27,
- },
- image: {
- alignSelf: 'center',
- width: 190,
- height: 224,
- },
- closeIcon: {
- width: 24,
- height: 24,
- alignSelf: 'flex-end',
- },
-
- button: {
- backgroundColor: Colors.orange[100],
- width: '80%',
- borderRadius: 30,
- paddingTop: 12,
- paddingRight: 22,
- paddingBottom: 12,
- paddingLeft: 20,
- gap: 8,
- alignContent: 'center',
- },
- buttonText: {
- ...InterSemiBold,
- fontSize: 18,
- textAlign: 'center',
- alignSelf: 'center',
- color: Colors.orange[300],
- },
-
- textStyle: {
- color: 'white',
- fontWeight: 'bold',
- textAlign: 'center',
- },
- modalText: {
- marginBottom: 15,
- textAlign: 'center',
- },
- modalCloseIconWrapper: { width: '100%', alignContent: 'flex-end' },
- modalCloseIcon: { width: 24, height: 24, alignSelf: 'flex-end' },
-});
-
-export default ErrorModal;
diff --git a/packages/app/src/components/modals/StopDonationModal.tsx b/packages/app/src/components/modals/StopDonationModal.tsx
deleted file mode 100644
index 82be1c7a..00000000
--- a/packages/app/src/components/modals/StopDonationModal.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Modal, StyleSheet, Text, View, Image, TouchableOpacity, Platform } from 'react-native';
-import { InterRegular, InterSemiBold } from '../../utils/webFonts';
-import { Colors } from '../../utils/colors';
-import { QuestionImg } from '../../assets';
-
-interface StopDonationModalProps {
- openModal: boolean;
- setOpenModal: any;
-}
-
-const StopDonationModal = ({ openModal, setOpenModal }: StopDonationModalProps) => {
- return (
-
-
-
- ARE YOU SURE YOU WANT TO STOP YOUR DONATION?
-
- If so, please sign with your wallet. If not, please click below to return to the GoodCollective you support.
-
-
-
- setOpenModal(false)}>
- GO BACK
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- centeredView: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 22,
- },
- modalView: {
- maxWidth: '90%',
- maxHeight: '90%',
- // @ts-ignore
- overflowY: Platform.select({
- native: 'scroll',
- default: 'auto',
- }),
- margin: 20,
- backgroundColor: Colors.blue[100],
- borderRadius: 20,
- paddingTop: 24,
- paddingHorizontal: 24,
- paddingBottom: 40,
- gap: 24,
- alignItems: 'center',
- shadowColor: Colors.black,
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 4,
- elevation: 5,
- },
- title: {
- fontSize: 30,
- textAlign: 'center',
- marginHorizontal: 0,
- ...InterSemiBold,
- },
- paragraph: {
- ...InterRegular,
- fontSize: 18,
- textAlign: 'center',
- width: '100%',
- lineHeight: 27,
- },
- image: {
- alignSelf: 'center',
- width: 190,
- height: 224,
- },
- closeIcon: {
- width: 24,
- height: 24,
- alignSelf: 'flex-end',
- },
-
- button: {
- backgroundColor: Colors.orange[100],
- width: '100%',
- borderRadius: 30,
- paddingTop: 12,
- paddingRight: 22,
- paddingBottom: 12,
- paddingLeft: 20,
- gap: 8,
- alignContent: 'center',
- },
- buttonText: {
- ...InterSemiBold,
- fontSize: 18,
- textAlign: 'center',
- alignSelf: 'center',
- color: Colors.orange[300],
- },
-
- textStyle: {
- color: 'white',
- fontWeight: 'bold',
- textAlign: 'center',
- },
- modalText: {
- marginBottom: 15,
- textAlign: 'center',
- },
-});
-
-export default StopDonationModal;
diff --git a/packages/app/src/components/modals/ThankYouModal.tsx b/packages/app/src/components/modals/ThankYouModal.tsx
deleted file mode 100644
index fc15ddb5..00000000
--- a/packages/app/src/components/modals/ThankYouModal.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import { Modal, StyleSheet, Text, View, Image, TouchableOpacity, Platform } from 'react-native';
-import { InterRegular, InterSemiBold } from '../../utils/webFonts';
-import { Colors } from '../../utils/colors';
-import { CloseIcon, ThankYouImg } from '../../assets';
-import { IpfsCollective } from '../../models/models';
-import useCrossNavigate from '../../routes/useCrossNavigate';
-import { modalStyles } from '../shared';
-import { useEffect, useState } from 'react';
-
-interface ThankYouModalProps {
- openModal: boolean;
- address?: `0x${string}`;
- collective: IpfsCollective;
- isStream: boolean;
-}
-
-const ThankYouModal = ({ openModal, address, collective, isStream }: ThankYouModalProps) => {
- const [isOpen, setOpenModal] = useState(openModal);
- const { navigate } = useCrossNavigate();
- const onClick = () => navigate(`/profile/${address}`);
- useEffect(() => {
- setOpenModal(openModal);
- }, [openModal]);
- return (
-
-
-
-
- setOpenModal(false)}>
-
-
-
- THANK YOU!
- {`You have just donated to ${collective.name} GoodCollective!`}
- {isStream && (
-
- {`To stop your donation, visit the ${collective.name} GoodCollective page.`}
-
- )}
-
-
- GO TO PROFILE
-
-
-
-
- );
-};
-
-const styles = StyleSheet.create({
- centeredView: {
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 22,
- },
- modalView: {
- maxWidth: '90%',
- maxHeight: '90%',
- // @ts-ignore
- overflowY: Platform.select({
- native: 'scroll',
- default: 'auto',
- }),
- margin: 20,
- backgroundColor: Colors.blue[100],
- borderRadius: 20,
- paddingTop: 24,
- paddingHorizontal: 24,
- paddingBottom: 40,
- gap: 24,
- alignItems: 'center',
- shadowColor: Colors.black,
- shadowOffset: {
- width: 0,
- height: 2,
- },
- shadowOpacity: 0.25,
- shadowRadius: 4,
- elevation: 5,
- },
- title: {
- fontSize: 30,
- textAlign: 'center',
- marginHorizontal: 0,
- ...InterSemiBold,
- },
- paragraph: {
- ...InterRegular,
- fontSize: 18,
- textAlign: 'center',
- width: '100%',
- lineHeight: 27,
- },
- image: {
- alignSelf: 'center',
- width: 286,
- height: 214,
- },
- closeIcon: {
- width: 24,
- height: 24,
- alignSelf: 'flex-end',
- },
-
- button: {
- backgroundColor: Colors.orange[100],
- width: '100%',
- borderRadius: 30,
- paddingTop: 12,
- paddingBottom: 12,
- gap: 8,
- alignContent: 'center',
- },
- buttonText: {
- ...InterSemiBold,
- fontSize: 18,
- textAlign: 'center',
- alignSelf: 'center',
- color: Colors.orange[300],
- },
- textStyle: {
- color: 'white',
- fontWeight: 'bold',
- textAlign: 'center',
- },
- modalText: {
- marginBottom: 15,
- textAlign: 'center',
- },
-});
-
-export default ThankYouModal;
diff --git a/packages/app/src/hooks/useGetTokenBalance.ts b/packages/app/src/hooks/useGetTokenBalance.ts
index 73a408d2..318d03f1 100644
--- a/packages/app/src/hooks/useGetTokenBalance.ts
+++ b/packages/app/src/hooks/useGetTokenBalance.ts
@@ -1,7 +1,6 @@
import { SupportedNetwork } from '../models/constants';
import { useEffect, useState } from 'react';
import { fetchBalance } from 'wagmi/actions';
-import { useToken } from './useTokenList';
export const useGetTokenBalance = (
currencyAddress: string,
diff --git a/packages/app/src/hooks/useSwapRoute.tsx b/packages/app/src/hooks/useSwapRoute.tsx
index 928539a0..99171e85 100644
--- a/packages/app/src/hooks/useSwapRoute.tsx
+++ b/packages/app/src/hooks/useSwapRoute.tsx
@@ -1,3 +1,4 @@
+import { useEffect, useState } from 'react';
import {
AlphaRouter,
OnChainQuoteProvider,
@@ -8,14 +9,16 @@ import {
} from '@uniswap/smart-order-router';
import { CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core';
import { useAccount, useNetwork } from 'wagmi';
+import { encodeRouteToPath } from '@uniswap/v3-sdk';
+import { Protocol } from '@uniswap/router-sdk';
+import Decimal from 'decimal.js';
+import { ethers } from 'ethers';
+
import { SupportedNetwork } from '../models/constants';
import { useEthersProvider } from './useEthers';
import { calculateRawTotalDonation } from '../lib/calculateRawTotalDonation';
-import Decimal from 'decimal.js';
-import { useEffect, useState } from 'react';
-import { encodeRouteToPath } from '@uniswap/v3-sdk';
+
import { useToken } from './useTokenList';
-import { Protocol } from '@uniswap/router-sdk';
export enum SwapRouteState {
LOADING,
@@ -37,6 +40,7 @@ export function useSwapRoute(
rawMinimumAmountOut?: string;
priceImpact?: number;
status: SwapRouteState;
+ gasEstimate?: string;
} {
const { address } = useAccount();
const { chain } = useNetwork();
@@ -121,6 +125,8 @@ export function useSwapRoute(
const quote = new Decimal(route.quote.toFixed(18));
const rawMinimumAmountOut = route.trade.minimumAmountOut(slippageTolerance).numerator.toString();
const priceImpact = parseFloat(route.trade.priceImpact.toFixed(4));
- return { path, quote, rawMinimumAmountOut, priceImpact, status: SwapRouteState.READY };
+ const gasEstimate = ethers.utils.formatUnits(route.estimatedGasUsed, 'gwei');
+
+ return { path, quote, rawMinimumAmountOut, priceImpact, status: SwapRouteState.READY, gasEstimate };
}
}
diff --git a/packages/app/src/models/constants.ts b/packages/app/src/models/constants.ts
index ded429f6..e13db213 100644
--- a/packages/app/src/models/constants.ts
+++ b/packages/app/src/models/constants.ts
@@ -38,7 +38,7 @@ export const coingeckoTokenMapping: Record = {
export enum Frequency {
OneTime = 'One-Time',
- Monthly = 'Monthly',
+ Monthly = 'Monthly', // streaming
}
// constructed from Frequency
diff --git a/packages/app/src/pages/ActivityLogPage.tsx b/packages/app/src/pages/ActivityLogPage.tsx
index e2f120a1..83fa27b9 100644
--- a/packages/app/src/pages/ActivityLogPage.tsx
+++ b/packages/app/src/pages/ActivityLogPage.tsx
@@ -42,7 +42,6 @@ const styles = StyleSheet.create({
body: {
gap: 24,
backgroundColor: Colors.white,
- maxWidth: 1280,
},
container: {
diff --git a/packages/app/src/theme/theme.ts b/packages/app/src/theme/theme.ts
index 5e2bddbe..e5ff5681 100644
--- a/packages/app/src/theme/theme.ts
+++ b/packages/app/src/theme/theme.ts
@@ -12,6 +12,7 @@ export const nbTheme = extendTheme({
// text
goodGrey: {
+ 25: '#959090',
50: '#F3F3F3',
100: '#F4F4F4',
200: '#E6E6E6',
@@ -94,6 +95,13 @@ export const nbTheme = extendTheme({
lineHeight: '150%' /* 15px */,
},
variants: {
+ bold: () => ({
+ fontFamily: 'Inter',
+ fontSize: 'sm',
+ fontStyle: 'normal',
+ fontWeight: 700,
+ lineHeight: '150%' /* 30px */,
+ }),
// title/heading fontsize variants
'2xs-grey': () => ({
fontSize: 10,
diff --git a/packages/app/src/utils/index.tsx b/packages/app/src/utils/index.tsx
index 38b9b0f0..c9567dce 100644
--- a/packages/app/src/utils/index.tsx
+++ b/packages/app/src/utils/index.tsx
@@ -44,7 +44,7 @@ const BUTTON_STATE = {
textColor: Colors.gray[300],
},
isZeroDonation: {
- copy: 'Donation amount is zero',
+ copy: 'Confirm',
bgColor: Colors.gray[1000],
textColor: Colors.gray[300],
},
diff --git a/yarn.lock b/yarn.lock
index bf013e1c..a6f184a0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4489,7 +4489,7 @@ __metadata:
"@celo-tools/celo-ethers-wrapper": ^0.4.0
"@ethersproject/shims": ^5.7.0
"@gooddollar/good-design": ^0.1.57
- "@gooddollar/goodcollective-sdk": ^1.1.2
+ "@gooddollar/goodcollective-sdk": ^1.2.1
"@gooddollar/web3sdk-v2": ^0.2.34
"@nerdwallet/apollo-cache-policies": ^3.2.0
"@react-native-aria/interactions": 0.2.3
@@ -4615,7 +4615,7 @@ __metadata:
languageName: unknown
linkType: soft
-"@gooddollar/goodcollective-sdk@^1.1.2, @gooddollar/goodcollective-sdk@workspace:packages/sdk-js":
+"@gooddollar/goodcollective-sdk@^1.2.1, @gooddollar/goodcollective-sdk@workspace:packages/sdk-js":
version: 0.0.0-use.local
resolution: "@gooddollar/goodcollective-sdk@workspace:packages/sdk-js"
dependencies: