Skip to content

Commit

Permalink
feat: simplify withdraw onchain funds ui
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Sep 4, 2024
1 parent af12fc7 commit d6778ca
Showing 1 changed file with 168 additions and 130 deletions.
298 changes: 168 additions & 130 deletions frontend/src/screens/wallet/WithdrawOnchainFunds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import AppHeader from "src/components/AppHeader";
import ExternalLink from "src/components/ExternalLink";
import Loading from "src/components/Loading";
import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "src/components/ui/alert-dialog";
import { Button } from "src/components/ui/button";
import { Checkbox } from "src/components/ui/checkbox";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
Expand All @@ -21,85 +31,63 @@ export default function WithdrawOnchainFunds() {
const { toast } = useToast();
const { data: balances } = useBalances();
const [onchainAddress, setOnchainAddress] = React.useState("");
const [confirmOnchainAddress, setConfirmOnchainAddress] = React.useState("");
const [checkedConfirmation, setCheckedConfirmation] = React.useState(false);
const [amount, setAmount] = React.useState("");
const [sendAll, setSendAll] = React.useState(false);
const [transactionId, setTransactionId] = React.useState("");
const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false);

const copy = (text: string) => {
copyToClipboard(text, toast);
};

const redeemFunds = React.useCallback(
async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
try {
await new Promise((resolve) => setTimeout(resolve, 100));
if (!onchainAddress) {
throw new Error("No onchain address");
}

if (onchainAddress !== confirmOnchainAddress) {
throw new Error(
"Onchain addresses do not match. Please check the onchain addresses you provided"
);
}

if (!checkedConfirmation) {
throw new Error("Please confirm");
}
} catch (error) {
console.error(error);
toast({
title: "Something went wrong",
description: "" + error,
variant: "destructive",
});
setLoading(false);
return;
const redeemFunds = React.useCallback(async () => {
setLoading(true);
try {
await new Promise((resolve) => setTimeout(resolve, 100));
if (!onchainAddress) {
throw new Error("No onchain address");
}
} catch (error) {
console.error(error);
toast({
title: "Something went wrong",
description: "" + error,
variant: "destructive",
});
setLoading(false);
return;
}

try {
const response = await request<RedeemOnchainFundsResponse>(
"/api/wallet/redeem-onchain-funds",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
toAddress: onchainAddress,
amount: +amount,
sendAll,
}),
}
);
console.info("Redeemed onchain funds", response);
if (!response?.txId) {
throw new Error("No address in response");
try {
const response = await request<RedeemOnchainFundsResponse>(
"/api/wallet/redeem-onchain-funds",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
toAddress: onchainAddress,
amount: +amount,
sendAll,
}),
}
setTransactionId(response.txId);
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "Failed to redeem onchain funds",
description: "" + error,
});
);
console.info("Redeemed onchain funds", response);
if (!response?.txId) {
throw new Error("No address in response");
}
setLoading(false);
},
[
amount,
checkedConfirmation,
confirmOnchainAddress,
onchainAddress,
sendAll,
toast,
]
);
setTransactionId(response.txId);
} catch (error) {
console.error(error);
toast({
variant: "destructive",
title: "Failed to redeem onchain funds",
description: "" + error,
});
}
setLoading(false);
}, [amount, onchainAddress, sendAll, toast]);

