Big changes and fixes. Uses action history. Improve role selection. Remove unused logs
This commit is contained in:
@@ -7,6 +7,7 @@ import { useActionWizard } from './useActionWizard.js';
|
||||
|
||||
// Steps
|
||||
import { InfoStep } from './steps/InfoStep.js';
|
||||
import { RoleSelectStep } from './steps/RoleSelectStep.js';
|
||||
import { VariablesStep } from './steps/VariablesStep.js';
|
||||
import { InputsStep } from './steps/InputsStep.js';
|
||||
import { ReviewStep } from './steps/ReviewStep.js';
|
||||
@@ -21,6 +22,19 @@ export function ActionWizardScreen(): React.ReactElement {
|
||||
// Tab to cycle between content area and button bar
|
||||
if (key.tab) {
|
||||
if (wizard.focusArea === 'content') {
|
||||
// Within the role-select step, tab through roles first
|
||||
if (
|
||||
wizard.currentStepData?.type === 'role-select' &&
|
||||
wizard.availableRoles.length > 0
|
||||
) {
|
||||
if (
|
||||
wizard.selectedRoleIndex <
|
||||
wizard.availableRoles.length - 1
|
||||
) {
|
||||
wizard.setSelectedRoleIndex((prev) => prev + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Within the inputs step, tab through UTXOs first
|
||||
if (
|
||||
wizard.currentStepData?.type === 'inputs' &&
|
||||
@@ -47,11 +61,27 @@ export function ActionWizardScreen(): React.ReactElement {
|
||||
wizard.setFocusArea('content');
|
||||
wizard.setFocusedInput(0);
|
||||
wizard.setSelectedUtxoIndex(0);
|
||||
wizard.setSelectedRoleIndex(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys for role selection in the content area
|
||||
if (
|
||||
wizard.focusArea === 'content' &&
|
||||
wizard.currentStepData?.type === 'role-select'
|
||||
) {
|
||||
if (key.upArrow) {
|
||||
wizard.setSelectedRoleIndex((p) => Math.max(0, p - 1));
|
||||
} else if (key.downArrow) {
|
||||
wizard.setSelectedRoleIndex((p) =>
|
||||
Math.min(wizard.availableRoles.length - 1, p + 1)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys for UTXO selection in the content area
|
||||
if (
|
||||
wizard.focusArea === 'content' &&
|
||||
@@ -131,6 +161,16 @@ export function ActionWizardScreen(): React.ReactElement {
|
||||
actionName={wizard.actionName}
|
||||
/>
|
||||
);
|
||||
case 'role-select':
|
||||
return (
|
||||
<RoleSelectStep
|
||||
template={wizard.template!}
|
||||
actionIdentifier={wizard.actionIdentifier!}
|
||||
availableRoles={wizard.availableRoles}
|
||||
selectedRoleIndex={wizard.selectedRoleIndex}
|
||||
focusArea={wizard.focusArea}
|
||||
/>
|
||||
);
|
||||
case 'variables':
|
||||
return (
|
||||
<VariablesStep
|
||||
@@ -189,8 +229,8 @@ export function ActionWizardScreen(): React.ReactElement {
|
||||
{logoSmall} - Action Wizard
|
||||
</Text>
|
||||
<Text color={colors.textMuted}>
|
||||
{wizard.template?.name} {">"} {wizard.actionName} (as{" "}
|
||||
{wizard.roleIdentifier})
|
||||
{wizard.template?.name} {">"} {wizard.actionName}
|
||||
{wizard.roleIdentifier ? ` (as ${wizard.roleIdentifier})` : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
120
src/tui/screens/action-wizard/steps/RoleSelectStep.tsx
Normal file
120
src/tui/screens/action-wizard/steps/RoleSelectStep.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Role Selection Step - Allows the user to choose which role they want
|
||||
* to take for the selected action.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { colors } from '../../../theme.js';
|
||||
import type { XOTemplate } from '@xo-cash/types';
|
||||
import type { FocusArea } from '../types.js';
|
||||
|
||||
interface RoleSelectStepProps {
|
||||
/** The loaded template definition. */
|
||||
template: XOTemplate;
|
||||
/** The selected action identifier. */
|
||||
actionIdentifier: string;
|
||||
/** Role identifiers available for this action. */
|
||||
availableRoles: string[];
|
||||
/** The currently focused role index. */
|
||||
selectedRoleIndex: number;
|
||||
/** Whether the content area or button bar is focused. */
|
||||
focusArea: FocusArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the available roles for the selected action and
|
||||
* lets the user navigate between them with arrow keys.
|
||||
*/
|
||||
export function RoleSelectStep({
|
||||
template,
|
||||
actionIdentifier,
|
||||
availableRoles,
|
||||
selectedRoleIndex,
|
||||
focusArea,
|
||||
}: RoleSelectStepProps): React.ReactElement {
|
||||
const action = template.actions?.[actionIdentifier];
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text color={colors.text} bold>
|
||||
Select your role for this action:
|
||||
</Text>
|
||||
|
||||
{/* Action info */}
|
||||
{action && (
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color={colors.textMuted}>
|
||||
{action.description || 'No description available'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Role list */}
|
||||
<Box
|
||||
marginTop={1}
|
||||
flexDirection="column"
|
||||
borderStyle="single"
|
||||
borderColor={colors.border}
|
||||
paddingX={1}
|
||||
>
|
||||
{availableRoles.length === 0 ? (
|
||||
<Text color={colors.textMuted}>No roles available</Text>
|
||||
) : (
|
||||
availableRoles.map((roleId, index) => {
|
||||
const isCursor =
|
||||
selectedRoleIndex === index && focusArea === 'content';
|
||||
const roleDef = template.roles?.[roleId];
|
||||
const actionRole = action?.roles?.[roleId];
|
||||
const requirements = actionRole?.requirements;
|
||||
|
||||
return (
|
||||
<Box key={roleId} flexDirection="column" marginY={0}>
|
||||
<Text
|
||||
color={isCursor ? colors.focus : colors.text}
|
||||
bold={isCursor}
|
||||
>
|
||||
{isCursor ? '▸ ' : ' '}
|
||||
{roleDef?.name || roleId}
|
||||
</Text>
|
||||
|
||||
{/* Show role description indented below the name */}
|
||||
{roleDef?.description && (
|
||||
<Text color={colors.textMuted}>
|
||||
{' '}
|
||||
{roleDef.description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Show a brief summary of requirements */}
|
||||
{requirements && (
|
||||
<Box flexDirection="row" paddingLeft={4}>
|
||||
{requirements.variables && requirements.variables.length > 0 && (
|
||||
<Text color={colors.textMuted} dimColor>
|
||||
{requirements.variables.length} variable
|
||||
{requirements.variables.length !== 1 ? 's' : ''}
|
||||
{' '}
|
||||
</Text>
|
||||
)}
|
||||
{requirements.slots && requirements.slots.min > 0 && (
|
||||
<Text color={colors.textMuted} dimColor>
|
||||
{requirements.slots.min} input slot
|
||||
{requirements.slots.min !== 1 ? 's' : ''}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1}>
|
||||
<Text color={colors.textMuted} dimColor>
|
||||
↑↓: Navigate • Next: Confirm selection
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './InfoStep.js';
|
||||
export * from './RoleSelectStep.js';
|
||||
export * from './VariablesStep.js';
|
||||
export * from './InputsStep.js';
|
||||
export * from './ReviewStep.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { XOTemplate } from '@xo-cash/types';
|
||||
|
||||
export type StepType = 'info' | 'variables' | 'inputs' | 'review' | 'publish';
|
||||
export type StepType = 'info' | 'role-select' | 'variables' | 'inputs' | 'review' | 'publish';
|
||||
|
||||
export interface WizardStep {
|
||||
name: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useNavigation } from '../../hooks/useNavigation.js';
|
||||
import { useAppContext, useStatus } from '../../hooks/useAppContext.js';
|
||||
import { formatSatoshis } from '../../theme.js';
|
||||
@@ -18,11 +18,29 @@ export function useActionWizard() {
|
||||
const { setStatus } = useStatus();
|
||||
|
||||
// ── Navigation data ──────────────────────────────────────────────
|
||||
// Role is no longer passed via navigation — it is selected in the wizard.
|
||||
const templateIdentifier = navData.templateIdentifier as string | undefined;
|
||||
const actionIdentifier = navData.actionIdentifier as string | undefined;
|
||||
const roleIdentifier = navData.roleIdentifier as string | undefined;
|
||||
const template = navData.template as XOTemplate | undefined;
|
||||
|
||||
// ── Role selection state ────────────────────────────────────────
|
||||
const [roleIdentifier, setRoleIdentifier] = useState<string | undefined>();
|
||||
const [selectedRoleIndex, setSelectedRoleIndex] = useState(0);
|
||||
|
||||
/**
|
||||
* Roles that can start this action, derived from the template's
|
||||
* `start` entries filtered to the current action.
|
||||
*/
|
||||
const availableRoles = useMemo(() => {
|
||||
if (!template || !actionIdentifier) return [];
|
||||
const starts = template.start ?? [];
|
||||
const roleIds = starts
|
||||
.filter((s) => s.action === actionIdentifier)
|
||||
.map((s) => s.role);
|
||||
// Deduplicate while preserving order
|
||||
return [...new Set(roleIds)];
|
||||
}, [template, actionIdentifier]);
|
||||
|
||||
// ── Wizard state ─────────────────────────────────────────────────
|
||||
const [steps, setSteps] = useState<WizardStep[]>([]);
|
||||
const [currentStep, setCurrentStep] = useState(0);
|
||||
@@ -61,51 +79,55 @@ export function useActionWizard() {
|
||||
currentStepData?.type === 'variables' && focusArea === 'content';
|
||||
|
||||
// ── Initialization ───────────────────────────────────────────────
|
||||
// Builds the wizard steps dynamically based on the selected role.
|
||||
// Re-runs when roleIdentifier changes to add role-specific steps.
|
||||
useEffect(() => {
|
||||
if (!template || !actionIdentifier || !roleIdentifier) {
|
||||
if (!template || !actionIdentifier) {
|
||||
showError('Missing wizard data');
|
||||
goBack();
|
||||
return;
|
||||
}
|
||||
|
||||
const act = template.actions?.[actionIdentifier];
|
||||
const role = act?.roles?.[roleIdentifier];
|
||||
const requirements = role?.requirements;
|
||||
|
||||
// const wizardSteps: WizardStep[] = [{ name: 'Welcome', type: 'info' }];
|
||||
const wizardSteps: WizardStep[] = [];
|
||||
|
||||
// Add variables step if needed
|
||||
if (requirements?.variables && requirements.variables.length > 0) {
|
||||
wizardSteps.push({ name: 'Variables', type: 'variables' });
|
||||
// Always start with role selection
|
||||
wizardSteps.push({ name: 'Select Role', type: 'role-select' });
|
||||
|
||||
const varInputs = requirements.variables.map((varId) => {
|
||||
const varDef = template.variables?.[varId];
|
||||
return {
|
||||
id: varId,
|
||||
name: varDef?.name || varId,
|
||||
type: varDef?.type || 'string',
|
||||
hint: varDef?.hint,
|
||||
value: '',
|
||||
};
|
||||
});
|
||||
setVariables(varInputs);
|
||||
// Add role-specific steps only after role is selected
|
||||
if (roleIdentifier) {
|
||||
const act = template.actions?.[actionIdentifier];
|
||||
const role = act?.roles?.[roleIdentifier];
|
||||
const requirements = role?.requirements;
|
||||
|
||||
// Add variables step if needed
|
||||
if (requirements?.variables && requirements.variables.length > 0) {
|
||||
wizardSteps.push({ name: 'Variables', type: 'variables' });
|
||||
|
||||
const varInputs = requirements.variables.map((varId) => {
|
||||
const varDef = template.variables?.[varId];
|
||||
return {
|
||||
id: varId,
|
||||
name: varDef?.name || varId,
|
||||
type: varDef?.type || 'string',
|
||||
hint: varDef?.hint,
|
||||
value: '',
|
||||
};
|
||||
});
|
||||
setVariables(varInputs);
|
||||
}
|
||||
|
||||
// Add inputs step if role requires slots (funding inputs)
|
||||
if (requirements?.slots && requirements.slots.min > 0) {
|
||||
wizardSteps.push({ name: 'Select UTXOs', type: 'inputs' });
|
||||
}
|
||||
}
|
||||
|
||||
// Add inputs step if role requires slots (funding inputs)
|
||||
// Slots indicate the role needs to provide transaction inputs/outputs
|
||||
if (requirements?.slots && requirements.slots.min > 0) {
|
||||
wizardSteps.push({ name: 'Select UTXOs', type: 'inputs' });
|
||||
}
|
||||
|
||||
// Add review step
|
||||
// Always add review and publish at the end
|
||||
wizardSteps.push({ name: 'Review', type: 'review' });
|
||||
|
||||
// Add publish step
|
||||
wizardSteps.push({ name: 'Publish', type: 'publish' });
|
||||
|
||||
setSteps(wizardSteps);
|
||||
setStatus(`${actionIdentifier}/${roleIdentifier}`);
|
||||
setStatus(roleIdentifier ? `${actionIdentifier}/${roleIdentifier}` : actionIdentifier);
|
||||
}, [
|
||||
template,
|
||||
actionIdentifier,
|
||||
@@ -115,6 +137,17 @@ export function useActionWizard() {
|
||||
setStatus,
|
||||
]);
|
||||
|
||||
// ── Auto-advance from role-select after role is chosen ──────────
|
||||
// This runs after the main useEffect has rebuilt steps, ensuring
|
||||
// we advance to the correct step (variables, inputs, or review).
|
||||
useEffect(() => {
|
||||
if (roleIdentifier && currentStep === 0 && steps[0]?.type === 'role-select') {
|
||||
setCurrentStep(1);
|
||||
setFocusArea('content');
|
||||
setFocusedInput(0);
|
||||
}
|
||||
}, [roleIdentifier, currentStep, steps]);
|
||||
|
||||
// ── Update a single variable value ───────────────────────────────
|
||||
const updateVariable = useCallback((index: number, value: string) => {
|
||||
setVariables((prev) => {
|
||||
@@ -255,103 +288,108 @@ export function useActionWizard() {
|
||||
]);
|
||||
|
||||
// ── Create invitation and persist variables ─────────────────────
|
||||
const createInvitationWithVariables = useCallback(async () => {
|
||||
if (
|
||||
!templateIdentifier ||
|
||||
!actionIdentifier ||
|
||||
!roleIdentifier ||
|
||||
!template ||
|
||||
!appService
|
||||
) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Creates an invitation, optionally persists variable values,
|
||||
* and adds template-required outputs.
|
||||
*
|
||||
* Accepts an explicit `roleId` to avoid stale-closure issues
|
||||
* when called immediately after setting role state.
|
||||
*
|
||||
* Does NOT advance the wizard step — the caller is responsible.
|
||||
*
|
||||
* @returns `true` on success, `false` on failure.
|
||||
*/
|
||||
const createInvitationWithVariables = useCallback(
|
||||
async (roleId?: string): Promise<boolean> => {
|
||||
const effectiveRole = roleId ?? roleIdentifier;
|
||||
|
||||
setIsProcessing(true);
|
||||
setStatus('Creating invitation...');
|
||||
if (
|
||||
!templateIdentifier ||
|
||||
!actionIdentifier ||
|
||||
!effectiveRole ||
|
||||
!template ||
|
||||
!appService
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create via the engine
|
||||
const xoInvitation = await appService.engine.createInvitation({
|
||||
templateIdentifier,
|
||||
actionIdentifier,
|
||||
});
|
||||
setIsProcessing(true);
|
||||
setStatus('Creating invitation...');
|
||||
|
||||
// Wrap and track
|
||||
const invitationInstance =
|
||||
await appService.createInvitation(xoInvitation);
|
||||
|
||||
let inv = invitationInstance.data;
|
||||
const invId = inv.invitationIdentifier;
|
||||
setInvitationId(invId);
|
||||
|
||||
// Persist variable values
|
||||
if (variables.length > 0) {
|
||||
const variableData = variables.map((v) => {
|
||||
const isNumeric =
|
||||
['integer', 'number', 'satoshis'].includes(v.type) ||
|
||||
(v.hint && ['satoshis', 'amount'].includes(v.hint));
|
||||
|
||||
return {
|
||||
variableIdentifier: v.id,
|
||||
roleIdentifier,
|
||||
value: isNumeric ? BigInt(v.value || '0') : v.value,
|
||||
};
|
||||
try {
|
||||
// Create via the engine
|
||||
const xoInvitation = await appService.engine.createInvitation({
|
||||
templateIdentifier,
|
||||
actionIdentifier,
|
||||
});
|
||||
await invitationInstance.addVariables(variableData);
|
||||
inv = invitationInstance.data;
|
||||
}
|
||||
|
||||
// Add template-required outputs for the current role
|
||||
const act = template.actions?.[actionIdentifier];
|
||||
const transaction = act?.transaction
|
||||
? template.transactions?.[act.transaction]
|
||||
: null;
|
||||
// Wrap and track
|
||||
const invitationInstance =
|
||||
await appService.createInvitation(xoInvitation);
|
||||
|
||||
if (transaction?.outputs && transaction.outputs.length > 0) {
|
||||
setStatus('Adding required outputs...');
|
||||
let inv = invitationInstance.data;
|
||||
const invId = inv.invitationIdentifier;
|
||||
setInvitationId(invId);
|
||||
|
||||
const outputsToAdd = transaction.outputs.map(
|
||||
(outputId: string) => ({
|
||||
outputIdentifier: outputId,
|
||||
})
|
||||
// Persist variable values
|
||||
if (variables.length > 0) {
|
||||
const variableData = variables.map((v) => {
|
||||
const isNumeric =
|
||||
['integer', 'number', 'satoshis'].includes(v.type) ||
|
||||
(v.hint && ['satoshis', 'amount'].includes(v.hint));
|
||||
|
||||
return {
|
||||
variableIdentifier: v.id,
|
||||
roleIdentifier: effectiveRole,
|
||||
value: isNumeric ? BigInt(v.value || '0') : v.value,
|
||||
};
|
||||
});
|
||||
await invitationInstance.addVariables(variableData);
|
||||
inv = invitationInstance.data;
|
||||
}
|
||||
|
||||
// Add template-required outputs for the current role
|
||||
const act = template.actions?.[actionIdentifier];
|
||||
const transaction = act?.transaction
|
||||
? template.transactions?.[act.transaction]
|
||||
: null;
|
||||
|
||||
if (transaction?.outputs && transaction.outputs.length > 0) {
|
||||
setStatus('Adding required outputs...');
|
||||
|
||||
const outputsToAdd = transaction.outputs.map(
|
||||
(outputId: string) => ({
|
||||
outputIdentifier: outputId,
|
||||
})
|
||||
);
|
||||
|
||||
await invitationInstance.addOutputs(outputsToAdd);
|
||||
inv = invitationInstance.data;
|
||||
}
|
||||
|
||||
setInvitation(inv);
|
||||
setStatus('Invitation created');
|
||||
return true;
|
||||
} catch (error) {
|
||||
showError(
|
||||
`Failed to create invitation: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
|
||||
await invitationInstance.addOutputs(outputsToAdd);
|
||||
inv = invitationInstance.data;
|
||||
return false;
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
|
||||
setInvitation(inv);
|
||||
|
||||
// Advance and optionally kick off UTXO loading
|
||||
const nextStepType = steps[currentStep + 1]?.type;
|
||||
if (nextStepType === 'inputs') {
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
setTimeout(() => loadAvailableUtxos(), 100);
|
||||
} else {
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
}
|
||||
|
||||
setStatus('Invitation created');
|
||||
} catch (error) {
|
||||
showError(
|
||||
`Failed to create invitation: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}, [
|
||||
templateIdentifier,
|
||||
actionIdentifier,
|
||||
roleIdentifier,
|
||||
template,
|
||||
variables,
|
||||
appService,
|
||||
steps,
|
||||
currentStep,
|
||||
showError,
|
||||
setStatus,
|
||||
loadAvailableUtxos,
|
||||
]);
|
||||
},
|
||||
[
|
||||
templateIdentifier,
|
||||
actionIdentifier,
|
||||
roleIdentifier,
|
||||
template,
|
||||
variables,
|
||||
appService,
|
||||
showError,
|
||||
setStatus,
|
||||
]
|
||||
);
|
||||
|
||||
// ── Add selected inputs + change output to the invitation ───────
|
||||
const addInputsAndOutputs = useCallback(async () => {
|
||||
@@ -465,6 +503,41 @@ export function useActionWizard() {
|
||||
|
||||
const stepType = currentStepData?.type;
|
||||
|
||||
// ── Role selection ──────────────────────────────────────────
|
||||
if (stepType === 'role-select') {
|
||||
const selectedRole = availableRoles[selectedRoleIndex];
|
||||
if (!selectedRole) {
|
||||
showError('Please select a role');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check what the selected role requires
|
||||
const act = template?.actions?.[actionIdentifier ?? ''];
|
||||
const role = act?.roles?.[selectedRole];
|
||||
const requirements = role?.requirements;
|
||||
|
||||
const hasVariables =
|
||||
requirements?.variables && requirements.variables.length > 0;
|
||||
const hasSlots = requirements?.slots && requirements.slots.min > 0;
|
||||
|
||||
// If there is no variables step, the invitation must be created now
|
||||
// because the variables step would normally handle it.
|
||||
if (!hasVariables) {
|
||||
const success = await createInvitationWithVariables(selectedRole);
|
||||
if (!success) return;
|
||||
|
||||
// If we're going to the inputs step, load UTXOs
|
||||
if (hasSlots) {
|
||||
setTimeout(() => loadAvailableUtxos(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Set role — this triggers the useEffect to rebuild steps and advance
|
||||
setRoleIdentifier(selectedRole);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Variables ───────────────────────────────────────────────
|
||||
if (stepType === 'variables') {
|
||||
const emptyVars = variables.filter(
|
||||
(v) => !v.value || v.value.trim() === ''
|
||||
@@ -475,30 +548,53 @@ export function useActionWizard() {
|
||||
);
|
||||
return;
|
||||
}
|
||||
await createInvitationWithVariables();
|
||||
|
||||
// Create the invitation and persist the variable values
|
||||
const success = await createInvitationWithVariables();
|
||||
if (!success) return;
|
||||
|
||||
// Advance, optionally kicking off UTXO loading
|
||||
const nextStepType = steps[currentStep + 1]?.type;
|
||||
if (nextStepType === 'inputs') {
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
setTimeout(() => loadAvailableUtxos(), 100);
|
||||
} else {
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
}
|
||||
|
||||
setFocusArea('content');
|
||||
setFocusedInput(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Inputs ──────────────────────────────────────────────────
|
||||
if (stepType === 'inputs') {
|
||||
await addInputsAndOutputs();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Review ──────────────────────────────────────────────────
|
||||
if (stepType === 'review') {
|
||||
await publishInvitation();
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Generic advance (e.g. publish → done) ───────────────────
|
||||
setCurrentStep((prev) => prev + 1);
|
||||
setFocusArea('content');
|
||||
setFocusedInput(0);
|
||||
}, [
|
||||
currentStep,
|
||||
steps.length,
|
||||
steps,
|
||||
currentStepData,
|
||||
availableRoles,
|
||||
selectedRoleIndex,
|
||||
template,
|
||||
actionIdentifier,
|
||||
variables,
|
||||
showError,
|
||||
createInvitationWithVariables,
|
||||
loadAvailableUtxos,
|
||||
addInputsAndOutputs,
|
||||
publishInvitation,
|
||||
]);
|
||||
@@ -529,6 +625,11 @@ export function useActionWizard() {
|
||||
action,
|
||||
actionName,
|
||||
|
||||
// Role selection
|
||||
availableRoles,
|
||||
selectedRoleIndex,
|
||||
setSelectedRoleIndex,
|
||||
|
||||
// Steps
|
||||
steps,
|
||||
currentStep,
|
||||
|
||||
Reference in New Issue
Block a user