Introductie

Wat bouw je precies?

Je maakt een Film Tracker: een app waarin je films kunt bijhouden die je wilt kijken of al gezien hebt. Denk aan een overzicht met filmkaarten, filters op genre en status, en een detailpagina per film.

Dit moet er minimaal in komen:

  • Een overzichtspagina met filmkaarten
  • Films kunnen toevoegen via een formulier
  • Films filteren op genre en status (gezien / wil kijken)
  • Films markeren als gezien of nog te kijken
  • Zoeken in films
  • Films bewaren in localStorage
  • Meerdere pagina's met Vue Router (overzicht, login, filmdetail)
  • Een Pinia store voor de inlogstatus

Hoe werk je deze opdrachten uit?

  • Gebruik één Vue oefenproject voor alle niveaus van deze Film Tracker
  • Breid per niveau je bestaande code uit in plaats van opnieuw te beginnen
  • Maak aparte componenten, pagina's en folders zodat je structuur netjes blijft
  • Begin klein: eerst template, daarna reactivity, daarna interactie
  • Gebruik de theorie-links als je vastloopt
  • Werk per opdracht in losse .vue-bestanden, niet alles in App.vue
  • Test steeds tussendoor in de browser en in de Vue DevTools

Styling door de hele app

Je werkt gedurende alle niveaus aan de styling van je app. Per niveau staan er kleine styling-doelen die je meeneemt — de styling groeit dus mee met de functionaliteit. In niveau 7 maak je de puntjes op de i.

Kies je eigen aanpak: je mag zelf kiezen of je werkt met scoped CSS in elk .vue-bestand (<style scoped>) of met Tailwind CSS (utility classes in de template). Beide zijn prima. Wissel niet halverwege.

Voorstel projectstructuur

film-tracker/
├── src/
│   ├── components/
│   │   ├── AppHeader.vue
│   │   ├── AppSidebar.vue
│   │   ├── FilmCard.vue
│   │   ├── FilmList.vue
│   │   ├── FilmForm.vue
│   │   ├── FilterBar.vue
│   │   ├── SearchBar.vue
│   │   ├── InfoPanel.vue
│   │   └── BasePanel.vue
│   ├── pages/
│   │   ├── HomeView.vue
│   │   ├── LoginView.vue
│   │   ├── FilmDetailView.vue
│   │   └── WatchlistView.vue
│   ├── stores/
│   │   └── auth.js
│   ├── composables/
│   │   └── useFilms.js
│   ├── data/
│   │   └── films.js
│   ├── router/
│   │   └── index.js
│   ├── App.vue
│   └── main.js

Niveau 1: Basisopzet van de Film Tracker

1.1 Maak je project aan

Maak een nieuw Vue-project aan met Vite. Verwijder daarna alle demo-inhoud die Vite automatisch aanmaakt, zodat je met een schone lei begint.

Wat moet je zien: een lege pagina met alleen de tekst Film Tracker.

Bestanden
  • Nieuw project: npm create vite@latest film-tracker -- --template vue
  • Aanpassen: src/App.vue (alle demo-inhoud weghalen, alleen Film Tracker tonen)
  • Aanpassen: src/style.css (Vite-stijlen leeghalen)
  • Verwijderen: src/components/HelloWorld.vue

Klaar als npm run dev werkt en de browser toont alleen de titel, zonder Vite-voorbeeldcontent.

Theorie Project Setup

1.2 Maak je eerste Single File Component: FilmCard

Maak een apart .vue-bestand voor een FilmCard component. Dit component toont de informatie van één film: de titel, het genre, het jaar en de status (gezien of wil kijken).

Wat moet je zien: een kaart op de pagina met bijvoorbeeld:

  • Titel: Interstellar
  • Genre: Sci-Fi
  • Jaar: 2014
  • Status: Wil kijken

Tip gebruik <script setup> — dat is de moderne Vue 3 manier. Je hebt dan geen export default nodig.

Bestanden
  • Nieuw: src/components/FilmCard.vue (template met hardcoded filminfo)
  • Aanpassen: src/App.vue (importeer FilmCard en gebruik hem in de template)

Klaar als de kaart zichtbaar is op de pagina met de hardcoded filmgegevens.

Theorie Single File Components · Template Syntax

1.3 Zet de basisstructuur op

