Real-World Examples

Practical examples of using Laravel Headless Wizard in production applications.


User Onboarding Wizard

A complete 3-step user onboarding flow with profile creation, preferences, and email verification.

Step 1: Personal Information

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Models\User;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class PersonalInfoStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'personal-info',
            title: 'Personal Information',
            order: 1
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\PersonalInfoRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        $user = User::create([
            'name' => $data->get('name'),
            'email' => $data->get('email'),
            'date_of_birth' => $data->get('date_of_birth'),
        ]);

        return StepResult::success(
            data: ['user_id' => $user->id],
            message: 'Profile created successfully!'
        );
    }
}

Step 2: Preferences (Optional)

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Models\UserPreferences;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class PreferencesStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'preferences',
            title: 'Your Preferences',
            order: 2,
            isOptional: true,
            canSkip: true
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\PreferencesRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        $userId = $this->getWizardData('personal-info.user_id');

        UserPreferences::create([
            'user_id' => $userId,
            'newsletter' => $data->get('newsletter', false),
            'notifications' => $data->get('notifications', true),
            'theme' => $data->get('theme', 'light'),
        ]);

        return StepResult::success(
            message: 'Preferences saved!'
        );
    }
}

Step 3: Email Verification

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Mail\VerificationEmail;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class EmailVerificationStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'email-verification',
            title: 'Verify Your Email',
            order: 3,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\EmailVerificationRequest::class;
    }

    public function getDependencies(): array
    {
        return ['personal-info'];
    }


    public function beforeProcess(StepData $data): void
    {
        $userId = $this->getWizardData('personal-info.user_id');
        $user = User::find($userId);

        Mail::to($user)->send(new VerificationEmail());
    }

    public function process(StepData $data): StepResult
    {
        $userId = $this->getWizardData('personal-info.user_id');
        $user = User::find($userId);

        if ($user->verification_code !== $data->get('verification_code')) {
            return StepResult::failure(
                message: 'Invalid verification code',
                errors: ['verification_code' => ['The code is incorrect']]
            );
        }

        $user->update(['email_verified_at' => now()]);

        return StepResult::success(
            message: 'Email verified successfully!'
        );
    }
}

E-Commerce Checkout Wizard

A complete checkout flow with cart, shipping, payment, and confirmation.

Step 1: Cart Review

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Models\Cart;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class CartReviewStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'cart-review',
            title: 'Review Cart',
            order: 1,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\CartReviewRequest::class;
    }

    public function shouldSkip(array $wizardData): bool
    {
        $cart = Cart::where('user_id', auth()->id())->first();
        return $cart?->items()->count() === 0;
    }


    public function process(StepData $data): StepResult
    {
        $cart = Cart::firstOrCreate(['user_id' => auth()->id()]);
        
        foreach ($data->get('items', []) as $item) {
            $cart->items()->updateOrCreate(
                ['product_id' => $item['id']],
                ['quantity' => $item['quantity']]
            );
        }

        return StepResult::success(
            data: ['cart_id' => $cart->id],
            message: 'Cart updated'
        );
    }
}

Step 2: Shipping Address

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Models\ShippingAddress;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class ShippingAddressStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'shipping-address',
            title: 'Shipping Address',
            order: 2,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\ShippingAddressRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        $address = ShippingAddress::create([
            'user_id' => auth()->id(),
            'street' => $data->get('street'),
            'city' => $data->get('city'),
            'state' => $data->get('state'),
            'zip' => $data->get('zip'),
            'country' => $data->get('country'),
        ]);

        return StepResult::success(
            data: ['address_id' => $address->id],
            message: 'Shipping address saved'
        );
    }
}

