Jake Baker

Registration Starter Kit

Starter Kit used as the basis for over 400 client projects.

Registration Starter Kit

Overview

I developed AirLST Suite Starter, aka Registration Starter Kit, a production-ready foundation for enterprise event registration platforms. Built with Nuxt 4 and Vue 3, this starter kit handles the full lifecycle of event management: from initial invitations through multi-step registration flows to final confirmation and attendee management. This is a migration from, and evolution of a previous boilerplate I created for Nuxt 2 to do the same thing.

The starter kit was designed to solve real-world challenges in event technology: dynamic form generation driven by server configuration, real-time capacity management to prevent overbooking, and complex guest relationship modeling (companions, recommendations). The result is a scalable, maintainable codebase that demonstrates advanced Vue architecture patterns and deep domain expertise in event management.

It doesn’t look like a killer in the design field. But that’s by design. The idea is that this is the base for all projects to start from and then we layer on the client specific changes for layout, styling, theming and functionality.

Tech Stack: Nuxt 4.1.2, Vue 3.5 (Composition API), Pinia, TypeScript, Tailwind CSS, Yup validation


The Challenge

Event registration systems face unique technical challenges that distinguish them from standard form applications:

  1. Complex State Machines: Registration isn’t a linear flow as users can be in different states (Invited, Confirmed, Cancelled) and the UI must adapt accordingly
  2. Nested Data Relationships: Managing companion guests (+1s) with cross-validation and shared capacity constraints
  3. Real-Time Availability: Preventing overbooking when multiple users select the same limited-capacity options simultaneously
  4. Dynamic Form Generation: Supporting event-specific custom fields with varying validation rules
  5. Multi-Tenant Architecture: One codebase serving multiple events with different configurations and branding

Technical Approach

Architecture: Composable-First Design

I structured the application around Vue 3’s Composition API, extracting all business logic into reusable composables. This pattern provides several benefits:

Key Composables:

State Management: Domain-Driven Stores

Using Pinia, I organized state into four domain-specific stores:

// Event Store - Global event context
export const useEventStore = defineStore('event', () => {
	const event = ref(null)
	const getEvent = async () => {
		/* Fetch event config */
	}
	return { event, getEvent }
})

// Form Store - Registration state
export const useFormStore = defineStore('form', () => {
	const form = ref(null)
	const setFormData = () => {
		/* Intelligent form initialization */
	}
	return { form, setFormData, reset }
})

This separation ensures:

Component Architecture: Slot-Based Composition

Registration flows are managed through a flexible view system:

<BaseViews :views="['start', 'confirming', 'confirmed', 'cancelled']">
  <template #start><RegistrationStart /></template>
  <template #confirming><RegistrationConfirming /></template>
  <template #confirmed><RegistrationConfirmed /></template>
</BaseViews>

The BaseViews component handles conditional rendering based on reactive state from the steps store, enabling clean separation of each registration phase.


Key Features & Solutions

1. Real-Time Capacity Management

The Problem: Events often have limited-capacity options (e.g., 50 seats for a workshop). When a user selects an option but hasn’t submitted yet, those selections should still count toward the capacity to prevent overbooking.

My Solution: I built a sophisticated capacity tracking system in the useForm composable that:

  1. Aggregates all selections across the main guest and all companion guests
  2. Distinguishes between “saved” selections (already in the database) and “new” selections (pending submission)
  3. Calculates remaining capacity in real-time, adjusting the UI to disable sold-out options
const mapGroupOptions = (guestSelectRef, companionGuestSelectRef) => {
	const allSelections = [
		guestSelectRef.value,
		...companionGuestSelectRef.value
	].flat()

	const newSelections = {}
	allSelections.forEach((opt) => {
		if (opt.selected && !opt.saved) {
			newSelections[opt.value] = (newSelections[opt.value] || 0) + 1
		}
	})

	const remaining = Math.max(incomingRemaining - adjustment, 0)
	return {
		disabled: remaining === 0,
		label: remaining >= 1 ? `${baseLabel} [${remaining} remaining]` : baseLabel
	}
}

