Files
xo-cli/src/tui/components/VariableInputField.tsx
2026-04-27 08:42:51 +00:00

144 lines
3.9 KiB
TypeScript

import React, { useMemo } from "react";
import { Box, Text } from "ink";
import TextInput from "./TextInput.js";
import { useSatoshisConversion } from "../hooks/useSatoshisConversion.js";
interface VariableInputFieldProps {
variable: {
id: string;
name: string;
type: string;
hint?: string;
value: string;
};
index: number;
isFocused: boolean;
onChange: (index: number, value: string) => void;
onSubmit: () => void;
borderColor: string;
focusColor: string;
}
const SATOSHIS_PER_BCH = 100_000_000n;
/**
* Returns true when the variable is an integer satoshis field.
*/
function isSatoshisVariable(variable: VariableInputFieldProps["variable"]): boolean {
return (
variable.type === "integer" &&
variable.hint?.toLowerCase().includes("satoshi") === true
);
}
/**
* Parse a strict integer string into bigint.
*/
function parseSatoshis(value: string): bigint | null {
const trimmed = value.trim();
if (!/^[-]?\d+$/.test(trimmed)) {
return null;
}
try {
return BigInt(trimmed);
} catch {
return null;
}
}
/**
* Format satoshis as BCH with fixed 8 decimals, preserving bigint precision.
*/
function formatBchFromSatoshis(satoshis: bigint): string {
const sign = satoshis < 0n ? "-" : "";
const absolute = satoshis < 0n ? satoshis * -1n : satoshis;
const whole = absolute / SATOSHIS_PER_BCH;
const fractional = absolute % SATOSHIS_PER_BCH;
return `${sign}${whole.toString()}.${fractional.toString().padStart(8, "0")} BCH`;
}
export function VariableInputField({
variable,
index,
isFocused,
onChange,
onSubmit,
borderColor,
focusColor,
}: VariableInputFieldProps): React.ReactElement {
const { currencyCode, formattedFiatPerBchRate, formatSatoshisToFiat } =
useSatoshisConversion("USD");
const satoshisValue = useMemo(
() => parseSatoshis(variable.value),
[variable.value],
);
const formattedBch = useMemo(() => {
if (satoshisValue === null) {
return null;
}
return formatBchFromSatoshis(satoshisValue);
}, [satoshisValue]);
const formattedFiat = useMemo(() => {
if (satoshisValue === null) {
return null;
}
return formatSatoshisToFiat(satoshisValue);
}, [satoshisValue, formatSatoshisToFiat]);
const shouldShowSatoshisConversion = isSatoshisVariable(variable);
return (
<Box flexDirection="column" marginBottom={1}>
<Text color={focusColor}>{variable.name}</Text>
{variable.hint && (
<Text color={borderColor} dimColor>
({variable.hint})
</Text>
)}
<Box
borderStyle="single"
borderColor={isFocused ? focusColor : borderColor}
paddingX={1}
marginTop={1}
gap={1}
>
<TextInput
value={variable.value}
onChange={(value) => onChange(index, value)}
onSubmit={onSubmit}
focus={isFocused}
placeholder={`Enter ${variable.name}...`}
/>
{/* TODO: this may need to be conditional. Need to play around with other templates though */}
<Text color={borderColor} dimColor>{variable.hint}</Text>
</Box>
{shouldShowSatoshisConversion && (
<Box flexDirection="column">
{formattedBch ? (
<>
<Text color={borderColor} dimColor>
{formattedBch}
</Text>
<Text color={borderColor} dimColor>
{formattedFiat
? `Approx. ${currencyCode}: ${formattedFiat}`
: `Approx. ${currencyCode}: waiting for live rate...`}
</Text>
{formattedFiatPerBchRate && (
<Text color={borderColor} dimColor>
1 BCH = {formattedFiatPerBchRate}
</Text>
)}
</>
) : (
<Text color={borderColor} dimColor>
Enter a whole satoshi amount to preview BCH/{currencyCode} conversion.
</Text>
)}
</Box>
)}
</Box>
);
}