Step 3: Payment

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use App\Services\PaymentGateway;
use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class PaymentStep extends AbstractStep
{
    public function __construct(
        private readonly PaymentGateway $paymentGateway
    ) {
        parent::__construct(
            id: 'payment',
            title: 'Payment',
            order: 3,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\PaymentRequest::class;
    }

    public function getDependencies(): array
    {
        return ['cart-review', 'shipping-address'];
    }


    public function process(StepData $data): StepResult
    {
        $cartId = $this->getWizardData('cart-review.cart_id');
        $addressId = $this->getWizardData('shipping-address.address_id');

        try {
            $payment = $this->paymentGateway->charge([
                'cart_id' => $cartId,
                'address_id' => $addressId,
                'payment_method' => $data->get('payment_method'),
                'card_number' => $data->get('card_number'),
                'card_exp' => $data->get('card_exp'),
                'card_cvv' => $data->get('card_cvv'),
            ]);

            return StepResult::success(
                data: ['payment_id' => $payment->id],
                message: 'Payment processed successfully'
            );
        } catch (\Exception $e) {
            return StepResult::failure(
                message: 'Payment failed: ' . $e->getMessage()
            );
        }
    }
}

Survey Wizard with Conditional Logic

A dynamic survey that shows/hides questions based on previous answers.

Step 1: Basic Info

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class BasicInfoStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'basic-info',
            title: 'Basic Information',
            order: 1,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\BasicInfoRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        return StepResult::success(
            data: $data->all(),
            message: 'Basic information saved'
        );
    }
}

Step 2: Employment Details (Conditional)

<?php

namespace App\Wizards\OnboardingWizard\Steps;

use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class EmploymentDetailsStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'employment-details',
            title: 'Employment Details',
            order: 2,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\EmploymentDetailsRequest::class;
    }

    public function shouldSkip(array $wizardData): bool
    {
        return $wizardData['basic-info']['employment_status'] !== 'employed';
    }


    public function process(StepData $data): StepResult
    {
        return StepResult::success(
            data: $data->all(),
            message: 'Employment details saved'
        );
    }
}

Complete Demo: Registration Wizard

This is a complete working example from the demo application showing both Blade and Vue implementations.

Project Structure

app/
└── Wizards/
    └── RegistrationWizard/
        └── Steps/
            ├── PersonalInfoStep.php
            ├── PreferencesStep.php
            └── SummaryStep.php
            
app/Http/
├── Controllers/
│   └── WizardViewController.php
└── Requests/
    └── Wizards/
        ├── PersonalInfoRequest.php
        └── PreferencesRequest.php

Steps Implementation

PersonalInfoStep.php

<?php

namespace App\Wizards\RegistrationWizard\Steps;

use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class PersonalInfoStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'personal-info',
            title: 'Personal Info',
            order: 1,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\PersonalInfoRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        return StepResult::success(
            data: $data->all(),
            message: 'Step completed successfully'
        );
    }
}

PreferencesStep.php

<?php

namespace App\Wizards\RegistrationWizard\Steps;

use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class PreferencesStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'preferences',
            title: 'Preferences',
            order: 2,
        );
    }

    public function getFormRequest(): ?string
    {
        return \App\Http\Requests\Wizards\PreferencesRequest::class;
    }

    public function process(StepData $data): StepResult
    {
        return StepResult::success(
            data: $data->all(),
            message: 'Step completed successfully'
        );
    }
}

SummaryStep.php

<?php

namespace App\Wizards\RegistrationWizard\Steps;

use Invelity\WizardPackage\Steps\AbstractStep;
use Invelity\WizardPackage\ValueObjects\StepData;
use Invelity\WizardPackage\ValueObjects\StepResult;

class SummaryStep extends AbstractStep
{
    public function __construct()
    {
        parent::__construct(
            id: 'summary',
            title: 'Summary',
            order: 3,
        );
    }

    public function getFormRequest(): ?string
    {
        return null; // No validation needed for summary
    }

    public function process(StepData $data): StepResult
    {
        return StepResult::success(
            data: $data->all(),
            message: 'Ready to complete'
        );
    }
}

FormRequest Validators

