- aggiornati form registrazione

- Login
- Password dimenticata
- Aggiorna password.
- Email registrazione
- Ammetti Utente
This commit is contained in:
Surya Paolo
2025-11-24 17:42:26 +01:00
parent 9faaa1a4c3
commit e9fa53a637
25 changed files with 4440 additions and 1051 deletions

View File

@@ -22,6 +22,7 @@ const msg_website_it = {
myhosps2: 'myhosps2',
mygood2: 'mygood2',
InvitoReg: 'Invito',
Ammetti: 'Ammetti',
installaApp: 'Installa App',
fundraising: 'Sostieni il Progetto',
notifs: 'Configura le Notifiche',
@@ -45,7 +46,7 @@ const msg_website_it = {
presentazione: 'Presentazione',
presentazione2: 'Presentazione',
invita: 'Invita Persone',
SignUp: 'Modulo di Registrazione',
SignUp: 'Crea il tuo account',
SignUpCollettivo: 'Reg. Collettiva:',
SignUpCollettivo2: 'Registrazione Collettiva:',
need_Telegram: 'Per poter utilizzare la Piattaforma occorre avere <a href="https://play.google.com/store/apps/details?id=org.telegram.messenger" target="_blank">Telegram</a> installato<br>',

View File

@@ -0,0 +1,667 @@
// ========================================
// VARIABILI (Sincronizzate con CSignUp)
// ========================================
$primary-color: #1976d2;
$primary-light: #42a5f5;
$primary-dark: #1565c0;
$accent-color: #26a69a;
$positive-color: #21ba45;
$negative-color: #c10015;
$info-color: #31ccec;
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
$transition-speed: 0.3s;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
$mobile-breakpoint: 768px;
// ========================================
// CONTAINER PRINCIPALE
// ========================================
.registration-container {
width: 100%;
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 6px;
min-height: 100vh;
align-items: flex-start;
}
}
// ========================================
// BOTTONE INIZIALE
// ========================================
.start-button-wrapper {
text-align: center;
animation: fadeInUp 0.6s ease;
}
.start-btn {
min-width: 200px;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
box-shadow: $shadow-lg;
background: linear-gradient(135deg, $primary-color, $primary-light);
transition: all $transition-speed ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(25, 118, 210, 0.4);
}
&:active {
transform: translateY(-2px);
}
@media (max-width: $mobile-breakpoint) {
min-width: 180px;
height: 52px;
font-size: 1rem;
}
}
// ========================================
// CARD PRINCIPALE
// ========================================
.registration-card {
width: 100%;
max-width: 700px;
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
background: white;
animation: fadeInUp 0.6s ease;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
border-radius: $border-radius;
}
}
// ========================================
// CAROUSEL
// ========================================
.modern-carousel {
background: transparent;
@media (max-width: $mobile-breakpoint) {
height: auto !important;
}
}
.carousel-slide {
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
// ========================================
// SLIDE CONTENT
// ========================================
.slide-content {
width: 100%;
padding: 10px 8px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
animation: fadeIn 0.4s ease;
@media (max-width: $mobile-breakpoint) {
padding: 10px 8px;
gap: 16px;
}
}
.slide-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, rgba(25, 118, 210, 0.1), rgba(38, 166, 154, 0.1));
margin-bottom: 8px;
animation: scaleIn 0.6s ease;
@media (max-width: $mobile-breakpoint) {
width: 100px;
height: 100px;
.q-icon {
font-size: 48px !important;
}
}
}
.slide-title {
font-size: 1.75rem;
font-weight: 600;
color: #1a1a1a;
text-align: center;
margin: 0;
line-height: 1.3;
@media (max-width: $mobile-breakpoint) {
font-size: 1.375rem;
}
}
// ========================================
// ACTION BUTTONS (Yes/No)
// ========================================
.action-buttons {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
max-width: 400px;
@media (max-width: $mobile-breakpoint) {
gap: 12px;
max-width: 100%;
}
}
.action-btn {
width: 100%;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
box-shadow: $shadow-md;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
&:active {
transform: translateY(0);
}
@media (max-width: $mobile-breakpoint) {
height: 52px;
font-size: 1rem;
}
}
.yes-btn {
background: linear-gradient(135deg, $positive-color, #26a69a);
}
.no-btn {
background: linear-gradient(135deg, $negative-color, #e91e63);
}
// ========================================
// LINKS LIST
// ========================================
.links-list {
width: 100%;
max-width: 500px;
display: flex;
flex-direction: column;
gap: 12px;
@media (max-width: $mobile-breakpoint) {
gap: 8px;
}
}
.link-item {
background: linear-gradient(to right, rgba(25, 118, 210, 0.05), transparent);
border-radius: $border-radius-sm;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
transition: all $transition-speed ease;
&:hover {
transform: translateX(4px);
box-shadow: $shadow-sm;
background: linear-gradient(to right, rgba(25, 118, 210, 0.1), transparent);
}
@media (max-width: $mobile-breakpoint) {
padding: 8px;
}
}
.custom-link {
margin-top: 16px;
padding: 16px;
text-align: center;
@media (max-width: $mobile-breakpoint) {
margin-top: 12px;
padding: 12px;
}
}
.custom-link-text {
display: inline-flex;
align-items: center;
gap: 8px;
color: $primary-color;
font-size: 1.125rem;
font-weight: 600;
text-decoration: none;
transition: all $transition-speed ease;
&:hover {
color: $primary-dark;
transform: translateX(4px);
}
@media (max-width: $mobile-breakpoint) {
font-size: 1rem;
}
}
// ========================================
// NO INVITED CONTENT
// ========================================
.no-invited-content {
width: 100%;
max-width: 550px;
}
.instructions {
display: flex;
flex-direction: column;
gap: 24px;
margin-top: 24px;
@media (max-width: $mobile-breakpoint) {
gap: 16px;
margin-top: 16px;
}
}
.instruction-item {
display: flex;
gap: 16px;
align-items: flex-start;
@media (max-width: $mobile-breakpoint) {
gap: 12px;
}
}
.instruction-number {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, $primary-color, $accent-color);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
font-weight: 700;
box-shadow: $shadow-sm;
@media (max-width: $mobile-breakpoint) {
width: 36px;
height: 36px;
font-size: 1.125rem;
}
}
.instruction-content {
flex: 1;
p {
margin: 0 0 12px;
font-size: 1rem;
line-height: 1.6;
color: #333;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
margin: 0 0 8px;
}
}
}
.instruction-note {
font-size: 0.9375rem;
color: #666;
font-style: italic;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
.telegram-btn {
margin-top: 12px;
border-radius: $border-radius-sm;
text-transform: none;
font-weight: 600;
box-shadow: $shadow-sm;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-md;
}
@media (max-width: $mobile-breakpoint) {
margin-top: 8px;
width: 100%;
}
}
// ========================================
// WITH INVITED CONTENT
// ========================================
.with-invited-content {
width: 100%;
max-width: 800px;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
}
}
.registration-options {
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
margin-top: 24px;
@media (max-width: $mobile-breakpoint) {
gap: 16px;
margin-top: 16px;
}
}
.option-btn {
width: 100%;
height: 64px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
box-shadow: $shadow-md;
transition: all $transition-speed ease;
position: relative;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
@media (max-width: $mobile-breakpoint) {
height: 76px;
font-size: 1rem;
}
}
.telegram-option {
background: linear-gradient(135deg, $primary-color, $primary-light);
}
.email-option {
border-width: 2px;
&:hover {
background: rgba(25, 118, 210, 0.08);
}
}
.recommended-badge {
top: -8px;
right: -8px;
padding: 4px 12px;
font-size: 0.75rem;
font-weight: 700;
border-radius: 12px;
box-shadow: $shadow-sm;
@media (max-width: $mobile-breakpoint) {
font-size: 0.6875rem;
padding: 3px 10px;
}
}
.divider {
position: relative;
text-align: center;
margin: 8px 0;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: calc(50% - 40px);
height: 1px;
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.12));
}
&::before {
left: 0;
}
&::after {
right: 0;
background: linear-gradient(to left, transparent, rgba(0, 0, 0, 0.12));
}
span {
display: inline-block;
padding: 4px 12px;
background: white;
color: #666;
font-size: 0.875rem;
font-weight: 500;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
}
}
// ========================================
// SINGLE REGISTRATION
// ========================================
.single-registration {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
margin-top: 24px;
@media (max-width: $mobile-breakpoint) {
margin-top: 16px;
gap: 12px;
}
}
.registration-text {
font-size: 1.125rem;
color: #666;
margin: 0;
@media (max-width: $mobile-breakpoint) {
font-size: 1rem;
}
}
.register-btn {
width: 100%;
max-width: 300px;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $primary-color, $primary-light);
box-shadow: $shadow-md;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
height: 52px;
font-size: 1rem;
}
}
// ========================================
// BACK BUTTON
// ========================================
.back-btn {
margin-top: 24px;
border-radius: $border-radius-sm;
text-transform: none;
font-weight: 500;
transition: all $transition-speed ease;
&:hover {
background: rgba(0, 0, 0, 0.04);
}
@media (max-width: $mobile-breakpoint) {
margin-top: 16px;
width: 100%;
}
}
// ========================================
// NAVIGATION DOTS
// ========================================
.navigation-dots {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
padding: 16px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.02), transparent);
@media (max-width: $mobile-breakpoint) {
padding: 12px;
gap: 6px;
}
}
.nav-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #d0d0d0;
cursor: pointer;
transition: all $transition-speed ease;
opacity: 0;
pointer-events: none;
&.visible {
opacity: 1;
pointer-events: auto;
}
&.active {
width: 24px;
border-radius: 5px;
background: $primary-color;
}
&:hover:not(.active) {
background: #a0a0a0;
transform: scale(1.2);
}
@media (max-width: $mobile-breakpoint) {
width: 8px;
height: 8px;
&.active {
width: 20px;
}
}
}
// ========================================
// ANIMAZIONI
// ========================================
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
// ========================================
// RESPONSIVE UTILITIES
// ========================================
@media (max-width: $mobile-breakpoint) {
// Fix per iOS Safari
.registration-container {
min-height: -webkit-fill-available;
}
}
// ========================================
// UTILITY CLASSES (Legacy)
// ========================================
.centermydiv2 {
display: flex;
justify-content: center;
align-items: center;
}
.dialog_card {
background: white;
border-radius: $border-radius;
}
.my-custom-border {
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: $border-radius-sm;
}

View File

