Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lightning channel flows #1000

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/assets/images/confirmation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 70 additions & 14 deletions frontend/src/components/PayLightningInvoice.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { Invoice, fiat } from "@getalby/lightning-tools";
import { CopyIcon, LightbulbIcon } from "lucide-react";
import React from "react";
import React, { useState } from "react";
import { LightningIcon } from "src/components/icons/Lightning";
import Loading from "src/components/Loading";
import QRCode from "src/components/QRCode";
import { Button, ExternalLinkButton } from "src/components/ui/button";
import { LoadingButton } from "src/components/ui/loading-button";
import { useToast } from "src/components/ui/use-toast";
import { copyToClipboard } from "src/lib/clipboard";
import useChannelOrderStore from "src/state/ChannelOrderStore";
import { LSPOrderResponse } from "src/types";
import { request } from "src/utils/request";

type PayLightningInvoiceProps = {
invoice: string;
lspOrderResponse?: LSPOrderResponse;
canPayInternally?: boolean | undefined;
};

export function PayLightningInvoice({ invoice }: PayLightningInvoiceProps) {
export function PayLightningInvoice({
invoice,
lspOrderResponse,
canPayInternally,
}: PayLightningInvoiceProps) {
const amount = new Invoice({
pr: invoice,
}).satoshi;
Expand All @@ -27,11 +37,42 @@ export function PayLightningInvoice({ invoice }: PayLightningInvoiceProps) {
copyToClipboard(invoice, toast);
};

const [isPaying, setPaying] = useState(false);

const handlePayment = async () => {
try {
setPaying(true);

await request(`/api/payments/${lspOrderResponse?.invoice}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});

useChannelOrderStore.getState().updateOrder({
status: "paid",
});

toast({
title: "Channel successfully requested",
});
} catch (e) {
toast({
variant: "destructive",
title: "Failed to send: " + e,
});
console.error(e);
} finally {
setPaying(false);
}
};

return (
<div className="w-96 flex flex-col gap-6 p-6 items-center justify-center">
<div className="flex items-center justify-center gap-2 text-muted-foreground">
<div className="w-80 flex flex-col gap-6 px-8 py-6 items-center justify-center border rounded-xl">
<div className="flex items-center justify-center gap-2">
<Loading variant="loader" />
<p>Waiting for lightning payment...</p>
<p className="text-secondary-foreground">Waiting for payment...</p>
</div>
<div className="w-full relative flex items-center justify-center">
<QRCode value={invoice} className="w-full" />
Expand All @@ -43,7 +84,7 @@ export function PayLightningInvoice({ invoice }: PayLightningInvoiceProps) {
<p className="text-lg font-semibold">
{new Intl.NumberFormat().format(amount)} sats
</p>
<p className="flex flex-col items-center justify-center">
<p className="flex flex-col items-center justify-center text-muted-foreground">
{new Intl.NumberFormat("en-US", {
currency: "USD",
style: "currency",
Expand All @@ -54,18 +95,33 @@ export function PayLightningInvoice({ invoice }: PayLightningInvoiceProps) {
<Button
onClick={copy}
variant="outline"
size={"sm"}
className="flex-1 flex gap-2 items-center justify-center"
>
<CopyIcon className="w-4 h-4 mr-2" />
Copy Invoice
Copy
</Button>
<ExternalLinkButton
to="https://guides.getalby.com/user-guide/alby-account-and-browser-extension/alby-hub/wallet/open-your-first-channel"
variant="secondary"
className="flex-1 flex gap-2 items-center justify-center"
>
<LightbulbIcon className="w-4 h-4" /> How to pay
</ExternalLinkButton>
<>
{canPayInternally ? (
<LoadingButton
loading={isPaying}
className="whitespace-nowrap"
size={"sm"}
onClick={handlePayment}
>
Pay and open channel
</LoadingButton>
) : (
<ExternalLinkButton
to="https://guides.getalby.com/user-guide/alby-account-and-browser-extension/alby-hub/wallet/open-your-first-channel"
variant="secondary"
className="flex-1 flex gap-2 items-center justify-center"
>
<LightbulbIcon className="w-4 h-4" />
How to pay
</ExternalLinkButton>
)}
</>
</div>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/SidebarHint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ function SidebarHint() {
title="New Channel"
description="You're currently opening a new channel"
buttonText="View Channel"
buttonLink="/channels/order"
buttonLink={`${
order.paymentMethod === "lightning"
? "/channels/incoming"
: "/channels/outgoing"
}`}
/>
);
}
Expand Down
58 changes: 32 additions & 26 deletions frontend/src/components/channels/ChannelWaitingForConfirmations.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Footprints } from "lucide-react";
import EmptyState from "src/components/EmptyState";
import Lottie from "react-lottie";
import animationData from "src/assets/lotties/loading.json";
import Loading from "src/components/Loading";
import {
Card,
Expand All @@ -20,31 +20,37 @@ export function ChannelWaitingForConfirmations({
return <Loading />;
}

const defaultOptions = {
loop: true,
autoplay: true,
animationData: animationData,
rendererSettings: {
preserveAspectRatio: "xMidYMid slice",
},
};

return (
<div className="flex flex-col justify-center gap-2">
<Card>
<CardHeader>
<CardTitle>Your channel is being opened</CardTitle>
<CardDescription>
Waiting for {channel.confirmationsRequired} confirmations
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-row gap-2">
<Loading />
{channel.confirmations ?? "0"} /{" "}
{channel.confirmationsRequired ?? "unknown"} confirmations
</div>
</CardContent>
</Card>
<div className="w-full mt-40 flex flex-col items-center justify-center">
<EmptyState
icon={Footprints}
title="Browse While You Wait"
description="Feel free to leave this page or browse around Alby Hub! We'll send you an email as soon as your channel is active."
buttonText="Explore Apps"
buttonLink="/appstore"
/>
<div className="flex flex-col gap-4">
<p className="text-muted-foreground ">
You can now leave this page.We’ll notify your by email once your channel
is active.
</p>
<div className="w-80 flex justify-center">
<Card className="text-center">
<CardHeader>
<CardTitle>Opening new lightning channel...</CardTitle>
<CardDescription>
Waiting for {channel.confirmationsRequired} confirmations
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-5 justify-center text-center">
<Lottie options={defaultOptions} height={256} width={256} />
{channel.confirmations ?? "0"} /{" "}
{channel.confirmationsRequired ?? "unknown"} confirmations
</div>
</CardContent>
</Card>
</div>
</div>
);
Expand Down
Loading
Loading