Business Impact: Prevents user frustration from failed submissions due to race conditions, improving conversion rates.

2. Multi-Guest Transaction Handling

The Problem: Users registering with companions need to fill out multiple forms, but all data should be submitted as a single atomic transaction. Validation errors for any guest (main or companion) must be mapped back to the correct form field.

My Solution: I implemented a hierarchical guest model with role-based validation:

const createCompanionGuest = () => {
	const companion = cloneDeep(guestModel)
	companion.id = `local-${uuidv4()}`
	companion.role = 'companion'
	return companion
}

For server error handling, I built a mapping system that translates API errors to nested form paths:

if (role === 'companion') {
	const index = form.value.companion_guests.findIndex((i) => i.id === id)
	name = `companion_guests.${index}.`
}
serverErrors.value.push({ message: value[0].message, name: `${name}${key}` })
formRef.value.setErrors(serverErrors.value)

UX Enhancement: After adding a companion guest, the form automatically focuses the first input field, reducing friction and improving completion rates.

3. Dynamic Form Generation with Validation

The Problem: Different events require different registration fields. The frontend must dynamically render forms based on server-provided field definitions while maintaining robust client-side validation.

My Solution: I created a mapping system that transforms server field definitions into validated form inputs:

Progressive Disclosure: Validation occurs per-step in the multi-step form, so users get immediate feedback without being overwhelmed by all validation errors at once.

4. Intelligent Error UX

When validation fails, the application:

  1. Auto-scrolls to the first error with smooth behavior
  2. Auto-focuses the invalid input for immediate correction
  3. Displays contextualized error messages using i18n for multiple languages
await nextTick()
setTimeout(() => {
	const element = document.getElementById(event.errors[0].id)
	element?.focus()
	element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}, 300)

Technical Highlights

Modern Vue Patterns

Template Refs with Component Exposure: I used Vue 3’s useTemplateRef() to access child component validation schemas from the parent, enabling per-step validation:

const step1Ref = useTemplateRef('step1Ref')
const schema = computed(() => ({
	1: step1Ref.value?.schema || yup.object(),
	2: step2Ref.value?.schema || yup.object()
}))

Computed Properties for Derived State: Complex selections with nested dependencies are modeled as computed properties with automatic tracking:

const guest_select = computed(() =>
	mapOptions({
		field: 'field_2',
		showRemaining: true,
		event,
		form,
		user
	})
)

const select_options_grouped = computed(() =>
	mapGroupOptions(guest_select, companion_guest_select)
)

Performance Optimizations

TypeScript Integration

I leveraged TypeScript for type-safe API calls and composables:

export function useAPI<T>(
	url: string | (() => string),
	options?: UseFetchOptions<T>
) {
	return useFetch(url, {
		...options,
		$fetch: useNuxtApp().$api as typeof $fetch
	})
}

Internationalization

Full i18n support with:


Domain Expertise: Event Management

This project demonstrates specialized knowledge of event technology requirements:

Registration Flow Optimization

Capacity & Conflict Management

Admin/Organizer Tools


Results & Impact

Architectural Benefits:

User Experience:

Developer Experience:


Key Takeaways

Building this event registration platform reinforced several important principles:

  1. Domain Modeling Matters: The quality of your data structures (Guest, Event, Form) determines how easy complex features are to implement. Investing time in thoughtful modeling pays dividends.

  2. Composition Over Inheritance: Vue 3’s Composition API enabled me to share logic without component inheritance, resulting in cleaner, more testable code.

  3. User Context is State: Treating registration as a state machine (not just a form) enabled dynamic, adaptive UX that handles edge cases gracefully.

  4. Validation is Multi-Layered: Client-side validation (Yup) provides immediate feedback, but robust server error mapping is essential for data integrity.

  5. Real-Time Calculations Create Complexity: Managing capacity across multiple related entities (main guest + companions) requires careful state aggregation and reactivity.

This project showcases my ability to build production-grade Vue applications with sophisticated state management, complex business logic, and deep attention to user experience, all while maintaining clean, maintainable architecture patterns.