Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /guides/vue/saas/wizard.md for this page in Markdown format</div>
Vue.js SaaS Wizard - Developer Wiki
Table of Contents
- Getting Started
- Architecture Deep Dive
- Creating Custom Wizards
- Validation System
- State Management
- API Integration
Getting Started
Understanding the Wizard System
The application provides two main wizard types:
- Initial Wizard (
InitialWizard.vue): Full-page onboarding experience - Tasks Wizard (
TasksWizard.vue): Modal-based task completion
Both wizards share common functionality through the createWizardStore factory and validation system.
Basic Setup
javascript
// 1. Import the main app
import { advisableSaasApp } from './apps'
// 2. Set up global data
window.vueData = {
initialData: {
// Your wizard data structure
generalSettings: {},
paymentSettings: {},
// ... other steps
},
wizardType: 'initial' // or 'tasks'
}
// 3. Mount the app (automatic)
window.advisableSaasApp = advisableSaasAppArchitecture Deep Dive
Component Hierarchy
AdvisableSaasApp (Root)
├── InitialWizard
│ ├── Step Components (dynamic)
│ └── Progress Indicators
├── TasksWizard
│ ├── Modal Container
│ ├── Step Components (dynamic)
│ └── Navigation Controls
└── TasksButton
└── Progress RingData Flow
- User Input → Component
- Component → Store Action (
updateStepData) - Store → Validation System
- Validation → Error State
- Auto-save → API Persistence
Store Architecture
javascript
// Store modules are created using the factory pattern
const wizardStore = createWizardStore({
name: 'myWizard',
steps: [
{ name: 'step1', component: 'Step1Component' },
{ name: 'step2', component: 'Step2Component' }
],
createSchemas: (l10n, steps) => ({
step1: { /* validation schema */ },
step2: { /* validation schema */ }
})
})Creating Custom Wizards
Step 1: Define Your Wizard Configuration
javascript
// store/myCustomWizard.js
import { createWizardStore } from './createWizardStore'
import Step1 from '../components/steps/Step1.vue'
import Step2 from '../components/steps/Step2.vue'
const steps = [
{
name: 'basicInfo',
component: Step1,
title: 'wizard.steps.basicInfo.title',
label: 'wizard.steps.basicInfo.label',
icon: 'fa-solid fa-info-circle'
},
{
name: 'advanced',
component: Step2,
title: 'wizard.steps.advanced.title',
label: 'wizard.steps.advanced.label',
icon: 'fa-solid fa-cog'
}
]
const createSchemas = (l10n, steps) => ({
basicInfo: {
name: {
type: 'string',
required: true,
minLength: 2,
maxLength: 50
},
email: {
type: 'string',
required: true,
pattern: ValidationRules.email()
}
},
advanced: {
settings: {
type: 'object',
required: false
}
}
})
export default createWizardStore({
name: 'myCustomWizard',
steps,
createSchemas
})Step 2: Create Step Components
vue
<!-- components/steps/Step1.vue -->
<template>
<div class="step-container">
<div class="step-header">
<h2 class="step-title">{{ l10n.t('wizard.steps.basicInfo.title') }}</h2>
</div>
<form class="step-form">
<div class="form-group">
<label class="form-label" :data-required="true">
{{ l10n.t('wizard.fields.name.label') }}
</label>
<input
v-model="stepData.name"
type="text"
class="form-control"
:class="{ error: hasFieldError('name') }"
@blur="validateField('name')"
/>
<span v-if="hasFieldError('name')" class="error-message">
{{ getFieldError('name') }}
</span>
</div>
<div class="form-actions">
<button type="button" class="btn btn-outline" @click="$emit('previous')">
Previous
</button>
<button type="button" class="btn btn-primary" @click="handleNext">
Next
</button>
</div>
</form>
</div>
</template>
<script>
import { useWizardStore } from '../../composables/useWizardStore'
import { useFormValidation } from '../../composables/useFormValidation'
import useLocalization from '../../../productBundles/composables/useLocalization'
export default {
name: 'Step1',
emits: ['next', 'previous'],
setup(props, { emit }) {
const stepName = 'basicInfo'
const { stepData } = useWizardStore(stepName)
const { validateField, hasFieldError, getFieldError, validateStep } = useFormValidation(stepName, 'myCustomWizard')
const l10n = useLocalization(['wizard'], ['wizard.'])
const handleNext = async () => {
const result = await validateStep()
if (result.isValid) {
emit('next')
}
}
return {
stepData,
validateField,
hasFieldError,
getFieldError,
handleNext,
l10n
}
}
}
</script>Step 3: Register Your Wizard
javascript
// store/index.js
import myCustomWizard from './myCustomWizard'
const store = new Vuex.Store({
modules: {
common,
initialWizard,
tasksWizard,
myCustomWizard // Add your wizard here
}
})Validation System
Schema Definition
javascript
const schema = {
// Basic field validation
fieldName: {
type: 'string', // 'string', 'number', 'boolean', 'array', 'object'
required: true, // or function: (formData) => boolean
minLength: 5,
maxLength: 100,
pattern: /^[A-Za-z]+$/ // RegExp or ValidationRule function
},
// Nested object validation
address: {
street: { type: 'string', required: true },
city: { type: 'string', required: true },
zipCode: { type: 'string', pattern: /^\d{5}$/ }
},
// Array validation
items: {
type: 'array',
required: true
},
// Dynamic field validation (using dot notation)
'items[0].name': {
type: 'string',
required: true
}
}Custom Validation Rules
javascript
import { ValidationRules } from '../validation'
// Using built-in rules
const emailRule = ValidationRules.email('Please enter a valid email')
const phoneRule = ValidationRules.phone('Invalid phone number')
// Custom validation function
const customRule = ValidationRules.custom(
(value, formData) => {
// Your validation logic
if (formData.type === 'premium' && !value) {
return false
}
return true
},
'This field is required for premium accounts'
)
// Pattern-based validation
const schema = {
email: {
type: 'string',
required: true,
pattern: emailRule
},
phone: {
type: 'string',
pattern: phoneRule
},
premiumFeature: {
type: 'string',
pattern: customRule
}
}Dynamic Validation
For validation rules that change based on form data:
javascript
// Register dynamic validation generators
import { dynamicValidationRegistry } from '../validation/dynamicValidationRegistry'
dynamicValidationRegistry.register('myWizard', {
stepName: new Map([
['paymentFields', (formData) => {
const fields = {}
if (formData.paymentMethod === 'credit_card') {
fields['cardNumber'] = {
type: 'string',
required: true,
pattern: /^\d{16}$/
}
fields['expiryDate'] = {
type: 'string',
required: true,
pattern: /^\d{2}\/\d{2}$/
}
}
if (formData.paymentMethod === 'paypal') {
fields['paypalEmail'] = {
type: 'string',
required: true,
pattern: ValidationRules.email()
}
}
return fields
}]
])
})State Management
Store Structure
Each wizard store contains:
javascript
state: {
currentStep: 1,
steps: [...], // Step definitions
wizardData: {...}, // Form data
validationErrors: {...}, // Validation state
touchedFields: {...}, // Field interaction tracking
validators: {...}, // Validator instances
isLoading: false,
isSaving: false,
wizardProgress: {...}, // Progress tracking
wizardConfig: {...} // External configuration
}Key Actions
javascript
// Navigation
await store.dispatch('myWizard/setCurrentStep', 2)
await store.dispatch('myWizard/completeStep', 1)
// Data management
store.dispatch('myWizard/updateStepData', {
section: 'stepName',
data: { field: 'value' }
})
// Validation
const result = await store.dispatch('myWizard/validateStep', {
stepName: 'basicInfo',
l10n
})
const fieldResult = await store.dispatch('myWizard/validateField', {
stepName: 'basicInfo',
fieldName: 'email',
l10n
})
// Persistence
await store.dispatch('myWizard/saveWizardProgress')
await store.dispatch('myWizard/loadWizardProgress', { l10n })Using Composables
javascript
// In your component
import { useWizardStore } from '../composables/useWizardStore'
import { useFormValidation } from '../composables/useFormValidation'
export default {
setup() {
const stepName = 'myStep'
const wizardNamespace = 'myWizard'
// Get reactive step data
const { stepData } = useWizardStore(stepName)
// Get validation methods
const {
validateField,
validateStep,
hasFieldError,
getFieldError,
isStepValid
} = useFormValidation(stepName, wizardNamespace)
return {
stepData,
validateField,
validateStep,
hasFieldError,
getFieldError,
isStepValid
}
}
}API Integration
Expected Endpoints
javascript
// Configuration endpoint
GET /saas/api/wizard/data
Response: {
success: true,
data: {
paymentGateways: [...],
transporters: [...],
countries: [...],
languages: [...]
}
}
// State persistence
POST /saas/api/wizard/saveState
Body: {
wizardName: 'initialWizard',
currentStep: 2,
steps: [...],
wizardData: {...},
wizardProgress: {...}
}
// State loading
GET /saas/api/wizard/getState?wizard_name=initialWizard
Response: {
success: true,
data: {
currentStep: 2,
wizardData: {...},
// ... saved state
}
}
// Wizard completion
POST /saas/api/wizard/complete
Body: FormData with wizardData and files
Response: {
success: true,
redirect: '/dashboard'
}