Skip to content

Commit

Permalink
fix: responsiveness, fix: input validation and estimation
Browse files Browse the repository at this point in the history
  • Loading branch information
L03TJ3 committed Nov 25, 2024
1 parent 585a14f commit 45c470c
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 86 deletions.
112 changes: 78 additions & 34 deletions packages/app/src/components/DonateComponent.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
Expand All @@ -162,12 +162,44 @@ const DonateComponent = ({ collective }: DonateComponentProps) => {
}

const [frequency, setFrequency] = useState<Frequency>(Frequency.OneTime);
const [streamRate, setRate] = useState<number | undefined>(undefined);
const [streamRate, setRate] = useState<string | number>(1);
const [duration, setDuration] = useState(12);
const [decimalDonationAmount, setDecimalDonationAmount] = useState<any | number>(0);

const [inputAmount, setInputAmount] = useState<string | undefined>(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) => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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);
Expand All @@ -391,7 +435,7 @@ const DonateComponent = ({ collective }: DonateComponentProps) => {
const isWarning = isInsufficientBalance || isInsufficientLiquidity || isUnacceptablePriceImpact || confirmNoAmount;

return (
<Box height="100vh">
<Box height="100vh" paddingBottom={8}>
{/* todo: find simpler solution to render different modals */}
<BaseModal
type="error"
Expand Down Expand Up @@ -452,10 +496,10 @@ const DonateComponent = ({ collective }: DonateComponentProps) => {
<Text>by donating any amount you want either one time or streaming at a monthly rate.</Text>
</VStack>
</VStack>
<VStack space={8} backgroundColor="white" shadow="1" padding={4} borderRadius={16}>
<HStack space={8}>
<VStack space={8} backgroundColor="white" shadow="1" paddingY={4} borderRadius={16} {...container}>
<HStack space={8} {...direction}>
{/* Donation frequency */}
<VStack space={2} w="320" maxW="320">
<VStack space={2} width={isDesktopView ? '320' : 'auto'} maxW="320" mb={8}>
<VStack space={2}>
<Text variant="bold" fontSize="lg">
Donation Frequency
Expand All @@ -478,8 +522,8 @@ const DonateComponent = ({ collective }: DonateComponentProps) => {
)}
</VStack>
{/* Amount and token */}
<VStack space={2} maxW="320">
<VStack space={2}>
<VStack space={2} maxW="320" mb={8}>
<VStack space={2} zIndex={1}>
<Text variant="bold" fontSize="lg">
How much?
</Text>
Expand All @@ -496,7 +540,7 @@ const DonateComponent = ({ collective }: DonateComponentProps) => {
/>
</VStack>

<VStack space={2} minW="343">
<VStack space={2} {...(isMobileView ? { maxWidth: '343' } : { minWidth: '343', maxWidth: '10%' })}>
{frequency !== 'One-Time' && !currency.includes('G$') ? (
<>
<VStack space={2}>
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/components/DonateFrequency.tsx
Original file line number Diff line number Diff line change
@@ -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[];
Expand All @@ -10,9 +12,10 @@ const WhiteDot = () => <Box color="blue" backgroundColor="white" width={3} heigh

const FrequencySelector = ({ onSelect, options = ['One-Time', 'Monthly'] }: DropdownProps) => {
const [value, setValue] = useState('One-Time');
const { isTabletView } = useScreenSize();

return (
<HStack justifyContent={'space-between'} width="100%" flexDir="row">
<HStack justifyContent={'space-between'} w={isTabletView ? '100%' : '90%'} m="auto">
<Radio.Group
name="donationFrequency"
value={value}
Expand Down
5 changes: 3 additions & 2 deletions packages/app/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,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();
Expand All @@ -38,8 +38,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 (
Expand Down
115 changes: 66 additions & 49 deletions packages/app/src/components/NumberInput.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useCallback } from 'react';
import { Box, HStack, Input, Text, VStack } from 'native-base';
import { noop } from 'lodash';

Expand All @@ -19,56 +20,72 @@ const NumberInput = ({
options?: { value: string; label: string }[];
isWarning?: boolean;
inputValue?: string | undefined;
}) => (
<VStack>
<HStack
justifyContent="space-between"
flexShrink={1}
borderColor={isWarning ? 'goodOrange.300' : 'goodPurple.500'}
borderWidth="1"
borderRadius="20"
padding={2}
paddingRight={4}
space={4}>
{type === 'token' ? <Dropdown value={dropdownValue} onSelect={onSelect} options={options} /> : <Box />}
<HStack alignItems="center" flexGrow={1} justifyContent="flex-end">
<HStack justifyContent="flex-end">
<Input
defaultValue=""
keyboardType="decimal-pad"
multiline={false}
placeholder={'0.00'}
outlineStyle="none"
borderColor="white"
width="159"
bgColor="blue"
color={isWarning ? 'goodOrange.300' : 'goodPurple.400'}
fontWeight={isWarning ? '700' : '400'}
textAlign="right"
paddingLeft={2.5}
fontSize="l"
_focus={{
outlineStyle: 'none',
borderColor: 'white',
bgColor: 'white',
}}
value={inputValue ?? ''}
maxLength={9}
onChangeText={onChangeAmount}
/>
}) => {
const onChange = useCallback(
(v: string) => {
if (!/^\d+(\.\d{0,18})?$/.test(v)) {
console.error('Invalid input', v);
if (v !== '') {
return;
}
}

onChangeAmount(v);
},
[onChangeAmount]
);
return (
<VStack>
<HStack
justifyContent="space-between"
flexShrink={1}
borderColor={isWarning ? 'goodOrange.300' : 'goodPurple.500'}
borderWidth="1"
borderRadius="20"
padding={2}
paddingRight={4}
space={4}
maxWidth={290}>
{type === 'token' ? <Dropdown value={dropdownValue} onSelect={onSelect} options={options} /> : <Box />}
<HStack alignItems="center" flexGrow={1} justifyContent="flex-end">
<HStack justifyContent="flex-end">
<Input
defaultValue=""
keyboardType="decimal-pad"
multiline={false}
placeholder={'0.00'}
outlineStyle="none"
borderColor="white"
maxWidth={type === 'duration' ? 100 : 159}
bgColor="blue"
color={isWarning ? 'goodOrange.300' : 'goodPurple.400'}
fontWeight={isWarning ? '700' : '400'}
textAlign="right"
paddingLeft={2.5}
fontSize="l"
_focus={{
outlineStyle: 'none',
borderColor: 'white',
bgColor: 'white',
}}
value={inputValue ?? ''}
maxLength={9}
onChangeText={onChange}
/>
</HStack>
</HStack>
{type === 'duration' ? (
<HStack backgroundColor="goodGrey.100" paddingX={2} paddingY={2.5} borderRadius={12}>
<Text variant="bold" fontSize="md">
/ Month
</Text>
</HStack>
) : (
<></>
)}
</HStack>
{type === 'duration' ? (
<HStack backgroundColor="goodGrey.100" paddingX={2} paddingY={2.5} borderRadius={12}>
<Text variant="bold" fontSize="md">
/ Month
</Text>
</HStack>
) : (
<></>
)}
</HStack>
</VStack>
);
</VStack>
);
};

export default NumberInput;

0 comments on commit 45c470c

Please sign in to comment.