Het patroon

Een login- of register-form is een gewoon Vue formulier dat na succes drie dingen doet:

  1. De auth store updaten via auth.login(user)
  2. De gebruiker doorsturen naar het dashboard met router.push()
  3. Errors tonen als er iets misging

Deze pagina bouwt voort op Forms, Auth Store Setup en useRouter.

Login formulier

<!-- src/views/LoginView.vue -->
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = useRouter()
const auth = useAuthStore()

const email = ref('')
const password = ref('')
const error = ref('')

const handleLogin = () => {
  error.value = ''

  // Validatie
  if (!email.value || !password.value) {
    error.value = 'Vul alle velden in'
    return
  }

  // Voor nu: dummy login (geen echte API)
  // Bij echte app: fetch naar /api/login (zie verderop)
  auth.login({
    email: email.value,
    name: email.value.split('@')[0]
  })

  router.push('/dashboard')
}
</script>

<template>
  <form @submit.prevent="handleLogin" class="auth-form">
    <h1>Inloggen</h1>

    <label>
      Email:
      <input v-model="email" type="email" required />
    </label>

    <label>
      Wachtwoord:
      <input v-model="password" type="password" required />
    </label>

    <p v-if="error" class="error">{{ error }}</p>

    <button type="submit">Inloggen</button>

    <p>
      Nog geen account?
      <RouterLink to="/register">Registreer</RouterLink>
    </p>
  </form>
</template>

Vier kritieke regels:

  1. @submit.prevent — voorkomt page refresh
  2. auth.login(...) — vult de Pinia store
  3. router.push('/dashboard') — stuurt door
  4. error ref voor feedback

Register formulier

Vrijwel identiek aan login — met een extra naam-veld en password-bevestiging.

<!-- src/views/RegisterView.vue -->
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = useRouter()
const auth = useAuthStore()

const form = ref({
  name: '',
  email: '',
  password: '',
  confirmPassword: ''
})
const error = ref('')

const handleRegister = () => {
  error.value = ''

  // Validatie
  if (form.value.password.length < 8) {
    error.value = 'Wachtwoord moet minimaal 8 tekens zijn'
    return
  }
  if (form.value.password !== form.value.confirmPassword) {
    error.value = 'Wachtwoorden komen niet overeen'
    return
  }

  // Account aanmaken + meteen inloggen
  auth.login({
    name: form.value.name,
    email: form.value.email
  })

  router.push('/dashboard')
}
</script>

<template>
  <form @submit.prevent="handleRegister" class="auth-form">
    <h1>Account aanmaken</h1>

    <label>
      Naam:
      <input v-model="form.name" type="text" required />
    </label>

    <label>
      Email:
      <input v-model="form.email" type="email" required />
    </label>

    <label>
      Wachtwoord:
      <input v-model="form.password" type="password" required />
    </label>

    <label>
      Herhaal wachtwoord:
      <input v-model="form.confirmPassword" type="password" required />
    </label>

    <p v-if="error" class="error">{{ error }}</p>

    <button type="submit">Registreer</button>
  </form>
</template>

Met een echte API

In een echte app vraag je de server om in te loggen. Vervang de dummy auth.login() aanroep door een fetch:

<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const router = useRouter()
const auth = useAuthStore()

const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)

const handleLogin = async () => {
  error.value = ''
  loading.value = true

  try {
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: email.value,
        password: password.value
      })
    })

    if (!res.ok) {
      throw new Error('Ongeldige inloggegevens')
    }

    const userData = await res.json()
    auth.login(userData)
    router.push('/dashboard')
  } catch (e) {
    error.value = e.message
  } finally {
    loading.value = false
  }
}
</script>

<template>
  <form @submit.prevent="handleLogin">
    <input v-model="email" type="email" />
    <input v-model="password" type="password" />

    <p v-if="error">{{ error }}</p>

    <button type="submit" :disabled="loading">
      {{ loading ? 'Bezig...' : 'Inloggen' }}
    </button>
  </form>
</template>

Het patroon is precies wat je leerde op de Loading & Error pagina: loading, error, finally. Hier toegepast op een form-submit i.p.v. een GET-fetch.

Belangrijke Regels

  • Altijd @submit.prevent op het <form>
  • Na auth.login() meteen router.push()
  • Error feedback via een ref, niet alleen alert()
  • Disable de submit-knop tijdens loading bij echte API-calls
  • Wachtwoord nooit in localStorage — alleen het token of user-object

Veelgemaakte Fouten

Fout — geen redirect na login:

const handleLogin = () => {
  auth.login({ name: email.value })
  // ❌ user blijft op login-pagina staan
}

Goed:

const handleLogin = () => {
  auth.login({ name: email.value })
  router.push('/dashboard')    // ✅
}

Fout — error niet resetten:

const handleLogin = async () => {
  // ❌ error van vorige poging blijft staan tijdens nieuwe poging
  try { ... } catch (e) { error.value = e.message }
}

Goed — reset bovenaan:

const handleLogin = async () => {
  error.value = ''                  // ✅ reset
  try { ... } catch (e) { error.value = e.message }
}