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-uito 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 output —
nuxt 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.localvars (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.ts→plugins/appwrite.ts - Change export pattern to Nuxt plugin format (
defineNuxtPlugin) - Provide
$appwriteviauseNuxtApp() - Copy all TypeScript type files verbatim
Phase 3 — Auth (1 day)
- Migrate
stores/auth.ts→stores/auth.ts(Pinia, near-verbatim) - Remove all password auth methods (createEmailPasswordSession, etc.)
- Keep:
createMagicURLToken,updateMagicURLSession,createEmailToken,updatePhoneSession - Remove
vue3-google-loginfrom 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):
- Public pages: login, signup, pwreset, terms, privacy (simple forms)
- Profile, Certification, Checklist, Boat (read-only/simple CRUD)
- Reference pages
- Admin pages (UserAdmin, BoatAdmin — q-table heavy)
- Schedule pages (most complex — last)
- 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→ newFullCalendarResourceView.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 removeBoatScheduleTableComponent.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.tsandcomposables/useGeolocation.ts- Detect Capacitor.isNativePlatform() and fall back to browser APIs on web
- Update
nuxt.config.tsssr: 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 pwawithnuxt build - Output dir:
.output/public/(static) or.output/(server) - Ansible deploy: serve
.output/publicas static site (nginx), same as current
Open Questions
- OPEN: Does OYS qualify for FullCalendar non-commercial license? (resource-timeline is premium)
- OPEN: Confirm
LeftDrawer.vueexists 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 |