PersonalInfoRequest.php

<?php

namespace App\Http\Requests\Wizards;

use Illuminate\Foundation\Http\FormRequest;

class PersonalInfoRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'max:255'],
            'age' => ['required', 'integer', 'min:18'],
        ];
    }
}

PreferencesRequest.php

<?php

namespace App\Http\Requests\Wizards;

use Illuminate\Foundation\Http\FormRequest;

class PreferencesRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'theme' => ['required', 'in:light,dark,auto'],
            'notifications' => ['required', 'array'],
            'notifications.email' => ['required', 'boolean'],
            'notifications.sms' => ['required', 'boolean'],
        ];
    }
}

Controller (Supports Both Blade and Vue)

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Invelity\WizardPackage\Contracts\WizardManagerInterface;

class WizardViewController extends Controller
{
    public function __construct(
        private readonly WizardManagerInterface $wizardManager
    ) {}

    public function show(string $wizard, string $step)
    {
        $this->wizardManager->initialize($wizard);
        
        $wizardData = $this->wizardManager->getAllData();
        
        return view("wizards.steps.{$step}", [
            'wizardData' => $wizardData,
        ]);
    }

    public function store(Request $request, string $wizard, string $step)
    {
        $this->wizardManager->initialize($wizard);
        
        $result = $this->wizardManager->processStep($step, $request->all());
        
        if (!$result->success) {
            if ($request->expectsJson()) {
                return response()->json([
                    'success' => false,
                    'errors' => $result->errors,
                ], 422);
            }
            return back()->withErrors($result->errors)->withInput();
        }
        
        $currentStep = $this->wizardManager->getCurrentStep();
        
        if ($request->expectsJson()) {
            if ($currentStep) {
                return response()->json([
                    'success' => true,
                    'completed' => false,
                    'next_step' => $currentStep->getId(),
                    'data' => $result->data,
                ]);
            }
            
            return response()->json([
                'success' => true,
                'completed' => true,
                'data' => $result->data,
            ]);
        }
        
        if ($currentStep) {
            return redirect()->route('wizard.show', [
                'wizard' => $wizard,
                'step' => $currentStep->getId(),
            ]);
        }
        
        return redirect()->route('wizard.show', [
            'wizard' => $wizard,
            'step' => 'summary',
        ]);
    }
}

Routes

// routes/web.php

use App\Http\Controllers\WizardViewController;

Route::prefix('wizard')->group(function () {
    Route::get('/{wizard}/{step}', [WizardViewController::class, 'show'])
        ->name('wizard.show');
    
    Route::post('/{wizard}/{step}', [WizardViewController::class, 'store'])
        ->name('wizard.store');
});

// Demo routes
Route::get('/blade/demo', function () {
    return redirect()->route('wizard.show', [
        'wizard' => 'registration',
        'step' => 'personal-info'
    ]);
});

Route::get('/vue/demo', function () {
    return view('vue-demo');
});

CSRF Configuration (for Vue/API)

// bootstrap/app.php

->withMiddleware(function (Middleware $middleware): void {
    $middleware->validateCsrfTokens(except: [
        'wizard/*',
    ]);
})

Environment Configuration

# Use file-based sessions for wizard state persistence
SESSION_DRIVER=file

Frontend Integration Examples

React + Axios

import { useState, useEffect } from 'react';
import axios from 'axios';

