Skip to content

Commit

Permalink
[AMB-324] refactor: move send flow into components (#79)
Browse files Browse the repository at this point in the history
* refactor: wallet settings redesign

* refactor: move send flow into components

---------

Co-authored-by: Anthony Potdevin <[email protected]>
  • Loading branch information
secondl1ght and apotdevin authored Sep 25, 2024
1 parent 376853d commit c159e3a
Show file tree
Hide file tree
Showing 10 changed files with 1,277 additions and 853 deletions.
289 changes: 289 additions & 0 deletions src/views/wallet/send/Default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import { Scanner } from '@yudiel/react-qr-scanner';
import bolt11 from 'bolt11';
import { ArrowLeft, Scan } from 'lucide-react';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { Dispatch, FC, SetStateAction, useState } from 'react';
import { useLocalStorage } from 'usehooks-ts';

import { Button } from '@/components/ui/button-v2';
import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useToast } from '@/components/ui/use-toast';
import { useGetWalletContactsQuery } from '@/graphql/queries/__generated__/contacts.generated';
import { useGetPriceCurrentQuery } from '@/graphql/queries/__generated__/prices.generated';
import { LOCALSTORAGE_KEYS } from '@/utils/constants';
import { handleApolloError } from '@/utils/error';
import { ROUTES } from '@/utils/routes';

import { checkSendString } from './checkSendString';
import { SendType, SendView } from './Send';

export const Default: FC<{
sendString: string;
setSendString: Dispatch<SetStateAction<string>>;
setAmountSatsInput: Dispatch<SetStateAction<string>>;
setAmountUSDInput: Dispatch<SetStateAction<string>>;
setSendType: Dispatch<SetStateAction<SendType>>;
setView: Dispatch<SetStateAction<SendView>>;
}> = ({
sendString,
setSendString,
setAmountSatsInput,
setAmountUSDInput,
setSendType,
setView,
}) => {
const t = useTranslations();
const { toast } = useToast();

const [showScanner, setShowScanner] = useState(false);

const [value] = useLocalStorage(LOCALSTORAGE_KEYS.currentWalletId, '');

const { data: contactsData, loading: contactsLoading } =
useGetWalletContactsQuery({
variables: { id: value },
onError: err => {
const messages = handleApolloError(err);

toast({
variant: 'destructive',
title: 'Error getting contacts.',
description: messages.join(', '),
});
},
});

const contacts = contactsData?.wallets.find_one.contacts.find_many;

const { data: priceData } = useGetPriceCurrentQuery({
onError: err => {
const messages = handleApolloError(err);

toast({
variant: 'destructive',
title: 'Error getting asset prices.',
description: messages.join(', '),
});
},
});

const latestPrice = priceData?.prices.current.value;

return (
<div className="mx-auto w-full max-w-lg space-y-6 py-4 lg:py-10">
<div className="relative">
{sendString ? (
<button
onClick={() => setSendString('')}
className="absolute left-0 top-0 transition-opacity hover:opacity-75"
>
<ArrowLeft size={24} />
</button>
) : (
<Link
href={ROUTES.dashboard}
className="absolute left-0 top-0 transition-opacity hover:opacity-75"
>
<ArrowLeft size={24} />
</Link>
)}

<p className="text-center text-2xl font-semibold">
{t('App.Wallet.send')}
</p>
</div>

<div className="space-y-2">
<Label htmlFor="send">{t('App.Wallet.send-options')}</Label>

<div className="flex items-center space-x-2">
<Input
value={sendString}
onChange={e => setSendString(e.target.value)}
id="send"
placeholder={t('App.Wallet.recipient')}
/>

<Drawer open={showScanner} onOpenChange={setShowScanner}>
<DrawerTrigger asChild>
<button className="transition-opacity hover:opacity-75">
<Scan size={24} />
</button>
</DrawerTrigger>

<DrawerContent>
<div className="mx-auto h-64 w-64 sm:h-96 sm:w-96">
<Scanner
onScan={result => {
setSendString(result[0].rawValue);
setShowScanner(false);
}}
onError={error => {
console.log(error);

toast({
variant: 'destructive',
title: 'Error Scanning QR',
description: String(error),
});

setShowScanner(false);
}}
/>
</div>
</DrawerContent>
</Drawer>
</div>
</div>

{!sendString ? (
<div className="space-y-3">
<div className="max-h-64 space-y-3 overflow-y-auto">
{contactsLoading ? (
Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="flex items-center space-x-2">
<div className="h-12 w-12 animate-pulse rounded-full bg-primary" />

<div className="space-y-1">
<p className="h-6 w-24 animate-pulse rounded-full bg-foreground" />

<p className="h-5 w-12 animate-pulse rounded-full bg-slate-600 dark:bg-neutral-400" />
</div>
</div>
))
) : contacts?.length ? (
contacts.map(c => (
<button
key={c.id}
onClick={() => setSendString(c.money_address)}
className="flex w-full items-center space-x-2 rounded-l-full transition-colors hover:bg-foreground/10"
>
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-lg font-medium uppercase text-black">
{c.money_address.slice(0, 2)}
</div>

<div className="space-y-1 text-left">
<p className="font-medium">
{c.money_address.split('@')[0]}
</p>

<p className="text-sm font-medium text-slate-600 dark:text-neutral-400">
{c.money_address.split('@')[1]}
</p>
</div>
</button>
))
) : (
<p className="text-sm font-medium text-slate-600 dark:text-neutral-400">
{t('App.Dashboard.no-contacts')}
</p>
)}
</div>

<Link
href={ROUTES.contacts.home}
className="inline-block font-medium text-primary transition-colors hover:text-primary-hover"
>
{t('App.view-all')}
</Link>

<Button asChild variant="neutral">
<Link href={ROUTES.contacts.home + '?add=true'}>
{t('App.Wallet.add-contact')}
</Link>
</Button>
</div>
) : (
<Button
onClick={() => {
if (!latestPrice) {
toast({
variant: 'destructive',
title: 'Waiting for latest asset price.',
});

return;
}

const latestPricePerSat = latestPrice / 100_000_000;

const type = checkSendString(sendString);

if (!type) {
toast({
variant: 'destructive',
title: 'Unrecognized send type.',
description:
'Please reach out to support if you think this is a mistake.',
});

return;
}

if (type === 'invoice') {
try {
const { satoshis } = bolt11.decode(sendString);

if (satoshis) {
setAmountSatsInput(satoshis.toString());
setAmountUSDInput((latestPricePerSat * satoshis).toFixed(2));
} else {
toast({
variant: 'destructive',
title: 'Could not decode invoice.',
});

return;
}
} catch (error) {
console.log(error);

toast({
variant: 'destructive',
title: 'Could not decode invoice.',
});

return;
}
}

if (type === 'liquid') {
const amount = sendString.split('?amount=');

if (amount.length > 1) {
const address = amount[0].split('liquid:')[1];

if (!address) {
toast({
variant: 'destructive',
title: 'Could not decode address.',
});

return;
}

const amountInSats = Number(amount[1]) * 100_000_000;

setAmountSatsInput(amountInSats.toFixed(0));
setAmountUSDInput(
(latestPricePerSat * amountInSats).toFixed(2)
);

setSendString(address);
}
}

setSendType(type);
setView('confirm');
}}
className="w-full"
>
{t('Common.continue')}
</Button>
)}
</div>
);
};
Loading

0 comments on commit c159e3a

Please sign in to comment.