feat: Implement client authentication and pre-fill functionality

- Added AuthController for client login and token management.
- Introduced AuthGate for handling authentication state and login UI.
- Updated API to support fetching client data for pre-filling forms.
- Modified App.vue to pre-fill client information if logged in.
- Enhanced client model to include username and password fields.
- Created migration for adding username and password to clients table.
- Added utility for generating secure usernames and passwords.
- Updated frontend to handle client tokens for pre-filling checkout forms.
- Removed old CSS file and updated HTML references to new assets.
- Added StatsOverview widget for displaying client and quote statistics.
- Improved error handling and user feedback in the login process.
This commit is contained in:
2026-08-24 17:24:46 +02:00
parent 1c40ef6601
commit d6a9978167
23 changed files with 666 additions and 54 deletions
+30 -2
View File
@@ -1,6 +1,6 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { api, resolveToken, resolveReturnUrl, clearToken, formatEuro } from './api.js'
import { api, resolveToken, resolveReturnUrl, resolveClienteToken, clearToken, formatEuro } from './api.js'
const step = ref('cart') // cart | checkout | done
const loading = ref(true)
@@ -16,9 +16,37 @@ const form = reactive({
nome: '', cognome: '', ragione_sociale: '', email: '', telefono: '',
indirizzo: '', cap: '', citta: '', provincia: '', note: '',
})
const anagraficaCaricata = ref(false)
const isEmpty = computed(() => !carrello.items || carrello.items.length === 0)
// Se il cliente è loggato nel configuratore, precompila il form con i suoi dati già
// salvati (solo i campi ancora vuoti, per non sovrascrivere quanto già digitato).
async function precompilaAnagrafica() {
if (anagraficaCaricata.value) return
anagraficaCaricata.value = true
const clienteToken = resolveClienteToken()
if (!clienteToken) return
try {
const data = await api.me(clienteToken)
const campi = ['nome', 'cognome', 'ragione_sociale', 'email', 'telefono', 'indirizzo', 'cap', 'citta', 'provincia', 'note']
for (const campo of campi) {
if (!form[campo] && data.cliente[campo]) {
form[campo] = data.cliente[campo]
}
}
} catch (e) {
// Token scaduto/non valido: l'utente compila il form manualmente.
}
}
function vaiAlCheckout() {
step.value = 'checkout'
precompilaAnagrafica()
}
async function loadCarrello() {
loading.value = true
errorMsg.value = ''
@@ -123,7 +151,7 @@ onMounted(loadCarrello)
</div>
</div>
<button class="btn-primary" @click="step = 'checkout'">
<button class="btn-primary" @click="vaiAlCheckout">
Richiedi il preventivo
</button>
<a v-if="returnUrl" class="btn-link" style="width:100%;margin-top:8px" :href="returnUrl">
+21 -2
View File
@@ -2,8 +2,12 @@ const BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000/api'
async function request(path, options = {}) {
const res = await fetch(`${BASE}${path}`, {
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
...options,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
...options.headers,
},
})
const data = await res.json().catch(() => ({}))
if (!res.ok) {
@@ -20,23 +24,34 @@ export const api = {
rimuoviItem: (token, itemId) => request(`/carrello/${token}/item/${itemId}`, { method: 'DELETE' }),
conferma: (token, dati) =>
request(`/carrello/${token}/conferma`, { method: 'POST', body: JSON.stringify(dati) }),
// Dati anagrafici del cliente loggato nel configuratore, per precompilare il checkout.
me: (clienteToken) =>
request('/auth/me', { headers: { Authorization: `Bearer ${clienteToken}` } }),
}
const STORAGE_KEY = 'kreios_cart_token'
const RETURN_URL_KEY = 'kreios_return_url'
const CLIENTE_TOKEN_KEY = 'kreios_cliente_token'
export function resolveToken() {
const url = new URL(window.location.href)
const fromUrl = url.searchParams.get('token')
const ritorno = url.searchParams.get('ritorno')
const clienteToken = url.searchParams.get('cliente_token')
if (ritorno) {
localStorage.setItem(RETURN_URL_KEY, ritorno)
}
// Token del cliente loggato nel configuratore (vedi AuthGate.getToken() in starter.js),
// usato per precompilare l'anagrafica nel checkout. Assente se non era loggato.
if (clienteToken) {
localStorage.setItem(CLIENTE_TOKEN_KEY, clienteToken)
}
if (fromUrl) {
localStorage.setItem(STORAGE_KEY, fromUrl)
// pulisci l'URL mantenendo token/ritorno in localStorage
// pulisci l'URL mantenendo token/ritorno/cliente_token in localStorage
url.searchParams.delete('token')
url.searchParams.delete('ritorno')
url.searchParams.delete('cliente_token')
window.history.replaceState({}, '', url.pathname + url.search)
return fromUrl
}
@@ -50,6 +65,10 @@ export function resolveReturnUrl() {
return localStorage.getItem(RETURN_URL_KEY)
}
export function resolveClienteToken() {
return localStorage.getItem(CLIENTE_TOKEN_KEY)
}
export function clearToken() {
localStorage.removeItem(STORAGE_KEY)
}