Files
bab-app/docs/archive/handoffs/handoff-2026-03-18-nuxt-migration-plan.md

8.7 KiB

Handoff: Nuxt 3 Migration Plan

Date: 2026-03-18 Type: Architecture / Planning

Decision

Migrate bab-app from Quasar 2 + Vue 3 PWA to Nuxt 3 + Capacitor + FullCalendar. Retain Appwrite backend unchanged. Auth simplified to magic link / email OTP only.

Current Stack (Source of Truth)

  • Quasar 2 / Vue 3 / TypeScript / Pinia / Appwrite
  • Build: PWA only (quasar build -m pwa)
  • Appwrite project: 65ede55a213134f2b688 @ https://appwrite.toal.ca/v1
  • Database: bab_prod
  • Collections: boat, reservation, interval, intervalTemplate, task, taskTags, skillTags
  • 9 Pinia stores, 21 components, 2 layouts, ~20 pages/routes
  • Auth: email token + magic link + password (vue3-google-login present but UNUSED)

Target Stack

  • Nuxt 3 (file-based routing, Nitro, Vite)
  • nuxt-quasar-ui module — keeps all Quasar components during migration, de-risks UI rewrite
  • @fullcalendar/vue3 — replaces @quasar/quasar-ui-qcalendar
  • @capacitor/core + plugins — camera, geolocation (native mobile future)
  • Pinia — unchanged
  • Appwrite SDK — unchanged, via Nuxt plugin
  • Auth: magic link + email OTP only; remove vue3-google-login

