227 lines
7.3 KiB
TypeScript
227 lines
7.3 KiB
TypeScript
/**
|
|
* PreviewInvitationStep — displays the current state of a fetched invitation.
|
|
*
|
|
* Shows which roles, inputs, outputs, and variables have already been filled
|
|
* so the user can understand what they're joining before proceeding.
|
|
* Press Enter to continue, Esc to cancel.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import { colors, formatSatoshis } from '../../../../theme.js';
|
|
import { useSatoshisConversion } from '../../../../hooks/useSatoshisConversion.js';
|
|
import { useLayeredInput } from '../../../../hooks/useInputLayer.js';
|
|
import {
|
|
getInvitationState,
|
|
getStateColorName,
|
|
getInvitationInputs,
|
|
getInvitationOutputs,
|
|
getInvitationVariables,
|
|
} from '../../../../../utils/invitation-utils.js';
|
|
import type { PreviewStepProps } from '../types.js';
|
|
|
|
/**
|
|
* Map a semantic color name to an actual theme color value.
|
|
*/
|
|
function stateColor(state: string): string {
|
|
const name = getStateColorName(state);
|
|
switch (name) {
|
|
case 'info': return colors.info as string;
|
|
case 'warning': return colors.warning as string;
|
|
case 'success': return colors.success as string;
|
|
case 'error': return colors.error as string;
|
|
case 'muted':
|
|
default: return colors.textMuted as string;
|
|
}
|
|
}
|
|
|
|
export function PreviewInvitationStep({
|
|
invitation,
|
|
template,
|
|
onComplete,
|
|
onCancel,
|
|
isActive,
|
|
}: PreviewStepProps): React.ReactElement {
|
|
const { formatSatoshisToFiat } = useSatoshisConversion();
|
|
|
|
useLayeredInput('import-flow', (_input, key) => {
|
|
if (key.return) onComplete();
|
|
if (key.escape) onCancel();
|
|
}, { isActive });
|
|
|
|
const state = getInvitationState(invitation);
|
|
const action = template?.actions?.[invitation.data.actionIdentifier];
|
|
const inputs = getInvitationInputs(invitation);
|
|
const outputs = getInvitationOutputs(invitation);
|
|
const variables = getInvitationVariables(invitation);
|
|
|
|
// Collect role identifiers that appear across all commits
|
|
const filledRoles = new Set<string>();
|
|
for (const commit of invitation.data.commits ?? []) {
|
|
for (const input of commit.data?.inputs ?? []) {
|
|
if (input.roleIdentifier) filledRoles.add(input.roleIdentifier);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box flexDirection="column">
|
|
{/* Template info */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Template:</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text color={colors.text}>{template?.name ?? invitation.data.templateIdentifier}</Text>
|
|
</Box>
|
|
{template?.description && (
|
|
<Box>
|
|
<Text color={colors.textMuted} dimColor>{template.description}</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Action info */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Action:</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text color={colors.text}>{action?.name ?? invitation.data.actionIdentifier}</Text>
|
|
</Box>
|
|
{action?.description && (
|
|
<Box>
|
|
<Text color={colors.textMuted} dimColor>{action.description}</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Status */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Status:</Text>
|
|
</Box>
|
|
<Box>
|
|
<Text color={stateColor(state)}>{state}</Text>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Roles already filled */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Roles Filled ({filledRoles.size}):</Text>
|
|
</Box>
|
|
|
|
<Box marginLeft={1}>
|
|
{filledRoles.size === 0 ? (
|
|
<Box>
|
|
<Text color={colors.textMuted}> None yet</Text>
|
|
</Box>
|
|
) : (
|
|
Array.from(filledRoles).map(role => {
|
|
const roleInfoRaw = template?.roles?.[role];
|
|
const roleInfo = roleInfoRaw && typeof roleInfoRaw === 'object' ? roleInfoRaw : null;
|
|
return (
|
|
<Box key={role}>
|
|
<Text color={colors.text}> • {roleInfo?.name ?? role}</Text>
|
|
</Box>
|
|
);
|
|
})
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Inputs */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Inputs ({inputs.length}):</Text>
|
|
</Box>
|
|
|
|
<Box marginLeft={1}>
|
|
{inputs.length === 0 ? (
|
|
<Box>
|
|
<Text color={colors.textMuted}> None yet</Text>
|
|
</Box>
|
|
) : (
|
|
inputs.map((input, idx) => {
|
|
const inputTemplate = template?.inputs?.[input.inputIdentifier ?? ''];
|
|
return (
|
|
<Box key={`input-${idx}`}>
|
|
<Text color={colors.text}>
|
|
{' '}• {inputTemplate?.name ?? input.inputIdentifier ?? `Input ${idx}`}
|
|
{input.roleIdentifier && ` (${input.roleIdentifier})`}
|
|
</Text>
|
|
</Box>
|
|
);
|
|
})
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Outputs */}
|
|
<Box flexDirection="column" marginBottom={1} marginLeft={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Outputs ({outputs.length}):</Text>
|
|
</Box>
|
|
|
|
<Box marginLeft={1}>
|
|
{outputs.length === 0 ? (
|
|
<Box>
|
|
<Text color={colors.textMuted}>None yet</Text>
|
|
</Box>
|
|
) : (
|
|
outputs.map((output, idx) => {
|
|
const outputTemplate = template?.outputs?.[output.outputIdentifier ?? ''];
|
|
const fiatValue = output.valueSatoshis !== undefined
|
|
? formatSatoshisToFiat(output.valueSatoshis)
|
|
: null;
|
|
return (
|
|
<Box key={`output-${idx}`}>
|
|
<Text color={colors.text}>
|
|
{' '}• {outputTemplate?.name ?? output.outputIdentifier ?? `Output ${idx}`}
|
|
{output.valueSatoshis !== undefined && ` (${formatSatoshis(output.valueSatoshis)})`}
|
|
{fiatValue && ` (~${fiatValue})`}
|
|
</Text>
|
|
</Box>
|
|
);
|
|
})
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Variables */}
|
|
<Box flexDirection="column" marginBottom={1}>
|
|
<Box>
|
|
<Text color={colors.primary} bold>Variables ({variables.length}):</Text>
|
|
</Box>
|
|
|
|
<Box marginLeft={1}>
|
|
{variables.length === 0 ? (
|
|
<Box>
|
|
<Text color={colors.textMuted}> None set</Text>
|
|
</Box>
|
|
) : (
|
|
variables.map((variable, idx) => {
|
|
const varTemplate = template?.variables?.[variable.variableIdentifier];
|
|
const displayValue = typeof variable.value === 'bigint'
|
|
? variable.value.toString()
|
|
: String(variable.value);
|
|
return (
|
|
<Box key={`var-${idx}`}>
|
|
<Text color={colors.text}>
|
|
{' '}• {varTemplate?.name ?? variable.variableIdentifier}: {displayValue}
|
|
</Text>
|
|
</Box>
|
|
);
|
|
})
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Navigation hint */}
|
|
<Box marginTop={1}>
|
|
<Text color={colors.textMuted}>Enter: Continue • Esc: Cancel</Text>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|