Maak een AppHeader en een AppSidebar als aparte componentbestanden. Zorg dat alle drie de componenten samen zichtbaar zijn op de pagina.

Wat moet je zien: bovenaan een header met de naam van de app, een sidebar met navigatie-items zoals Overzicht en Mijn Watchlist, en de FilmCard uit de vorige opdracht.

Stijl geef je layout een eerste vorm — header bovenaan, sidebar links, FilmCard met een duidelijke kaart-uitstraling. Netjes is genoeg, mooi hoeft nog niet.

Bestanden
  • Nieuw: src/components/AppHeader.vue
  • Nieuw: src/components/AppSidebar.vue
  • Aanpassen: src/App.vue (gebruik alle drie de componenten in de template)
  • Bij scoped CSS: <style scoped> per component voor de layout
  • Bij Tailwind: utility classes direct in de template (geen apart CSS-blok nodig)

Klaar als je hebt minstens drie aparte .vue-bestanden en de pagina toont alle drie de onderdelen tegelijk met een herkenbare layout.

Theorie Denken in Code · SFC gebruiken · Styling in Vue

Niveau 2: Components & Props

2.1 Breid de componentenstructuur uit

Voeg twee nieuwe componenten toe: een FilterBar en een FilmList. Gebruik ze allebei in je overzichtspagina.

Wat moet er in de componenten komen:

  • FilterBar: een rij met knoppen — Alles, Gezien en Wil kijken. De knoppen hoeven nog niets te doen, ze moeten alleen zichtbaar zijn.
  • FilmList: een container die één of meerdere FilmCard-componenten in zich heeft. Zet er voorlopig twee of drie FilmCard's in met vaste inhoud.

Wat moet je zien: onder je AppHeader de FilterBar met knoppen, en daaronder de FilmList met meerdere FilmCard's.

