144 lines
3.9 KiB
TypeScript
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>
|
|
);
|
|
} |