@@ -5,6 +5,7 @@ import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { CContactUser } from '@src/components/CContactUser'
import { CMyUser } from '@src/components/CMyUser'
import { Logo } from '@src/components/logo'
import { DefaultProfile, useUserStore } from '@store/UserStore'
import { useRoute, useRouter } from 'vue-router'
@@ -14,7 +15,7 @@ import { useGlobalStore } from '@store/globalStore'
export default defineComponent({
name: 'CRegistration',
emits: ['regEventEmail'],
components: { CMyUser, CContactUser },
components: { CMyUser, CContactUser, Logo },
props: {
slide: {
type: String,
@@ -57,14 +58,18 @@ export default defineComponent({
const actionType = ref(costanti.ACTIONTYPE.LINK_REG)
function clickToRegister() {
// Responsive detection
const isMobile = ref(false)
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
}
function clickToRegister() {
//if (site.value.confpages.enableRegByBot) {
//$router.push('/bot')
//} else {
$router.push('/registrati/' + tools.getInvitante())
//}
}
function mounted() {
@@ -76,6 +81,10 @@ export default defineComponent({
chooseReg.value = true
slide.value = 'second'
}
// Check mobile on mount
checkMobile()
window.addEventListener('resize', checkMobile)
}
function regEventEmail() {
@@ -91,16 +100,29 @@ export default defineComponent({
if (invitante) {
start.value = true
slide.value = 'second';
noInvited.value = false;
chooseReg.value = true;
slide.value = 'second'
noInvited.value = false
chooseReg.value = true
} else {
start.value = true
}
}
// Determina se mostrare un dot specifico
function shouldShowDot(slideName: string) {
// Non mostrare 'sceglilink' se non ci sono link
if (slideName === 'sceglilink' && listlinksreg.value.length === 0) {
return false
}
return true
}
onMounted(mounted)
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
return {
tools,
site,
@@ -115,6 +137,8 @@ export default defineComponent({
listlinksreg,
actionType,
t,
isMobile,
shouldShowDot,
}
},
})

View File

@@ -1,223 +1,275 @@
<template>
<div
v-if="site.confpages?.enableReg"
class="row q-ma-sm centermydiv2 q-pa-sm justify-center align-center"
class="registration-container"
>
<div v-if="!start">
<!-- Bottone Iniziale -->
<div
v-if="!start"
class="start-button-wrapper"
>
<q-btn
rounded
glossy
icon="fas fa-user-plus"
size="md"
unelevated
size="lg"
color="primary"
icon="person_add"
@click="buttRegistrati()"
:label="$t('reg.submit')"
>
</q-btn>
class="start-btn"
/>
</div>
<q-carousel
<!-- Carousel Registrazione -->
<q-card
v-if="start"
v-model="slide"
transition-prev="scale"
transition-next="scale"
animated
control-color="white"
navigation
padding
height="500px"
:class="`text-white bg-primary shadow-1 rounded-borders`"
class="registration-card"
>
<q-carousel-slide
name="start"
class="column no-wrap flex-center"
<q-carousel
v-model="slide"
transition-prev="slide-right"
transition-next="slide-left"
animated
swipeable
class="modern-carousel"
:height="isMobile ? 'auto' : '500px'"
>
<q-icon
name="fas fa-user-plus"
size="56px"
/>
<div class="q-mt-md text-center">
<span class="text-h6 text-white"> {{ t('reg.invitante') }}</span>
<q-card class="dialog_card q-mb-lg">
<q-card-section class="column q-ma-sm q-pa-sm q-col-gutter-sm">
<!-- Slide 1: Hai un Invitante? -->
<q-carousel-slide
name="start"
class="carousel-slide"
>
<div class="slide-content">
<div class="slide-icon-wrapper">
<q-icon
name="person_add"
size="64px"
color="primary"
/>
</div>
<h2 class="slide-title">{{ t('reg.invitante') }}</h2>
<div class="action-buttons">
<q-btn
rounded
glossy
unelevated
size="lg"
color="positive"
icon="check"
@click="
listlinksreg.length > 0 ? (slide = 'sceglilink') : (slide = 'second');
noInvited = false;
chooseReg = true;
"
:label="$t('dialog.yes')"
>
</q-btn>
class="action-btn yes-btn"
/>
<q-btn
rounded
glossy
unelevated
size="lg"
color="negative"
icon="close"
@click="
slide = 'second';
noInvited = true;
chooseReg = false;
"
:label="$t('dialog.no')"
class="action-btn no-btn"
/>
</div>
</div>
</q-carousel-slide>
<!-- Slide 2: Scegli Link (se disponibile) -->
<q-carousel-slide
name="sceglilink"
class="carousel-slide"
>
<div class="slide-content">
<h2 class="slide-title">{{ t('reg.title_reg_con_link') }}</h2>
<div class="links-list">
<div
v-for="(rec, i) in listlinksreg"
:key="i"
class="link-item"
>
</q-btn>
</q-card-section>
</q-card>
</div>
</q-carousel-slide>
<q-carousel-slide name="sceglilink">
<q-card class="dialog_card q-mb-lg">
<div class="q-ma-sm q-pa-sm text-h6 text-blue text-bold">{{ t('reg.title_reg_con_link') }}</div>
<q-card-section class="column q-ma-sm q-pa-sm q-col-gutter-sm text-black">
<div
v-for="(rec, i) in listlinksreg"
:key="i"
>
<div class="q-pa-xs q-ma-xs q-border q-rounded my-custom-border">
<CMyUser
:mycontact="rec"
:visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd"
:actionType="actionType"
:hideellipses="true"
>
</CMyUser>
<!--<CContactUser
:myuser="rec"
:showBtnActivities="false"
:sendRIS="false"
:actionType="actionType"
/>
</div>-->
</div>
<div class="q-ma-md q-pa-md">
<div class="custom-link">
<a
href="/registrati"
target="_blank"
>👉🏻 {{ t('reg.scelgo_l_invitante') }}</a
class="custom-link-text"
>
</div>
<div class="text-center">
<q-btn
rounded
glossy
size="md"
color="primary"
@click="slide = 'start'"
:label="$t('dialog.indietro')"
>
</q-btn>
<q-icon
name="arrow_forward"
size="20px"
/>
{{ t('reg.scelgo_l_invitante') }}
</a>
</div>
</div>
</q-card-section>
</q-card>
</q-carousel-slide>
<q-carousel-slide name="second">
<div
v-if="noInvited"
class="text-h7"
>
<div class="text-center text-bold text-h6">Se ancora non sei stato invitato:</div>
<br />
1 👉🏻 Entra nei gruppi Territoriali su Telegram:<br />
<div class="text-center">
<q-btn
type="a"
rounded
icon="fab fa-telegram"
color="positive"
href="https://t.me/riso_canale/3"
target="_blank"
label="Progetto RISO"
>
</q-btn>
</div>
<br />
2 👉🏻 sul post del canale fissato in alto, troverai tutte le info sul progetto e su come entrare nel gruppo
della tua provincia.<br />
Potrai cosi richiedere il link una volta entrato nella chat di gruppo.<br />
</div>
<div v-else-if="chooseReg">
<div class="row justify-center items-center">
<q-icon
name="fas fa-user-plus"
size="27px"
class="q-mx-md"
<q-btn
flat
color="grey-7"
icon="arrow_back"
@click="slide = 'start'"
:label="$t('dialog.indietro')"
class="back-btn"
/>
<span class="text-h6 text-white"> {{ t('reg.page_title') }}</span>
<q-card class="q-mt-sm dialog_card q-mb-sm">
<q-card-section>
<div
v-if="site.confpages?.enableRegMultiChoice"
style=""
class="column q-ma-sm centermydiv2 q-pa-sm justify-center"
>
<q-btn
rounded
type="a"
class="col-xs-12 col-sm-6 flex-item-btn items-center q-my-md"
icon="fab fa-telegram"
size="md"
color="primary"
:href="invited ? tools.getLinkBotTelegram(invited, regexpire) : `/bot`"
:label="$t('reg.bytelegram')"
>
<q-badge
color="red"
align="bottom"
floating
>Consigliato</q-badge
>
</q-btn>
<br />
<div :class="$q.dark.isActive ? `text-white` : `text-black` + ` col-12 text-center`">
<div class="bg-grey-4">
<br />
se non hai Telegram puoi registrarti con solo l'email ma
<span style="text-decoration: underline">non potrai contattare gli iscritti</span>.
<q-btn
rounded
class="flex-item-btn col-xs-12 col-sm-6"
outline
icon="fas fa-envelope"
size="0.75rem"
:color="$q.dark.isActive ? `black` : `white`"
:text-color="$q.dark.isActive ? `white` : `black`"
@click="regEventEmail"
:label="$t('reg.byemail')"
>
</q-btn>
</div>
</div>
</q-carousel-slide>
<!-- Slide 3: Scelta Registrazione -->
<q-carousel-slide
name="second"
class="carousel-slide"
>
<div class="slide-content">
<!-- Caso: Non Invitato -->
<div
v-if="noInvited"
class="no-invited-content"
>
<div class="slide-icon-wrapper">
<q-icon
name="info"
size="56px"
color="info"
/>
</div>
<h2 class="slide-title">Se ancora non sei stato invitato:</h2>
<div class="instructions">
<div class="instruction-item">
<div class="instruction-number">1</div>
<div class="instruction-content">
<p>Entra nei gruppi Territoriali su Telegram</p>
<q-btn
unelevated
color="positive"
icon="fab fa-telegram"
href="https://t.me/riso_canale/3"
target="_blank"
label="Progetto RISO"
class="telegram-btn"
/>
</div>
</div>
<div
v-else
style="margin-top: 10px; text-align: center"
>
Registrati<br />
<q-btn
rounded
size="md"
color="primary"
@click="clickToRegister"
:label="$t('reg.submit')"
>
</q-btn>
<div class="instruction-item">
<div class="instruction-number">2</div>
<div class="instruction-content">
<p>
Sul post del canale fissato in alto, troverai tutte le info sul
progetto e su come entrare nel gruppo della tua provincia.
</p>
<p class="instruction-note">
Potrai così richiedere il link una volta entrato nella chat di
gruppo.
</p>
</div>
</div>
</q-card-section>
</q-card>
</div>
</div>
<!-- Caso: Con Invitante -->
<div
v-else-if="chooseReg"
class="with-invited-content"
>
<div class="flex justify-center">
<logo
class="signup-logo "
mystyle="width: 50px !important; height: 50px !important;"
/>
</div>
<!-- Multi Choice: Telegram vs Email -->
<div
v-if="site.confpages?.enableRegMultiChoice"
class="registration-options"
>
<q-btn
unelevated
size="lg"
color="primary"
icon="fab fa-telegram"
type="a"
:href="invited ? tools.getLinkBotTelegram(invited, regexpire) : `/bot`"
:label="$t('reg.bytelegram')"
class="option-btn telegram-option"
>
<q-badge
color="positive"
floating
class="recommended-badge"
>
Consigliato
</q-badge>
</q-btn>
<div class="divider">
<span>oppure</span>
</div>
<q-btn
outline
size="lg"
color="primary"
icon="email"
@click="regEventEmail"
:label="$t('reg.byemail')"
class="option-btn email-option"
/>
</div>
<!-- Single Choice: Solo Registrati -->
<div
v-else
class="single-registration"
>
<p class="registration-text">Registrati</p>
<q-btn
unelevated
size="lg"
color="primary"
icon="person_add"
@click="clickToRegister"
:label="$t('reg.submit')"
class="register-btn"
/>
</div>
</div>
</div>
</div>
</q-carousel-slide>
</q-carousel>
</q-carousel-slide>
</q-carousel>
<!-- Navigation Indicators -->
<div
v-if="start"
class="navigation-dots"
>
<div
v-for="(s, i) in ['start', 'sceglilink', 'second']"
:key="i"
class="nav-dot"
:class="{ active: slide === s, visible: shouldShowDot(s) }"
@click="slide = s"
></div>
</div>
</q-card>
</div>
</template>

View File

@@ -1,5 +1,414 @@
// ========================================
// VARIABILI (Sincronizzate con CSignUp)
// ========================================
$primary-color: #1976d2;
$primary-light: #42a5f5;
$primary-dark: #1565c0;
$accent-color: #26a69a;
$positive-color: #21ba45;
$negative-color: #c10015;
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
$transition-speed: 0.3s;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
$mobile-breakpoint: 768px;
// ========================================
// CONTAINER PRINCIPALE
// ========================================
.signin-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 12px;
align-items: flex-start;
padding-top: 40px;
}
}
// ========================================
// PWA CHECK
// ========================================
.pwa-check {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
@media (max-width: $mobile-breakpoint) {
top: 12px;
right: 12px;
}
}
// ========================================
// CARD PRINCIPALE
// ========================================
.signin-card {
width: 100%;
max-width: 450px;
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
background: white;
animation: fadeInUp 0.6s ease;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
border-radius: $border-radius;
}
}
// ========================================
// HEADER
// ========================================
.signin-header {
text-align: center;
padding: 32px 24px 24px;
background: linear-gradient(to bottom, rgba(103, 126, 234, 0.05), transparent);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
@media (max-width: $mobile-breakpoint) {
padding: 24px 16px 16px;
}
}
.signin-logo {
margin: 0 auto 16px;
animation: scaleIn 0.6s ease;
@media (max-width: $mobile-breakpoint) {
margin: 0 auto 12px;
}
}
.signin-title {
font-size: 2rem;
font-weight: 600;
margin: 0 0 8px;
background: linear-gradient(135deg, $primary-color, $accent-color);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@media (max-width: $mobile-breakpoint) {
font-size: 1.5rem;
margin: 0 0 6px;
}
}
.signin-subtitle {
font-size: 1rem;
color: #666;
margin: 0;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
// ========================================
// FORM
// ========================================
.signin-form {
padding: 32px 24px;
@media (max-width: $mobile-breakpoint) {
padding: 20px 16px;
}
}
.form-fields {
display: flex;
flex-direction: column;
gap: 20px;
@media (max-width: $mobile-breakpoint) {
gap: 16px;
}
}
// ========================================
// INPUT FIELDS
// ========================================
.modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
min-height: 56px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
min-height: 52px;
border-radius: 10px;
}
&:before {
border-color: rgba(0, 0, 0, 0.12);
}
&:hover:before {
border-color: $primary-light;
}
}
:deep(.q-field__label) {
font-size: 1rem;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
}
}
:deep(.q-field__prepend) {
.q-icon {
font-size: 24px;
@media (max-width: $mobile-breakpoint) {
font-size: 20px;
}
}
}
&.q-field--focused {
:deep(.q-field__control) {
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
}
&.q-field--error {
animation: shake 0.4s ease;
}
}
// ========================================
// SUBMIT BUTTON
// ========================================
.submit-btn {
width: 100%;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $primary-color, $primary-light);
box-shadow: $shadow-md;
transition: all $transition-speed ease;
margin-top: 8px;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
background: linear-gradient(135deg, $primary-dark, $primary-color);
}
&:active {
transform: translateY(0);
}
@media (max-width: $mobile-breakpoint) {
height: 52px;
font-size: 1rem;
}
}
// ========================================
// FORGOT PASSWORD
// ========================================
.forgot-password {
text-align: center;
margin-top: -8px;
@media (max-width: $mobile-breakpoint) {
margin-top: -4px;
}
}
.forgot-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: #666;
text-decoration: none;
font-size: 0.9375rem;
transition: all $transition-speed ease;
padding: 8px 12px;
border-radius: $border-radius-sm;
&:hover {
color: $primary-color;
background: rgba(25, 118, 210, 0.06);
}
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
padding: 6px 10px;
}
}
// ========================================
// DIVIDER
// ========================================
.divider {
position: relative;
text-align: center;
margin: 8px 0;
&::before,
&::after {
content: '';
position: absolute;
top: 50%;
width: calc(50% - 80px);
height: 1px;
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.12));
}
&::before {
left: 0;
}
&::after {
right: 0;
background: linear-gradient(to left, transparent, rgba(0, 0, 0, 0.12));
}
span {
display: inline-block;
padding: 4px 16px;
background: white;
color: #666;
font-size: 0.875rem;
font-weight: 500;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
@media (max-width: $mobile-breakpoint) {
font-size: 0.8125rem;
padding: 3px 12px;
}
}
}
// ========================================
// REGISTRATION SECTION
// ========================================
.register-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 16px;
background: linear-gradient(to bottom, rgba(33, 186, 69, 0.05), transparent);
border-radius: $border-radius;
border: 1px solid rgba(33, 186, 69, 0.15);
@media (max-width: $mobile-breakpoint) {
padding: 12px;
gap: 10px;
}
}
.register-text {
margin: 0;
font-size: 0.9375rem;
color: #666;
text-align: center;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
.register-btn {
width: 100%;
max-width: 300px;
height: 52px;
border-radius: $border-radius;
font-size: 1.0625rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $positive-color, #26a69a);
box-shadow: $shadow-sm;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-md;
background: linear-gradient(135deg, #1e8e3e, $positive-color);
}
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
height: 48px;
font-size: 1rem;
}
}
// ========================================
// ANIMAZIONI
// ========================================
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-10px);
}
75% {
transform: translateX(10px);
}
}
// ========================================
// RESPONSIVE UTILITIES
// ========================================
@media (max-width: $mobile-breakpoint) {
// Fix per iOS Safari
.signin-container {
min-height: -webkit-fill-available;
}
}
// ========================================
// LEGACY CLASS (per compatibilità)
// ========================================
.signin {
width: 100%;
margin: 0 auto;
max-width: 450px;
}
}