if (transactionId) {
return (
Expand Down Expand Up @@ -152,33 +140,24 @@ export default function WithdrawOnchainFunds() {
/>

<div className="max-w-lg">
{!!balances?.onchain.reserved &&
(sendAll || +amount > balances.onchain.total * 0.9) && (
<Alert className="mb-4">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Channel Anchor Reserves may be depleted</AlertTitle>
<AlertDescription>
You have channels open and this withdrawal may deplete your
anchor reserves, which may make it harder to close channels
without depositing additional onchain funds to your savings
balance.
</AlertDescription>
</Alert>
)}
<p>
Your savings balance will be withdrawn to the onchain bitcoin wallet
address you specify below. Please make sure you are the owner of this
address and that it is for an{" "}
<span className="font-bold">external wallet</span> that you control
and have the seed phrase for.
address you specify below.
</p>
<form onSubmit={redeemFunds} className="grid gap-5 mt-4">
<form
onSubmit={(e) => {
e.preventDefault();
setConfirmDialogOpen(true);
}}
className="grid gap-5 mt-4"
>
<div className="">
<Label htmlFor="amount">Amount</Label>
<div className="flex justify-between items-center mb-1">
<p className="text-sm text-muted-foreground">
Current onchain balance:{" "}
{new Intl.NumberFormat().format(balances.onchain.total)} sats
{new Intl.NumberFormat().format(balances.onchain.spendable)}{" "}
sats
</p>
<div className="flex items-center gap-1">
<Checkbox
Expand All @@ -190,16 +169,63 @@ export default function WithdrawOnchainFunds() {
</Label>
</div>
</div>
<Input
id="amount"
type="number"
value={sendAll ? balances.onchain.total : amount}
disabled={sendAll}
required
onChange={(e) => {
setAmount(e.target.value);
}}
/>
{!sendAll && (
<Input
id="amount"
type="number"
value={amount}
required
onChange={(e) => {
setAmount(e.target.value);
}}
/>
)}
{sendAll && (
<Alert className="mt-4">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Entire wallet balance will be sent</AlertTitle>
<AlertDescription>
Your entire wallet balance
{balances.onchain.reserved > 0 && (
<>
{" "}
including reserves (
{new Intl.NumberFormat().format(
balances.onchain.reserved
)}{" "}
sats)
</>
)}{" "}
will be sent minus onchain transaction fees. The exact amount
cannot be determined until the payment is made.
{balances.onchain.reserved && (
<>
{" "}
You have channels open and this withdrawal will deplete
your anchor reserves, which may make it harder to close
channels without depositing additional onchain funds to
your savings balance.
</>
)}
</AlertDescription>
</Alert>
)}
{!!balances?.onchain.reserved &&
!sendAll &&
+amount > balances.onchain.spendable * 0.9 && (
<Alert className="mt-4">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>
Channel Anchor Reserves may be depleted
</AlertTitle>
<AlertDescription>
You have channels open and this withdrawal may deplete your
anchor reserves, which may make it harder to close channels
without depositing additional onchain funds to your savings
balance.
</AlertDescription>
</Alert>
)}
</div>
<div className="">
<Label htmlFor="onchain-address">Onchain Address</Label>
Expand All @@ -213,41 +239,53 @@ export default function WithdrawOnchainFunds() {
}}
/>
</div>
<div className="">
<Label htmlFor="confirm-onchain-address">
Confirm Onchain Address
</Label>
<Input
id="confirm-onchain-address"
type="text"
value={confirmOnchainAddress}
required
onChange={(e) => {
setConfirmOnchainAddress(e.target.value);
}}
/>
</div>
<div>
<div className="flex items-center mt-5">
<Checkbox
id="confirm"
required
onCheckedChange={() =>
setCheckedConfirmation(!checkedConfirmation)
}
/>
<Label htmlFor="confirm" className="ml-2">
I'm the owner of this wallet address and{" "}
<span className="bold">I realize no-one can help me</span> if I
send my funds to the wrong address. This transaction cannot be
reversed.
</Label>
</div>
</div>

<p className="text-sm text-muted-foreground">
Please double-check the destination address. This transaction cannot
be reversed.
</p>

<div>
<LoadingButton loading={isLoading} type="submit">
Withdraw
</LoadingButton>
<AlertDialog
onOpenChange={setConfirmDialogOpen}
open={confirmDialogOpen}
>
<Button>Withdraw</Button>

<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Confirm Onchain Transaction
</AlertDialogTitle>
<AlertDialogDescription>
<p>
Please confirm your payment to{" "}
<span className="font-bold">{onchainAddress}</span>
</p>
<p className="mt-4">
Amount:{" "}
<span className="font-bold">
{sendAll ? (
"entire savings balance"
) : (
<>{new Intl.NumberFormat().format(+amount)} sats</>
)}
</span>
</p>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>

<LoadingButton
loading={isLoading}
onClick={() => redeemFunds()}
>
Confirm
</LoadingButton>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</form>
</div>
Expand Down

0 comments on commit d6778ca

Please sign in to comment.