2024-01-26 10:12:35 +00:00
|
|
|
/* eslint-disable max-lines */
|
2024-01-04 17:01:18 +00:00
|
|
|
import classNames from "classnames";
|
2023-01-31 18:56:31 +00:00
|
|
|
import { useEffect, useState } from "react";
|
2023-03-02 15:23:53 +00:00
|
|
|
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
2024-01-04 17:01:18 +00:00
|
|
|
import { useNavigate } from "react-router-dom";
|
2023-01-31 18:56:31 +00:00
|
|
|
|
2024-01-04 13:48:19 +00:00
|
|
|
import AsyncButton from "@/Components/Button/AsyncButton";
|
2024-01-04 17:01:18 +00:00
|
|
|
import { AsyncIcon } from "@/Components/Button/AsyncIcon";
|
2024-01-08 11:15:20 +00:00
|
|
|
import NoteTime from "@/Components/Event/Note/NoteTime";
|
2024-01-04 13:48:19 +00:00
|
|
|
import Icon from "@/Components/Icons/Icon";
|
2024-01-02 18:11:44 +00:00
|
|
|
import { useRates } from "@/Hooks/useRates";
|
2024-01-04 17:01:18 +00:00
|
|
|
import { unwrap } from "@/Utils";
|
|
|
|
import { LNWallet, Sats, useWallet, WalletInvoice, Wallets } from "@/Wallet";
|
2023-01-31 18:56:31 +00:00
|
|
|
|
2024-01-02 18:11:44 +00:00
|
|
|
export default function WalletPage(props: { showHistory: boolean }) {
|
2023-03-02 15:23:53 +00:00
|
|
|
const navigate = useNavigate();
|
|
|
|
const { formatMessage } = useIntl();
|
2023-01-31 18:56:31 +00:00
|
|
|
const [balance, setBalance] = useState<Sats>();
|
|
|
|
const [history, setHistory] = useState<WalletInvoice[]>();
|
2023-03-02 15:23:53 +00:00
|
|
|
const [walletPassword, setWalletPassword] = useState<string>();
|
|
|
|
const [error, setError] = useState<string>();
|
|
|
|
const walletState = useWallet();
|
|
|
|
const wallet = walletState.wallet;
|
2024-01-02 18:11:44 +00:00
|
|
|
const rates = useRates("BTCUSD");
|
2023-01-31 18:56:31 +00:00
|
|
|
|
2023-02-13 15:29:25 +00:00
|
|
|
async function loadWallet(wallet: LNWallet) {
|
2023-03-02 15:23:53 +00:00
|
|
|
try {
|
2024-01-02 18:11:44 +00:00
|
|
|
setError(undefined);
|
|
|
|
setBalance(0);
|
|
|
|
setHistory(undefined);
|
2023-12-12 13:22:15 +00:00
|
|
|
if (wallet.canGetBalance()) {
|
|
|
|
const b = await wallet.getBalance();
|
|
|
|
setBalance(b as Sats);
|
|
|
|
}
|
2024-01-02 18:11:44 +00:00
|
|
|
if (wallet.canGetInvoices() && (props.showHistory ?? true)) {
|
2023-12-12 13:22:15 +00:00
|
|
|
const h = await wallet.getInvoices();
|
|
|
|
setHistory((h as WalletInvoice[]).sort((a, b) => b.timestamp - a.timestamp));
|
|
|
|
}
|
2023-03-02 15:23:53 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof Error) {
|
|
|
|
setError((e as Error).message);
|
|
|
|
} else {
|
2023-11-20 19:16:47 +00:00
|
|
|
setError(formatMessage({ defaultMessage: "Unknown error", id: "qDwvZ4" }));
|
2023-03-02 15:23:53 +00:00
|
|
|
}
|
2023-01-31 18:56:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-01-02 18:11:44 +00:00
|
|
|
if (wallet && wallet.isReady()) {
|
|
|
|
loadWallet(wallet).catch(console.warn);
|
2023-02-13 15:29:25 +00:00
|
|
|
}
|
|
|
|
}, [wallet]);
|
2023-01-31 18:56:31 +00:00
|
|
|
|
2023-03-02 15:23:53 +00:00
|
|
|
async function loginWallet(pw: string) {
|
2023-02-24 10:25:14 +00:00
|
|
|
if (wallet) {
|
2023-03-02 15:23:53 +00:00
|
|
|
await wallet.login(pw);
|
|
|
|
await loadWallet(wallet);
|
|
|
|
setWalletPassword(undefined);
|
2023-02-13 15:29:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-02 15:23:53 +00:00
|
|
|
function unlockWallet() {
|
|
|
|
if (!wallet || wallet.isReady()) return null;
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<h3>
|
2023-11-20 11:35:51 +00:00
|
|
|
<FormattedMessage defaultMessage="Enter wallet password" id="r5srDR" />
|
2023-03-02 15:23:53 +00:00
|
|
|
</h3>
|
|
|
|
<div className="flex w-max">
|
2023-10-17 13:02:59 +00:00
|
|
|
<div className="grow mr10">
|
2023-03-02 15:23:53 +00:00
|
|
|
<input
|
|
|
|
type="password"
|
|
|
|
placeholder={formatMessage({
|
2023-11-20 19:16:47 +00:00
|
|
|
defaultMessage: "Wallet password",
|
|
|
|
id: "MP54GY",
|
2023-03-02 15:23:53 +00:00
|
|
|
description: "Wallet password input placeholder",
|
|
|
|
})}
|
|
|
|
className="w-max"
|
|
|
|
value={walletPassword}
|
|
|
|
onChange={e => setWalletPassword(e.target.value)}
|
|
|
|
/>
|
2023-01-31 18:56:31 +00:00
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
<AsyncButton onClick={() => loginWallet(unwrap(walletPassword))} disabled={(walletPassword?.length ?? 0) < 8}>
|
2023-11-20 11:35:51 +00:00
|
|
|
<FormattedMessage defaultMessage="Unlock" id="xQtL3v" description="Unlock wallet" />
|
2023-03-02 15:23:53 +00:00
|
|
|
</AsyncButton>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function walletList() {
|
|
|
|
if (walletState.configs.length === 0) {
|
2024-01-04 16:11:10 +00:00
|
|
|
return (
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
<div>
|
|
|
|
<button onClick={() => navigate("/settings/wallet")}>
|
|
|
|
<FormattedMessage defaultMessage="Connect Wallet" id="cg1VJ2" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<small>
|
|
|
|
<FormattedMessage defaultMessage="Connect a wallet to send instant payments" id="Yf3DwC" />
|
|
|
|
</small>
|
2024-01-04 16:10:40 +00:00
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
2024-01-02 18:11:44 +00:00
|
|
|
<div className="flex items-center">
|
|
|
|
<h4 className="grow">
|
2023-11-20 11:35:51 +00:00
|
|
|
<FormattedMessage defaultMessage="Select Wallet" id="G1BGCg" />
|
2023-03-02 15:23:53 +00:00
|
|
|
</h4>
|
2024-01-02 18:11:44 +00:00
|
|
|
<div>
|
2023-03-02 15:23:53 +00:00
|
|
|
<select className="w-max" onChange={e => Wallets.switch(e.target.value)} value={walletState.config?.id}>
|
|
|
|
{Wallets.list().map(a => {
|
2024-01-04 12:05:13 +00:00
|
|
|
return (
|
|
|
|
<option value={a.id} key={a.id}>
|
|
|
|
{a.info.alias}
|
|
|
|
</option>
|
|
|
|
);
|
2023-03-02 15:23:53 +00:00
|
|
|
})}
|
|
|
|
</select>
|
2023-01-31 18:56:31 +00:00
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function walletHistory() {
|
2024-01-02 18:11:44 +00:00
|
|
|
if (!wallet?.canGetInvoices() || !(props.showHistory ?? true)) return;
|
2023-03-02 15:23:53 +00:00
|
|
|
|
|
|
|
return (
|
2024-01-02 18:11:44 +00:00
|
|
|
<div className="flex flex-col gap-1">
|
2023-03-02 15:23:53 +00:00
|
|
|
<h3>
|
2024-01-02 18:11:44 +00:00
|
|
|
<FormattedMessage defaultMessage="Payments" id="pukxg/" description="Wallet transation history" />
|
2023-03-02 15:23:53 +00:00
|
|
|
</h3>
|
2024-01-04 16:11:10 +00:00
|
|
|
{history === undefined && (
|
|
|
|
<small>
|
|
|
|
<FormattedMessage defaultMessage="Your sent and received payments will show up here." id="i5gBFz" />
|
|
|
|
</small>
|
|
|
|
)}
|
2024-01-02 18:11:44 +00:00
|
|
|
{history?.map(a => {
|
|
|
|
const dirClassname = {
|
|
|
|
"text-[--success]": a.direction === "in",
|
|
|
|
"text-[--error]": a.direction === "out",
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<div className="flex gap-4 p-2 hover:bg-[--gray-superdark] rounded-xl items-center" key={a.timestamp}>
|
2023-12-12 13:22:15 +00:00
|
|
|
<div>
|
2024-01-02 18:11:44 +00:00
|
|
|
<div className="rounded-full aspect-square p-2 bg-[--gray-dark]">
|
|
|
|
<Icon
|
|
|
|
name="arrow-up-right"
|
|
|
|
className={classNames(dirClassname, {
|
|
|
|
"rotate-180": a.direction === "in",
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="grow flex justify-between">
|
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
<div>{a.memo?.length === 0 ? CONFIG.appNameCapitalized : a.memo}</div>
|
|
|
|
<div className="text-secondary text-sm">
|
|
|
|
<NoteTime
|
|
|
|
from={a.timestamp * 1000}
|
|
|
|
fallback={formatMessage({ defaultMessage: "now", id: "kaaf1E" })}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-1 text-right">
|
|
|
|
<div className={classNames(dirClassname)}>
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="{sign} {amount} sats"
|
|
|
|
id="tj6kdX"
|
|
|
|
values={{
|
|
|
|
sign: a.direction === "in" ? "+" : "-",
|
|
|
|
amount: <FormattedNumber value={a.amount / 1e3} />,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="text-secondary text-sm">
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="~{amount}"
|
|
|
|
id="3QwfJR"
|
|
|
|
values={{
|
|
|
|
amount: (
|
|
|
|
<FormattedNumber
|
|
|
|
style="currency"
|
|
|
|
currency="USD"
|
|
|
|
value={(rates?.ask ?? 0) * a.amount * 1e-11}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-12-12 13:22:15 +00:00
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
</div>
|
2024-01-02 18:11:44 +00:00
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function walletBalance() {
|
2023-12-12 13:22:15 +00:00
|
|
|
if (!wallet?.canGetBalance()) return;
|
2023-03-02 15:23:53 +00:00
|
|
|
return (
|
2024-01-02 18:11:44 +00:00
|
|
|
<div className="flex items-center gap-2">
|
2023-03-02 15:23:53 +00:00
|
|
|
<FormattedMessage
|
2024-01-02 18:11:44 +00:00
|
|
|
defaultMessage="<big>{amount}</big> <small>sats</small>"
|
|
|
|
id="E5ZIPD"
|
2023-03-02 15:23:53 +00:00
|
|
|
values={{
|
2024-01-03 16:46:18 +00:00
|
|
|
big: c => <span className="text-5xl font-bold">{c}</span>,
|
|
|
|
small: c => <span className="text-secondary text-sm">{c}</span>,
|
2023-03-02 15:23:53 +00:00
|
|
|
amount: <FormattedNumber value={balance ?? 0} />,
|
|
|
|
}}
|
|
|
|
/>
|
2024-01-02 18:11:44 +00:00
|
|
|
<AsyncIcon size={20} className="text-secondary cursor-pointer" iconName="closedeye" />
|
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function walletInfo() {
|
2024-01-04 16:10:40 +00:00
|
|
|
if (!wallet) return;
|
|
|
|
|
2023-03-02 15:23:53 +00:00
|
|
|
return (
|
|
|
|
<>
|
2024-01-04 16:10:40 +00:00
|
|
|
<div className="flex flex-col items-center px-6 py-4 bg-[--gray-ultradark] rounded-2xl gap-1">
|
2024-01-02 18:11:44 +00:00
|
|
|
{walletBalance()}
|
|
|
|
<div className="text-secondary">
|
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="~{amount}"
|
|
|
|
id="3QwfJR"
|
|
|
|
values={{
|
|
|
|
amount: (
|
|
|
|
<FormattedNumber style="currency" currency="USD" value={(rates?.ask ?? 0) * (balance ?? 0) * 1e-8} />
|
|
|
|
),
|
|
|
|
}}
|
|
|
|
/>
|
2023-12-12 13:55:18 +00:00
|
|
|
</div>
|
2024-01-04 16:10:40 +00:00
|
|
|
<div className="flex gap-2">
|
2024-01-04 16:11:10 +00:00
|
|
|
{wallet?.canCreateInvoice() && (
|
|
|
|
<AsyncButton className="secondary" onClick={() => navigate("/wallet/receive")}>
|
|
|
|
<FormattedMessage defaultMessage="Receive" id="ULXFfP" />
|
|
|
|
<Icon name="arrow-up-right" className="rotate-180" />
|
|
|
|
</AsyncButton>
|
|
|
|
)}
|
|
|
|
{wallet?.canPayInvoice() && (
|
2024-01-09 08:53:36 +00:00
|
|
|
<AsyncButton onClick={() => navigate("/wallet/send")} className="primary">
|
2024-01-04 16:11:10 +00:00
|
|
|
<FormattedMessage defaultMessage="Send" id="9WRlF4" />
|
|
|
|
<Icon name="arrow-up-right" />
|
|
|
|
</AsyncButton>
|
|
|
|
)}
|
2024-01-04 16:10:40 +00:00
|
|
|
</div>
|
2023-03-02 21:56:25 +00:00
|
|
|
</div>
|
2023-03-02 15:23:53 +00:00
|
|
|
{walletHistory()}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2024-01-02 18:11:44 +00:00
|
|
|
<div className="main-content">
|
2023-03-02 15:23:53 +00:00
|
|
|
{walletList()}
|
2023-12-12 13:22:15 +00:00
|
|
|
{error && <b className="warning">{error}</b>}
|
2023-03-02 15:23:53 +00:00
|
|
|
{unlockWallet()}
|
|
|
|
{walletInfo()}
|
|
|
|
</div>
|
2023-01-31 18:56:31 +00:00
|
|
|
);
|
2024-01-26 11:18:06 +00:00
|
|
|
}
|