Events Architecture
This document describes the event-driven architecture used throughout the Alviere UI component library.
Overview
The Alviere UI components use a standardized event system to communicate state changes, user actions, and data flow between components. This event-driven architecture enables:
- Loose Coupling: Components communicate through events rather than direct dependencies
- Composability: Components can be easily combined in flows and workflows
- Extensibility: New event listeners can be added without modifying component internals
- Type Safety: All events have typed interfaces for their detail payloads
Event Types
Core Event Categories
Events are organized into the following categories:
- Input Component Events - User input and validation state changes
- Form Component Events - Form submission, success, and error states
- Flow Component Events - Multi-step flow progress and completion
- Domain-Specific Events - Business logic events (account creation, payment methods, etc.)
Event Names
All event names are defined in ComponentEvents constant (src/types/events.ts):
export const ComponentEvents = {
// Form component events
FORM_SUCCESS: 'form-success',
FORM_ERROR: 'form-error',
FORM_SUBMIT: 'form-submit',
FORM_RESET: 'form-reset',
// Flow component events
FLOW_STEP_COMPLETE: 'flow-step-complete',
FLOW_STEP_ERROR: 'flow-step-error',
FLOW_COMPLETE: 'flow-complete',
FLOW_RESET: 'flow-reset',
// Domain-specific events
ACCOUNT: 'account',
PAYMENT_METHOD: 'payment-method',
} as const;
Event Flow Architecture
Single Component Usage
When a form component is used standalone:
User Action → Component Logic → Backend API Call → Event Dispatch
Example with CreateConsumerAccount:
// 1. User submits form
// 2. Form validates and calls backend
// 3. Backend returns successful response
// 4. Component dispatches TWO events:
// Event A: Domain-specific 'account' event
{
type: 'account',
detail: {
account_uuid: '...',
status: 'ACTIVE'
}
}
// Event B: Generic 'form-success' event
{
type: 'form-success',
detail: {
stepType: 'CREATE_CONSUMER_ACCOUNT',
account_uuid: '...',
success: true
}
}
Multi-Step Flow Usage
When components are orchestrated in a MultiStepFlow:
Step Component → form-success → Flow Controller → FLOW_STEP_COMPLETE → Next Step
The flow automatically:
- Listens for
form-successevents from step components - Accumulates results from completed steps
- Passes accumulated data to subsequent steps
- Dispatches flow-level events for overall progress
Event Details Structure
Form Success Event
Dispatched when a form component successfully completes its operation:
interface FormEventDetail {
stepType: string; // Component identifier (e.g., 'CREATE_CONSUMER_ACCOUNT')
stepIndex?: number; // Step index if in a flow
success: boolean; // Always true for success events
[key: string]: any; // Component-specific data (account_uuid, payment_method_uuid, etc.)
}
Example:
{
stepType: 'CREATE_CONSUMER_ACCOUNT',
stepIndex: 0,
success: true,
account_uuid: '862a2524-ce32-4fa7-b138-e7ac7e93c022'
}
Account Event
Dispatched when an account is created or activated:
interface AccountEventDetail {
account_uuid: string;
status: 'CREATED' | 'ACTIVE';
}
Example:
{
account_uuid: '862a2524-ce32-4fa7-b138-e7ac7e93c022',
status: 'ACTIVE'
}
Payment Method Event
Dispatched when a payment method is created:
interface PaymentMethodEventDetail {
payment_method_uuid: string;
status: string;
}
Example:
{
payment_method_uuid: '12345678-1234-1234-1234-123456789012',
status: 'ACTIVE'
}
Flow Step Complete Event
Dispatched when a flow step completes successfully:
interface StepEventDetail {
stepIndex: number;
stepType: string;
result: any; // Accumulated results from all completed steps
error?: Error;
}
Flow Complete Event
Dispatched when all steps in a flow are completed:
interface FlowEventDetail {
currentStep: number;
totalSteps: number;
stepResults: Record<number, any>;
isComplete: boolean;
result?: any; // Merged results from all steps
}
Usage Examples
Listening to Events in HTML/JavaScript
<alviere-create-consumer-account
jwt="your-jwt-token"
account-uuid="your-account-uuid">
</alviere-create-consumer-account>
<script>
const component = document.querySelector('alviere-create-consumer-account');
// Listen for domain-specific account event
component.addEventListener('account', (event) => {
console.log('Account created:', event.detail.account_uuid);
console.log('Status:', event.detail.status);
});
// Listen for generic form success event
component.addEventListener('form-success', (event) => {
console.log('Form completed:', event.detail);
// Navigate to next step, update UI, etc.
});
// Listen for errors
component.addEventListener('form-error', (event) => {
console.error('Form error:', event.detail.error);
});
</script>
Multi-Step Flow Events
<alviere-multi-step-flow
jwt="your-jwt-token"
config='{"steps":[...]}'>
</alviere-multi-step-flow>
<script>
const flow = document.querySelector('alviere-multi-step-flow');
// Listen for step completion
flow.addEventListener('flow-step-complete', (event) => {
console.log(`Step ${event.detail.stepIndex} completed`);
console.log('Accumulated results:', event.detail.result);
});
// Listen for flow completion
flow.addEventListener('flow-complete', (event) => {
console.log('Flow completed!');
console.log('All results:', event.detail.result);
// Navigate to success page, etc.
});
// Listen for domain events from within the flow
flow.addEventListener('account', (event) => {
console.log('Account created in flow:', event.detail.account_uuid);
// Update external state, analytics, etc.
});
flow.addEventListener('payment-method', (event) => {
console.log('Payment method created:', event.detail.payment_method_uuid);
// Update external state, analytics, etc.
});
</script>
React/Next.js Integration
import { useRef, useEffect, useState } from 'react';
function PaymentFlow() {
const flowRef = useRef<HTMLElement>(null);
const [accountUuid, setAccountUuid] = useState<string | null>(null);
const [paymentMethodUuid, setPaymentMethodUuid] = useState<string | null>(null);
const [isComplete, setIsComplete] = useState(false);
useEffect(() => {
const flow = flowRef.current;
if (!flow) return;
// Listen for account creation
const handleAccount = (event: CustomEvent) => {
setAccountUuid(event.detail.account_uuid);
console.log('Account created:', event.detail.account_uuid);
};
// Listen for payment method creation
const handlePaymentMethod = (event: CustomEvent) => {
setPaymentMethodUuid(event.detail.payment_method_uuid);
console.log('Payment method created:', event.detail.payment_method_uuid);
};
// Listen for flow completion
const handleFlowComplete = (event: CustomEvent) => {
setIsComplete(true);
console.log('Flow completed with results:', event.detail.result);
// Navigate to success page or update app state
};
flow.addEventListener('account', handleAccount as EventListener);
flow.addEventListener('payment-method', handlePaymentMethod as EventListener);
flow.addEventListener('flow-complete', handleFlowComplete as EventListener);
return () => {
flow.removeEventListener('account', handleAccount as EventListener);
flow.removeEventListener('payment-method', handlePaymentMethod as EventListener);
flow.removeEventListener('flow-complete', handleFlowComplete as EventListener);
};
}, []);
return (
<alviere-multi-step-flow
ref={flowRef}
jwt={yourJwt}
config={JSON.stringify(flowConfig)}
/>
);
}
Vue.js Integration
<template>
<alviere-multi-step-flow
ref="flowRef"
:jwt="jwt"
:config="JSON.stringify(flowConfig)"
@account="handleAccount"
@payment-method="handlePaymentMethod"
@flow-complete="handleFlowComplete"
/>
</template>
<script setup>
import { ref } from 'vue';
const flowRef = ref(null);
const accountUuid = ref(null);
const paymentMethodUuid = ref(null);
const isComplete = ref(false);
const handleAccount = (event) => {
accountUuid.value = event.detail.account_uuid;
console.log('Account created:', event.detail);
};
const handlePaymentMethod = (event) => {
paymentMethodUuid.value = event.detail.payment_method_uuid;
console.log('Payment method created:', event.detail);
};
const handleFlowComplete = (event) => {
isComplete.value = true;
console.log('Flow completed:', event.detail);
// Navigate or update state
};
</script>
Event Flow Examples
Example 1: Create Account Flow
This example shows the sequence of events when creating a consumer account:
1. User fills form and submits
↓
2. CreateConsumerAccount component calls backend API
↓
3. Backend returns account with status 'CREATED'
↓
4. Component dispatches 'account' event with status 'CREATED'
↓
5. Component polls backend for activation
↓
6. Backend returns account with status 'ACTIVE'
↓
7. Component dispatches 'account' event with status 'ACTIVE'
↓
8. Component dispatches 'form-success' event
↓
9. If in flow: MultiStepFlow receives 'form-success' and moves to next step
Example 2: Add Bank Account Flow
1. User enters bank details and submits
↓
2. AddBankAccount component calls backend API
↓
3. Backend creates payment method and returns response
↓
4. Component immediately dispatches 'payment-method' event
↓
5. Component refreshes bank account list
↓
6. Component dispatches 'form-success' event
↓
7. If in flow: MultiStepFlow receives 'form-success' and moves to next step
Example 3: Complete Multi-Step Payment Flow
Step 1: CREATE_CONSUMER_ACCOUNT
→ 'account' event (status: CREATED)
→ 'account' event (status: ACTIVE)
→ 'form-success' event { account_uuid: '...' }
→ MultiStepFlow accumulates: { account_uuid: '...' }
→ MultiStepFlow dispatches 'flow-step-complete'
→ Move to Step 2
Step 2: ADD_BANK_ACCOUNT
→ Receives account_uuid from Step 1
→ 'payment-method' event { payment_method_uuid: '...', status: 'ACTIVE' }
→ 'form-success' event { payment_method_uuid: '...' }
→ MultiStepFlow accumulates: { account_uuid: '...', payment_method_uuid: '...' }
→ MultiStepFlow dispatches 'flow-step-complete'
→ Move to Step 3
Step 3: CHECKOUT_CONFIRM
→ Receives account_uuid and payment_method_uuid from previous steps
→ User confirms payment
→ 'form-success' event { amount: 60000 }
→ MultiStepFlow accumulates: { account_uuid: '...', payment_method_uuid: '...', amount: 60000 }
→ MultiStepFlow dispatches 'flow-step-complete'
→ MultiStepFlow dispatches 'flow-complete' with all accumulated results
Data Flow and Accumulation
How Data Flows Between Steps
The MultiStepFlow component automatically accumulates data from completed steps and passes it to subsequent steps:
- Step Completion: When a step completes, it dispatches a
form-successevent with its result data - Accumulation: The flow stores this result in
stepResults[stepIndex] - Propagation: When the next step starts, it receives accumulated data from all previous steps
- Merging: At flow completion, all step results are merged into a single result object
Example accumulation:
// After Step 1 (CREATE_CONSUMER_ACCOUNT)
stepResults = {
0: { stepType: 'CREATE_CONSUMER_ACCOUNT', account_uuid: 'abc-123', success: true }
}
// After Step 2 (ADD_BANK_ACCOUNT)
stepResults = {
0: { stepType: 'CREATE_CONSUMER_ACCOUNT', account_uuid: 'abc-123', success: true },
1: { stepType: 'ADD_BANK_ACCOUNT', payment_method_uuid: 'xyz-789', success: true }
}
// Step 3 receives: { account_uuid: 'abc-123', payment_method_uuid: 'xyz-789' }
Accessing Previous Step Data
Step components can access data from previous steps through their props:
// In getStepProps() function (MultiStepFlow.svelte)
case 'CHECKOUT_CONFIRM':
const previousStepResult = flowState.stepResults[flowState.currentStepIndex - 1];
const accountUuid = previousStepResult?.account_uuid || accountUuid;
const paymentMethodUuid = previousStepResult?.payment_method_uuid || null;
return {
accountUuid,
paymentMethodUuid,
amount: config.amount // From flow-level config
};
Best Practices
1. Always Listen for Domain-Specific Events
For key business operations, listen to domain-specific events (account, payment-method) in addition to generic form events:
// ✅ Good - Listen to specific domain event
element.addEventListener('account', (event) => {
updateUserState(event.detail.account_uuid);
trackAnalytics('account_created', event.detail);
});
// ❌ Less ideal - Only listen to generic event
element.addEventListener('form-success', (event) => {
// Have to check stepType to know what happened
if (event.detail.stepType === 'CREATE_CONSUMER_ACCOUNT') {
// ...
}
});
2. Handle Errors Gracefully
Always provide error handlers for critical operations:
element.addEventListener('form-error', (event) => {
const error = event.detail.error;
console.error('Operation failed:', error);
// Show user-friendly error message
showErrorNotification(error.message);
// Report to error tracking service
reportError(error);
});
3. Use Typed Event Listeners
For TypeScript projects, use typed event listeners:
import type { AccountEventDetail, PaymentMethodEventDetail } from '@alviere/ui-svelte/types';
const element = document.querySelector('alviere-create-consumer-account');
element?.addEventListener('account', ((event: CustomEvent<AccountEventDetail>) => {
// event.detail is now typed
const { account_uuid, status } = event.detail;
}) as EventListener);
4. Clean Up Event Listeners
Always remove event listeners when components unmount:
// React example
useEffect(() => {
const flow = flowRef.current;
const handleAccount = (event) => { /* ... */ };
const handleComplete = (event) => { /* ... */ };
flow.addEventListener('account', handleAccount);
flow.addEventListener('flow-complete', handleComplete);
// Cleanup
return () => {
flow.removeEventListener('account', handleAccount);
flow.removeEventListener('flow-complete', handleComplete);
};
}, []);
Component Event Reference
CreateConsumerAccount
Dispatches:
account(when account is created with status 'CREATED')account(when account becomes 'ACTIVE')form-success(when operation completes)form-error(on failure)
Event Details:
// account event
{ account_uuid: string, status: 'CREATED' | 'ACTIVE' }
// form-success event
{ stepType: 'CREATE_CONSUMER_ACCOUNT', account_uuid: string, success: true, stepIndex?: number }
AddBankAccount
Dispatches:
payment-method(immediately after backend creates payment method)form-success(when operation completes)form-error(on failure)
Event Details:
// payment-method event
{ payment_method_uuid: string, status: 'ACTIVE' }
// form-success event
{ stepType: 'ADD_BANK_ACCOUNT', payment_method_uuid: string, success: true, stepIndex?: number }
CheckoutConfirm
Dispatches:
form-success(when user confirms payment)
Event Details:
// form-success event
{ stepType: 'CHECKOUT_CONFIRM', success: true, amount: number }
MultiStepFlow
Dispatches:
flow-step-complete(after each step completes)flow-step-error(if a step fails)flow-complete(when all steps are done)flow-reset(when flow is reset)
Bubbles Up:
All events from child components bubble up through the flow, so you can listen for account, payment-method, etc. on the flow element itself.
Related Documentation
Flows Components GuideLink to a page in the guide