Add variable step
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* VariablesStep — collects all required variable values for invitation import.
|
||||
*
|
||||
* This runs after role selection and before input selection so cashasm
|
||||
* expressions can resolve required variables during `getSatsOut()`.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback } from "react";
|
||||
import { Box, Text } from "ink";
|
||||
import { colors } from "../../../../theme.js";
|
||||
import { useLayeredInput } from "../../../../hooks/useInputLayer.js";
|
||||
import { VariableInputField } from "../../../../components/VariableInputField.js";
|
||||
import type { VariablesStepProps } from "../types.js";
|
||||
|
||||
/**
|
||||
* Build a user-facing validation error for empty required fields.
|
||||
*/
|
||||
function validateVariables(
|
||||
variables: VariablesStepProps["variables"],
|
||||
): string | null {
|
||||
const empty = variables.filter((v) => v.value.trim() === "");
|
||||
if (empty.length === 0) return null;
|
||||
return `Please enter values for: ${empty.map((v) => v.name).join(", ")}`;
|
||||
}
|
||||
|
||||
export function VariablesStep({
|
||||
variables,
|
||||
onUpdateVariable,
|
||||
onComplete,
|
||||
onCancel,
|
||||
isActive,
|
||||
}: VariablesStepProps): React.ReactElement {
|
||||
const [focusedInput, setFocusedInput] = useState(0);
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
|
||||
const helpText = useMemo(() => {
|
||||
if (variables.length === 0) {
|
||||
return "No variables required for this role.";
|
||||
}
|
||||
return "Enter a value for each variable, then press Enter on the last field to continue.";
|
||||
}, [variables.length]);
|
||||
|
||||
/**
|
||||
* Move focus to next input, or finish the step if this is the last one.
|
||||
*/
|
||||
const handleInputSubmit = useCallback(() => {
|
||||
if (variables.length === 0) {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusedInput < variables.length - 1) {
|
||||
setFocusedInput((prev) => prev + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = validateVariables(variables);
|
||||
setValidationError(validation);
|
||||
if (!validation) {
|
||||
onComplete();
|
||||
}
|
||||
}, [variables, focusedInput, onComplete]);
|
||||
|
||||
// Keyboard navigation for non-text actions.
|
||||
useLayeredInput(
|
||||
"import-flow",
|
||||
(input, key) => {
|
||||
if (key.upArrow || input === "k") {
|
||||
setFocusedInput((prev) => Math.max(0, prev - 1));
|
||||
} else if (key.downArrow || input === "j") {
|
||||
setFocusedInput((prev) => Math.min(variables.length - 1, prev + 1));
|
||||
} else if (key.escape) {
|
||||
onCancel();
|
||||
}
|
||||
},
|
||||
{ isActive },
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text color={colors.primary} bold>
|
||||
Required Variables
|
||||
</Text>
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
{variables.map((variable, index) => (
|
||||
<VariableInputField
|
||||
key={variable.id}
|
||||
variable={variable}
|
||||
index={index}
|
||||
isFocused={focusedInput === index}
|
||||
onChange={onUpdateVariable}
|
||||
onSubmit={handleInputSubmit}
|
||||
borderColor={colors.border as string}
|
||||
focusColor={colors.primary as string}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{validationError && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={colors.error}>{validationError}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text color={colors.textMuted}>
|
||||
{helpText} ↑↓: Change field • Esc: Cancel
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user