Resolve the request
"Add Confirm and Cancel." Before adding buttons, clarify what each action changes. Our exercise policy permits draft -> confirmed, draft -> cancelled and confirmed -> cancelled. Cancelled is terminal. Only drafts can be edited. Confirmation requires a valid quote. This simple policy has no delivery or inventory model.
Write the transition table and the meaning of each state. A cancelled order is not merely a draft whose button became gray.
export function transition(order, action, validateForConfirmation) {
if (action === 'confirm' && order.status === 'draft') {
validateForConfirmation(order);
return { ...order, status: 'confirmed' };
}
if (action === 'cancel' && ['draft', 'confirmed'].includes(order.status)) {
return { ...order, status: 'cancelled' };
}
throw new Error('Action is not allowed in the current state');
}
Pass your existing calculateQuote as validateForConfirmation: it validates and throws on invalid input, and its returned quote need not be used here. This function handles only a local simulation. It neither saves a server record nor grants permission to a real user.
Separate two axes
Order status is draft, confirmed or cancelled. Interface phase may be ready, pending or error. Confirming a draft might set phase pending while a server operation runs; do not mark it confirmed before receiving authority to do so. Later modules model that boundary.
Build it yourself
Add a visible status label. Connect action buttons to the transition function, not direct property assignment inside arbitrary handlers. After a permitted transition, replace state and render. Catch rejection at the interaction boundary and explain it without erasing the order.
Verification
Confirm a valid draft, try to confirm it again, cancel it, then try to edit or confirm the cancelled order. Under this local policy the repeated confirmation is rejected, cancellation succeeds and later edits/confirmation fail. Assert the original object remains unchanged after each successful transition.
Change request and correction
"A confirmed order can only be cancelled within ten minutes." You need a confirmation timestamp and an authoritative clock policy. Decide the exact boundary and timezone representation before implementing. A browser clock is not reliable authorization evidence; keep this as a proposed extension until the server contract is defined.
Review
A transition table is executable reasoning. Test forbidden paths as deliberately as allowed ones. Hiding a button is helpful communication, but the transition function must still reject the action when called directly.