function OnboardingWizard() {
    const [step, setStep] = useState(null);
    const [formData, setFormData] = useState({});
    const [progress, setProgress] = useState({});

    useEffect(() => {
        fetchCurrentStep();
    }, []);

    const fetchCurrentStep = async () => {
        const response = await axios.get('/wizard/onboarding');
        setStep(response.data.step);
        setProgress(response.data.progress);
    };

    const submitStep = async (stepId, data) => {
        try {
            const response = await axios.post(`/wizard/onboarding/${stepId}`, data);
            
            if (response.data.success) {
                if (response.data.next_step) {
                    setStep(response.data.next_step);
                } else {
                    alert('Wizard completed!');
                }
                setProgress(response.data.progress);
            }
        } catch (error) {
            console.error('Validation errors:', error.response.data.errors);
        }
    };

    return (
        <div>
            <h1>{step?.title}</h1>
            <div className="progress">
                {progress.percentage}% complete
            </div>
            <form onSubmit={(e) => {
                e.preventDefault();
                submitStep(step.id, formData);
            }}>
                {/* Form fields */}
                <button type="submit">Next</button>
            </form>
        </div>
    );
}

Vue 3 + Composition API

<template>
  <div class="wizard">
    <h1></h1>
    
    <div class="progress-bar">
      <div 
        class="progress-fill" 
        :style="{ width: progress.percentage + '%' }"
      ></div>
    </div>

    <form @submit.prevent="submitStep">
      <!-- Dynamic form fields based on step.id -->
      <component :is="stepComponent" v-model="formData" />
      
      <div class="buttons">
        <button 
          type="button" 
          @click="goBack" 
          :disabled="!navigation.can_go_back"
        >
          Back
        </button>
        <button type="submit">Next</button>
      </div>
    </form>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue';
import axios from 'axios';

const step = ref(null);
const progress = ref({});
const navigation = ref({});
const formData = ref({});

const stepComponent = computed(() => {
  const componentMap = {
    'personal-info': PersonalInfoForm,
    'preferences': PreferencesForm,
    'email-verification': EmailVerificationForm
  };
  return componentMap[step.value?.id];
});

onMounted(async () => {
  const response = await axios.get('/wizard/onboarding');
  step.value = response.data.step;
  progress.value = response.data.progress;
  navigation.value = response.data.navigation;
});

const submitStep = async () => {
  try {
    const response = await axios.post(
      `/wizard/onboarding/${step.value.id}`,
      formData.value
    );
    
    if (response.data.success) {
      step.value = response.data.next_step;
      progress.value = response.data.progress;
      navigation.value = response.data.navigation;
      formData.value = {};
    }
  } catch (error) {
    console.error(error.response.data.errors);
  }
};

const goBack = async () => {
  const response = await axios.get(`/wizard/onboarding/${navigation.value.previous_step.id}`);
  step.value = response.data.step;
  navigation.value = response.data.navigation;
};
</script>

Livewire Component

<?php

namespace App\Livewire;

use Invelity\WizardPackage\Facades\Wizard;
use Livewire\Component;

class OnboardingWizard extends Component
{
    public $currentStep;
    public $progress;
    public $formData = [];
    
    public function mount()
    {
        Wizard::initialize('onboarding');
        $this->currentStep = Wizard::getCurrentStep();
        $this->progress = Wizard::getProgress();
    }

    public function submitStep()
    {
        $result = Wizard::processStep(
            $this->currentStep->getId(),
            $this->formData
        );

        if ($result->isSuccess()) {
            $nextStep = Wizard::getNextStep();
            
            if ($nextStep) {
                $this->currentStep = $nextStep;
                $this->formData = [];
            } else {
                Wizard::complete();
                return redirect()->route('dashboard');
            }
        } else {
            $this->addError('form', $result->message());
        }

        $this->progress = Wizard::getProgress();
    }

    public function render()
    {
        return view('livewire.onboarding-wizard');
    }
}

Blade Wizard with Components

Complete Blade implementation using pre-built components:

personal-info.blade.php

