import { For, ParentComponent, Show, createResource, createSignal, onMount } from "solid-js"; import { Button } from "~/components/layout"; import { useMegaStore } from "~/state/megaStore"; import { satsToUsd, usdToSats } from "~/utils/conversions"; import { Dialog } from "@kobalte/core"; import close from "~/assets/icons/close.svg"; import pencil from "~/assets/icons/pencil.svg"; import { InlineAmount } from "./AmountCard"; import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs"; import { InfoBox } from "./InfoBox"; import { Network } from "~/logic/mutinyWalletSetup"; const CHARACTERS = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "DEL" ]; const FIXED_AMOUNTS_SATS = [ { label: "10k", amount: "10000" }, { label: "100k", amount: "100000" }, { label: "1m", amount: "1000000" } ]; const FIXED_AMOUNTS_USD = [ { label: "$1", amount: "1" }, { label: "$10", amount: "10" }, { label: "$100", amount: "100" } ]; function fiatInputSanitizer(input: string): string { // Make sure only numbers and a single decimal point are allowed const numeric = input.replace(/[^0-9.]/g, "").replace(/(\..*)\./g, "$1"); // Remove leading zeros if not a decimal, add 0 if starts with a decimal const cleaned = numeric.replace(/^0([^.]|$)/g, "$1").replace(/^\./g, "0."); // If there are three characters after the decimal, shift the decimal const shifted = cleaned.match(/(\.[0-9]{3}).*/g) ? (parseFloat(cleaned) * 10).toFixed(2) : cleaned; // Truncate any numbers two past the decimal const twoDecimals = shifted.replace(/(\.[0-9]{2}).*/g, "$1"); return twoDecimals; } function satsInputSanitizer(input: string): string { // Make sure only numbers are allowed const numeric = input.replace(/[^0-9]/g, ""); // If it starts with a 0, remove the 0 const noLeadingZero = numeric.replace(/^0([^.]|$)/g, "$1"); return noLeadingZero; } function SingleDigitButton(props: { character: string; onClick: (c: string) => void; fiat: boolean; }) { return ( // Skip the "." if it's fiat } > ); } function BigScalingText(props: { text: string; fiat: boolean }) { const chars = () => props.text.length; return (

9, "scale-95": chars() > 8, "scale-100": chars() > 7, "scale-105": chars() > 6, "scale-110": chars() > 5, "scale-125": chars() > 4, "scale-150": chars() <= 4 }} > {props.text}  {props.fiat ? "USD" : "SATS"}

); } function SmallSubtleAmount(props: { text: string; fiat: boolean }) { return (

≈ {props.text}  {props.fiat ? "USD" : "SATS"}

); } function toDisplayHandleNaN(input: string, _fiat: boolean): string { const parsed = Number(input); if (isNaN(parsed)) { return "0"; } else { return parsed.toLocaleString(); } } export const AmountEditable: ParentComponent<{ initialAmountSats: string; initialOpen: boolean; setAmountSats: (s: bigint) => void; }> = (props) => { const [isOpen, setIsOpen] = createSignal(props.initialOpen); const [state, _actions] = useMegaStore(); const [mode, setMode] = createSignal<"fiat" | "sats">("sats"); const [localSats, setLocalSats] = createSignal( props.initialAmountSats || "0" ); const [localFiat, setLocalFiat] = createSignal( satsToUsd( state.price, parseInt(props.initialAmountSats || "0") || 0, false ) ); const displaySats = () => toDisplayHandleNaN(localSats(), false); const displayFiat = () => `$${toDisplayHandleNaN(localFiat(), true)}`; let satsInputRef!: HTMLInputElement; let fiatInputRef!: HTMLInputElement; const [inboundCapacity] = createResource(async () => { const channels = await state.mutiny_wallet?.list_channels(); let inbound = 0; for (const channel of channels) { inbound += channel.size - (channel.balance + channel.reserve); } return inbound; }); const warningText = () => { if ((state.balance?.lightning || 0n) === 0n) { const network = state.mutiny_wallet?.get_network() as Network; if (network === "bitcoin") { return "Your first lightning receive needs to be 50,000 sats or greater."; } else { return "Your first lightning receive needs to be 10,000 sats or greater."; } } const parsed = Number(localSats()); if (isNaN(parsed)) { return undefined; } if (parsed > (inboundCapacity() || 0)) { return "A lightning setup fee will be charged if paid over lightning."; } return undefined; }; function handleCharacterInput(character: string) { const isFiatMode = mode() === "fiat"; const inputSanitizer = isFiatMode ? fiatInputSanitizer : satsInputSanitizer; const localValue = isFiatMode ? localFiat : localSats; let sane; if (character === "DEL") { sane = inputSanitizer(localValue().slice(0, -1)); } else { if (localValue() === "0") { sane = inputSanitizer(character); } else { sane = inputSanitizer(localValue() + character); } } if (isFiatMode) { setLocalFiat(sane); setLocalSats( usdToSats(state.price, parseFloat(sane || "0") || 0, false) ); } else { setLocalSats(sane); setLocalFiat(satsToUsd(state.price, Number(sane) || 0, false)); } // After a button press make sure we re-focus the input focus(); } function setFixedAmount(amount: string) { if (mode() === "fiat") { setLocalFiat(amount); setLocalSats( usdToSats(state.price, parseFloat(amount || "0") || 0, false) ); } else { setLocalSats(amount); setLocalFiat(satsToUsd(state.price, Number(amount) || 0, false)); } } // What we're all here for in the first place: returning a value function handleSubmit(e: SubmitEvent | MouseEvent) { e.preventDefault(); props.setAmountSats(BigInt(localSats())); setIsOpen(false); } function handleSatsInput(e: InputEvent) { const { value } = e.target as HTMLInputElement; const sane = satsInputSanitizer(value); setLocalSats(sane); setLocalFiat(satsToUsd(state.price, Number(sane) || 0, false)); } function handleFiatInput(e: InputEvent) { const { value } = e.target as HTMLInputElement; const sane = fiatInputSanitizer(value); setLocalFiat(sane); setLocalSats( usdToSats(state.price, parseFloat(sane || "0") || 0, false) ); } function toggle() { setMode((m) => (m === "sats" ? "fiat" : "sats")); focus(); } onMount(() => { focus(); }); function focus() { // Make sure we actually have the inputs mounted before we try to focus them if (isOpen() && satsInputRef && fiatInputRef) { if (mode() === "sats") { satsInputRef.focus(); } else { fiatInputRef.focus(); } } } return ( {/* */}
setIsOpen(false)} > {/* TODO: figure out how to submit on enter */}
{/*
*/} (satsInputRef = el)} disabled={mode() === "fiat"} type="text" value={localSats()} onInput={handleSatsInput} inputMode="none" /> (fiatInputRef = el)} disabled={mode() === "sats"} type="text" value={localFiat()} onInput={handleFiatInput} inputMode="none" />
{warningText()}
{(amount) => ( )}
{(character) => ( )}
); };