/** * 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(); for (const commit of invitation.data.commits ?? []) { for (const input of commit.data?.inputs ?? []) { if (input.roleIdentifier) filledRoles.add(input.roleIdentifier); } } return ( {/* Template info */} Template: {template?.name ?? invitation.data.templateIdentifier} {template?.description && ( {template.description} )} {/* Action info */} Action: {action?.name ?? invitation.data.actionIdentifier} {action?.description && ( {action.description} )} {/* Status */} Status: {state} {/* Roles already filled */} Roles Filled ({filledRoles.size}): {filledRoles.size === 0 ? ( None yet ) : ( Array.from(filledRoles).map(role => { const roleInfoRaw = template?.roles?.[role]; const roleInfo = roleInfoRaw && typeof roleInfoRaw === 'object' ? roleInfoRaw : null; return ( • {roleInfo?.name ?? role} ); }) )} {/* Inputs */} Inputs ({inputs.length}): {inputs.length === 0 ? ( None yet ) : ( inputs.map((input, idx) => { const inputTemplate = template?.inputs?.[input.inputIdentifier ?? '']; return ( {' '}• {inputTemplate?.name ?? input.inputIdentifier ?? `Input ${idx}`} {input.roleIdentifier && ` (${input.roleIdentifier})`} ); }) )} {/* Outputs */} Outputs ({outputs.length}): {outputs.length === 0 ? ( None yet ) : ( outputs.map((output, idx) => { const outputTemplate = template?.outputs?.[output.outputIdentifier ?? '']; const fiatValue = output.valueSatoshis !== undefined ? formatSatoshisToFiat(output.valueSatoshis) : null; return ( {' '}• {outputTemplate?.name ?? output.outputIdentifier ?? `Output ${idx}`} {output.valueSatoshis !== undefined && ` (${formatSatoshis(output.valueSatoshis)})`} {fiatValue && ` (~${fiatValue})`} ); }) )} {/* Variables */} Variables ({variables.length}): {variables.length === 0 ? ( None set ) : ( variables.map((variable, idx) => { const varTemplate = template?.variables?.[variable.variableIdentifier]; const displayValue = typeof variable.value === 'bigint' ? variable.value.toString() : String(variable.value); return ( {' '}• {varTemplate?.name ?? variable.variableIdentifier}: {displayValue} ); }) )} {/* Navigation hint */} Enter: Continue • Esc: Cancel ); }