mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-17 06:14:21 +01:00
method chooser for send
This commit is contained in:
@@ -7,8 +7,7 @@ import {
|
||||
Show
|
||||
} from "solid-js";
|
||||
|
||||
import { AmountSmall, BigMoney } from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { AmountSats, BigMoney } from "~/components";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import {
|
||||
btcFloatRounding,
|
||||
@@ -19,17 +18,32 @@ import {
|
||||
toDisplayHandleNaN
|
||||
} from "~/utils";
|
||||
|
||||
export type MethodChoice = {
|
||||
method: "lightning" | "onchain";
|
||||
maxAmountSats?: bigint;
|
||||
};
|
||||
|
||||
// Make sure to update this when we get the fedi option in here!
|
||||
function methodToIcon(method: MethodChoice["method"]) {
|
||||
if (method === "lightning") {
|
||||
return "lightning";
|
||||
} else if (method === "onchain") {
|
||||
return "chain";
|
||||
}
|
||||
}
|
||||
|
||||
export const AmountEditable: ParentComponent<{
|
||||
initialAmountSats: string | bigint;
|
||||
setAmountSats: (s: bigint) => void;
|
||||
maxAmountSats?: bigint;
|
||||
fee?: string;
|
||||
frozenAmount?: boolean;
|
||||
onSubmit?: () => void;
|
||||
activeMethod?: MethodChoice;
|
||||
methods?: MethodChoice[];
|
||||
setChosenMethod?: (method: MethodChoice) => void;
|
||||
}> = (props) => {
|
||||
const [state, _actions] = useMegaStore();
|
||||
const [mode, setMode] = createSignal<"fiat" | "sats">("sats");
|
||||
const i18n = useI18n();
|
||||
const [localSats, setLocalSats] = createSignal(
|
||||
props.initialAmountSats.toString() || "0"
|
||||
);
|
||||
@@ -229,12 +243,47 @@ export const AmountEditable: ParentComponent<{
|
||||
onFocus={() => focus()}
|
||||
/>
|
||||
</div>
|
||||
<Show when={props.maxAmountSats}>
|
||||
<p class="flex gap-2 px-4 py-2 text-sm font-light text-m-grey-400 md:text-base">
|
||||
{`${i18n.t("receive.amount_editable.balance")} `}
|
||||
<AmountSmall amountSats={props.maxAmountSats!} />
|
||||
</p>
|
||||
<Show when={props.methods?.length && props.activeMethod}>
|
||||
<MethodChooser
|
||||
methods={props.methods!}
|
||||
activeMethod={props.activeMethod!}
|
||||
setChosenMethod={props.setChosenMethod}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function MethodChooser(props: {
|
||||
activeMethod: MethodChoice;
|
||||
methods: MethodChoice[];
|
||||
setChosenMethod?: (method: MethodChoice) => void;
|
||||
}) {
|
||||
function setNextMethod() {
|
||||
const activeIndex = props.methods.findIndex(
|
||||
(m) => m.method === props.activeMethod.method
|
||||
);
|
||||
const nextMethod =
|
||||
props.methods[
|
||||
activeIndex === props.methods.length - 1 ? 0 : activeIndex + 1
|
||||
];
|
||||
props.setChosenMethod && props.setChosenMethod(nextMethod);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={setNextMethod}
|
||||
disabled={props.methods.length === 1}
|
||||
class="flex gap-2 rounded px-2 py-1 text-sm font-light text-m-grey-400 md:text-base"
|
||||
classList={{
|
||||
"border-b border-t border-b-white/10 border-t-white/50 bg-neutral-700":
|
||||
props.methods?.length > 1
|
||||
}}
|
||||
>
|
||||
<AmountSats
|
||||
amountSats={props.activeMethod.maxAmountSats!}
|
||||
denominationSize="sm"
|
||||
icon={methodToIcon(props.activeMethod.method)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { createMemo, Match, Switch } from "solid-js";
|
||||
|
||||
import { StyledRadioGroup } from "~/components";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
|
||||
type SendSource = "lightning" | "onchain";
|
||||
|
||||
export function MethodChooser(props: {
|
||||
source: SendSource;
|
||||
setSource: (source: string) => void;
|
||||
both?: boolean;
|
||||
}) {
|
||||
const [store, _actions] = useMegaStore();
|
||||
|
||||
const methods = createMemo(() => {
|
||||
const lnBalance =
|
||||
(store.balance?.lightning || 0n) +
|
||||
(store.balance?.federation || 0n);
|
||||
const onchainBalance =
|
||||
(store.balance?.confirmed || 0n) +
|
||||
(store.balance?.unconfirmed || 0n);
|
||||
return [
|
||||
{
|
||||
value: "lightning",
|
||||
label: "Lightning Balance",
|
||||
caption:
|
||||
lnBalance > 0n
|
||||
? `${lnBalance.toLocaleString()} SATS`
|
||||
: "No balance",
|
||||
disabled: lnBalance === 0n
|
||||
},
|
||||
{
|
||||
value: "onchain",
|
||||
label: "On-chain Balance",
|
||||
caption:
|
||||
onchainBalance > 0n
|
||||
? `${onchainBalance.toLocaleString()} SATS`
|
||||
: "No balance",
|
||||
disabled: onchainBalance === 0n
|
||||
}
|
||||
];
|
||||
});
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.both}>
|
||||
<StyledRadioGroup
|
||||
accent="white"
|
||||
initialValue={props.source}
|
||||
onValueChange={props.setSource}
|
||||
choices={methods()}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.source === "lightning"}>
|
||||
<StyledRadioGroup
|
||||
accent="white"
|
||||
initialValue={props.source}
|
||||
onValueChange={props.setSource}
|
||||
choices={[methods()[0]]}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.source === "onchain"}>
|
||||
<StyledRadioGroup
|
||||
accent="white"
|
||||
initialValue={props.source}
|
||||
onValueChange={props.setSource}
|
||||
choices={[methods()[1]]}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
@@ -49,5 +49,4 @@ export * from "./BigMoney";
|
||||
export * from "./FeeDisplay";
|
||||
export * from "./ReceiveWarnings";
|
||||
export * from "./SimpleInput";
|
||||
export * from "./MethodChooser";
|
||||
export * from "./LabelCircle";
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
MegaCheck,
|
||||
MegaClock,
|
||||
MegaEx,
|
||||
MethodChoice,
|
||||
MutinyWalletGuard,
|
||||
NavBar,
|
||||
showToast,
|
||||
@@ -637,6 +638,59 @@ export function Send() {
|
||||
);
|
||||
});
|
||||
|
||||
const lightningMethod = createMemo<MethodChoice>(() => {
|
||||
return {
|
||||
method: "lightning",
|
||||
maxAmountSats: maxLightning()
|
||||
};
|
||||
});
|
||||
|
||||
const onchainMethod = createMemo<MethodChoice>(() => {
|
||||
return {
|
||||
method: "onchain",
|
||||
maxAmountSats: maxOnchain()
|
||||
};
|
||||
});
|
||||
|
||||
const sendMethods = createMemo<MethodChoice[]>(() => {
|
||||
if (lnAddress() || lnurlp() || nodePubkey()) {
|
||||
return [lightningMethod()];
|
||||
}
|
||||
|
||||
if (invoice() && address()) {
|
||||
return [lightningMethod(), onchainMethod()];
|
||||
}
|
||||
|
||||
if (invoice()) {
|
||||
return [lightningMethod()];
|
||||
}
|
||||
|
||||
if (address()) {
|
||||
return [onchainMethod()];
|
||||
}
|
||||
|
||||
// We should never get here
|
||||
console.error("No send methods found");
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
function setSourceFromMethod(method: MethodChoice) {
|
||||
if (method.method === "lightning") {
|
||||
setSource("lightning");
|
||||
} else if (method.method === "onchain") {
|
||||
setSource("onchain");
|
||||
}
|
||||
}
|
||||
|
||||
const activeMethod = createMemo(() => {
|
||||
if (source() === "lightning") {
|
||||
return lightningMethod();
|
||||
} else if (source() === "onchain") {
|
||||
return onchainMethod();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<DefaultMain>
|
||||
@@ -726,23 +780,27 @@ export function Send() {
|
||||
<AmountEditable
|
||||
initialAmountSats={amountSats()}
|
||||
setAmountSats={setAmountInput}
|
||||
maxAmountSats={maxAmountSats()}
|
||||
fee={feeEstimate()?.toString()}
|
||||
onSubmit={() =>
|
||||
sendButtonDisabled() ? undefined : handleSend()
|
||||
}
|
||||
activeMethod={activeMethod()}
|
||||
methods={sendMethods()}
|
||||
setChosenMethod={setSourceFromMethod}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!isAmtEditable()}>
|
||||
<AmountEditable
|
||||
initialAmountSats={amountSats()}
|
||||
setAmountSats={setAmountInput}
|
||||
maxAmountSats={maxAmountSats()}
|
||||
fee={feeEstimate()?.toString()}
|
||||
frozenAmount={true}
|
||||
onSubmit={() =>
|
||||
sendButtonDisabled() ? undefined : handleSend()
|
||||
}
|
||||
activeMethod={activeMethod()}
|
||||
methods={sendMethods()}
|
||||
setChosenMethod={setSourceFromMethod}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={feeEstimate()}>
|
||||
|
||||
@@ -25,10 +25,8 @@ import {
|
||||
LargeHeader,
|
||||
MegaCheck,
|
||||
MegaEx,
|
||||
MethodChooser,
|
||||
MutinyWalletGuard,
|
||||
NavBar,
|
||||
SafeArea,
|
||||
showToast,
|
||||
SuccessModal,
|
||||
TextField,
|
||||
@@ -36,7 +34,6 @@ import {
|
||||
} from "~/components";
|
||||
import { useI18n } from "~/i18n/context";
|
||||
import { Network } from "~/logic/mutinyWalletSetup";
|
||||
import { SendSource } from "~/routes/Send";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { eify, vibrateSuccess } from "~/utils";
|
||||
|
||||
@@ -57,7 +54,6 @@ export function Swap() {
|
||||
const navigate = useNavigate();
|
||||
const i18n = useI18n();
|
||||
|
||||
const [source, setSource] = createSignal<SendSource>("onchain");
|
||||
const [amountSats, setAmountSats] = createSignal(0n);
|
||||
const [isConnecting, setIsConnecting] = createSignal(false);
|
||||
|
||||
@@ -92,7 +88,6 @@ export function Swap() {
|
||||
}
|
||||
|
||||
function resetState() {
|
||||
setSource("onchain");
|
||||
setAmountSats(0n);
|
||||
setIsConnecting(false);
|
||||
setLoading(false);
|
||||
@@ -275,7 +270,6 @@ export function Swap() {
|
||||
|
||||
return (
|
||||
<MutinyWalletGuard>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<BackLink />
|
||||
<LargeHeader>{i18n.t("swap.header")}</LargeHeader>
|
||||
@@ -359,13 +353,9 @@ export function Swap() {
|
||||
</Match>
|
||||
</Switch>
|
||||
</SuccessModal>
|
||||
<div class="flex flex-1 flex-col justify-between gap-2">
|
||||
<div class="flex-1" />
|
||||
<VStack biggap>
|
||||
<MethodChooser
|
||||
source={source()}
|
||||
setSource={setSource}
|
||||
both={false}
|
||||
/>
|
||||
<VStack>
|
||||
<Show when={!hasLsp()}>
|
||||
<Card>
|
||||
<VStack>
|
||||
@@ -382,18 +372,12 @@ export function Swap() {
|
||||
onChange={handlePeerSelect}
|
||||
value={selectedPeer()}
|
||||
>
|
||||
<option
|
||||
value=""
|
||||
class=""
|
||||
selected
|
||||
>
|
||||
<option value="" class="" selected>
|
||||
{i18n.t("swap.choose_peer")}
|
||||
</option>
|
||||
<For each={peers()}>
|
||||
{(peer) => (
|
||||
<option
|
||||
value={peer.pubkey}
|
||||
>
|
||||
<option value={peer.pubkey}>
|
||||
{peer.alias ??
|
||||
peer.pubkey}
|
||||
</option>
|
||||
@@ -430,24 +414,28 @@ export function Swap() {
|
||||
disabled={isConnecting()}
|
||||
>
|
||||
{isConnecting()
|
||||
? i18n.t(
|
||||
"swap.connecting"
|
||||
)
|
||||
: i18n.t(
|
||||
"swap.connect"
|
||||
)}
|
||||
? i18n.t("swap.connecting")
|
||||
: i18n.t("swap.connect")}
|
||||
</Button>
|
||||
</Form>
|
||||
</Show>
|
||||
</VStack>
|
||||
</Card>
|
||||
</Show>
|
||||
</VStack>
|
||||
<AmountEditable
|
||||
initialAmountSats={amountSats()}
|
||||
setAmountSats={setAmountSats}
|
||||
fee={feeEstimate()?.toString()}
|
||||
maxAmountSats={maxOnchain()}
|
||||
activeMethod={{
|
||||
method: "onchain",
|
||||
maxAmountSats: maxOnchain()
|
||||
}}
|
||||
methods={[
|
||||
{
|
||||
method: "onchain",
|
||||
maxAmountSats: maxOnchain()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Show when={feeEstimate() && amountSats() > 0n}>
|
||||
<FeeDisplay
|
||||
@@ -471,9 +459,9 @@ export function Swap() {
|
||||
{i18n.t("swap.confirm_swap")}
|
||||
</Button>
|
||||
</VStack>
|
||||
</div>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="none" />
|
||||
</SafeArea>
|
||||
</MutinyWalletGuard>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user