Key Architectural Decisions

  • Use nuxt-quasar-ui to avoid a big-bang UI rewrite — keeps q-* components working
  • File-based routing replaces routes.ts — directory structure maps 1:1 to current routes
  • Boot file → Nuxt plugin (appwrite.ts becomes plugins/appwrite.ts)
  • Router guards → Nuxt middleware (auth.ts global middleware)
  • PWA only initially — Capacitor deferred until native camera/GPS features are needed
  • FullCalendar resource-timeline view for multi-boat scheduling (replaces QCalendarResource)
  • FullCalendar non-commercial license confirmed — key: CC-Attribution-NonCommercial-NoDerivatives (OYS is a registered Ontario not-for-profit #000082982)
  • Static outputnuxt generate, served via nginx, consistent with current Ansible deploy
  • TestFlight / App Store deferred — revisit when PWA hits UX or capability limits

Migration Phases (see plan in this file)

Phase 1 — Foundation (1-2 days) COMPLETE 2026-03-19

Scaffold new Nuxt 3 project alongside existing. Do NOT delete old project until Phase 6 complete.

New project: /home/ptoal/Dev/mobile-projects/bab-app-nuxt/

Tasks:

  • npx nuxi@latest init bab-app-nuxt
  • Add modules: nuxt-quasar-ui, @pinia/nuxt, @vite-pwa/nuxt
  • Configure nuxt.config.ts: Quasar options, Vite aliases, env vars
  • Migrate .env.local vars (VITE_ prefix → NUXT_PUBLIC_ for public vars)
  • Capacitor: DEFERRED — PWA only until native features needed

Phase 2 — Appwrite Plugin + Types (0.5 days)

  • Copy src/boot/appwrite.tsplugins/appwrite.ts
  • Change export pattern to Nuxt plugin format (defineNuxtPlugin)
  • Provide $appwrite via useNuxtApp()
  • Copy all TypeScript type files verbatim

Phase 3 — Auth (1 day)

  • Migrate stores/auth.tsstores/auth.ts (Pinia, near-verbatim)
  • Remove all password auth methods (createEmailPasswordSession, etc.)
  • Keep: createMagicURLToken, updateMagicURLSession, createEmailToken, updatePhoneSession
  • Remove vue3-google-login from package.json
  • Create middleware/auth.global.ts — replaces router/index.ts navigation guard
  • Public routes: /login, /signup, /pwreset, /terms-of-service, /privacy-policy
  • Magic link callback: pages/auth/callback.vue — handles ?userId=&secret= params

Phase 4 — Remaining Stores (0.5 days)

All stores are framework-agnostic Pinia — direct copy with minor import path updates:

  • stores/boat.ts
  • stores/reservation.ts
  • stores/interval.ts
  • stores/intervalTemplate.ts
  • stores/task.ts
  • stores/reference.ts
  • stores/realtime.ts
  • stores/memberProfile.ts

Phase 5 — File-Based Routing (Page Scaffold) (0.5 days)

Create empty page files matching Nuxt convention. Route mapping:

Old path Nuxt file
/ pages/index.vue
/boat pages/boat.vue
/certification pages/certification.vue
/profile pages/profile.vue
/checklist pages/checklist.vue
/reference pages/reference/index.vue
/reference/:id/view pages/reference/[id]/view.vue
/schedule pages/schedule/index.vue
/schedule/book pages/schedule/book.vue
/schedule/view pages/schedule/view.vue
/schedule/list pages/schedule/list.vue
/schedule/edit/:id pages/schedule/edit/[id].vue
/schedule/manage pages/schedule/manage.vue
/task PARKED — task feature not migrated (collections absent from bab_prod)
/task/:id/edit PARKED
/admin/user pages/admin/user.vue
/admin/boat pages/admin/boat.vue
/login pages/login.vue
/signup pages/signup.vue
/pwreset pages/pwreset.vue
/terms-of-service pages/terms-of-service.vue
/privacy-policy pages/privacy-policy.vue

Admin route guard: definePageMeta({ middleware: ['auth', 'admin'] }) in admin pages. Schedule manage guard: definePageMeta({ middleware: ['auth', 'schedule-admin'] }).

Phase 6 — Layouts + Global Components (1 day)

  • layouts/default.vue — replaces MainLayout.vue (q-layout, q-header, LeftDrawer, BottomNav)
  • layouts/admin.vue — replaces AdminLayout.vue
  • Migrate components verbatim first; replace Quasar components later if needed:
    • components/BottomNavComponent.vue
    • components/LeftDrawer.vue (OPEN: was listed in agent findings but not in original glob — confirm file exists)
    • components/ToolbarComponent.vue

Phase 7 — Page Migration (3-5 days)

Migrate pages in this order (lowest to highest complexity):

  1. Public pages: login, signup, pwreset, terms, privacy (simple forms)
  2. Profile, Certification, Checklist, Boat (read-only/simple CRUD)
  3. Reference pages
  4. Admin pages (UserAdmin, BoatAdmin — q-table heavy)
  5. Schedule pages (most complex — last)
  6. Task pages — PARKED. Skip until bab_prod collections (task, taskTags, skillTags) are created. Affected: stores/task.ts, components/task/, pages/task/, BottomNav task link, navlinks.ts task entry.

Phase 8 — FullCalendar (2-3 days)

Replace QCalendar with FullCalendar Vue 3.

Packages:

@fullcalendar/vue3
@fullcalendar/core
@fullcalendar/resource-timeline   # multi-boat resource view
@fullcalendar/daygrid             # month/day grid
@fullcalendar/timegrid            # week/day time grid
@fullcalendar/interaction         # drag and drop
@fullcalendar/list                # list view

Mapping:

  • ResourceScheduleViewerComponent.vue → new FullCalendarResourceView.vue
    • Resources = boats (one row per boat)
    • Events = reservations + intervals
    • DnD for admin interval management (replaces IntervalTemplateComponent drag behavior)
  • CalendarHeaderComponent.vue — FullCalendar has built-in header toolbar, simplify or remove
  • BoatScheduleTableComponent.vue — replace with FullCalendar list/grid view

FullCalendar license: open-source plugins are free; resource-timeline requires FullCalendar Premium license ($0 for open-source/non-commercial projects — confirm this applies, otherwise budget ~$200/yr).

OPEN: Confirm if OYS Borrow a Boat qualifies for FullCalendar open-source license.

Phase 9 — Capacitor (1 day)

  • npx cap add ios / npx cap add android
  • Install plugins (not wired to features yet, just scaffold):
    • @capacitor/camera
    • @capacitor/geolocation
  • Create composables/useCamera.ts and composables/useGeolocation.ts
    • Detect Capacitor.isNativePlatform() and fall back to browser APIs on web
  • Update nuxt.config.ts ssr: false (Capacitor requires SPA mode)
  • capacitor.config.ts: webDir: '.output/public'

Phase 10 — CI/CD (0.5 days)

Update .gitea/workflows/build.yaml:

  • Replace quasar build -m pwa with nuxt build
  • Output dir: .output/public/ (static) or .output/ (server)
  • Ansible deploy: serve .output/public as static site (nginx), same as current

Open Questions

  • OPEN: Does OYS qualify for FullCalendar non-commercial license? (resource-timeline is premium)
  • OPEN: Confirm LeftDrawer.vue exists in src/layouts/ or src/components/ — agent referenced it but not in initial glob
  • OPEN: Is Capacitor native app publishing (App Store / Play Store) in scope, or just Capacitor for future native API access with PWA as primary delivery?
  • ASSUMED: SSR is not needed — Appwrite client SDK runs browser-side; deploy as SPA/static
  • ASSUMED: Nuxt deployed as static output (not Node server) — consistent with current nginx/Ansible deploy

Effort Estimate

Phase Days
1-4 (Foundation, Plugin, Auth, Stores) 3
5-6 (Routing, Layouts) 1.5
7 (Page migration) 4
8 (FullCalendar) 2.5
9 (Capacitor) 1
10 (CI/CD) 0.5
Total ~12.5 days