<x-wizard::layout title="User Registration">
    <x-wizard::progress-bar :steps="$steps" :currentStep="'personal-info'" />
    
    <x-wizard::form-wrapper :action="route('wizard.registration.store', 'personal-info')">
        <h2>Personal Information</h2>
        <p class="text-gray-600 mb-4">Please provide your basic information</p>
        
        <div class="space-y-4">
            <div>
                <label for="name" class="block text-sm font-medium">Full Name</label>
                <input 
                    type="text" 
                    id="name" 
                    name="name" 
                    value=""
                    class="mt-1 block w-full rounded-md border-gray-300"
                    required
                />
                @error('name')
                    <p class="text-red-500 text-sm mt-1"></p>
                @enderror
            </div>
            
            <div>
                <label for="email" class="block text-sm font-medium">Email Address</label>
                <input 
                    type="email" 
                    id="email" 
                    name="email" 
                    value=""
                    class="mt-1 block w-full rounded-md border-gray-300"
                    required
                />
                @error('email')
                    <p class="text-red-500 text-sm mt-1"></p>
                @enderror
            </div>
            
            <div>
                <label for="age" class="block text-sm font-medium">Age</label>
                <input 
                    type="number" 
                    id="age" 
                    name="age" 
                    value=""
                    class="mt-1 block w-full rounded-md border-gray-300"
                    required
                />
                @error('age')
                    <p class="text-red-500 text-sm mt-1"></p>
                @enderror
            </div>
        </div>
        
        <x-wizard::step-navigation 
            :canGoBack="false"
            :canGoForward="true"
            :isLastStep="false"
            :nextStep="'preferences'"
            nextText="Continue to Preferences"
        />
    </x-wizard::form-wrapper>
</x-wizard::layout>

Vue SPA Wizard with useWizard()

Complete Vue 3 implementation using the composable:

RegistrationWizard.vue

