Clean up and fixes

This commit is contained in:
2026-02-08 02:32:50 +00:00
parent eb1bf9020e
commit da096af0fa
36 changed files with 2119 additions and 1751 deletions

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { Box, Text } from 'ink';
import { colors } from '../../../theme.js';
import type { WizardStepProps } from '../types.js';
type Props = Pick<
WizardStepProps,
'template' | 'actionIdentifier' | 'roleIdentifier' | 'actionName'
>;
export function InfoStep({
template,
actionIdentifier,
roleIdentifier,
actionName,
}: Props): React.ReactElement {
const action = template?.actions?.[actionIdentifier];
const role = action?.roles?.[roleIdentifier];
return (
<Box flexDirection='column'>
<Text color={colors.primary} bold>
Action: {actionName}
</Text>
<Text color={colors.textMuted}>
{action?.description || 'No description'}
</Text>
<Box marginTop={1}>
<Text color={colors.text}>Your Role: </Text>
<Text color={colors.accent}>{roleIdentifier}</Text>
</Box>
{role?.requirements && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Requirements:</Text>
{role.requirements.variables?.map((v) => (
<Text key={v} color={colors.textMuted}>
{' '} Variable: {v}
</Text>
))}
{role.requirements.slots && (
<Text color={colors.textMuted}>
{' '} Slots: {role.requirements.slots.min} min (UTXO selection
required)
</Text>
)}
</Box>
)}
</Box>
);
}

View File

