import { defineStore } from 'pinia' import { ref } from 'vue' import { getPortalUser } from 'src/api/portal' /** * Customer session store. * * Two hydration paths: * 1. Magic-link JWT in the URL (`?token=JWT`). Decoded client-side — the * hub already signed it and the user received it through their own * verified channel (SMS/email), so trust is established. No network * call needed. * 2. ERPNext session (legacy / staff impersonation). Only tried when * there's no token AND no existing store state. Failure is silent — * the router guard will bounce the user to /login. * * For the token path we also strip `?token=` from the URL so a page refresh * or bookmark doesn't replay the token forever. The store state carries us * for the rest of the session; if the user refreshes without a token, the * guard sees `customerId` still set and lets them through. */ function readTokenFromLocation () { if (typeof window === 'undefined') return '' const hash = window.location.hash || '' // Hash form: #/paiement/merci?token=xxx OR #/?token=xxx const qIdx = hash.indexOf('?') if (qIdx === -1) return '' const qs = new URLSearchParams(hash.slice(qIdx + 1)) return qs.get('token') || '' } function decodeJwtPayload (token) { try { const parts = token.split('.') if (parts.length !== 3) return null const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))) if (payload.exp && Date.now() / 1000 > payload.exp) return null return payload } catch { return null } } function stripTokenFromUrl () { if (typeof window === 'undefined') return const hash = window.location.hash || '' const qIdx = hash.indexOf('?') if (qIdx === -1) return const before = hash.slice(0, qIdx) const params = new URLSearchParams(hash.slice(qIdx + 1)) params.delete('token') const rest = params.toString() const newHash = rest ? `${before}?${rest}` : before // Use replaceState so the token doesn't stay in browser history window.history.replaceState(null, '', window.location.pathname + window.location.search + newHash) } export const useCustomerStore = defineStore('customer', () => { const email = ref('') const customerId = ref('') const customerName = ref('') const loading = ref(true) const error = ref(null) function hydrateFromToken () { const token = readTokenFromLocation() if (!token) return false const payload = decodeJwtPayload(token) if (!payload || payload.scope !== 'customer') return false customerId.value = payload.sub customerName.value = payload.name || payload.sub email.value = payload.email || '' stripTokenFromUrl() return true } async function init () { loading.value = true error.value = null try { // Path 1: magic-link token in URL if (hydrateFromToken()) return // Already authenticated in this tab? (e.g. subsequent nav) if (customerId.value) return // Path 2: fall back to ERPNext session (staff impersonation / legacy) const user = await getPortalUser() email.value = user.email customerId.value = user.customer_id customerName.value = user.customer_name } catch (e) { // Silent — router guard will push to /login when customerId is empty error.value = null } finally { loading.value = false } } function clear () { email.value = '' customerId.value = '' customerName.value = '' error.value = null } return { email, customerId, customerName, loading, error, init, hydrateFromToken, clear } })