<template>
    <div v-if="!state.loading" class="max-w-2xl mx-auto p-6">
        <!-- Progress Bar -->
        <div class="mb-8">
            <div class="flex justify-between mb-2">
                <span class="text-sm font-medium">
                    Step  of 
                </span>
                <span class="text-sm font-medium">
                    % Complete
                </span>
            </div>
            <div class="w-full bg-gray-200 rounded-full h-2">
                <div 
                    class="bg-blue-600 h-2 rounded-full transition-all duration-300"
                    :style="{ width: Math.round((state.currentStepIndex + 1) / state.steps.length * 100) + '%' }"
                ></div>
            </div>
        </div>
        
        <!-- Step Content -->
        <div class="bg-white rounded-lg shadow p-6">
            <h2 class="text-2xl font-bold mb-4"></h2>
            
            <!-- Personal Info Step -->
            <form v-if="currentStep?.id === 'personal-info'" @submit.prevent="handleSubmit">
                <div class="space-y-4">
                    <div>
                        <label class="block text-sm font-medium mb-1">Full Name</label>
                        <input 
                            v-model="formData.name"
                            type="text" 
                            class="w-full px-3 py-2 border rounded-md"
                            :class="{ 'border-red-500': getFieldError('name') }"
                        />
                        <p v-if="getFieldError('name')" class="text-red-500 text-sm mt-1">
                            
                        </p>
                    </div>
                    
                    <div>
                        <label class="block text-sm font-medium mb-1">Email</label>
                        <input 
                            v-model="formData.email"
                            type="email" 
                            class="w-full px-3 py-2 border rounded-md"
                            :class="{ 'border-red-500': getFieldError('email') }"
                        />
                        <p v-if="getFieldError('email')" class="text-red-500 text-sm mt-1">
                            
                        </p>
                    </div>
                    
                    <div>
                        <label class="block text-sm font-medium mb-1">Age</label>
                        <input 
                            v-model="formData.age"
                            type="number" 
                            class="w-full px-3 py-2 border rounded-md"
                            :class="{ 'border-red-500': getFieldError('age') }"
                        />
                        <p v-if="getFieldError('age')" class="text-red-500 text-sm mt-1">
                            
                        </p>
                    </div>
                </div>
                
                <div class="flex justify-between mt-6">
                    <button 
                        v-if="canGoBack"
                        type="button"
                        @click="handleBack"
                        class="px-4 py-2 border rounded-md hover:bg-gray-50"
                    >
                        Previous
                    </button>
                    <button 
                        type="submit"
                        :disabled="state.loading"
                        class="ml-auto px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
                    >
                        
                    </button>
                </div>
            </form>
            
            <!-- Preferences Step -->
            <form v-else-if="currentStep?.id === 'preferences'" @submit.prevent="handleSubmit">
                <div class="space-y-4">
                    <div>
                        <label class="block text-sm font-medium mb-1">Theme</label>
                        <select 
                            v-model="formData.theme"
                            class="w-full px-3 py-2 border rounded-md"
                        >
                            <option value="light">Light</option>
                            <option value="dark">Dark</option>
                            <option value="auto">Auto</option>
                        </select>
                    </div>
                    
                    <div>
                        <label class="block text-sm font-medium mb-2">Notifications</label>
                        <div class="space-y-2">
                            <label class="flex items-center">
                                <input 
                                    v-model="formData.notifications.email"
                                    type="checkbox" 
                                    class="mr-2"
                                />
                                Email notifications
                            </label>
                            <label class="flex items-center">
                                <input 
                                    v-model="formData.notifications.sms"
                                    type="checkbox" 
                                    class="mr-2"
                                />
                                SMS notifications
                            </label>
                        </div>
                    </div>
                </div>
                
                <div class="flex justify-between mt-6">
                    <button 
                        type="button"
                        @click="handleBack"
                        class="px-4 py-2 border rounded-md hover:bg-gray-50"
                    >
                        Previous
                    </button>
                    <button 
                        type="submit"
                        :disabled="state.loading"
                        class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
                    >
                        
                    </button>
                </div>
            </form>
            
            <!-- Summary Step -->
            <div v-else-if="currentStep?.id === 'summary'">
                <div class="bg-gray-50 rounded-md p-4 mb-4">
                    <h3 class="font-medium mb-2">Registration Summary</h3>
                    <dl class="space-y-1">
                        <div>
                            <dt class="text-sm text-gray-600">Name:</dt>
                            <dd class="font-medium"></dd>
                        </div>
                        <div>
                            <dt class="text-sm text-gray-600">Email:</dt>
                            <dd class="font-medium"></dd>
                        </div>
                        <div>
                            <dt class="text-sm text-gray-600">Theme:</dt>
                            <dd class="font-medium"></dd>
                        </div>
                    </dl>
                </div>
                
                <div class="flex justify-between mt-6">
                    <button 
                        type="button"
                        @click="handleBack"
                        class="px-4 py-2 border rounded-md hover:bg-gray-50"
                    >
                        Previous
                    </button>
                    <button 
                        @click="handleComplete"
                        :disabled="state.loading"
                        class="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50"
                    >
                        Complete Registration
                    </button>
                </div>
            </div>
        </div>
    </div>
    
    <div v-else class="max-w-2xl mx-auto p-6">
        <div class="text-center">Loading...</div>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useWizard } from '@/composables/useWizard';
import { useRouter } from 'vue-router';

const router = useRouter();

const { 
    state, 
    currentStep, 
    canGoBack,
    canGoForward, 
    isLastStep,
    initialize, 
    submitStep, 
    goToStep,
    getFieldError,
    clearErrors
} = useWizard('registration');

const formData = ref<Record<string, any>>({
    notifications: { email: false, sms: false }
});

onMounted(async () => {
    await initialize();
});

const handleSubmit = async () => {
    clearErrors();
    const result = await submitStep(formData.value);
    
    if (result.success) {
        formData.value = { notifications: { email: false, sms: false } };
        
        if (result.completed) {
            router.push('/dashboard');
        }
    }
};

const handleBack = async () => {
    const previousStep = state.steps[state.currentStepIndex - 1];
    if (previousStep) {
        await goToStep(previousStep.id);
    }
};

const handleComplete = async () => {
    const result = await submitStep({});
    if (result.success) {
        router.push('/dashboard');
    }
};
</script>

Next Steps