@@ -0,0 +1,92 @@
import React from 'react';
import { Box, Text } from 'ink';
import { colors, formatSatoshis, formatHex } from '../../../theme.js';
import type { WizardStepProps } from '../types.js';
type Props = Pick<
WizardStepProps,
| 'availableUtxos'
| 'selectedUtxoIndex'
| 'requiredAmount'
| 'fee'
| 'selectedAmount'
| 'changeAmount'
| 'focusArea'
>;
export function InputsStep({
availableUtxos,
selectedUtxoIndex,
requiredAmount,
fee,
selectedAmount,
changeAmount,
focusArea,
}: Props): React.ReactElement {
return (
<Box flexDirection='column'>
<Text color={colors.text} bold>
Select UTXOs to fund the transaction:
</Text>
<Box marginTop={1} flexDirection='column'>
<Text color={colors.textMuted}>
Required: {formatSatoshis(requiredAmount)} +{' '}
{formatSatoshis(fee)} fee
</Text>
<Text
color={
selectedAmount >= requiredAmount + fee
? colors.success
: colors.warning
}
>
Selected: {formatSatoshis(selectedAmount)}
</Text>
{selectedAmount > requiredAmount + fee && (
<Text color={colors.info}>
Change: {formatSatoshis(changeAmount)}
</Text>
)}
</Box>
<Box
marginTop={1}
flexDirection='column'
borderStyle='single'
borderColor={colors.border}
paddingX={1}
>
{availableUtxos.length === 0 ? (
<Text color={colors.textMuted}>No UTXOs available</Text>
) : (
availableUtxos.map((utxo, index) => {
const isCursor =
selectedUtxoIndex === index && focusArea === 'content';
return (
<Box
key={`${utxo.outpointTransactionHash}:${utxo.outpointIndex}`}
>
<Text
color={isCursor ? colors.focus : colors.text}
bold={isCursor}
>
{isCursor ? '▸ ' : ' '}[{utxo.selected ? 'X' : ' '}]{' '}
{formatSatoshis(utxo.valueSatoshis)} -{' '}
{formatHex(utxo.outpointTransactionHash, 12)}:
{utxo.outpointIndex}
</Text>
</Box>
);
})
)}
</Box>
<Box marginTop={1}>
<Text color={colors.textMuted} dimColor>
Space/Enter: Toggle a: Select all n: Deselect all
</Text>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { Box, Text } from 'ink';
import { colors } from '../../../theme.js';
interface PublishStepProps {
invitationId: string | null;
}
export function PublishStep({
invitationId,
}: PublishStepProps): React.ReactElement {
return (
<Box flexDirection='column'>
<Text color={colors.success} bold>
Invitation Created & Published!
</Text>
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Invitation ID:</Text>
<Box
borderStyle='single'
borderColor={colors.primary}
paddingX={1}
marginTop={1}
>
<Text color={colors.accent}>
{invitationId ?? '(unknown)'}
</Text>
</Box>
</Box>
<Box marginTop={1}>
<Text color={colors.textMuted}>
Share this ID with the other party to complete the transaction.
</Text>
</Box>
<Box marginTop={1}>
<Text color={colors.warning}>
Press 'c' to copy ID to clipboard
</Text>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,93 @@
import React from 'react';
import { Box, Text } from 'ink';
import { colors, formatSatoshis } from '../../../theme.js';
import type { VariableInput, SelectableUTXO } from '../types.js';
import type { XOTemplate } from '@xo-cash/types';
interface ReviewStepProps {
template: XOTemplate;
actionName: string;
roleIdentifier: string;
variables: VariableInput[];
availableUtxos: SelectableUTXO[];
changeAmount: bigint;
}
export function ReviewStep({
template,
actionName,
roleIdentifier,
variables,
availableUtxos,
changeAmount,
}: ReviewStepProps): React.ReactElement {
const selectedUtxos = availableUtxos.filter((u) => u.selected);
return (
<Box flexDirection='column'>
<Text color={colors.text} bold>
Review your invitation:
</Text>
{/* Summary */}
<Box marginTop={1} flexDirection='column'>
<Text color={colors.textMuted}>Template: {template?.name}</Text>
<Text color={colors.textMuted}>Action: {actionName}</Text>
<Text color={colors.textMuted}>Role: {roleIdentifier}</Text>
</Box>
{/* Variables */}
{variables.length > 0 && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Variables:</Text>
{variables.map((v) => (
<Text key={v.id} color={colors.textMuted}>
{' '}
{v.name}: {v.value || '(empty)'}
</Text>
))}
</Box>
)}
{/* Inputs */}
{selectedUtxos.length > 0 && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>
Inputs ({selectedUtxos.length}):
</Text>
{selectedUtxos.slice(0, 3).map((u) => (
<Text
key={`${u.outpointTransactionHash}:${u.outpointIndex}`}
color={colors.textMuted}
>
{' '}
{formatSatoshis(u.valueSatoshis)}
</Text>
))}
{selectedUtxos.length > 3 && (
<Text color={colors.textMuted}>
{' '}...and {selectedUtxos.length - 3} more
</Text>
)}
</Box>
)}
{/* Outputs */}
{changeAmount > 0n && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Outputs:</Text>
<Text color={colors.textMuted}>
{' '}Change: {formatSatoshis(changeAmount)}
</Text>
</Box>
)}
{/* Confirmation prompt */}
<Box marginTop={1}>
<Text color={colors.warning}>
Press Next to create and publish the invitation.
</Text>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Box, Text } from 'ink';
import { colors } from '../../../theme.js';
import { VariableInputField } from '../../../components/VariableInputField.js';
import type { WizardStepProps } from '../types.js';
type Props = Pick<
WizardStepProps,
| 'variables'
| 'updateVariable'
| 'handleTextInputSubmit'
| 'focusArea'
| 'focusedInput'
>;
export function VariablesStep({
variables,
updateVariable,
handleTextInputSubmit,
focusArea,
focusedInput,
}: Props): React.ReactElement {
return (
<Box flexDirection='column'>
<Text color={colors.text} bold>
Enter required values:
</Text>
<Box marginTop={1} flexDirection='column'>
{variables.map((variable, index) => (
<VariableInputField
key={variable.id}
variable={variable}
index={index}
isFocused={focusArea === 'content' && focusedInput === index}
onChange={updateVariable}
onSubmit={handleTextInputSubmit}
borderColor={colors.border as string}
focusColor={colors.primary as string}
/>
))}
</Box>
<Box marginTop={1}>
<Text color={colors.textMuted} dimColor>
Type your value, then press Enter to continue
</Text>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,5 @@
export * from './InfoStep.js';
export * from './VariablesStep.js';
export * from './InputsStep.js';
export * from './ReviewStep.js';
export * from './PublishStep.js';