View File

@@ -1,127 +1,151 @@
<template>
<div>
<div v-if="enablePwa"><CCheckAppRunning :login="true"/></div>
<div class="text-center">
<p>
<logo></logo>
</p>
<div class="signin-container">
<!-- PWA Check -->
<div v-if="enablePwa" class="pwa-check">
<CCheckAppRunning :login="true" />
</div>
<q-form ref="myForm" @submit="onSubmit" @reset="onReset">
<div class="q-gutter-xs">
<q-input
ref="refUsername"
v-model="signin.username"
rounded
outlined
dense
lazy-rules
:label="$t('reg.username_login')"
:rules="[
(val) => !!val || t('reg.err.required'),
(val) =>
val.length >= 4 ||
t('reg.err.atleast') + ' 4 ' + t('reg.err.char'),
]"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
<q-input
ref="refPassword"
v-model="signin.password"
:type="typePassword"
rounded
outlined
dense
debounce="500"
@update:model-value="checkAutoCompletion"
v-on:keyup.enter="onSubmit()"
:label="$t('reg.password')"
:rules="[
(val) => !!val || t('reg.err.required'),
(val) =>
val.length >= 8 ||
t('reg.err.atleast') + ' 8 ' + t('reg.err.char'),
]"
>
<template v-slot:append>
<q-btn
v-if="!autoCompleteTriggered"
tabindex="-1"
:icon="
typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye`
"
@click="showPassword"
>
</q-btn>
</template>
<template v-slot:prepend>
<q-icon name="vpn_key" />
</template>
</q-input>
<div style="text-align: center">
<q-btn
type="submit"
rounded
size="md"
color="primary"
:label="$t('login.enter')"
>
</q-btn>
</div>
<br />
<div class="text-center" style="margin-bottom: 10px">
<a :href="getlinkforgetpwd()" style="color: gray">{{
t('reg.forgetpassword')
}}</a>
</div>
<div
v-if="
site.confpages &&
site.confpages?.enableReg &&
showregbutt &&
site.confpages?.enableRegByBot
"
style="margin-top: 10px; text-align: center"
>
Se non sei ancora Registrato:<br />
<q-btn
type="a"
rounded
size="md"
color="primary"
href="/bot"
:label="$t('reg.submit')"
>
</q-btn>
</div>
<div
v-else-if="site.confpages && site.confpages?.enableReg && showregbutt"
style="margin-top: 10px; text-align: center"
>
Se non sei ancora Registrato:<br />
<q-btn
rounded
size="md"
color="primary"
to="/registrati"
:label="$t('reg.submit')"
>
</q-btn>
</div>
<!-- Login Card -->
<q-card class="signin-card">
<!-- Header con Logo -->
<div class="signin-header">
<logo class="signin-logo" mystyle="width: 60px !important; height: 60px !important;" />
<h1 class="signin-title">{{ t('login.enter') }}</h1>
<p class="signin-subtitle">Accedi al tuo account</p>
</div>
</q-form>
<!-- Form -->
<q-card-section class="signin-form">
<q-form ref="myForm" @submit="onSubmit" @reset="onReset">
<div class="form-fields">
<!-- Username Input -->
<q-input
ref="refUsername"
v-model="signin.username"
filled
class="modern-input"
:label="$t('reg.username_login')"
tabindex="1"
lazy-rules
:rules="[
(val) => !!val || t('reg.err.required'),
(val) =>
val.length >= 4 ||
t('reg.err.atleast') + ' 4 ' + t('reg.err.char'),
]"
>
<template v-slot:prepend>
<q-icon name="person" color="primary" />
</template>
</q-input>
<!-- Password Input -->
<q-input
ref="refPassword"
v-model="signin.password"
:type="typePassword"
filled
class="modern-input"
:label="$t('reg.password')"
tabindex="2"
debounce="500"
@update:model-value="checkAutoCompletion"
v-on:keyup.enter="onSubmit()"
lazy-rules
:rules="[
(val) => !!val || t('reg.err.required'),
(val) =>
val.length >= 8 ||
t('reg.err.atleast') + ' 8 ' + t('reg.err.char'),
]"
>
<template v-slot:prepend>
<q-icon name="lock" color="primary" />
</template>
<template v-slot:append>
<q-btn
v-if="!autoCompleteTriggered"
flat
round
dense
tabindex="-1"
:icon="typePassword === 'password' ? 'visibility_off' : 'visibility'"
@click="showPassword"
/>
</template>
</q-input>
<!-- Submit Button -->
<q-btn
type="submit"
unelevated
size="lg"
color="primary"
:label="$t('login.enter')"
class="submit-btn"
tabindex="3"
/>
<!-- Forgot Password Link -->
<div class="forgot-password">
<a :href="getlinkforgetpwd()" class="forgot-link">
<q-icon name="help_outline" size="18px" />
{{ t('reg.forgetpassword') }}
</a>
</div>
<!-- Divider -->
<div v-if="site.confpages && site.confpages?.enableReg && showregbutt" class="divider">
<span>Non hai un account?</span>
</div>
<!-- Registration Button (Bot) -->
<div
v-if="
site.confpages &&
site.confpages?.enableReg &&
showregbutt &&
site.confpages?.enableRegByBot
"
class="register-section"
>
<p class="register-text">Se non sei ancora registrato:</p>
<q-btn
type="a"
unelevated
size="lg"
color="positive"
icon="person_add"
href="/bot"
:label="$t('reg.submit')"
class="register-btn"
/>
</div>
<!-- Registration Button (Standard) -->
<div
v-else-if="site.confpages && site.confpages?.enableReg && showregbutt"
class="register-section"
>
<p class="register-text">Se non sei ancora registrato:</p>
<q-btn
unelevated
size="lg"
color="positive"
icon="person_add"
to="/registrati"
:label="$t('reg.submit')"
class="register-btn"
/>
</div>
</div>
</q-form>
</q-card-section>
</q-card>
</div>
</template>
<script lang="ts" src="./CSignIn.ts">
</script>
<script lang="ts" src="./CSignIn.ts"></script>
<style lang="scss" scoped>
@import './CSignIn.scss';

View File

@@ -1,46 +1,843 @@
// ========================================
// VARIABILI E CONFIGURAZIONE
// ========================================
$primary-color: #1976d2;
$primary-light: #42a5f5;
$primary-dark: #1565c0;
$accent-color: #26a69a;
$positive-color: #21ba45;
$negative-color: #c10015;
$warning-color: #f2c037;
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
$transition-speed: 0.3s;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
$mobile-breakpoint: 768px;
$mobile-footer-height: 80px;
// ========================================
// CONTAINER PRINCIPALE
// ========================================
.signup-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
position: relative;
@media (max-width: $mobile-breakpoint) {
padding: 0;
align-items: flex-start;
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
}
}
// ========================================
// CARDS DI SISTEMA (Success, Error, Warning)
// ========================================
.already-logged,
.already-registered {
width: 100%;
max-width: 500px;
animation: fadeInUp 0.5s ease;
}
.success-card,
.error-card,
.warning-card,
.telegram-card {
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
background: white;
.q-icon {
animation: scaleIn 0.5s ease;
}
}
.success-card {
border-top: 4px solid $positive-color;
}
.error-card {
border-top: 4px solid $negative-color;
}
.warning-card {
border-top: 4px solid $warning-color;
}
.action-btn {
min-width: 140px;
border-radius: $border-radius-sm;
text-transform: none;
font-weight: 500;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-md;
}
}
// ========================================
// FORM PRINCIPALE
// ========================================
.signup-form {
width: 100%;
max-width: 800px;
animation: fadeInUp 0.5s ease;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
}
}
// ========================================
// HEADER
// ========================================
.signup-header {
text-align: center;
padding: 0 10px 10px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: $border-radius-lg $border-radius-lg 0 0;
box-shadow: $shadow-sm;
@media (max-width: $mobile-breakpoint) {
padding: 6px 8px;
border-radius: 0;
margin: 8px;
background: white;
}
}
.signup-logo {
margin: 2px auto 2px;
animation: rotateIn 0.6s ease;
@media (max-width: $mobile-breakpoint) {
margin: 2px auto;
}
}
.signup-title {
h1 {
margin: 0;
background: linear-gradient(135deg, $primary-color, $accent-color);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 2rem;
@media (max-width: $mobile-breakpoint) {
font-size: 1.5rem;
}
}
p {
margin: 8px 0 0;
font-size: 1rem;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
margin: 4px 0 0;
}
}
}
// ========================================
// FORM CONTENT
// ========================================
.form-content {
background: white;
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
@media (max-width: $mobile-breakpoint) {
border-radius: 0;
flex: 1;
display: flex;
flex-direction: column;
}
}
// ========================================
// PROGRESS STEPPER
// ========================================
.progress-stepper {
display: flex;
justify-content: center;
align-items: center;
padding: 24px 20px;
background: linear-gradient(to bottom, rgba(103, 126, 234, 0.05), transparent);
@media (max-width: $mobile-breakpoint) {
padding: 12px 16px;
}
}
.step-item {
display: flex;
align-items: center;
position: relative;
&.active .step-circle {
background: $primary-color;
color: white;
transform: scale(1.15);
box-shadow: 0 4px 12px rgba(25, 118, 210, 0.4);
}
&.completed .step-circle {
background: $positive-color;
color: white;
}
}
.step-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: #e0e0e0;
color: #757575;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
transition: all $transition-speed ease;
position: relative;
z-index: 2;
@media (max-width: $mobile-breakpoint) {
width: 32px;
height: 32px;
font-size: 0.75rem;
}
}
.step-line {
width: 60px;
height: 3px;
background: linear-gradient(to right, #e0e0e0, #bdbdbd);
margin: 0 4px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
width: 40px;
}
.completed + & {
background: linear-gradient(to right, $positive-color, $primary-color);
}
}
// ========================================
// CAROUSEL
// ========================================
.carousel-wrapper {
@media (max-width: $mobile-breakpoint) {
flex: 1;
display: flex;
flex-direction: column;
padding-bottom: $mobile-footer-height;
}
}
.modern-carousel {
background: transparent;
@media (max-width: $mobile-breakpoint) {
flex: 1;
height: auto !important;
}
}
.carousel-slide {
padding: 0;
@media (max-width: $mobile-breakpoint) {
padding: 0;
}
}
// ========================================
// SLIDE CONTENT
// ========================================
.slide-content {
padding: 5px 16px 16px;
display: flex;
flex-direction: column;
gap: 12px;
animation: fadeIn 0.4s ease;
@media (max-width: $mobile-breakpoint) {
padding: 0px 16px 16px;
gap: 12px;
min-height: calc(100vh - 280px);
}
&.final-slide {
@media (max-width: $mobile-breakpoint) {
min-height: auto;
}
}
}
.slide-header {
text-align: center;
padding-bottom: 16px;
border-bottom: 2px solid rgba(0, 0, 0, 0.05);
@media (max-width: $mobile-breakpoint) {
padding-bottom: 12px;
}
.q-icon {
margin-bottom: 12px;
animation: bounceIn 0.6s ease;
@media (max-width: $mobile-breakpoint) {
font-size: 32px !important;
margin-bottom: 8px;
}
}
}
.slide-title {
font-size: 1.75rem;
font-weight: 600;
margin: 12px 0 8px;
color: #1a1a1a;
@media (max-width: $mobile-breakpoint) {
font-size: 1.25rem;
margin: 8px 0 4px;
}
}
.slide-subtitle {
font-size: 1rem;
color: #666;
margin: 0;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
// ========================================
// FORM FIELDS
// ========================================
.form-fields {
display: flex;
flex-direction: column;
gap: 16px;
@media (max-width: $mobile-breakpoint) {
gap: 10px;
}
}
.modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
min-height: 56px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
min-height: 48px;
border-radius: 10px;
}
&:before {
border-color: rgba(0, 0, 0, 0.12);
}
&:hover:before {
border-color: $primary-light;
}
}
:deep(.q-field__label) {
font-size: 1rem;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
:deep(.q-field__prepend) {
.q-icon {
font-size: 24px;
@media (max-width: $mobile-breakpoint) {
font-size: 20px;
}
}
}
&.q-field--focused {
:deep(.q-field__control) {
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
}
}
.password-hint {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: rgba(103, 126, 234, 0.08);
border-radius: $border-radius-sm;
font-size: 0.875rem;
color: #666;
margin-top: 8px;
@media (max-width: $mobile-breakpoint) {
padding: 8px;
font-size: 0.8125rem;
margin-top: 4px;
}
}
// ========================================
// SUMMARY CARD (Slide 4)
// ========================================
.summary-card {
background: linear-gradient(135deg, rgba(103, 126, 234, 0.08), rgba(118, 75, 162, 0.08));
padding: 20px;
border-radius: $border-radius;
display: flex;
flex-direction: column;
gap: 12px;
@media (max-width: $mobile-breakpoint) {
padding: 12px;
gap: 8px;
}
}
.summary-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: white;
border-radius: $border-radius-sm;
font-size: 1rem;
color: #333;
box-shadow: $shadow-sm;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
padding: 10px;
gap: 10px;
font-size: 0.875rem;
}
&:hover {
transform: translateX(4px);
box-shadow: $shadow-md;
}
.q-icon {
font-size: 24px;
flex-shrink: 0;
@media (max-width: $mobile-breakpoint) {
font-size: 20px;
}
}
span {
font-weight: 500;
word-break: break-word;
}
}
// ========================================
// POLICY SECTION
// ========================================
.policy-section {
display: flex;
flex-direction: column;
gap: 16px;
padding-top: 16px;
@media (max-width: $mobile-breakpoint) {
gap: 12px;
padding-top: 3px;
}
}
.policy-btn {
text-transform: none;
font-weight: 500;
align-self: center;
border-radius: $border-radius-sm;
padding: 8px 16px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
padding: 4px 16px;
}
&:hover {
background: rgba(25, 118, 210, 0.08);
}
}
.terms-checkbox {
padding: 16px;
background: rgba(0, 0, 0, 0.02);
border-radius: $border-radius-sm;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
padding: 0px 12px;
}
&:hover {
background: rgba(0, 0, 0, 0.04);
}
:deep(.q-checkbox__label) {
font-size: 0.9375rem;
line-height: 1.5;
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
}
}
}
.terms-text {
color: #333;
font-weight: 500;
}
// ========================================
// NAVIGATION BUTTONS
// ========================================
.navigation-buttons {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 20px 32px;
background: linear-gradient(to top, rgba(0, 0, 0, 0.02), transparent);
@media (max-width: $mobile-breakpoint) {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
background: white;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
z-index: 1000;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
&.mobile {
.nav-btn {
flex: 1;
max-width: none;
}
.back-btn {
flex: 0 0 auto;
min-width: 48px;
padding: 0 12px;
}
}
}
.nav-btn {
min-height: 48px;
border-radius: $border-radius-sm;
font-weight: 600;
text-transform: none;
font-size: 1rem;
transition: all $transition-speed ease;
box-shadow: $shadow-sm;
@media (max-width: $mobile-breakpoint) {
min-height: 44px;
font-size: 0.9375rem;
}
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: $shadow-md;
}
&:active:not(:disabled) {
transform: translateY(0);
}
&:disabled {
opacity: 0.5;
}
}
.back-btn {
min-width: 120px;
@media (max-width: $mobile-breakpoint) {
min-width: auto;
}
}
.next-btn,
.submit-btn {
flex: 1;
max-width: 300px;
background: linear-gradient(135deg, $primary-color, $primary-light);
&:hover:not(:disabled) {
background: linear-gradient(135deg, $primary-dark, $primary-color);
}
@media (max-width: $mobile-breakpoint) {
max-width: none;
}
}
.submit-btn {
background: linear-gradient(135deg, $positive-color, #26a69a);
&:hover:not(:disabled) {
background: linear-gradient(135deg, #1e8e3e, $positive-color);
}
}
// ========================================
// DEBUG STEPPER
// ========================================
.debug-stepper {
padding: 16px 32px;
@media (max-width: $mobile-breakpoint) {
padding: 12px 16px;
margin-bottom: $mobile-footer-height;
}
}
// ========================================
// TELEGRAM SECTION
// ========================================
.telegram-section {
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 16px;
}
}
// ========================================
// ANIMAZIONI
// ========================================
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes rotateIn {
from {
opacity: 0;
transform: rotate(-180deg);
}
to {
opacity: 1;
transform: rotate(0);
}
}
// ========================================
// UTILITY CLASSES
// ========================================
.signup {
width: 100%;
margin: 0 auto;
max-width: 450px;
max-width: 800px;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
}
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.clCellCode {
border-radius: 32px;
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
// Legacy classes (mantenute per compatibilità)
.clCellCode,
.clCell {
border-radius: 32px;
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
.vue-country-select{
.vue-country-select {
border-radius: 32px;
}
.myuserinvitante{
.myuserinvitante {
font-weight: bold;
color: red;
color: $negative-color;
font-size: 1.5rem;
}
.cosa_chiedere{
font-weight: bold;
color: blue;
font-size: 1rem;
padding: 10px;
// ========================================
// RESPONSIVE UTILITIES
// ========================================
@media (max-width: $mobile-breakpoint) {
// Assicura che il body non scrolli quando necessario
body.signup-open {
overflow: hidden;
position: fixed;
width: 100%;
}
// Fix per iOS Safari
.signup-container {
min-height: -webkit-fill-available;
}
}
// ========================================
// DARK MODE SUPPORT (opzionale)
// ========================================
// @media (prefers-color-scheme: dark) {
// .signup-container {
// background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
// }
// .signup-header,
// .form-content {
// background: #2a2a3e;
// color: #ffffff;
// }
// .slide-title {
// color: #ffffff;
// }
// .slide-subtitle {
// color: #b0b0b0;
// }
// .summary-card {
// background: linear-gradient(135deg, rgba(103, 126, 234, 0.15), rgba(118, 75, 162, 0.15));
// }
// .summary-item {
// background: #3a3a4e;
// color: #ffffff;
// }
// .modern-input {
// :deep(.q-field__control) {
// background: #3a3a4e;
// color: #ffffff;
// }
// :deep(.q-field__label) {
// color: #b0b0b0;
// }
// }
// }
.signup-header {
position: relative;
}
.header-content {
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.signup-logo {
position: absolute;
left: 0;
margin: 0 !important;
}
.signup-title {
text-align: center;
h1 {
margin: 0;
font-size: 1.5rem;
@media (max-width: $mobile-breakpoint) {
font-size: 1.25rem;
}
}
}

View File

@@ -10,7 +10,16 @@ import { CTitleBanner } from '../CTitleBanner';
import { CCopyBtn } from '../CCopyBtn';
import { CRegistration } from '../CRegistration';
import { PagePolicy } from '../PagePolicy';
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue';
import {
computed,
defineComponent,
nextTick,
onMounted,
onUnmounted,
reactive,
ref,
watch,
} from 'vue';
import { CSignIn } from '@src/components/CSignIn';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
@@ -123,6 +132,14 @@ export default defineComponent({
const inputPassword = ref(<any>null);
const inputPassword2 = ref(<any>null);
const submitBtn = ref(<any>null); // AGGIUNGI QUESTA RIGA
// Responsive detection
const isMobile = ref(false);
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const invitaStore = useInvitaAmicoStore();
const checkifDisabled = computed(() => {
@@ -132,7 +149,7 @@ export default defineComponent({
ret =
!signup.email ||
(tools.getAskToVerifyReg() &&
(!signup.aportador_solidario || inputAportador.value.hasError)) ||
(!signup.aportador_solidario || inputAportador.value?.hasError)) ||
(inputEmail.value && inputEmail.value.hasError);
} else if (slide.value === '2') {
// Username
@@ -158,6 +175,37 @@ export default defineComponent({
return ret;
});
// Auto-focus sul primo campo quando cambia slide
watch(
() => slide.value,
async (newSlide) => {
await nextTick();
if (newSlide === '1') {
// Step 1: Focus su aportador o email
if (inputAportador.value && props.showaportador) {
inputAportador.value.focus();
} else if (inputEmail.value) {
inputEmail.value.focus();
}
} else if (newSlide === '2') {
// Step 2: Focus su username
if (inputUsername.value) {
inputUsername.value.focus();
}
} else if (newSlide === '3') {
// Step 3: Focus su password
if (inputPassword.value) {
inputPassword.value.focus();
}
} else if (newSlide === '4') {
if (submitBtn.value) {
submitBtn.value.$el.focus();
}
}
}
);
const typePassword = ref('password');
const ap_iniziale = ref('');
@@ -452,20 +500,27 @@ export default defineComponent({
onMounted(async () => {
const token = props.token;
// Check mobile on mount
checkMobile();
window.addEventListener('resize', checkMobile);
if (token) {
// carica le info della registrazione
const risinvite = await invitaStore.ottieniInvitoByToken(token);
if (risinvite && risinvite.email) {
signup.email = risinvite.email;
if (risinvite.usernameInvitante)
signup.aportador_solidario = risinvite.usernameInvitante
slide.value = '2'
signup.aportador_solidario = risinvite.usernameInvitante;
slide.value = '2';
}
}
});
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
created();
return {
@@ -502,6 +557,8 @@ export default defineComponent({
inputPassword,
inputPassword2,
shared_consts,
isMobile,
submitBtn,
};
},
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,68 @@
// ========================================
// VARIABILI
// ========================================
$transition-speed: 0.3s;
$mobile-breakpoint: 768px;
.svgclass {
color: white;
transform: translateY(0px);
}
.svgclass_animate {
transform: translateY(-70px);
color: red;
}
#sun {
animation: gravity 5s infinite;
}
#logoimg {
height: 100px;
width: auto;
@media screen and (max-width: 600px) {
// ========================================
// LOGO WRAPPER
// ========================================
.logo-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
transition: all $transition-speed ease;
// Hover effect subtile
&:hover {
.logo-image {
transform: scale(1.05);
}
}
}
@keyframes gravity {
// ========================================
// LOGO IMAGE
// ========================================
.logo-image {
height: 100px;
width: auto;
max-width: 100%;
object-fit: contain;
transition: all $transition-speed ease;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
// Animazione ingresso
animation: logoEntrance 0.8s ease;
@media (max-width: $mobile-breakpoint) {
height: 80px;
}
// Animazione hover
&:hover {
filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.15));
}
}
// ========================================
// ANIMAZIONI
// ========================================
// Animazione ingresso logo
@keyframes logoEntrance {
from {
opacity: 0;
transform: scale(0.8) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
// Animazione rotazione 3D (per elementi speciali)
@keyframes rotate3D {
0% {
transform: rotateY(0deg);
}
@@ -32,8 +71,161 @@
}
}
// ========================================
// CLASSI UTILITY (per animazioni personalizzate)
// ========================================
// Rotazione continua
.logo-rotate {
.logo-image {
animation: rotate3D 10s linear infinite;
}
}
// Pulsazione
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.logo-pulse {
.logo-image {
animation: pulse 2s ease-in-out infinite;
}
}
// Flottante
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.logo-float {
.logo-image {
animation: float 3s ease-in-out infinite;
}
}
// ========================================
// LEGACY SUPPORT (per compatibilità)
// ========================================
// Supporto per ID legacy
#logo {
display: inline-flex;
align-items: center;
justify-content: center;
}
#logoimg {
height: 100px;
width: auto;
max-width: 100%;
object-fit: contain;
transition: all $transition-speed ease;
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
@media (max-width: $mobile-breakpoint) {
height: 80px;
}
}
// Classi SVG legacy
.svgclass {
color: white;
transform: translateY(0);
transition: all $transition-speed ease;
}
.svgclass_animate {
transform: translateY(-70px);
color: red;
}
// Animazione legacy per elementi specifici
#sun {
animation: rotate3D 10s linear infinite;
}
#smile {
opacity: 0.1 !important;
fill: red;
}
// ========================================
// TEMI SPECIALI
// ========================================
// Logo con glow effect
.logo-glow {
.logo-image {
filter: drop-shadow(0 0 20px rgba(103, 126, 234, 0.5));
animation: glowPulse 2s ease-in-out infinite;
}
}
@keyframes glowPulse {
0%, 100% {
filter: drop-shadow(0 0 20px rgba(103, 126, 234, 0.5));
}
50% {
filter: drop-shadow(0 0 30px rgba(103, 126, 234, 0.8));
}
}
// Logo con effetto rainbow (opzionale)
.logo-rainbow {
.logo-image {
animation: rainbowFilter 3s linear infinite;
}
}
@keyframes rainbowFilter {
0% {
filter: hue-rotate(0deg) drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
}
100% {
filter: hue-rotate(360deg) drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
}
}
// ========================================
// DARK MODE SUPPORT
// ========================================
@media (prefers-color-scheme: dark) {
.logo-image {
filter: drop-shadow(0 2px 8px rgba(255, 255, 255, 0.15));
&:hover {
filter: drop-shadow(0 4px 16px rgba(255, 255, 255, 0.25));
}
}
#logoimg {
filter: drop-shadow(0 2px 8px rgba(255, 255, 255, 0.15));
}
}
// ========================================
// PRINT STYLES
// ========================================
@media print {
.logo-wrapper,
#logo {
page-break-inside: avoid;
}
.logo-image,
#logoimg {
max-height: 60px;
filter: none;
}
}

View File

@@ -1,11 +1,16 @@
<template>
<div id="logo">
<img id="logoimg" :alt="logoalt()" :src=logoimg() :style="mystyle">
</div>
<div class="logo-wrapper">
<img
class="logo-image"
:alt="logoalt()"
:src="logoimg()"
:style="mystyle"
>
</div>
</template>
<script lang="ts" src="./logo.ts">
</script>
<script lang="ts" src="./logo.ts"></script>
<style lang="scss" scoped>
@import './logo.scss';
@import './logo.scss';
</style>

View File

@@ -43,7 +43,7 @@ const msg_website_it = {
presentazione: 'Presentazione',
presentazione2: 'Presentazione',
invita: 'Invita Persone',
SignUp: 'Modulo di Registrazione:',
SignUp: 'Unisciti a {sitename}!',
SignUpCollettivo: 'Reg. Collettiva:',
SignUpCollettivo2: 'Registrazione Collettiva:',
need_Telegram: 'Per poter utilizzare la Piattaforma occorre avere <a href="https://play.google.com/store/apps/details?id=org.telegram.messenger" target="_blank">Telegram</a> installato<br>',

View File

@@ -10,6 +10,11 @@ export interface ILinkReg {
idlink: string
}
export interface IAmmetti {
token: string
username: string
}
export interface ICallResult {
code?: string
msg?: string

View File

@@ -666,6 +666,17 @@ function getRoutesAd(site: ISites) {
infooter: false,
separator: false
},
{
active: true,
order: 1005,
path: '/ammetti/:token/:username',
materialIcon: 'how_to_reg',
name: 'pages.Ammetti',
component: () => import('@src/views/login/ammetti/ammetti.vue'),
inmenu: false,
infooter: false,
separator: false
},
{
active: true,
order: 1001,

View File

@@ -704,6 +704,7 @@ const msg_it = {
onlyadult: 'Confermo di essere Maggiorenne',
submit: 'Registrati',
title_verif_reg: 'Verifica Registrazione',
title_ammetti_membro: 'Ammetti Membro',
reg_ok: 'Registrazione Effettuata con Successo',
verificato: 'Verificato',
non_verificato: 'Non Verificato',
@@ -829,7 +830,7 @@ const msg_it = {
},
reset: {
title_reset_pwd: 'Reimposta la tua Password',
send_reset_pwd: 'Invia Reimposta la password',
send_reset_pwd: 'Reimposta la password',
resend_reset_pwd: 'Rimanda nuovamente',
incorso: 'Richiesta Nuova Email...',
email_sent: 'Email inviata',

View File

@@ -6,6 +6,9 @@ export const serv_constants = {
RIS_CODE_EMAIL_ALREADY_VERIFIED: -5,
RIS_CODE_EMAIL_VERIFIED: 1,
RIS_CODE_AMMESSO: 1,
RIS_CODE_GIA_AMMESSO: -5,
RIS_CODE_REC_DUPLICATED_DESCR_CITY_USER: -110,
RIS_CODE_REC_ALREADY_EXIST_SYMBOL: -102,
RIS_CODE_REC_ALREADY_EXIST_CODE: -101,

View File

@@ -19,7 +19,7 @@ import type {
import { IFriends, ISettings } from '@src/model';
import { tools } from '@tools';
import translate from '@src/globalroutines/util';
import type { ILinkReg, IToken } from '@model/other';
import type { IAmmetti, ILinkReg, IToken } from '@model/other';
import { ICallResult, IResult } from '@model/other';
import objectId from '@src/js/objectId';
@@ -1111,6 +1111,31 @@ export const useUserStore = defineStore('UserStore', {
return { code: this.getServerCode, msg: error.getMsgError() };
});
},
async ammetti(paramquery: IAmmetti) {
const usertosend = {
token: paramquery.token,
username: paramquery.username,
};
console.log(usertosend);
this.setServerCode(tools.CALLING);
return Api.SendReq('/ammetti', 'POST', usertosend)
.then((res) => {
// console.log("RITORNO 2 ");
// mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_AMMESSO) {
console.log('AMMESSO !!');
} else {
console.log('Risultato di ammetti: ', res.data.code);
}
return { code: res.data.code, msg: res.data.msg };
})
.catch((error) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
},
async unsubscribe(paramquery: any) {
return Api.SendReq('/news/unsubscribe', 'POST', paramquery)

View File

@@ -0,0 +1,37 @@
.mypanel {
padding: 10px;
margin: 10px;
}
.admission-header {
background: linear-gradient(135deg, var(--q-primary) 0%, #1976d2 100%);
border-radius: 16px;
padding: 48px 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
.result-card {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
.q-card-section {
padding: 32px 24px;
}
}
.opacity-80 {
opacity: 0.8;
}
// Responsive
@media (max-width: 600px) {
.admission-header {
padding: 32px 16px;
h4 {
font-size: 1.5rem;
}
}
}

View File

@@ -0,0 +1,94 @@
import { defineComponent, onMounted, ref } from 'vue';
import { serv_constants } from '../../../store/Modules/serv_constants';
import type { IAmmetti, ILinkReg } from '../../../model/other';
import { ICallResult } from '../../../model/other';
import { CSigninNoreg } from '../../../components/CSigninNoreg';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useGlobalStore } from '@store/globalStore';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@store/UserStore';
import { tools } from '@tools';
export default defineComponent({
name: 'Ammetti',
components: { CSigninNoreg },
setup(props) {
const $q = useQuasar();
const route = useRoute();
const $router = useRouter();
const { t } = useI18n();
const globalStore = useGlobalStore();
const userStore = useUserStore();
const router = useRouter();
const isLoading = ref(false);
const username = ref('')
const risultato = ref('---');
const riscode = ref(0);
function myrisultato() {
return risultato;
}
function giaammesso() {
return riscode.value !== serv_constants.RIS_CODE_AMMESSO;
}
function ammettiok() {
return riscode.value === serv_constants.RIS_CODE_AMMESSO;
}
function load() {
console.log('load Ammetti');
isLoading.value = true
username.value = (route.params.username) ? route.params.username.toString() : '';
let param: IAmmetti = { token: '', username: '' };
if (route.params.token) param = { token: route.params.token.toString(), username: username.value };
// console.log('idlink = ', param)
return userStore
.ammetti(param)
.then((ris: any) => {
riscode.value = ris.code;
risultato.value = ris.msg;
isLoading.value = false
})
.catch((err: any) => {
console.log('ERR = ' + err);
isLoading.value = false
});
}
onMounted(() => {
load();
});
const goToProfile = () => {
// Naviga al profilo del membro ammesso
router.push(`/my/${username.value}`);
};
const goHome = () => {
router.push('/');
};
return {
tools,
ammettiok,
giaammesso,
myrisultato,
t,
isLoading,
goHome,
goToProfile,
};
},
});

View File

@@ -0,0 +1,106 @@
<template>
<q-page class="vreg">
<div class="q-pa-md">
<!-- Header con gradient -->
<div class="admission-header q-mb-lg">
<div class="text-center">
<q-icon
name="person_add"
size="64px"
class="q-mb-md"
color="white"
/>
<h4 class="text-h4 text-white q-my-none q-mb-sm">
{{ t('reg.title_ammetti_membro') }}
</h4>
<p class="text-subtitle1 text-white opacity-80">
Gestione ammissione nuovo membro
</p>
</div>
</div>
<!-- Loading state -->
<transition
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
mode="out-in"
>
<div v-if="isLoading" class="text-center q-py-xl">
<q-spinner-dots color="primary" size="50px" />
<p class="text-subtitle1 text-grey-7 q-mt-md">
Elaborazione in corso...
</p>
</div>
<!-- Result card -->
<q-card v-else class="result-card" flat bordered>
<!-- Already admitted warning -->
<q-card-section
v-if="giaammesso()"
class="bg-warning text-white"
>
<div class="row items-center q-gutter-md">
<q-icon name="warning" size="48px" />
<div class="col">
<div class="text-h6 q-mb-xs">Attenzione</div>
<div class="text-body1">{{ myrisultato() }}</div>
</div>
</div>
</q-card-section>
<!-- Success message -->
<q-card-section
v-else-if="ammettiok()"
class="bg-positive text-white"
>
<div class="row items-center q-gutter-md">
<q-icon name="check_circle" size="48px" />
<div class="col">
<div class="text-h6 q-mb-xs">Ammissione Completata</div>
<div class="text-body1">{{ myrisultato() }}</div>
</div>
</div>
</q-card-section>
<!-- Actions -->
<q-card-actions align="center" class="q-pa-md">
<q-btn
v-if="ammettiok()"
label="Visualizza Profilo"
color="primary"
unelevated
icon="person"
@click="goToProfile"
class="q-px-xl"
/>
<q-btn
label="Torna alla Home"
color="primary"
outline
@click="goHome"
/>
</q-card-actions>
</q-card>
</transition>
<!-- Info card (optional) -->
<q-card flat bordered class="q-mt-md bg-blue-1">
<q-card-section>
<div class="row items-center q-gutter-sm">
<q-icon name="info" color="primary" size="24px" />
<div class="text-body2 text-grey-8">
L'ammissione del membro verrà notificata via email e Telegram
</div>
</div>
</q-card-section>
</q-card>
</div>
</q-page>
</template>
<script lang="ts" src="./ammetti.ts">
</script>
<style lang="scss" scoped>
@import './ammetti.scss';
</style>

View File

@@ -1,7 +1,10 @@
.signup {
width: 100%;
margin: 0 auto;
max-width: 450px;
max-width: 800px;
@media (max-width: 800px) {
max-width: 100%;
}
}

View File

@@ -0,0 +1,400 @@
// ========================================
// VARIABILI (Sincronizzate)
// ========================================
$primary-color: #1976d2;
$primary-light: #42a5f5;
$primary-dark: #1565c0;
$accent-color: #26a69a;
$positive-color: #21ba45;
$negative-color: #c10015;
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
$transition-speed: 0.3s;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
$mobile-breakpoint: 768px;
// ========================================
// CONTAINER PRINCIPALE
// ========================================
.reset-password-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 12px;
align-items: flex-start;
padding-top: 40px;
}
}
// ========================================
// CARD PRINCIPALE
// ========================================
.reset-card {
width: 100%;
max-width: 500px;
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
background: white;
animation: fadeInUp 0.6s ease;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
border-radius: $border-radius;
}
}
// ========================================
// HEADER
// ========================================
.reset-header {
text-align: center;
padding: 40px 24px 24px;
background: linear-gradient(to bottom, rgba(103, 126, 234, 0.05), transparent);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
&.success {
background: linear-gradient(to bottom, rgba(33, 186, 69, 0.08), transparent);
border-bottom-color: rgba(33, 186, 69, 0.15);
}
@media (max-width: $mobile-breakpoint) {
padding: 32px 16px 20px;
}
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
margin: 0 auto 20px;
border-radius: 50%;
background: linear-gradient(135deg, rgba(25, 118, 210, 0.1), rgba(38, 166, 154, 0.1));
animation: scaleIn 0.6s ease;
&.success {
background: linear-gradient(135deg, rgba(33, 186, 69, 0.15), rgba(38, 166, 154, 0.15));
}
@media (max-width: $mobile-breakpoint) {
width: 100px;
height: 100px;
margin: 0 auto 16px;
.q-icon {
font-size: 48px !important;
}
}
}
.reset-title {
font-size: 1.875rem;
font-weight: 600;
margin: 0 0 12px;
color: #1a1a1a;
line-height: 1.3;
@media (max-width: $mobile-breakpoint) {
font-size: 1.5rem;
margin: 0 0 10px;
}
}
.reset-subtitle {
font-size: 1rem;
color: #666;
margin: 0;
line-height: 1.5;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
}
strong {
color: #333;
font-weight: 600;
}
}
// ========================================
// FORM
// ========================================
.reset-form {
padding: 32px 24px;
@media (max-width: $mobile-breakpoint) {
padding: 24px 16px;
}
}
.form-fields {
display: flex;
flex-direction: column;
gap: 20px;
@media (max-width: $mobile-breakpoint) {
gap: 16px;
}
}
// ========================================
// INPUT FIELDS
// ========================================
.modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
min-height: 56px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
min-height: 52px;
border-radius: 10px;
}
&:before {
border-color: rgba(0, 0, 0, 0.12);
}
&:hover:before {
border-color: $primary-light;
}
}
:deep(.q-field__label) {
font-size: 1rem;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
}
}
:deep(.q-field__prepend) {
.q-icon {
font-size: 24px;
@media (max-width: $mobile-breakpoint) {
font-size: 20px;
}
}
}
&.q-field--focused {
:deep(.q-field__control) {
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
}
&.q-field--error {
animation: shake 0.4s ease;
}
}
// ========================================
// CODE INPUT
// ========================================
.code-input-wrapper {
display: flex;
flex-direction: column;
gap: 12px;
@media (max-width: $mobile-breakpoint) {
gap: 10px;
}
}
.code-input {
:deep(.q-field__control) {
input {
text-align: center;
font-size: 1.5rem;
letter-spacing: 8px;
font-weight: 600;
@media (max-width: $mobile-breakpoint) {
font-size: 1.25rem;
letter-spacing: 6px;
}
}
}
}
.code-hint {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: rgba(103, 126, 234, 0.06);
border-radius: $border-radius-sm;
font-size: 0.875rem;
color: #666;
@media (max-width: $mobile-breakpoint) {
padding: 10px;
font-size: 0.8125rem;
}
.q-icon {
flex-shrink: 0;
}
}
// ========================================
// BUTTONS
// ========================================
.submit-btn {
width: 100%;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $primary-color, $primary-light);
box-shadow: $shadow-md;
transition: all $transition-speed ease;
margin-top: 8px;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: $shadow-lg;
background: linear-gradient(135deg, $primary-dark, $primary-color);
}
&:active:not(:disabled) {
transform: translateY(0);
}
&:disabled {
opacity: 0.5;
}
@media (max-width: $mobile-breakpoint) {
height: 52px;
font-size: 1rem;
}
}
.confirm-btn {
background: linear-gradient(135deg, $positive-color, #26a69a);
&:hover:not(:disabled) {
background: linear-gradient(135deg, #1e8e3e, $positive-color);
}
}
// ========================================
// LINKS
// ========================================
.back-link,
.resend-link {
text-align: center;
margin-top: 4px;
@media (max-width: $mobile-breakpoint) {
margin-top: 0;
}
}
.link-text {
display: inline-flex;
align-items: center;
gap: 6px;
color: #666;
text-decoration: none;
font-size: 0.9375rem;
transition: all $transition-speed ease;
padding: 8px 12px;
border-radius: $border-radius-sm;
cursor: pointer;
&:hover {
color: $primary-color;
background: rgba(25, 118, 210, 0.06);
}
@media (max-width: $mobile-breakpoint) {
font-size: 0.875rem;
padding: 6px 10px;
}
.q-icon {
font-size: inherit;
}
}
// ========================================
// ANIMAZIONI
// ========================================
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-10px);
}
75% {
transform: translateX(10px);
}
}
// ========================================
// RESPONSIVE UTILITIES
// ========================================
@media (max-width: $mobile-breakpoint) {
.reset-password-container {
min-height: -webkit-fill-available;
}
}
// ========================================
// LEGACY CLASSES
// ========================================
.padding {
padding: 20px;
}
.center {
display: flex;
justify-content: center;
}
.mybanner {
font-size: 1.125rem;
font-weight: 600;
}

View File

@@ -1,92 +1,133 @@
<template>
<form
v-if="!emailinviata()"
@submit.prevent.stop="submit"
class="row justify-center text-center padding"
>
<div class="q-gutter-sm q-ma-sm">
<div>
<q-banner
rounded
class="bg-primary text-white"
style="text-align: center"
>
<span class="mybanner">{{ t('reset.title_reset_pwd') }}</span>
</q-banner>
<br />
<q-input
ref="emailRef"
v-model="form.email"
rounded
outlined
autocomplete="email"
maxlength="50"
debounce="1000"
:error="v$.email.$error"
:error-message="tools.errorMsg('email', v$.email)"
:label="$t('reg.email')"
>
<template v-slot:prepend>
<q-icon name="email" />
</template>
</q-input>
<br />
<div class="center q-ma-sm">
<q-btn
rounded
size="lg"
color="primary"
type="submit"
:disable="v$.$error || v$.$invalid"
>{{ t('reset.send_reset_pwd') }}
</q-btn>
<div class="reset-password-container">
<!-- Card Reset Password -->
<q-card class="reset-card">
<!-- Stato 1: Richiesta Email -->
<form v-if="!emailinviata()" @submit.prevent.stop="submit">
<!-- Header -->
<div class="reset-header">
<div class="icon-wrapper">
<q-icon name="lock_reset" size="56px" color="primary" />
</div>
<h1 class="reset-title">{{ t('reset.title_reset_pwd') }}</h1>
<p class="reset-subtitle">Inserisci la tua email per recuperare l'accesso</p>
</div>
<!-- Form Section -->
<q-card-section class="reset-form">
<div class="form-fields">
<!-- Email Input -->
<q-input
ref="emailRef"
v-model="form.email"
filled
class="modern-input"
autocomplete="email"
maxlength="50"
debounce="1000"
tabindex="1"
:error="v$.email.$error"
:error-message="tools.errorMsg('email', v$.email)"
:label="$t('reg.email')"
>
<template v-slot:prepend>
<q-icon name="email" color="primary" />
</template>
</q-input>
<!-- Submit Button -->
<q-btn
type="submit"
unelevated
size="lg"
color="primary"
icon="send"
:disable="v$.$error || v$.$invalid"
:label="t('reset.send_reset_pwd')"
class="submit-btn"
tabindex="2"
/>
<!-- Back to Login -->
<div class="back-link">
<a href="/signin" class="link-text">
<q-icon name="arrow_back" size="18px" />
Torna al login
</a>
</div>
</div>
</q-card-section>
</form>
<!-- Stato 2: Conferma Codice -->
<div v-else>
<!-- Success Header -->
<div class="reset-header success">
<div class="icon-wrapper success">
<q-icon name="mark_email_read" size="56px" color="positive" />
</div>
<h1 class="reset-title">{{ t('reset.email_sent') }}</h1>
<p class="reset-subtitle">
<strong>{{ t('reset.check_email') }}</strong>
</p>
</div>
<!-- Code Verification Section -->
<q-card-section class="reset-form">
<div class="form-fields">
<!-- Code Input -->
<div class="code-input-wrapper">
<q-input
v-model="form.tokenforgot_code"
filled
class="modern-input code-input"
label="Inserisci il codice a 6 cifre"
debounce="1000"
:maxlength="6"
type="text"
inputmode="numeric"
pattern="[0-9]*"
tabindex="1"
>
<template v-slot:prepend>
<q-icon name="pin" color="primary" />
</template>
</q-input>
<div class="code-hint">
<q-icon name="info" size="16px" color="grey-6" />
<span>Controlla la tua casella email e spam</span>
</div>
</div>
<!-- Confirm Button -->
<q-btn
@click="checkCode"
unelevated
size="lg"
color="positive"
icon="check_circle"
type="submit"
:disable="v$.$error || v$.$invalid"
:label="t('reset.confirmcode_reset')"
class="submit-btn confirm-btn"
tabindex="2"
/>
<!-- Resend Link -->
<div class="resend-link">
<a @click.prevent="submit" class="link-text">
<q-icon name="refresh" size="18px" />
Non hai ricevuto il codice? Invia di nuovo
</a>
</div>
</div>
</q-card-section>
</div>
</div>
</form>
<div v-else>
<q-banner rounded class="bg-positive text-white" style="text-align: center">
<span class="mybanner">{{ t('reset.email_sent') }}</span>
</q-banner>
<br />
<div>
<strong>{{ t('reset.check_email') }}</strong>
</div>
<br>
<q-input
v-model="form.tokenforgot_code"
rounded
outlined
label="Inserisci il codice a 6 cifre"
debounce="1000"
:maxlength="6"
type="number"
>
</q-input>
<br /><br />
<div class="center q-ma-sm">
<q-btn
@click="checkCode"
rounded
size="lg"
color="primary"
type="submit"
:disable="v$.$error || v$.$invalid"
>{{ t('reset.confirmcode_reset') }}
</q-btn>
</div>
</q-card>
</div>
</template>
<script lang="ts" src="./requestresetpwd.ts">
</script>
<script lang="ts" src="./requestresetpwd.ts"></script>
<style lang="scss" scoped>
@import './requestresetpwd';

View File

@@ -1,6 +1,376 @@
.mypanel {
padding: 10px;
margin: 10px;
// ========================================
// VARIABILI (Sincronizzate)
// ========================================
$primary-color: #1976d2;
$primary-light: #42a5f5;
$primary-dark: #1565c0;
$accent-color: #26a69a;
$positive-color: #21ba45;
$negative-color: #c10015;
$border-radius: 16px;
$border-radius-sm: 12px;
$border-radius-lg: 24px;
$transition-speed: 0.3s;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
$shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
$mobile-breakpoint: 768px;
// ========================================
// CONTAINER PRINCIPALE
// ========================================
.update-password-container {
width: 100%;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 12px;
align-items: flex-start;
padding-top: 40px;
}
}
// ========================================
// CARD PRINCIPALE
// ========================================
.update-card {
width: 100%;
max-width: 500px;
border-radius: $border-radius-lg;
box-shadow: $shadow-lg;
overflow: hidden;
background: white;
animation: fadeInUp 0.6s ease;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
border-radius: $border-radius;
}
}
// ========================================
// HEADER
// ========================================
.update-header {
text-align: center;
padding: 40px 24px 24px;
background: linear-gradient(to bottom, rgba(103, 126, 234, 0.05), transparent);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
&.success {
background: linear-gradient(to bottom, rgba(33, 186, 69, 0.08), transparent);
border-bottom-color: rgba(33, 186, 69, 0.15);
}
@media (max-width: $mobile-breakpoint) {
padding: 32px 16px 20px;
}
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 120px;
margin: 0 auto 20px;
border-radius: 50%;
background: linear-gradient(135deg, rgba(25, 118, 210, 0.1), rgba(38, 166, 154, 0.1));
animation: scaleIn 0.6s ease;
&.success {
background: linear-gradient(135deg, rgba(33, 186, 69, 0.15), rgba(38, 166, 154, 0.15));
}
@media (max-width: $mobile-breakpoint) {
width: 100px;
height: 100px;
margin: 0 auto 16px;
.q-icon {
font-size: 48px !important;
}
}
}
.update-title {
font-size: 1.875rem;
font-weight: 600;
margin: 0 0 12px;
color: #1a1a1a;
line-height: 1.3;
@media (max-width: $mobile-breakpoint) {
font-size: 1.5rem;
margin: 0 0 10px;
}
}
.update-subtitle {
font-size: 1rem;
color: #666;
margin: 0;
line-height: 1.5;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
}
strong {
color: #333;
font-weight: 600;
}
}
// ========================================
// FORM
// ========================================
.update-form {
padding: 32px 24px;
@media (max-width: $mobile-breakpoint) {
padding: 24px 16px;
}
}
.form-fields {
display: flex;
flex-direction: column;
gap: 20px;
@media (max-width: $mobile-breakpoint) {
gap: 16px;
}
}
// ========================================
// INPUT FIELDS
// ========================================
.modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
min-height: 56px;
transition: all $transition-speed ease;
@media (max-width: $mobile-breakpoint) {
min-height: 52px;
border-radius: 10px;
}
&:before {
border-color: rgba(0, 0, 0, 0.12);
}
&:hover:before {
border-color: $primary-light;
}
}
:deep(.q-field__label) {
font-size: 1rem;
@media (max-width: $mobile-breakpoint) {
font-size: 0.9375rem;
}
}
:deep(.q-field__prepend) {
.q-icon {
font-size: 24px;
@media (max-width: $mobile-breakpoint) {
font-size: 20px;
}
}
}
&.q-field--focused {
:deep(.q-field__control) {
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
}
&.q-field--error {
animation: shake 0.4s ease;
}
}
// ========================================
// PASSWORD HINT
// ========================================
.password-hint {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
background: rgba(103, 126, 234, 0.08);
border-radius: $border-radius-sm;
font-size: 0.875rem;
color: #666;
margin-top: -8px;
@media (max-width: $mobile-breakpoint) {
padding: 10px;
font-size: 0.8125rem;
margin-top: -4px;
}
.q-icon {
flex-shrink: 0;
}
}
// ========================================
// BUTTONS
// ========================================
.submit-btn {
width: 100%;
height: 56px;
border-radius: $border-radius;
font-size: 1.125rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $positive-color, #26a69a);
box-shadow: $shadow-md;
transition: all $transition-speed ease;
margin-top: 8px;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: $shadow-lg;
background: linear-gradient(135deg, #1e8e3e, $positive-color);
}
&:active:not(:disabled) {
transform: translateY(0);
}
&:disabled {
opacity: 0.5;
}
@media (max-width: $mobile-breakpoint) {
height: 52px;
font-size: 1rem;
}
}
// ========================================
// SUCCESS MESSAGE
// ========================================
.success-message {
text-align: center;
padding: 20px;
@media (max-width: $mobile-breakpoint) {
padding: 16px;
}
p {
margin: 0 0 24px;
font-size: 1rem;
color: #333;
line-height: 1.6;
@media (max-width: $mobile-breakpoint) {
margin: 0 0 20px;
font-size: 0.9375rem;
}
}
}
.action-btn {
min-width: 200px;
height: 52px;
border-radius: $border-radius;
font-size: 1.0625rem;
font-weight: 600;
text-transform: none;
background: linear-gradient(135deg, $primary-color, $primary-light);
box-shadow: $shadow-md;
transition: all $transition-speed ease;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
background: linear-gradient(135deg, $primary-dark, $primary-color);
}
@media (max-width: $mobile-breakpoint) {
width: 100%;
min-width: auto;
height: 48px;
font-size: 1rem;
}
}
// ========================================
// ANIMAZIONI
// ========================================
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
25% {
transform: translateX(-10px);
}
75% {
transform: translateX(10px);
}
}
// ========================================
// RESPONSIVE UTILITIES
// ========================================
@media (max-width: $mobile-breakpoint) {
.update-password-container {
min-height: -webkit-fill-available;
}
}
// ========================================
// LEGACY CLASSES
// ========================================
.mypanel {
width: 100%;
}
.padding {
padding: 20px;
}
.mybanner {
font-size: 1.125rem;
font-weight: 600;
}

View File

@@ -1,107 +1,132 @@
<template>
<div class="mypanel">
<form @submit.prevent.stop="submit" class="q-col-gutter-lg row justify-center text-center padding q-pa-md">
<div v-if="!emailsent">
<q-banner
rounded
dense
class="bg-primary text-white"
style="text-align: center;">
<span class="mybanner">{{ t('reset.title_update_pwd') }}</span>
</q-banner>
<div class="q-my-lg"></div>
<div class="column">
<q-input
v-model="form.password"
:type="typePassword"
dense
rounded outlined
@blur="v$.password.$touch"
:error="v$.password.$error"
:error-message="`${tools.errorMsg('password', v$.password)}`"
maxlength="30"
:label="$t('reg.password')">
<template v-slot:append>
<q-btn
tabindex="-1"
:icon="typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye`"
@click="showPassword"
>
</q-btn>
</template>
<template v-slot:prepend>
<q-icon name="vpn_key"/>
</template>
</q-input>
<div class="q-my-sm"></div>
<q-input
v-model="form.repeatPassword"
:type="typePassword"
dense
maxlength="30"
rounded outlined
@blur="v$.repeatPassword.$touch"
:error="v$.repeatPassword.$error"
:error-message="`${tools.errorMsg('repeatpassword', v$.repeatPassword)}`"
:label="$t('reg.repeatPassword')">
<template v-slot:prepend>
<q-icon name="vpn_key"/>
</template>
<template v-slot:append>
<q-btn
tabindex="-1"
:icon="typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye`"
@click="showPassword"
>
</q-btn>
</template>
</q-input>
<div class="q-my-sm"></div>
<div align="center">
<q-btn rounded size="lg" color="primary" type="submit" :disable="v$.$error">
{{ t('reset.update_password') }}
</q-btn>
<div class="update-password-container">
<!-- Card Update Password -->
<q-card class="update-card">
<!-- Stato 1: Form Aggiornamento Password -->
<form v-if="!emailsent" @submit.prevent.stop="submit">
<!-- Header -->
<div class="update-header">
<div class="icon-wrapper">
<q-icon name="lock_open" size="56px" color="primary" />
</div>
<h1 class="update-title">{{ t('reset.title_update_pwd') }}</h1>
<p class="update-subtitle">Crea una nuova password sicura</p>
</div>
</div>
<!-- Form Section -->
<q-card-section class="update-form">
<div class="form-fields">
<!-- Password Input -->
<q-input
v-model="form.password"
:type="typePassword"
filled
class="modern-input"
@blur="v$.password.$touch"
:error="v$.password.$error"
:error-message="`${tools.errorMsg('password', v$.password)}`"
maxlength="30"
tabindex="1"
:label="$t('reg.password')"
>
<template v-slot:prepend>
<q-icon name="vpn_key" color="primary" />
</template>
<template v-slot:append>
<q-btn
flat
round
dense
tabindex="-1"
:icon="typePassword === 'password' ? 'visibility_off' : 'visibility'"
@click="showPassword"
/>
</template>
</q-input>
<!-- Repeat Password Input -->
<q-input
v-model="form.repeatPassword"
:type="typePassword"
filled
class="modern-input"
maxlength="30"
@blur="v$.repeatPassword.$touch"
:error="v$.repeatPassword.$error"
:error-message="`${tools.errorMsg('repeatpassword', v$.repeatPassword)}`"
tabindex="2"
:label="$t('reg.repeatPassword')"
>
<template v-slot:prepend>
<q-icon name="lock" color="primary" />
</template>
<template v-slot:append>
<q-btn
flat
round
dense
tabindex="-1"
:icon="typePassword === 'password' ? 'visibility_off' : 'visibility'"
@click="showPassword"
/>
</template>
</q-input>
<!-- Password Hint -->
<div class="password-hint">
<q-icon name="info" size="16px" color="grey-6" />
<span>Minimo 8 caratteri, includi lettere e numeri</span>
</div>
<!-- Submit Button -->
<q-btn
type="submit"
unelevated
size="lg"
color="positive"
icon="check_circle"
:disable="v$.$error"
:label="t('reset.update_password')"
class="submit-btn"
tabindex="3"
/>
</div>
</q-card-section>
</form>
<!-- Stato 2: Successo -->
<div v-else>
<q-banner
rounded
class="bg-primary text-white"
style="text-align: center;">
<span class="mybanner">{{ t('reset.email_sent') }}</span>
</q-banner>
<br>
<div>
{{ t('reset.check_email') }}
<!-- Success Header -->
<div class="update-header success">
<div class="icon-wrapper success">
<q-icon name="check_circle" size="56px" color="positive" />
</div>
<h1 class="update-title">{{ t('reset.email_sent') }}</h1>
<p class="update-subtitle">
<strong>{{ t('reset.check_email') }}</strong>
</p>
</div>
<!-- Success Message -->
<q-card-section class="update-form">
<div class="success-message">
<p>La tua password è stata aggiornata con successo!</p>
<q-btn
unelevated
color="primary"
icon="login"
label="Vai al Login"
to="/login"
class="action-btn"
/>
</div>
</q-card-section>
</div>
</form>
</q-card>
</div>
</template>
<script lang="ts" src="./updatepassword.ts">
</script>
<script lang="ts" src="./updatepassword.ts"></script>
<style lang="scss" scoped>
@import './updatepassword';