Bestanden
  • Nieuw: src/components/FilterBar.vue (knoppen, nog geen actie)
  • Nieuw: src/components/FilmList.vue (container met meerdere FilmCard's)
  • Aanpassen: src/App.vue (importeer en gebruik beide componenten)

Klaar als FilterBar en FilmList staan elk in een eigen bestand en zijn zichtbaar op de pagina.

Theorie Single File Components

2.2 Werk met props via defineProps()

Pas FilmCard aan zodat hij de filmgegevens ontvangt via props in plaats van hardcoded waarden. Gebruik defineProps() met een object zodat je ook typen kunt aangeven. Render daarna minstens drie verschillende films met hetzelfde component.

Wat moet je zien: drie filmkaarten met elk andere inhoud, allemaal gemaakt met hetzelfde FilmCard component. Bijvoorbeeld:

  • Kaart 1: Interstellar — Sci-Fi — 2014 — Gezien
  • Kaart 2: Parasite — Thriller — 2019 — Gezien
  • Kaart 3: Dune: Part Two — Sci-Fi — 2024 — Wil kijken

Stijl geef FilmCard een herkenbare opmaak met padding, een rand of schaduw, en zorg dat de filmtitel opvalt — dikker en groter dan de rest.

Bestanden
  • Aanpassen: src/components/FilmCard.vue (defineProps() toevoegen, hardcoded waarden vervangen door prop-waarden in de template)
  • Aanpassen: src/components/FilmList.vue (drie keer FilmCard met verschillende props)

Klaar als de gegevens van elke film worden doorgegeven via props en er staat niets meer hardcoded in FilmCard.vue.

Theorie Props & defineProps()

2.3 Gebruik slots voor een herbruikbaar paneel

Maak een herbruikbaar BasePanel component met een titel (via een prop) en een plek voor willekeurige inhoud via <slot>. Gebruik dit component op minstens twee plekken met telkens andere inhoud.

Wat moet je zien: twee panelen op de pagina, elk met een andere titel en andere inhoud erin. Bijvoorbeeld een "Nu trending"-blok en een "Aanbevolen voor jou"-blok — beide gebouwd met hetzelfde BasePanel.

Bestanden
  • Nieuw: src/components/BasePanel.vue (prop title + <slot /> in de template)
  • Aanpassen: src/App.vue (twee keer BasePanel met andere inhoud erin)

Klaar als je hebt één BasePanel component dat je minstens twee keer gebruikt met telkens andere slot-inhoud.

Theorie Slots

2.4 Render een filmlijst met v-for

Maak een array met filmobjecten in een apart databestand en render voor elk object een FilmCard via v-for. Geef elk object een unieke id die je als :key gebruikt.

Wat moet je zien: minstens vier filmkaarten die automatisch uit de array worden gegenereerd — je hebt ze dus niet handmatig in de template geschreven.

Bestanden
  • Nieuw: src/data/films.js (array met filmobjecten, elk met een unieke id)
  • Aanpassen: src/components/FilmList.vue (importeer de array, gebruik v-for met :key="film.id" om FilmCard's te renderen)

Klaar als als je een extra object aan de array toevoegt, verschijnt er automatisch een extra kaart op de pagina. Er staan geen sleutelwaarschuwingen in de console.

Theorie Lists & Keys · Props

Niveau 3: Reactivity & Events

3.1 Maak een gezien-toggle

Voeg bij elke filmkaart een knop toe waarmee je de status wisselt tussen Gezien en Wil kijken. Gebruik ref() voor de lokale status en @click voor de knop.

Wat moet je zien: als je op de knop klikt, verandert de tekst of stijl van de kaart direct — zonder paginarefresh.

Tip voor nu mag elke FilmCard z'n eigen lokale status-ref hebben. In niveau 4 tillen we dit omhoog zodat je ook kunt filteren op status.

Bestanden
  • Aanpassen: src/components/FilmCard.vue (ref() voor status + knop met @click die de waarde omwisselt)

Klaar als elke filmkaart heeft een eigen knop die de status wisselt zonder andere kaarten te beïnvloeden.

Theorie Reactivity (ref & reactive) · Events

3.2 Toggle een detailpaneel met v-if

Voeg aan elke filmkaart een knop Meer info toe. Als je erop klikt, verschijnt extra informatie over die film in de kaart zelf. Klik je nog een keer, dan verdwijnt de informatie weer. Gebruik v-if (of v-show) om dit te regelen.

Wat moet je zien: bij een klik klapt er extra informatie open, zoals een korte beschrijving of een regisseur. Een tweede klik sluit het weer.

Let op dit is een eenvoudige inline-toggle — geen aparte detailpagina. In niveau 6 bouw je pas een echte detailpagina met een eigen URL.

Bestanden
  • Aanpassen: src/components/FilmCard.vue (extra ref() voor toonDetails + v-if op de extra info)

Klaar als de extra informatie is standaard verborgen en wordt zichtbaar na een klik, zonder dat de pagina ververst.

Theorie v-if vs v-show · ref()

3.3 Actieve filterknop met dynamische class-binding

Maak drie filterknoppen — Alles, Gezien en Wil kijken. De knop die je aanklikt moet er visueel anders uitzien dan de andere twee. Gebruik :class binding voor de conditionele styling.

Wat moet je zien: de actieve knop heeft een andere kleur of stijl. Als je op een andere knop klikt, wisselt de actieve stijl mee.

Let op de filmlijst hoeft in deze stap nog niet echt gefilterd te worden. Je oefent hier alleen met ref en dynamische class-binding — het echte filteren komt in niveau 4.

Stijl zorg dat het verschil tussen actieve en inactieve knoppen duidelijk zichtbaar is.

Bestanden
  • Aanpassen: src/components/FilterBar.vue (ref() voor actieveFilter + :class op de knoppen op basis van de actieve waarde)

Klaar als er is altijd precies één actieve knop en de styling wisselt correct mee bij elke klik.

Theorie Events · ref() · :bind & class-binding

3.4 Live zoekpreview met v-model

Maak een invoerveld waarin een gebruiker een filmtitel kan typen. Gebruik v-model om de waarde bij te houden. Onder het veld verschijnt meteen een preview van wat er getypt wordt.

Wat moet je zien: terwijl je typt verschijnt er onder het veld: Je zoekt naar: Interstellar. De preview past zich aan bij elke toetsaanslag.

Bestanden
  • Nieuw: src/components/SearchBar.vue (input met v-model + live preview eronder met {{ zoekterm }})
  • Aanpassen: src/App.vue (importeer en gebruik SearchBar)

Klaar als de preview verandert live terwijl je typt, zonder dat je op een knop hoeft te klikken.

Theorie v-model Deep Dive · Events

3.5 Uitklapbare infopaneel in de sidebar

Maak een paneel in de sidebar met extra informatie, zoals "Recente toevoegingen" of een tip. Het paneel moet open en dicht kunnen via een knop.

Wat moet je zien: een knop waarmee het paneel zichtbaar en onzichtbaar wordt. Het paneel is standaard gesloten.

Bestanden
  • Nieuw of aanpassen: src/components/InfoPanel.vue (ref() voor open/dicht + v-show op de inhoud)
  • Aanpassen: src/components/AppSidebar.vue (gebruik het InfoPanel)

Klaar als het paneel opent en sluit via één knop, zonder paginarefresh.

Theorie ref() · v-if vs v-show

Niveau 4: Formulieren & Computed

4.1 Filter op status met computed

Voeg de filterknoppen toe boven je filmlijst — Alles, Gezien en Wil kijken. Gebruik een computed() property om de gefilterde lijst te berekenen op basis van de actieve filter.

Wat moet je zien: klik je op Gezien, dan verdwijnen de films met status Wil kijken. Klik je op Alles, dan zijn alle films weer zichtbaar.

Bestanden
  • Aanpassen: src/App.vue (state voor actieveFilter + een computed() die de filmarray filtert)
  • Aanpassen: src/components/FilterBar.vue (emit de gekozen filter omhoog naar de parent via defineEmits())
  • Aanpassen: src/components/FilmList.vue (ontvangt de gefilterde lijst als prop)

Klaar als elke filterknop toont alleen de bijbehorende films en de computed herberekent automatisch als de filter of de filmarray verandert.

Theorie Computed Properties · ref() & reactive()

4.2 Bouw een film-toevoegformulier met v-model

Maak een formulier waarmee je een nieuwe film kunt toevoegen. Na het invullen en versturen verschijnt de nieuwe film meteen in de lijst. Gebruik v-model op alle invoervelden.

Wat moet je zien: een formulier met minimaal een invoerveld voor de filmtitel en een knop Toevoegen. Na het klikken verschijnt de nieuwe film in de lijst en is het invoerveld weer leeg.

Let op de pagina mag niet refreshen bij het versturen van het formulier. Gebruik @submit.prevent.

Bestanden
  • Nieuw: src/components/FilmForm.vue (v-model op input + @submit.prevent, emit de nieuwe film omhoog)
  • Aanpassen: src/App.vue (ontvang de emit, voeg de film toe aan de array)

Klaar als nieuwe films verschijnen direct in de lijst na het versturen, zonder paginarefresh.

Theorie Forms & Input · v-model Deep Dive

4.3 Breid het formulier uit met meerdere velden

Voeg extra velden toe aan je filmformulier: titel, genre, jaar, regisseur en status (Gezien / Wil kijken). Sla alle velden op in één reactive() object in plaats van losse refs.

Wat moet je zien: een formulier met vijf velden. Na het versturen toont de nieuwe filmkaart al deze gegevens.

Bestanden
  • Aanpassen: src/components/FilmForm.vue (reactive() voor een formulierobject, v-model met property-binding per veld)
  • Aanpassen: src/components/FilmCard.vue (de extra velden tonen)

Klaar als alle velden zijn gebonden aan één reactive object en de nieuwe filmkaart toont alle ingevulde gegevens.

Theorie Forms & Input · reactive()

4.4 Voeg formuliervalidatie toe

Zorg dat het formulier een foutmelding toont in drie situaties:

  • Het titelsveld is leeg.
  • Het jaarveld bevat geen getal of een ongeldig jaar (voor 1888 of na het huidige jaar).
  • Er is nog geen genre geselecteerd.

De film mag pas worden toegevoegd als alle invoer correct is.

Wat moet je zien: laat je het titelveld leeg en klik je op Toevoegen, dan verschijnt er een duidelijke foutmelding. Bij elk fout veld een eigen melding.

Bestanden
  • Aanpassen: src/components/FilmForm.vue (een ref of reactive voor errors, validatielogica in de submit-handler, toon fouten met v-if)

Klaar als elke ongeldige situatie geeft een eigen zichtbare foutmelding en blokkeert het toevoegen van de film.

Theorie Forms & validatie · v-if

4.5 Reageer op wijzigingen met een watcher

Voeg een watch() toe die reageert op de zoekterm in je zoekbalk. Elke keer als de zoekterm verandert, wordt een melding gelogd in de console met de nieuwe waarde. Voeg daarna een watcher toe die ook de filmlijst live filtert op de zoekterm via een computed().

Wat moet je zien: typ je in de zoekbalk, dan past de filmlijst zich direct aan en zie je de zoekterm in de console.

Bestanden
  • Aanpassen: src/App.vue (watch(zoekterm, ...) voor de console-log, combineer zoeken én filteren in één computed())
  • Aanpassen: src/components/SearchBar.vue (emit de zoekterm omhoog of geef hem mee via props)

Klaar als de filmlijst filtert live mee en de console toont de zoekterm bij elke wijziging. Als er geen resultaten zijn, staat er een melding op het scherm.

Theorie Watchers · Computed Properties

Niveau 5: Data & Opslag

5.1 Bewaar films in localStorage

Zorg dat de filmlijst bewaard blijft als de gebruiker de pagina refresht of de browser sluit. Gebruik onMounted() om de opgeslagen films te laden en een watch() om ze op te slaan als de lijst verandert.

Wat moet je zien: voeg een film toe, refresh de browser, en de film staat er nog steeds.

Bestanden
  • Aanpassen: src/App.vue (onMounted() om films te laden uit localStorage, watch(films, ...) om op te slaan bij elke wijziging)

Klaar als films verdwijnen niet meer na een paginarefresh.

Theorie localStorage in Vue · Lifecycle Hooks · watch()

5.2 Haal filmdata op van een API

Voeg een nieuw component TrendingMovies.vue toe dat een lijst van populaire films ophaalt van de DummyJSON API. Toon de opgehaalde films als een apart blok naast of onder je eigen lijst.

API https://dummyjson.com/products?limit=10&category=smartphones — of gebruik een andere openbare API naar keuze. Haal data op in onMounted() met fetch en async/await.

Let op dit blok staat los van je eigen filmlijst. De opgehaalde data kun je niet bewerken of toevoegen aan je lijst. Het doel is leren hoe je externe data ophaalt en toont.

Wat moet je zien: een apart blok op de pagina met minstens vijf items uit de API, elk met minstens een naam en een extra gegeven. De data wordt geladen bij het openen van de pagina.

Bestanden
  • Nieuw: src/components/TrendingMovies.vue (onMounted() met fetch, opgeslagen in een ref([]), weergegeven met v-for)
  • Aanpassen: src/App.vue (importeer en gebruik TrendingMovies)

Klaar als het blok staat op de pagina, haalt data op bij laden, en toont minstens vijf items.

Theorie Data Fetching · onMounted()

5.3 Voeg loading- en error-states toe

Breid opdracht 5.2 uit met drie extra situaties in het trending-blok: een laadmelding terwijl de data wordt opgehaald, een foutmelding als het ophalen mislukt, en een melding als er geen resultaten zijn.

Wat moet je zien:

  • Tijdens het laden: Films laden...
  • Bij een fout: een duidelijke foutmelding
  • Als er geen items zijn: Geen films gevonden

Tip voor testen maak tijdelijk de URL kapot om de foutmelding te zien. Vergeet hem daarna weer terug te zetten.

Bestanden
  • Aanpassen: src/components/TrendingMovies.vue (extra ref's voor loading en error, met v-if / v-else-if / v-else voor de drie situaties)

Klaar als alle drie de situaties een eigen zichtbare melding tonen.

Theorie Loading & Error States · v-if / v-else

5.4 Bewaar gebruikersvoorkeur

Maak een voorkeur die bewaard blijft na het refreshen van de pagina. Kies er één: een dark mode toggle of een standaard sorteerorde voor de filmlijst (bijv. op jaar of op titel).

Wat moet je zien: zet dark mode aan, refresh de pagina, en de app staat nog steeds in dark mode. Of: kies een sortering, refresh, en de sortering is bewaard.

Bestanden
  • Aanpassen: src/App.vue (ref voor de voorkeur, init vanuit localStorage, watch die opslaat bij wijziging)
  • Bij scoped CSS: dark mode via een extra class op de root-wrapper
  • Bij Tailwind: dark-class conditioneel op de wrapper + dark: variants

Klaar als de gekozen voorkeur blijft bewaard na een paginarefresh.

Theorie localStorage · watch()

Niveau 6: Routing & Auth

6.1 Maak meerdere pagina's met Vue Router

Bouw je app uit naar meerdere pagina's: een overzichtspagina, een loginpagina en een filmdetailpagina. Voeg een navigatiebalk toe zodat je tussen de pagina's kunt wisselen.

Aanpak in 4 stappen:

  1. Installeer vue-router en maak src/router/index.js aan.
  2. Maak drie views aan in src/pages/: HomeView.vue, LoginView.vue, FilmDetailView.vue. Verhuis de huidige inhoud van App.vue naar HomeView.vue.
  3. Koppel de routes in router/index.js en registreer de router in main.js.
  4. Zet <RouterView /> in App.vue en gebruik <RouterLink> in je navigatie.

Wat moet je zien: je kunt via de navigatiebalk wisselen tussen minstens drie pagina's, zonder dat de browser ververst.

Stijl laat duidelijk zien op welke pagina de gebruiker zich bevindt — de RouterLink met de actieve klasse (.router-link-active) anders stylen.

Bestanden
  • Installeren: npm install vue-router
  • Nieuw: src/router/index.js (routes voor home, login en filmdetail)
  • Aanpassen: src/main.js (router registreren met app.use(router))
  • Nieuw: src/pages/HomeView.vue (inhoud van huidige App.vue hierheen)
  • Nieuw: src/pages/LoginView.vue
  • Nieuw: src/pages/FilmDetailView.vue
  • Aanpassen: src/App.vue (alleen nog <RouterView /> en de navigatiebalk)

Klaar als alle pagina's bereikbaar zijn via de navigatiebalk en de URL verandert mee bij elke pagina.

Theorie Vue Router

6.2 Werk met URL parameters voor filmdetail

Maak de filmkaarten klikbaar. Als je op een film klikt, ga je naar een detailpagina met de ID van die film in de URL. Op de detailpagina zie je de volledige informatie van die specifieke film.

Wat moet je zien: klik je op film 3, dan ga je naar een URL zoals /films/3. Op die pagina staan alle gegevens van die film.

Bestanden
  • Aanpassen: src/router/index.js (route toevoegen: /films/:id)
  • Aanpassen: src/pages/FilmDetailView.vue (useRoute() om id te lezen, film opzoeken in de array of store)
  • Aanpassen: src/components/FilmCard.vue (<RouterLink :to="`/films/${film.id}`"> rond de kaart of titel)

Klaar als elke film heeft een eigen detailpagina met de juiste gegevens en een werkende URL.

Theorie useRouter & useRoute

6.3 Navigeer programmatisch na een actie

Maak een eenvoudig loginformulier met een veld voor gebruikersnaam en wachtwoord. Na het versturen navigeert de app automatisch naar de overzichtspagina met useRouter(), zonder dat de gebruiker op een link hoeft te klikken.

Wat moet je zien: vul het formulier in, klik op Inloggen, en de app stuurt je automatisch naar het overzicht.

Houd het simpel: je hoeft de ingevulde gegevens nog niet echt te controleren — elke invoer mag meteen doorsturen. Het doel is programmatisch navigeren met useRouter(). In 6.4 maken we dit af met een echte Pinia store.

Bestanden
  • Aanpassen: src/pages/LoginView.vue (formulier + useRouter(), in de submit-handler → router.push('/'))

Klaar als na het versturen navigeert de app automatisch naar het overzicht via router.push().

Theorie useRouter · Login & navigatie

6.4 Maak een Pinia store voor de auth-state

Bouw een Pinia store die bijhoudt of een gebruiker ingelogd is en wat de gebruikersnaam is. De navigatiebalk toont andere opties afhankelijk van de inlogstatus. Bewaar de staat ook in localStorage zodat de gebruiker na een refresh ingelogd blijft.

Pas opdracht 6.3 aan: bij het versturen van het loginformulier roep je login() aan in de store vóór je navigeert.

Wat moet je zien:

  • Niet ingelogd: navigatiebalk toont alleen Login
  • Wel ingelogd: navigatiebalk toont Overzicht, de gebruikersnaam en Uitloggen
  • Refresh na login: je blijft ingelogd
Bestanden
  • Installeren: npm install pinia
  • Aanpassen: src/main.js (app.use(createPinia()))
  • Nieuw: src/stores/auth.js (defineStore met user state, login() en logout() actions, sync met localStorage)
  • Aanpassen: src/pages/LoginView.vue (useAuthStore() aanroepen)
  • Aanpassen: src/components/AppHeader.vue of navigatiebalk (useAuthStore() voor conditionele navigatie-items)

Klaar als de navigatiebalk past automatisch aan als de inlogstatus verandert, en een refresh gooit je er niet uit.

Theorie Pinia · Auth Store Setup · localStorage

6.5 Bescherm routes met een Navigation Guard

Zorg dat de overzichtspagina en de filmdetailpagina alleen toegankelijk zijn voor ingelogde gebruikers. Niet-ingelogde bezoekers worden automatisch doorgestuurd naar de loginpagina via een router.beforeEach() guard.

Wat moet je zien: log uit, typ / of /films/1 in de adresbalk, en je komt automatisch op de loginpagina terecht.

Bestanden
  • Aanpassen: src/router/index.js (router.beforeEach() die meta.requiresAuth controleert + doorverwijst naar login)
  • Aanpassen: routes voor home en filmdetail (meta: { requiresAuth: true } toevoegen)

Klaar als beveiligde routes zijn onbereikbaar voor niet-ingelogde gebruikers, ook als ze de URL direct intypen.

Theorie Protected Routes · Navigation Guards

Niveau 7: Vue Specials & Afwerking

7.1 Deel thema-state via Provide / Inject

Verplaats je dark mode state van App.vue naar een centrale plek via provide(). Componenten diep in de boom kunnen de staat dan uitlezen met inject() — zonder dat je hem door elke laag als prop hoeft door te geven.

Wat moet je zien: een dark mode toggle in de header die via inject() de staat opheft, en de rest van de app reageert direct zonder props te gebruiken.

Bestanden
  • Aanpassen: src/App.vue (provide('darkMode', darkMode) en provide('toggleDarkMode', toggleFn))
  • Aanpassen: src/components/AppHeader.vue (inject('darkMode') en inject('toggleDarkMode'))

Klaar als de dark mode toggle werkt via provide/inject zonder dat de state als prop wordt doorgegeven.

Theorie Provide / Inject

7.2 Extraheer logica naar een Composable

Maak een composable useFilms.js die alle filmlogica bevat: de filmarray, de computed voor filteren/zoeken, de functie voor toevoegen, en de localStorage-synchronisatie. Importeer deze composable in HomeView.vue in plaats van dat alle logica direct in de component staat.

Wat moet je zien: HomeView.vue is een stuk kleiner en roept alleen nog useFilms() aan. Alle filmlogica zit netjes in één apart bestand.

Bestanden
  • Nieuw: src/composables/useFilms.js (alle film-state, computed en functies hierheen verplaatsen, alles retourneren)
  • Aanpassen: src/pages/HomeView.vue (import { useFilms } from '../composables/useFilms' + destructuren)

Klaar als de app werkt nog steeds exact hetzelfde, maar de filmlogica zit nu in een aparte composable.

Theorie Composables

7.3 Gebruik Template Refs voor DOM-toegang

Voeg een zoekbalk toe die automatisch focus krijgt als de pagina laadt. Gebruik daarvoor een ref in de template (ref="zoekInput") en roep .focus() aan in onMounted().

Wat moet je zien: als je naar de overzichtspagina navigeert, staat de cursor meteen in het zoekveld — zonder dat je erop hoeft te klikken.

Bestanden
  • Aanpassen: src/components/SearchBar.vue of src/pages/HomeView.vue (const zoekInput = ref(null) + ref="zoekInput" op het input-element + zoekInput.value.focus() in onMounted())

Klaar als het zoekveld heeft automatisch focus als de pagina laadt.

Theorie Template Refs · Lifecycle Hooks

7.4 Bouw een modal met Teleport

Maak een bevestigingsmodal die verschijnt als je een film wilt verwijderen uit je lijst. Gebruik <Teleport to="body"> zodat de modal buiten de normale componentenstructuur in de DOM staat — wat z-index-problemen voorkomt.

Wat moet je zien: klik je op een verwijderknop bij een film, dan verschijnt er een modal met de vraag "Weet je het zeker?" met twee knoppen: Verwijderen en Annuleren. De modal staat in de body in de DOM-inspector.

Bestanden
  • Nieuw: src/components/ConfirmModal.vue (<Teleport to="body"> als wrapper, twee knoppen met emits)
  • Aanpassen: src/components/FilmCard.vue (verwijderknop + gebruik van ConfirmModal)
  • Aanpassen: src/composables/useFilms.js of HomeView.vue (verwijder-handler op basis van film-id)

Klaar als de modal verschijnt bij het klikken op verwijderen, Verwijderen verwijdert de film echt, en in de DevTools staat de modal direct als kind van body.

Theorie Teleport

7.5 Maak de styling af

Je hebt tijdens de vorige niveaus al stap voor stap aan styling gewerkt. In deze opdracht poets je de app op tot een geheel dat er samenhangend uitziet. Loop je app door van de loginpagina tot de filmdetailpagina en zorg dat alles consistent oogt.

Wat moet je zien:

  • Eén herkenbaar kleurenpalet door de hele app.
  • Consistente afstanden, lettergroottes en knopstijlen tussen pagina's.
  • De app werkt ook goed op een kleiner scherm (responsive).
  • Foutmeldingen, statusindicatoren en actieve knoppen vallen duidelijk op.

Je werkt met wat je aan het begin hebt gekozen: scoped CSS of Tailwind. Voeg geen nieuwe stylingmethode toe — polish wat je hebt.

Bestanden
  • Aanpassen: src/style.css (globale stijlen: CSS-variabelen voor kleuren, basis lettertype)
  • Aanpassen: <style scoped> blokken in alle componenten (consistente spacing en kleuren)
  • Of bij Tailwind: tailwind.config.js (eigen kleuren in theme.extend) + responsive classes toepassen

Klaar als de app oogt als één geheel met consistente kleuren, lettertypes en knoppen op alle pagina's, en de layout schaalt netjes op een smaller scherm.

Theorie Styling in Vue · Tailwind CSS

7.6 Publiceer je app op GitHub Pages

Zet je Film Tracker online zodat iedereen hem kan bekijken. Gebruik de stappen uit de theoriepagina om je Vue-app te builden en te deployen via GitHub Pages.

Wat moet je zien: een werkende URL (bijv. https://jouwgebruikersnaam.github.io/film-tracker/) waarop je app volledig functioneert, inclusief routing.

Let op Vue Router in history-mode werkt niet standaard op GitHub Pages. Schakel over naar hash-mode of volg de stappen in de theoriepagina voor een 404.html-oplossing.

Bestanden
  • Aanpassen: vite.config.js (base instellen op de naam van je GitHub-repository)
  • Aanpassen: src/router/index.js (eventueel omschakelen naar hash-mode)
  • Uitvoeren: npm run build + deployen via GitHub Pages of GitHub Actions

Klaar als je app bereikbaar is op een publieke GitHub Pages URL en de routing werkt.

Theorie Website Publiceren

7.7 Eindchallenge

Voeg zelf minstens drie extra features toe aan je Film Tracker. Kies features die voor jou interessant zijn.

Voorbeelden van extra features:

  • Sterrenbeoordeling toevoegen aan gezien films (1-5 sterren)
  • Sorteren op jaar, titel of beoordeling
  • Favorieten markeren met een hartje
  • Een watchlist-pagina met alleen de films die je nog wilt zien
  • Animaties bij het toevoegen of verwijderen van films met v-motion of CSS transitions
  • Een statistiekenpagina: totaal gezien, favoriete genre, gemiddelde beoordeling
  • Films bewerken na het toevoegen

Wat moet je kunnen laten zien: welke drie features je hebt toegevoegd, waarom je ze hebt gekozen en wat ze doen in de app.

Bestanden
  • Afhankelijk van je keuze — per feature meestal een nieuw component in src/components/ en aanpassingen in de relevante view of store
  • Voorbeelden:
    • Sterrenbeoordeling → src/components/StarRating.vue (knopjes + emit naar parent)
    • Favorieten → src/components/FilmCard.vue (hartje-knop) + useFilms.js (favoriet-toggle opslaan)
    • Statistieken → src/pages/StatsView.vue + route + computed-berekeningen

Klaar als minstens drie zelfgekozen features werken aantoonbaar in de app.

Theorie Composables · Pinia · Computed