diff --git a/packages/app/src/components/DonateComponent.tsx b/packages/app/src/components/DonateComponent.tsx index 69efc0c4..06b1ca65 100644 --- a/packages/app/src/components/DonateComponent.tsx +++ b/packages/app/src/components/DonateComponent.tsx @@ -1,6 +1,6 @@ import { useCallback, useMemo, useState } from 'react'; import { Image, View } from 'react-native'; -import { Box, HStack, Link, Text, VStack } from 'native-base'; +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'; @@ -137,7 +137,7 @@ const WarningBox = ({ content, explanationProps = {} }: any) => { }; const DonateComponent = ({ collective }: DonateComponentProps) => { - const { isDesktopView } = useScreenSize(); + const { isDesktopView, isMobileView } = useScreenSize(); const { id: collectiveId = '0x' } = useParams(); const { address } = useAccount(); @@ -162,12 +162,44 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { } const [frequency, setFrequency] = useState(Frequency.OneTime); - const [streamRate, setRate] = useState(undefined); + const [streamRate, setRate] = useState(1); const [duration, setDuration] = useState(12); 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%', + }, + }); + + const direction = useBreakpointValue({ + base: { + flexDirection: 'column', + }, + lg: { + flexDirection: 'row', + flexWrap: 'wrap', + }, + }); + const tokenList = useTokenList(); const gdEnvSymbol = Object.keys(tokenList).find((key) => { @@ -337,34 +369,53 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { onReset(); }; - const estimateDuration = useCallback((estDuration: number) => { - const estimatedEndDate = moment().add(estDuration, 'months').format('DD.MM.YY HH:mm'); + 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(estDuration); - }, []); + 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.substring(v.length - 1) === '.' || v === '0') return; + if (v.endsWith('.') || ['0', ''].includes(v)) return; setDecimalDonationAmount(formatDecimalStringInput(v)); - if (frequency === Frequency.Monthly && currency.includes('G$')) { - const estDuration = parseFloat(donorCurrencyBalance) / parseFloat(v); - estimateDuration(estDuration); - } else if (frequency === Frequency.Monthly) { - const estDuration = parseFloat(v) / (streamRate as number); - estimateDuration(estDuration); - - const gdValue = parseFloat(v) / tokenPrice; - setSwapValue(gdValue); - } + estimateDuration(v); }, - [currency, donorCurrencyBalance, streamRate, estimateDuration, frequency, tokenPrice] + [estimateDuration] ); const onChangeFrequency = (value: string) => { @@ -375,13 +426,6 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { onReset(); }; - const onChangeRate = (value: string) => { - setRate(Number(value)); - - const estDuration = parseFloat(decimalDonationAmount) / parseFloat(value); - estimateDuration(estDuration); - }; - const onCloseErrorModal = () => setErrorMessage(undefined); const onCloseThankYouModal = () => { setThankYouModalVisible(false); @@ -391,7 +435,7 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { const isWarning = isInsufficientBalance || isInsufficientLiquidity || isUnacceptablePriceImpact || confirmNoAmount; return ( - + {/* todo: find simpler solution to render different modals */} { by donating any amount you want either one time or streaming at a monthly rate. - - + + {/* Donation frequency */} - + Donation Frequency @@ -478,8 +522,8 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { )} {/* Amount and token */} - - + + How much? @@ -496,7 +540,7 @@ const DonateComponent = ({ collective }: DonateComponentProps) => { /> - + {frequency !== 'One-Time' && !currency.includes('G$') ? ( <> diff --git a/packages/app/src/components/DonateFrequency.tsx b/packages/app/src/components/DonateFrequency.tsx index 3e0f4654..82a92ce3 100644 --- a/packages/app/src/components/DonateFrequency.tsx +++ b/packages/app/src/components/DonateFrequency.tsx @@ -1,6 +1,8 @@ import { useState } from 'react'; import { Box, HStack, Radio } from 'native-base'; +import { useScreenSize } from '../theme/hooks'; + interface DropdownProps { onSelect: (value: string) => void; options?: string[]; @@ -10,9 +12,10 @@ const WhiteDot = () => { const [value, setValue] = useState('One-Time'); + const { isTabletView } = useScreenSize(); return ( - + ( - - - {type === 'token' ? : } - - - +}) => { + 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 + + + ) : ( + <> + )} - {type === 'duration' ? ( - - - / Month - - - ) : ( - <> - )} - - -); + + ); +}; export default NumberInput;