Jake Baker

One computed property driving an entire registration UI

8 July 2024

The registration page had six distinct areas. Train-the-trainer bookings. Mixed-format trainer slots. Group owner management. Group trainer assignments. Group participant responses. Mixed-format participant slots. Each area was visible only to users who were eligible for that specific training format, and eligibility depended on a combination of role, certification level, and existing bookings.

My first instinct was v-if conditions on each area component, reading from the auth store and the bookings store. That would have worked, but the conditions were complex enough that I’d be debugging visibility logic across six different template locations. When someone reports “I can’t see the group booking section,” I’d need to check six places to understand why.

Instead I wrote a single userMeta computed that resolves eligibility for all three formats upfront:

const userMeta = computed(() => {
  const trainTheTrainer = { regular: false, nomination: false, senior: false }
  const mixedSlot = { trainer: false, participant: false }
  const groupSlot = { owner: false, trainer: false, participant: false }

  if (!loggedIn.value) return { trainTheTrainer, mixedSlot, groupSlot }

  const profile = user.value.profile

  // Train-the-trainer eligibility
  if (profile.isCertified) trainTheTrainer.regular = true
  if (profile.role === 'lead' && profile.track === 'group')
    trainTheTrainer.nomination = true
  if (profile.role === 'manager' && profile.track === 'group')
    trainTheTrainer.senior = true

  // Mixed slot eligibility
  if (profile.isCertified) mixedSlot.trainer = true
  if (profile.track === 'mixed') mixedSlot.participant = true

  // Group slot eligibility
  if (profile.role === 'manager' && profile.track === 'group')
    groupSlot.owner = true
  if (profile.role === 'lead' && profile.track === 'group')
    groupSlot.trainer = true
  if (profile.role !== 'manager' && profile.track === 'group')
    groupSlot.participant = true

  return { trainTheTrainer, mixedSlot, groupSlot }
})

The registration page then uses userMeta and bookingsMeta (a separate computed that categorises existing bookings by type and status) to control what’s visible:

<AreaTrainTheTrainer v-if="showTrainTheTrainer" />
<AreaMixedTrainer v-if="showMixedTrainer" />
<AreaGroupOwner v-if="showGroupOwner" />
<AreaGroupTrainer v-if="showGroupTrainer" />
<AreaGroupParticipant v-if="showGroupParticipant && !mustWaitToRespond" />
<AreaMixedParticipant v-if="showMixedParticipant" />

Each show* is a computed that reads from userMeta and bookingsMeta:

const showTrainTheTrainer = computed(() => {
  return (
    userMeta.value.trainTheTrainer.regular ||
    !!bookingsMeta.value.GroupTrainer.active.length ||
    userMeta.value.trainTheTrainer.senior ||
    !!bookingsMeta.value.TrainTheTrainer.confirmed.length
  )
})

const showGroupOwner = computed(() => {
  return userMeta.value.groupSlot.owner
})

The bookingsMeta computed does a similar job for bookings — it categorises every booking by type (trainer, participant, mixed) and status (confirmed, invited, cancelled, active), so downstream computeds can ask “does this user have any active group trainer bookings?” without filtering arrays inline.

There are a few things I like about this pattern.

Eligibility bugs have one address. If someone can’t see a section they should see, I check userMeta. If the flags are wrong, the fix is in one place. If the flags are correct, the issue is in the show* computed that reads them.

The template stays readable. v-if="showGroupOwner" is self-documenting. The eligibility logic is complex but it’s not in the template — the template just asks boolean questions.

New formats are additive. When a mixed-format programme was added after the initial two formats, I added a mixedSlot object to userMeta and two new show* computeds. The existing trainer and group logic didn’t change.

The tradeoff is indirection. A developer reading the template needs to trace through showGroupOwneruserMeta.groupSlot.owner → the eligibility rules to understand why something is or isn’t visible. But I’ll take that over six independent condition chains that might disagree with each other.