193 lines
6.3 KiB
Vue
193 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
definePageMeta({
|
|
middleware: 'guest'
|
|
})
|
|
|
|
useHead({ title: 'Accedi - Visione' })
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const route = useRoute()
|
|
const { requestOtp, verifyOtp, handleLoginSuccess, startCieLogin } = useAuth()
|
|
|
|
const otpStep = ref<1 | 2>(1)
|
|
const isLoading = ref(false)
|
|
const isCieLoading = ref(false)
|
|
const errorMessage = ref('')
|
|
|
|
const email = ref('')
|
|
const otpCode = ref('')
|
|
|
|
const cieCallbackUrl = `${runtimeConfig.public.url}/auth/cie/callback`
|
|
|
|
const cieErrorMessage = computed(() => {
|
|
const error = route.query.cie_error
|
|
|
|
if (typeof error !== 'string') return ''
|
|
|
|
const messages: Record<string, string> = {
|
|
invalid_session: 'Sessione CIE non valida o incompleta. Avvia di nuovo accesso con CIE.',
|
|
cancelled: 'Accesso con CIE annullato. Se vuoi, riprova dal pulsante Entra con CIE.',
|
|
failed: 'Autenticazione CIE non completata. Riprova dal pulsante Entra con CIE.',
|
|
timeout: 'Tempo di attesa scaduto durante verifica CIE. Riprova.',
|
|
verification_error: 'Errore durante verifica autenticazione CIE. Riprova tra poco.'
|
|
}
|
|
|
|
return messages[error] || 'Accesso con CIE non riuscito. Riprova.'
|
|
})
|
|
|
|
const handleRequestOtp = async () => {
|
|
isLoading.value = true
|
|
errorMessage.value = ''
|
|
try {
|
|
await requestOtp({ email: email.value })
|
|
otpStep.value = 2
|
|
} catch (err: any) {
|
|
errorMessage.value = err.data?.message || 'Errore durante la richiesta OTP. Riprova.'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleVerifyOtp = async () => {
|
|
isLoading.value = true
|
|
errorMessage.value = ''
|
|
try {
|
|
const res = await verifyOtp({ email: email.value, code: otpCode.value })
|
|
await handleLoginSuccess(res.token)
|
|
} catch (err: any) {
|
|
errorMessage.value = 'Codice OTP non valido o scaduto.'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleCieLogin = async () => {
|
|
isCieLoading.value = true
|
|
errorMessage.value = ''
|
|
try {
|
|
const res = await startCieLogin(cieCallbackUrl)
|
|
await navigateTo(res.authorization_url, {
|
|
external: true,
|
|
replace: true,
|
|
})
|
|
} catch (err: any) {
|
|
errorMessage.value = err.data?.message || 'Avvio autenticazione CIE non riuscito. Riprova.'
|
|
isCieLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleOTPInput = async (value: string) => {
|
|
console.log(value)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="d-flex align-items-center bg-light py-5">
|
|
<div class="container">
|
|
<div class="row justify-content-center">
|
|
<div class="col-12 col-md-8 col-lg-5">
|
|
|
|
|
|
<div class="text-center mb-4">
|
|
<BIIcon name="it-user" size="xl" color="primary" class="mb-2" />
|
|
<h1 class="h3 font-weight-bold">Accedi a Visione</h1>
|
|
<p class="text-muted">Il portale delle segnalazioni urbane</p>
|
|
</div>
|
|
|
|
<div class="card card-bg shadow-sm">
|
|
<div class="card-body p-4 p-md-5">
|
|
|
|
|
|
<button
|
|
class="btn btn-primary w-100 mb-4 d-flex align-items-center justify-content-center gap-2 entraconcie"
|
|
:disabled="isCieLoading || isLoading"
|
|
@click="handleCieLogin"
|
|
>
|
|
<span v-if="isCieLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
​
|
|
</button>
|
|
|
|
<div class="d-flex align-items-center my-4">
|
|
<hr class="flex-grow-1">
|
|
<span class="mx-3 text-muted small text-uppercase">Oppure</span>
|
|
<hr class="flex-grow-1">
|
|
</div>
|
|
|
|
<div v-if="cieErrorMessage" class="alert alert-danger mb-4" role="alert">
|
|
{{ cieErrorMessage }}
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="alert alert-danger mb-4" role="alert">
|
|
{{ errorMessage }}
|
|
</div>
|
|
|
|
<form v-if="otpStep === 1" @submit.prevent="handleRequestOtp">
|
|
<div class="form-group mb-4">
|
|
<input type="email" class="form-control" id="email" v-model="email" required>
|
|
<label for="email" :class="{ 'active': email.length > 0 }">Indirizzo Email</label>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100" :disabled="isLoading">
|
|
<span v-if="isLoading" class="spinner-border spinner-border-sm me-2" role="status"></span>
|
|
Entra
|
|
</button>
|
|
</form>
|
|
|
|
<form v-else @submit.prevent="handleVerifyOtp">
|
|
<div v-show="!errorMessage" class="alert alert-success mb-4 small">
|
|
Abbiamo inviato un codice OTP a <strong>{{ email }}</strong>.
|
|
</div>
|
|
<div class="form-group mb-4">
|
|
<input
|
|
id="otp"
|
|
v-model="otpCode"
|
|
autocomplete="one-time-code"
|
|
class="form-control text-center text-tracking-widest fs-4"
|
|
inputmode="numeric"
|
|
maxlength="6"
|
|
pattern="[0-9]*"
|
|
required
|
|
type="text"
|
|
@input="otpCode = otpCode.replace(/\D/g, '')"
|
|
>
|
|
<label for="otp" :class="{ 'active': otpCode.length > 0 }">Codice a 6 cifre</label>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100 mb-3" :disabled="isLoading">
|
|
<span v-if="isLoading" class="spinner-border spinner-border-sm me-2" role="status"></span>
|
|
Verifica e Accedi
|
|
</button>
|
|
<div class="text-center">
|
|
<a href="#" class="small text-decoration-none" @click.prevent="otpStep = 1">Modifica indirizzo email</a>
|
|
</div>
|
|
</form>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-center mt-4">
|
|
<p class="small text-muted">Non hai un account? <NuxtLink to="/auth/register" class="font-weight-bold">Registrati ora</NuxtLink></p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.text-tracking-widest {
|
|
letter-spacing: 0.5em;
|
|
}
|
|
.pe-5 {
|
|
padding-right: 3rem !important;
|
|
}
|
|
|
|
button.entraconcie {
|
|
background-image: url('/entraconcie.png');
|
|
background-repeat: no-repeat;
|
|
background-position: center center;
|
|
background-size: 100%;
|
|
}
|
|
</style>
|