feat: withdraw button

This commit is contained in:
kieran 2024-05-01 11:50:35 +01:00
parent 890f5b2987
commit e3e04bd796
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
11 changed files with 3900 additions and 3322 deletions

File diff suppressed because one or more lines are too long

893
.yarn/releases/yarn-4.1.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
yarnPath: .yarn/releases/yarn-3.6.3.cjs
yarnPath: .yarn/releases/yarn-4.1.1.cjs

View File

@ -6,11 +6,11 @@
"@emoji-mart/react": "^1.1.1",
"@noble/curves": "^1.2.0",
"@scure/base": "^1.1.3",
"@snort/shared": "^1.0.14",
"@snort/system": "^1.2.12",
"@snort/system-react": "^1.2.12",
"@snort/shared": "^1.0.15",
"@snort/system": "^1.3.0",
"@snort/system-react": "^1.3.0",
"@snort/system-wasm": "^1.0.2",
"@snort/worker-relay": "^1.0.9",
"@snort/worker-relay": "^1.0.10",
"@sqlite.org/sqlite-wasm": "^3.45.1-build1",
"@szhsin/react-menu": "^4.0.2",
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
@ -90,11 +90,11 @@
"rollup-plugin-visualizer": "^5.10.0",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
"vite": "^5.1.4",
"vite": "^5.2.10",
"vite-plugin-pwa": "^0.17.2",
"vite-plugin-version-mark": "^0.0.10"
},
"packageManager": "yarn@3.6.3",
"packageManager": "yarn@4.1.1",
"prettier": {
"printWidth": 120,
"bracketSameLine": true,

View File

@ -77,7 +77,7 @@ export function LiveChat({
rb.withFilter().kinds([LIVE_STREAM_CHAT, LIVE_STREAM_RAID, LIVE_STREAM_CLIP]).tag("a", [aTag]).limit(200);
}
},
true,
true
);
const login = useLogin();
const started = useMemo(() => {

View File

@ -13,6 +13,7 @@ import Pill from "@/element/pill";
import { AddForwardInputs } from "./fowards";
import StreamKey from "./stream-key";
import AccountTopup from "./topup";
import AccountWithdrawl from "./withdraw";
export default function NostrProviderDialog({
provider,
@ -179,12 +180,8 @@ export default function NostrProviderDialog({
values={{ amount: info.balance?.toLocaleString() }}
/>
</div>
<AccountTopup
provider={provider}
onFinish={async () => {
loadInfo();
}}
/>
<AccountTopup provider={provider} onFinish={loadInfo} />
<AccountWithdrawl provider={provider} onFinish={loadInfo} />
</div>
<small>
<FormattedMessage defaultMessage="About {estimate}" id="Q3au2v" values={{ estimate: calcEstimate() }} />

View File

@ -0,0 +1,111 @@
import { DefaultButton } from "@/element/buttons";
import Modal from "@/element/modal";
import { NostrStreamProvider } from "@/providers";
import { LNURL } from "@snort/shared";
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
export default function AccountWithdrawl({
provider,
onFinish,
}: {
provider: NostrStreamProvider;
onFinish: () => void;
}) {
const [topup, setTopup] = useState(false);
const [addr, setAddress] = useState("");
const [error, setError] = useState("");
const [amount, setAmount] = useState<number>();
const { formatMessage } = useIntl();
useEffect(() => {
if (addr.startsWith("lnbc") && amount !== undefined) {
setAmount(undefined);
} else if (!addr.startsWith("lnbc") && amount === undefined) {
setAmount(0);
}
}, [addr, amount]);
async function withdraw() {
setError("");
let invoice = addr;
if (!invoice.startsWith("lnbc") && amount && amount > 1) {
const invoiceError = formatMessage({
defaultMessage: "Failed to get invoice",
});
try {
const lnurl = new LNURL(addr);
await lnurl.load();
const rsp = await lnurl.getInvoice(amount, "Withdrawal from zap.stream");
if (rsp.pr) {
invoice = rsp.pr;
} else {
setError(rsp.reason ?? invoiceError);
return;
}
} catch (e) {
if (e instanceof Error) {
setError(e.message);
} else {
setError(invoiceError);
}
}
}
try {
const res = await provider.withdraw(invoice);
if (res.preimage) {
setTopup(false);
onFinish();
}
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
}
return (
<>
<DefaultButton onClick={() => setTopup(true)}>
<FormattedMessage defaultMessage="Withdraw" />
</DefaultButton>
{topup && (
<Modal id="withdraw" onClose={() => setTopup(false)}>
<div className="flex flex-col gap-4">
<div className="text-xl">
<FormattedMessage defaultMessage="Withdraw funds" />
</div>
<div className="flex flex-col gap-1">
<small className="text-neutral-300">
<FormattedMessage defaultMessage="Destination" />
</small>
<input
type="text"
value={addr}
onChange={e => setAddress(e.target.value)}
placeholder={formatMessage({
defaultMessage: "LNURL or invoice",
})}
/>
</div>
{amount !== undefined && (
<>
<div className="flex flex-col gap-1">
<small className="text-neutral-300">
<FormattedMessage defaultMessage="Amount" />
</small>
<input type="number" value={amount} onChange={e => setAmount(e.target.valueAsNumber)} />
</div>
</>
)}
<DefaultButton disabled={addr.length < 3} onClick={withdraw}>
<FormattedMessage defaultMessage="Withdraw" />
</DefaultButton>
{error && <b className="text-warning">{error}</b>}
</div>
</Modal>
)}
</>
);
}

View File

@ -15,7 +15,7 @@ export default function VideoGridSorted({ evs, showAll }: { evs: Array<TaggedNos
(ev: NostrEvent) => {
return tags.find(t => t.at(1) === getHost(ev));
},
[tags],
[tags]
);
const { live, planned, ended } = useSortedStreams(evs, showAll ? 0 : undefined);
const hashtags = getTagValues(tags, "t");

View File

@ -19,7 +19,7 @@ const LangSelector = new LangStore();
export function useLang() {
const store = useSyncExternalStore(
c => LangSelector.hook(c),
() => LangSelector.snapshot(),
() => LangSelector.snapshot()
);
return {

View File

@ -87,6 +87,10 @@ export class NostrStreamProvider implements StreamProvider {
return rsp.pr;
}
async withdraw(invoice: string) {
return await this.#getJson<{ fee: number; preimage: string }>("POST", `withdraw?invoice=${invoice}`);
}
async acceptTos(): Promise<void> {
await this.#getJson("PATCH", "account", {
accept_tos: true,

5311
yarn.lock

File diff suppressed because it is too large Load Diff