import { createRouter, createWebHashHistory } from 'vue-router' import { useCustomerStore } from 'src/stores/customer' /** * Portal router. * * Two route trees: * /login → standalone (no drawer, no header) — LoginPage.vue * /* → PortalLayout.vue (drawer + header + nav) * * Navigation guard: customers without a valid session get bounced to /login. * A valid session is one of: * - URL carries ?token=JWT (handled by useMagicToken on page mount) * - customerStore.customerId is already set (previously authenticated) * * Routes marked in PUBLIC_ROUTES skip the guard — these are payment-return * landings where we want to show a success/cancel message even if the token * expired, plus the catalog which is intentionally browsable unauthenticated. */ // Routes that work without a customer session — payment returns and public // catalog. Must match the `name` value on the route, not the path. const PUBLIC_ROUTES = new Set([ 'login', 'catalog', 'cart', 'order-success', 'payment-success', 'payment-cancel', 'payment-card-added', ]) const routes = [ // Standalone — no layout wrapper { path: '/login', name: 'login', component: () => import('pages/LoginPage.vue') }, // Main portal — wrapped in drawer + header layout { path: '/', component: () => import('layouts/PortalLayout.vue'), children: [ { path: '', name: 'dashboard', component: () => import('pages/DashboardPage.vue') }, { path: 'invoices', name: 'invoices', component: () => import('pages/InvoicesPage.vue') }, { path: 'invoices/:name', name: 'invoice-detail', component: () => import('pages/InvoiceDetailPage.vue') }, { path: 'tickets', name: 'tickets', component: () => import('pages/TicketsPage.vue') }, { path: 'tickets/:name', name: 'ticket-detail', component: () => import('pages/TicketDetailPage.vue') }, { path: 'messages', name: 'messages', component: () => import('pages/MessagesPage.vue') }, { path: 'me', name: 'account', component: () => import('pages/AccountPage.vue') }, { path: 'catalogue', name: 'catalog', component: () => import('pages/CatalogPage.vue') }, { path: 'panier', name: 'cart', component: () => import('pages/CartPage.vue') }, { path: 'commande/confirmation', name: 'order-success', component: () => import('pages/OrderSuccessPage.vue') }, { path: 'paiement/merci', name: 'payment-success', component: () => import('pages/PaymentSuccessPage.vue') }, { path: 'paiement/annule', name: 'payment-cancel', component: () => import('pages/PaymentCancelPage.vue') }, { path: 'paiement/carte-ajoutee', name: 'payment-card-added', component: () => import('pages/PaymentCardAddedPage.vue') }, ], }, ] const router = createRouter({ history: createWebHashHistory(process.env.VUE_ROUTER_BASE), routes, }) // Navigation guard: push unauthenticated users to /login (unless the route // is public OR the URL carries a fresh ?token=). router.beforeEach((to, from, next) => { if (PUBLIC_ROUTES.has(to.name)) return next() const store = useCustomerStore() // Already authenticated from a previous nav in this session. if (store.customerId) return next() // URL carries a token? Hydrate synchronously (URL parse + base64 decode, // no network). Covers magic-link clicks landing on any page and avoids // the race between App.vue's async init() and the first navigation. if (to.query.token || (typeof window !== 'undefined' && window.location.hash.includes('token='))) { if (store.hydrateFromToken()) return next() } next({ name: 'login' }) }) export default router