- 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', myhosps2: 'myhosps2',
mygood2: 'mygood2', mygood2: 'mygood2',
InvitoReg: 'Invito', InvitoReg: 'Invito',
Ammetti: 'Ammetti',
installaApp: 'Installa App', installaApp: 'Installa App',
fundraising: 'Sostieni il Progetto', fundraising: 'Sostieni il Progetto',
notifs: 'Configura le Notifiche', notifs: 'Configura le Notifiche',
@@ -45,7 +46,7 @@ const msg_website_it = {
presentazione: 'Presentazione', presentazione: 'Presentazione',
presentazione2: 'Presentazione', presentazione2: 'Presentazione',
invita: 'Invita Persone', invita: 'Invita Persone',
SignUp: 'Modulo di Registrazione', SignUp: 'Crea il tuo account',
SignUpCollettivo: 'Reg. Collettiva:', SignUpCollettivo: 'Reg. Collettiva:',
SignUpCollettivo2: 'Registrazione 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>', 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 { useI18n } from 'vue-i18n'
import { CContactUser } from '@src/components/CContactUser' import { CContactUser } from '@src/components/CContactUser'
import { CMyUser } from '@src/components/CMyUser' import { CMyUser } from '@src/components/CMyUser'
import { Logo } from '@src/components/logo'
import { DefaultProfile, useUserStore } from '@store/UserStore' import { DefaultProfile, useUserStore } from '@store/UserStore'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
@@ -14,7 +15,7 @@ import { useGlobalStore } from '@store/globalStore'
export default defineComponent({ export default defineComponent({
name: 'CRegistration', name: 'CRegistration',
emits: ['regEventEmail'], emits: ['regEventEmail'],
components: { CMyUser, CContactUser }, components: { CMyUser, CContactUser, Logo },
props: { props: {
slide: { slide: {
type: String, type: String,
@@ -57,14 +58,18 @@ export default defineComponent({
const actionType = ref(costanti.ACTIONTYPE.LINK_REG) 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) { //if (site.value.confpages.enableRegByBot) {
//$router.push('/bot') //$router.push('/bot')
//} else { //} else {
$router.push('/registrati/' + tools.getInvitante()) $router.push('/registrati/' + tools.getInvitante())
//} //}
} }
function mounted() { function mounted() {
@@ -76,6 +81,10 @@ export default defineComponent({
chooseReg.value = true chooseReg.value = true
slide.value = 'second' slide.value = 'second'
} }
// Check mobile on mount
checkMobile()
window.addEventListener('resize', checkMobile)
} }
function regEventEmail() { function regEventEmail() {
@@ -91,16 +100,29 @@ export default defineComponent({
if (invitante) { if (invitante) {
start.value = true start.value = true
slide.value = 'second'; slide.value = 'second'
noInvited.value = false; noInvited.value = false
chooseReg.value = true; chooseReg.value = true
} else { } else {
start.value = true 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) onMounted(mounted)
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
return { return {
tools, tools,
site, site,
@@ -115,6 +137,8 @@ export default defineComponent({
listlinksreg, listlinksreg,
actionType, actionType,
t, t,
isMobile,
shouldShowDot,
} }
}, },
}) })

View File

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

View File

@@ -1,3 +1,412 @@
// ========================================
// 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 { .signin {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;

View File

@@ -1,22 +1,32 @@
<template> <template>
<div> <div class="signin-container">
<div v-if="enablePwa"><CCheckAppRunning :login="true"/></div> <!-- PWA Check -->
<div class="text-center"> <div v-if="enablePwa" class="pwa-check">
<p> <CCheckAppRunning :login="true" />
<logo></logo>
</p>
</div> </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>
<!-- Form -->
<q-card-section class="signin-form">
<q-form ref="myForm" @submit="onSubmit" @reset="onReset"> <q-form ref="myForm" @submit="onSubmit" @reset="onReset">
<div class="q-gutter-xs"> <div class="form-fields">
<!-- Username Input -->
<q-input <q-input
ref="refUsername" ref="refUsername"
v-model="signin.username" v-model="signin.username"
rounded filled
outlined class="modern-input"
dense
lazy-rules
:label="$t('reg.username_login')" :label="$t('reg.username_login')"
tabindex="1"
lazy-rules
:rules="[ :rules="[
(val) => !!val || t('reg.err.required'), (val) => !!val || t('reg.err.required'),
(val) => (val) =>
@@ -25,20 +35,23 @@
]" ]"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon name="person" color="primary" />
</template> </template>
</q-input> </q-input>
<!-- Password Input -->
<q-input <q-input
ref="refPassword" ref="refPassword"
v-model="signin.password" v-model="signin.password"
:type="typePassword" :type="typePassword"
rounded filled
outlined class="modern-input"
dense :label="$t('reg.password')"
tabindex="2"
debounce="500" debounce="500"
@update:model-value="checkAutoCompletion" @update:model-value="checkAutoCompletion"
v-on:keyup.enter="onSubmit()" v-on:keyup.enter="onSubmit()"
:label="$t('reg.password')" lazy-rules
:rules="[ :rules="[
(val) => !!val || t('reg.err.required'), (val) => !!val || t('reg.err.required'),
(val) => (val) =>
@@ -46,41 +59,47 @@
t('reg.err.atleast') + ' 8 ' + t('reg.err.char'), t('reg.err.atleast') + ' 8 ' + t('reg.err.char'),
]" ]"
> >
<template v-slot:prepend>
<q-icon name="lock" color="primary" />
</template>
<template v-slot:append> <template v-slot:append>
<q-btn <q-btn
v-if="!autoCompleteTriggered" v-if="!autoCompleteTriggered"
flat
round
dense
tabindex="-1" tabindex="-1"
:icon=" :icon="typePassword === 'password' ? 'visibility_off' : 'visibility'"
typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye`
"
@click="showPassword" @click="showPassword"
> />
</q-btn>
</template>
<template v-slot:prepend>
<q-icon name="vpn_key" />
</template> </template>
</q-input> </q-input>
<div style="text-align: center"> <!-- Submit Button -->
<q-btn <q-btn
type="submit" type="submit"
rounded unelevated
size="md" size="lg"
color="primary" color="primary"
:label="$t('login.enter')" :label="$t('login.enter')"
> class="submit-btn"
</q-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> </div>
<br /> <!-- Divider -->
<div v-if="site.confpages && site.confpages?.enableReg && showregbutt" class="divider">
<div class="text-center" style="margin-bottom: 10px"> <span>Non hai un account?</span>
<a :href="getlinkforgetpwd()" style="color: gray">{{
t('reg.forgetpassword')
}}</a>
</div> </div>
<!-- Registration Button (Bot) -->
<div <div
v-if=" v-if="
site.confpages && site.confpages &&
@@ -88,40 +107,45 @@
showregbutt && showregbutt &&
site.confpages?.enableRegByBot site.confpages?.enableRegByBot
" "
style="margin-top: 10px; text-align: center" class="register-section"
> >
Se non sei ancora Registrato:<br /> <p class="register-text">Se non sei ancora registrato:</p>
<q-btn <q-btn
type="a" type="a"
rounded unelevated
size="md" size="lg"
color="primary" color="positive"
icon="person_add"
href="/bot" href="/bot"
:label="$t('reg.submit')" :label="$t('reg.submit')"
> class="register-btn"
</q-btn> />
</div> </div>
<!-- Registration Button (Standard) -->
<div <div
v-else-if="site.confpages && site.confpages?.enableReg && showregbutt" v-else-if="site.confpages && site.confpages?.enableReg && showregbutt"
style="margin-top: 10px; text-align: center" class="register-section"
> >
Se non sei ancora Registrato:<br /> <p class="register-text">Se non sei ancora registrato:</p>
<q-btn <q-btn
rounded unelevated
size="md" size="lg"
color="primary" color="positive"
icon="person_add"
to="/registrati" to="/registrati"
:label="$t('reg.submit')" :label="$t('reg.submit')"
> class="register-btn"
</q-btn> />
</div> </div>
</div> </div>
</q-form> </q-form>
</q-card-section>
</q-card>
</div> </div>
</template> </template>
<script lang="ts" src="./CSignIn.ts"> <script lang="ts" src="./CSignIn.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './CSignIn.scss'; @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 { .signup {
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
max-width: 450px; max-width: 800px;
@media (max-width: $mobile-breakpoint) {
max-width: 100%;
}
} }
.wrapper { .wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.clCellCode { // Legacy classes (mantenute per compatibilità)
border-radius: 32px; .clCellCode,
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
.clCell { .clCell {
border-radius: 32px; border-radius: 32px;
border-right: #2d2260;
height: 50px; height: 50px;
font-size: 1rem; font-size: 1rem;
padding: 8px; padding: 8px;
} }
.vue-country-select{ .vue-country-select {
border-radius: 32px; border-radius: 32px;
} }
.myuserinvitante{ .myuserinvitante {
font-weight: bold; font-weight: bold;
color: red; color: $negative-color;
font-size: 1.5rem; font-size: 1.5rem;
} }
.cosa_chiedere{ // ========================================
font-weight: bold; // RESPONSIVE UTILITIES
color: blue; // ========================================
font-size: 1rem; @media (max-width: $mobile-breakpoint) {
padding: 10px; // 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 { CCopyBtn } from '../CCopyBtn';
import { CRegistration } from '../CRegistration'; import { CRegistration } from '../CRegistration';
import { PagePolicy } from '../PagePolicy'; 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 { CSignIn } from '@src/components/CSignIn';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -123,6 +132,14 @@ export default defineComponent({
const inputPassword = ref(<any>null); const inputPassword = ref(<any>null);
const inputPassword2 = 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 invitaStore = useInvitaAmicoStore();
const checkifDisabled = computed(() => { const checkifDisabled = computed(() => {
@@ -132,7 +149,7 @@ export default defineComponent({
ret = ret =
!signup.email || !signup.email ||
(tools.getAskToVerifyReg() && (tools.getAskToVerifyReg() &&
(!signup.aportador_solidario || inputAportador.value.hasError)) || (!signup.aportador_solidario || inputAportador.value?.hasError)) ||
(inputEmail.value && inputEmail.value.hasError); (inputEmail.value && inputEmail.value.hasError);
} else if (slide.value === '2') { } else if (slide.value === '2') {
// Username // Username
@@ -158,6 +175,37 @@ export default defineComponent({
return ret; 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 typePassword = ref('password');
const ap_iniziale = ref(''); const ap_iniziale = ref('');
@@ -452,20 +500,27 @@ export default defineComponent({
onMounted(async () => { onMounted(async () => {
const token = props.token; const token = props.token;
// Check mobile on mount
checkMobile();
window.addEventListener('resize', checkMobile);
if (token) { if (token) {
// carica le info della registrazione // carica le info della registrazione
const risinvite = await invitaStore.ottieniInvitoByToken(token); const risinvite = await invitaStore.ottieniInvitoByToken(token);
if (risinvite && risinvite.email) { if (risinvite && risinvite.email) {
signup.email = risinvite.email; signup.email = risinvite.email;
if (risinvite.usernameInvitante) if (risinvite.usernameInvitante)
signup.aportador_solidario = risinvite.usernameInvitante signup.aportador_solidario = risinvite.usernameInvitante;
slide.value = '2'
slide.value = '2';
} }
} }
}); });
onUnmounted(() => {
window.removeEventListener('resize', checkMobile);
});
created(); created();
return { return {
@@ -502,6 +557,8 @@ export default defineComponent({
inputPassword, inputPassword,
inputPassword2, inputPassword2,
shared_consts, shared_consts,
isMobile,
submitBtn,
}; };
}, },
}); });

View File

@@ -1,86 +1,150 @@
<template> <template>
<div> <div class="signup-container">
<!-- Banner utente già loggato -->
<div <div
v-if="tools.isLogged() && tools.getUsername() && !collettivo" v-if="tools.isLogged() && tools.getUsername() && !collettivo"
class="text-center" class="already-logged"
> >
<q-banner <q-card class="success-card">
rounded <q-card-section class="text-center">
class="bg-green text-white" <q-icon
style="text-align: center" name="check_circle"
size="48px"
color="positive"
/>
<div class="text-h6 q-mt-md">{{ tools.getUsername() }} sei già registrato!</div>
<p class="text-body2 q-mt-sm">Hai già accesso completo alla piattaforma</p>
</q-card-section>
<q-card-actions
align="center"
class="q-pb-md"
> >
<span class="mybanner">
{{ tools.getUsername() }} sei già correttamente registrato ed hai accesso alla
Piattaforma<br />
</span>
</q-banner>
<div class="row q-ma-sm q-pa-sm justify-center">
<q-btn <q-btn
class="q-ma-sm" unelevated
color="primary" color="primary"
icon="fas fa-home" icon="home"
label="Vai alla Home" label="Vai alla Home"
to="/" to="/"
></q-btn> class="action-btn"
/>
<q-btn <q-btn
class="q-ma-sm" outline
color="accent" color="primary"
icon="fas fa-sign" icon="visibility"
label="Voglio vedere la pagina di Registrazione" label="Mostra Registrazione"
@click="visureg = true" @click="visureg = true"
></q-btn> class="action-btn"
<br /> />
</div> </q-card-actions>
</q-card>
</div> </div>
<!-- Form di registrazione principale -->
<div <div
v-if="!tools.isLogged() || visureg || collettivo" v-if="!tools.isLogged() || visureg || collettivo"
class="text-center" class="signup-form"
> >
<div> <!-- Header con Logo -->
<div> <div
<logo mystyle="width: 40px !important; height: 40px !important; "></logo> v-if="!(visubuttBOT && needTelegram && !collettivo)"
class="signup-header"
<div v-if="!isalreadyReg && !(visubuttBOT && needTelegram)"> >
<CTitleBanner :title="$t('pages.SignUp')"></CTitleBanner> <div class="header-content">
<logo
class="signup-logo"
mystyle="width: 50px !important; height: 50px !important;"
/>
<div
v-if="!isalreadyReg && !(visubuttBOT && needTelegram)"
class="signup-title"
>
<h1 class="text-h5 text-weight-light">
{{ $t('pages.SignUp', { sitename: tools.getappname() }) }}
</h1>
</div> </div>
</div> </div>
</div> </div>
<!-- Telegram Bot Registration -->
<div <div
v-if="visubuttBOT && needTelegram && !collettivo" v-if="visubuttBOT && needTelegram && !collettivo"
class="q-gutter-md" class="telegram-section"
> >
<div class="q-ma-md"> <q-card class="telegram-card">
<q-card-section>
<CRegistration <CRegistration
:invited="signup.aportador_solidario" :invited="signup.aportador_solidario"
:regexpire="regexpire" :regexpire="regexpire"
@regEventEmail="regEventEmail" @regEventEmail="regEventEmail"
:signupform="true" :signupform="true"
/> />
</div> </q-card-section>
</q-card>
</div> </div>
<!-- Main Registration Form -->
<div <div
v-else-if="!isalreadyReg || collettivo" v-else-if="!isalreadyReg || collettivo"
class="q-gutter-sm q-mt-sm" class="form-content"
> >
<div v-if="signup.username === 'undefined'"> <!-- Username non impostato su Telegram -->
<br /> <q-card
Vai su <b>BOT RISO</b> Telegram ed imposta l'Username di Telegram.<br /><br /> v-if="signup.username === 'undefined'"
class="warning-card"
>
<q-card-section class="text-center">
<q-icon
name="warning"
size="48px"
color="warning"
/>
<div class="text-h6 q-mt-md">Configura Username Telegram</div>
<p class="q-mt-sm">
Vai su <strong>BOT RISO</strong> Telegram ed imposta l'Username.
</p>
<q-btn <q-btn
rounded unelevated
color="primary" color="primary"
icon="fab fa-telegram" icon="fab fa-telegram"
label="Apri BOT" label="Apri BOT"
type="a" type="a"
:href="tools.getLinkBotTelegram(signup.aportador_solidario, regexpire)" :href="tools.getLinkBotTelegram(signup.aportador_solidario, regexpire)"
target="_blank" target="_blank"
></q-btn> class="q-mt-md"
<br /><br /> />
</q-card-section>
</q-card>
<!-- Carousel Form -->
<div
v-else
class="carousel-wrapper"
>
<!-- Progress Stepper -->
<div class="progress-stepper">
<div
v-for="step in 4"
:key="step"
class="step-item"
:class="{
active: slide === String(step),
completed: parseInt(slide) > step,
}"
>
<div class="step-circle">
<q-icon
v-if="parseInt(slide) > step"
name="check"
size="16px"
/>
<span v-else>{{ step }}</span>
</div> </div>
<div v-else> <div
v-if="step < 4"
class="step-line"
></div>
</div>
</div>
<q-carousel <q-carousel
v-model="slide" v-model="slide"
ref="carousel" ref="carousel"
@@ -88,45 +152,35 @@
transition-next="slide-left" transition-next="slide-left"
animated animated
swipeable swipeable
:class="`shadow-1`" class="modern-carousel"
:height="isMobile ? 'auto' : '500px'"
> >
<template v-slot:control> <!-- Slide 1: Email e Invitante -->
<q-carousel-control <q-carousel-slide
position="bottom-right" name="1"
:offset="[18, 18]" class="carousel-slide"
class="q-gutter-xs"
> >
<q-btn <div class="slide-content">
v-if="slide !== '1'" <div class="slide-header">
push <q-icon
text-color="black" name="mail_outline"
icon="arrow_left" size="40px"
:label="$t('dialog.indietro')"
@click="$refs.carousel.previous()"
/>
<q-btn
v-if="slide !== '4'"
push
color="primary" color="primary"
icon="arrow_right"
:label="$t('dialog.avanti')"
:disabled="checkifDisabled"
@click="!checkifDisabled ? $refs.carousel.next() : null"
/> />
</q-carousel-control> <h2 class="slide-title">Iniziamo!</h2>
</template> </div>
<q-carousel-slide name="1">
<div class=""> <div class="form-fields">
<q-input <q-input
v-if=" v-if="
showaportador && signup.aportador_solidario !== tools.APORTADOR_NONE showaportador && signup.aportador_solidario !== tools.APORTADOR_NONE
" "
ref="inputAportador" ref="inputAportador"
bg-color="light-blue-4" tabindex="1"
:readonly="!!ap_iniziale"
v-model="signup.aportador_solidario" v-model="signup.aportador_solidario"
rounded :readonly="!!ap_iniziale"
outlined filled
class="modern-input"
@keyup.enter=" @keyup.enter="
v$.aportador_solidario.$touch && !v$.aportador_solidario.$error v$.aportador_solidario.$touch && !v$.aportador_solidario.$error
? $refs.inputEmail.focus() ? $refs.inputEmail.focus()
@@ -146,44 +200,71 @@
" "
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon
name="person"
color="primary"
/>
</template> </template>
</q-input> </q-input>
<div style="margin-top: 5px"></div>
<q-input <q-input
ref="inputEmail" ref="inputEmail"
tabindex="2"
v-model="signup.email" v-model="signup.email"
rounded filled
outlined class="modern-input"
@update:model-value="changeemail()" @update:model-value="changeemail()"
maxlength="50" maxlength="50"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null" v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
debounce="2000" debounce="2000"
:rules="[myRuleEmail]" :rules="[myRuleEmail]"
:label="collettivo ? t('reg.email_reg_collettivo') : t('reg.email_reg')" :label="
collettivo ? t('reg.email_reg_collettivo') : t('reg.email_reg')
"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="email" /> <q-icon
name="email"
color="primary"
/>
</template> </template>
</q-input> </q-input>
</div> </div>
</div>
</q-carousel-slide> </q-carousel-slide>
<q-carousel-slide name="2">
<div class="cosa_chiedere">{{ t('reg.scegli_username') }}</div> <!-- Slide 2: Username e Nome/Cognome -->
<q-carousel-slide
name="2"
class="carousel-slide"
>
<div class="slide-content">
<div class="slide-header">
<h2 class="slide-title">Scegli il tuo Username</h2>
<p class="slide-subtitle">Come vuoi essere chiamato?</p>
</div>
<div class="form-fields">
<q-input <q-input
ref="inputUsername" ref="inputUsername"
tabindex="1"
v-model="signup.username" v-model="signup.username"
:readonly=" :readonly="
tools.getAskToVerifyReg() && !site.confpages?.enableRegMultiChoice tools.getAskToVerifyReg() && !site.confpages?.enableRegMultiChoice
" "
rounded filled
outlined class="modern-input"
@blur="v$.username.$touch" @blur="v$.username.$touch"
@update:model-value="changeusername" @update:model-value="changeusername"
:error="v$.username.$error" :error="v$.username.$error"
@keydown.space="(event) => event.preventDefault()" @keydown.space="(event) => event.preventDefault()"
@keyup.enter="!v$.username.$error ? $refs.inputName.focus() : null" @keyup.enter="
!v$.username.$error
? $refs.inputName
? $refs.inputName.focus()
: null
: null
"
maxlength="20" maxlength="20"
debounce="500" debounce="500"
:error-message=" :error-message="
@@ -201,16 +282,19 @@
" "
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon
name="person"
color="primary"
/>
</template> </template>
</q-input> </q-input>
<div v-if="collettivo">
<q-input <q-input
v-if="collettivo"
ref="inputName" ref="inputName"
v-model="signup.name" v-model="signup.name"
rounded filled
outlined class="modern-input"
@blur="v$.name.$touch" @blur="v$.name.$touch"
:error="v$.name.$error" :error="v$.name.$error"
maxlength="30" maxlength="30"
@@ -220,240 +304,28 @@
:label="$t('reg.name_opt_collettivo')" :label="$t('reg.name_opt_collettivo')"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon
name="badge"
color="primary"
/>
</template> </template>
</q-input> </q-input>
</div>
<div v-else-if="show_namesurname"> <template v-else-if="show_namesurname">
<q-input <q-input
v-if="signup.name !== undefined"
ref="inputName" ref="inputName"
tabindex="2"
v-model="signup.name" v-model="signup.name"
rounded filled
outlined class="modern-input"
@blur="v$.name.$touch" @blur="v$.name.$touch"
:error="v$.name.$error" :error="v$.name.$error"
maxlength="30" maxlength="30"
debounce="1000" debounce="1000"
@keyup.enter="$refs.inputSurname.focus()"
:error-message="tools.errorMsg('name', v$.name)"
:label="
tools.getConfSiteOptionEnabled(
shared_consts.ConfSite.regNameSurnameMandatory
)
? t('reg.name')
: t('reg.name_opt')
"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
<q-input
ref="inputSurname"
v-model="signup.surname"
rounded
outlined
:error="v$.surname.$error"
@blur="v$.surname.$touch"
maxlength="30"
debounce="1000"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
:error-message="tools.errorMsg('surname', v$.surname)"
:label="
tools.getConfSiteOptionEnabled(
shared_consts.ConfSite.regNameSurnameMandatory
)
? t('reg.surname')
: t('reg.surname_opt')
"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
</div>
</q-carousel-slide>
<q-carousel-slide name="3">
<div class="cosa_chiedere">{{ t('reg.scegli_password') }}</div>
<q-input
ref="inputPassword"
v-model="signup.password"
class="q-mb-md"
:type="typePassword"
rounded
outlined
@blur="v$.password.$touch"
:error="v$.password.$error"
:error-message="`${tools.errorMsg('password', v$.password)}`"
@keyup.enter="!v$.password.$error ? $refs.inputPassword2.focus() : null"
maxlength="30"
debounce="1000"
:label="$t('reg.password_reg')"
>
<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>
<q-input
ref="inputPassword2"
v-model="signup.repeatPassword"
:type="typePassword"
maxlength="30"
rounded
outlined
@blur="v$.repeatPassword.$touch"
:error="v$.repeatPassword.$error"
:error-message="`${tools.errorMsg('repeatpassword', v$.repeatPassword)}`"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
:label="$t('reg.repeatPassword')"
>
<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>
</q-carousel-slide>
<q-carousel-slide name="4">
<div>
<q-input
v-if="
showaportador &&
signup.aportador_solidario !== tools.APORTADOR_NONE &&
v$.aportador_solidario.$error
"
ref="inputAportador"
bg-color="light-blue-4"
:readonly="!!ap_iniziale"
v-model="signup.aportador_solidario"
rounded
outlined
@keyup.enter=" @keyup.enter="
v$.aportador_solidario.$touch && !v$.aportador_solidario.$error $refs.inputSurname ? $refs.inputSurname.focus() : null
? $refs.inputEmail.focus()
: null
" "
@blur="v$.aportador_solidario.$touch"
:error="v$.aportador_solidario.$error"
:error-message="
tools.errorMsg('aportador_solidario', v$.aportador_solidario)
"
maxlength="20"
debounce="1000"
:label="
collettivo
? t('reg.username_admin_collettivo')
: t('reg.aportador_solidario')
"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
<div style="margin-top: 5px"></div>
<q-input
ref="inputEmail"
v-model="signup.email"
rounded
outlined
@update:model-value="changeemail()"
maxlength="50"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
debounce="2000"
:rules="[myRuleEmail]"
:label="collettivo ? t('reg.email_reg_collettivo') : t('reg.email_reg')"
>
<template v-slot:prepend>
<q-icon name="email" />
</template>
</q-input>
<q-input
ref="inputUsername"
v-model="signup.username"
:readonly="
tools.getAskToVerifyReg() && !site.confpages?.enableRegMultiChoice
"
rounded
outlined
@blur="v$.username.$touch"
@update:model-value="changeusername"
:error="v$.username.$error"
@keydown.space="(event) => event.preventDefault()"
@keyup.enter="!v$.username.$error ? $refs.inputName.focus() : null"
maxlength="20"
debounce="500"
:error-message="
tools.errorMsg('username', v$.username) ||
(isalreadyReg ? 'L\'Username è gia stato registrato!' : '')
"
:label="
tools.getConfSiteOptionEnabled(
shared_consts.ConfSite.askUSernameTelegramToTheReg
)
? t('reg.username_telegram')
: t('reg.username_reg')
"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
<div v-if="collettivo">
<q-input
ref="inputName"
v-model="signup.name"
rounded
outlined
@blur="v$.name.$touch"
:error="v$.name.$error"
maxlength="30"
debounce="1000"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
:error-message="tools.errorMsg('name', v$.name)"
:label="$t('reg.name_opt_collettivo')"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
</div>
<div v-else-if="show_namesurname">
<q-input
v-if="signup.name"
ref="inputName"
v-model="signup.name"
rounded
outlined
@blur="v$.name.$touch"
:error="v$.name.$error"
maxlength="30"
debounce="1000"
@keyup.enter="$refs.inputSurname.focus()"
:error-message="tools.errorMsg('name', v$.name)" :error-message="tools.errorMsg('name', v$.name)"
:label=" :label="
tools.getConfSiteOptionEnabled( tools.getConfSiteOptionEnabled(
@@ -464,16 +336,20 @@
" "
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon
name="person"
color="primary"
/>
</template> </template>
</q-input> </q-input>
<q-input <q-input
v-if="signup.surname" v-if="signup.surname !== undefined"
ref="inputSurname" ref="inputSurname"
tabindex="3"
v-model="signup.surname" v-model="signup.surname"
rounded filled
outlined class="modern-input"
:error="v$.surname.$error" :error="v$.surname.$error"
@blur="v$.surname.$touch" @blur="v$.surname.$touch"
maxlength="30" maxlength="30"
@@ -483,16 +359,41 @@
:label="$t('reg.surname_opt')" :label="$t('reg.surname_opt')"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="person" /> <q-icon
name="person_outline"
color="primary"
/>
</template> </template>
</q-input> </q-input>
</template>
</div>
</div>
</q-carousel-slide>
<!-- Slide 3: Password -->
<q-carousel-slide
name="3"
class="carousel-slide"
>
<div class="slide-content">
<div class="slide-header">
<q-icon
name="lock_outline"
size="40px"
color="primary"
/>
<h2 class="slide-title">Crea una Password</h2>
<p class="slide-subtitle">Proteggi il tuo account</p>
</div>
<div class="form-fields">
<q-input <q-input
v-if="false"
ref="inputPassword" ref="inputPassword"
tabindex="1"
v-model="signup.password" v-model="signup.password"
:type="typePassword" :type="typePassword"
rounded filled
outlined class="modern-input"
@blur="v$.password.$touch" @blur="v$.password.$touch"
:error="v$.password.$error" :error="v$.password.$error"
:error-message="`${tools.errorMsg('password', v$.password)}`" :error-message="`${tools.errorMsg('password', v$.password)}`"
@@ -503,107 +404,246 @@
debounce="1000" debounce="1000"
:label="$t('reg.password_reg')" :label="$t('reg.password_reg')"
> >
<template v-slot:prepend>
<q-icon
name="vpn_key"
color="primary"
/>
</template>
<template v-slot:append> <template v-slot:append>
<q-btn <q-btn
tabindex="-1" flat
round
dense
:icon=" :icon="
typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye` typePassword === 'password' ? 'visibility_off' : 'visibility'
" "
@click="showPassword" @click="showPassword"
> />
</q-btn>
</template>
<template v-slot:prepend>
<q-icon name="vpn_key" />
</template> </template>
</q-input> </q-input>
</div>
<q-input <q-input
ref="inputPassword2" ref="inputPassword2"
v-if="false" tabindex="2"
v-model="signup.repeatPassword" v-model="signup.repeatPassword"
:type="typePassword" :type="typePassword"
maxlength="30" maxlength="30"
rounded filled
outlined class="modern-input"
@blur="v$.repeatPassword.$touch" @blur="v$.repeatPassword.$touch"
:error="v$.repeatPassword.$error" :error="v$.repeatPassword.$error"
:error-message="`${tools.errorMsg('repeatpassword', v$.repeatPassword)}`" :error-message="`${tools.errorMsg('repeatpassword', v$.repeatPassword)}`"
v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null" v-on:keyup.enter="!checkifDisabled ? $refs.carousel.next() : null"
:label="$t('reg.repeatPassword')" :label="$t('reg.repeatPassword')"
> >
<template v-slot:prepend>
<q-icon
name="lock"
color="primary"
/>
</template>
<template v-slot:append> <template v-slot:append>
<q-btn <q-btn
tabindex="-1" flat
round
dense
:icon=" :icon="
typePassword === `password` ? `fas fa-eye-slash` : `fas fa-eye` typePassword === 'password' ? 'visibility_off' : 'visibility'
" "
@click="showPassword" @click="showPassword"
> />
</q-btn>
</template>
<template v-slot:prepend>
<q-icon name="vpn_key" />
</template> </template>
</q-input> </q-input>
<div class="text-center"> <div class="password-hint">
<q-btn <q-icon
label="Mostra Privacy" name="info"
@click="showpolicy = true" size="16px"
></q-btn> color="grey-6"
/>
<span>Minimo 8 caratteri, includi lettere e numeri</span>
</div> </div>
</div>
</div>
</q-carousel-slide>
<!-- Slide 4: Conferma e Policy -->
<q-carousel-slide
name="4"
class="carousel-slide"
>
<div class="slide-content final-slide">
<div class="slide-header">
<div class="flex items-center">
<q-icon
name="task_alt"
size="40px"
color="positive"
class="q-mr-md"
/>
<div>
<h2 class="slide-title">Conferma i dati!</h2>
<p class="slide-subtitle"></p>
</div>
</div>
</div>
<div class="summary-card">
<div class="summary-item">
<q-icon
name="email"
color="primary"
/>
<span>{{ signup.email }}</span>
</div>
<div class="summary-item">
<q-icon
name="person"
color="primary"
/>
<span>{{ signup.username }}</span>
</div>
<div
v-if="signup.name"
class="summary-item"
>
<q-icon
name="badge"
color="primary"
/>
<span>{{ signup.name }} {{ signup.surname }}</span>
</div>
</div>
<div class="policy-section">
<q-btn
dense
outline
color="primary"
label="Leggi la Privacy Policy"
icon="description"
@click="showpolicy = true"
class="policy-btn"
/>
<q-checkbox <q-checkbox
v-model="signup.terms" v-model="signup.terms"
color="secondary" color="primary"
@blur="v$.terms.$touch" @blur="v$.terms.$touch"
:error="v$.terms.$error" :error="v$.terms.$error"
:error-message="`${tools.errorMsg('terms', v$.terms)}`" class="terms-checkbox"
:label="$t('reg.terms')"
> >
<template v-slot:default>
<span class="terms-text">{{ $t('reg.terms') }}</span>
</template>
</q-checkbox> </q-checkbox>
<div class="column">
<q-btn
rounded
size="lg"
color="positive"
@click="submitOk"
:label="$t('reg.submit')"
>
</q-btn>
<br />
</div> </div>
</div> </div>
</q-carousel-slide> </q-carousel-slide>
</q-carousel> </q-carousel>
<div class="row justify-center"> <!-- Navigation Buttons - Fixed on Mobile -->
<q-btn-toggle <div
class="navigation-buttons"
:class="{ mobile: isMobile }"
>
<q-btn
v-if="slide !== '1'"
flat
color="grey-7"
icon="arrow_back"
:label="isMobile ? '' : $t('dialog.indietro')"
@click="$refs.carousel.previous()"
class="nav-btn back-btn"
/>
<q-btn
v-if="slide !== '4'"
unelevated
color="primary"
icon-right="arrow_forward"
:label="isMobile ? 'Avanti' : $t('dialog.avanti')"
:disabled="checkifDisabled"
@click="!checkifDisabled ? $refs.carousel.next() : null"
class="nav-btn next-btn"
/>
<q-btn
v-if="slide === '4'"
ref="submitBtn"
unelevated
color="positive"
icon="check"
:label="$t('reg.submit')"
:disabled="!allowSubmit()"
@click="submitOk"
class="nav-btn submit-btn"
/>
</div>
<!-- Debug Stepper (nascosto di default) -->
<div
v-if="!signup.terms" v-if="!signup.terms"
glossy class="debug-stepper"
>
<q-btn-toggle
v-model="slide" v-model="slide"
spread
glossy
toggle-color="primary"
:options="[ :options="[
{ label: 1, value: '1' }, { label: '1', value: '1' },
{ label: 2, value: '2' }, { label: '2', value: '2' },
{ label: 3, value: '3' }, { label: '3', value: '3' },
{ label: 4, value: '4' }, { label: '4', value: '4' },
]" ]"
/> />
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="isalreadyReg && !collettivo">
<q-banner <!-- Utente già registrato -->
class="bg-negative text-white text-h5" <div
transition-show="jump-down" v-else-if="isalreadyReg && !collettivo"
class="already-registered"
> >
Utente già registrato con l'username {{ signup.username }} <q-card class="error-card">
</q-banner> <q-card-section class="text-center">
<q-icon
name="error_outline"
size="48px"
color="negative"
/>
<div class="text-h6 q-mt-md">Utente già registrato</div>
<p class="q-mt-sm">
L'username <strong>{{ signup.username }}</strong> è già stato registrato
</p>
</q-card-section>
</q-card>
</div> </div>
</div> </div>
<!-- Policy Dialog -->
<q-dialog
v-model="showpolicy"
maximized
>
<q-card>
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Privacy Policy</div>
<q-space />
<q-btn
icon="close"
flat
round
dense
v-close-popup
/>
</q-card-section>
<q-card-section>
<PagePolicy />
</q-card-section>
</q-card>
</q-dialog>
</div> </div>
</template> </template>

View File

@@ -1,29 +1,68 @@
// ========================================
// VARIABILI
// ========================================
$transition-speed: 0.3s;
$mobile-breakpoint: 768px;
.svgclass { // ========================================
color: white; // LOGO WRAPPER
transform: translateY(0px); // ========================================
.logo-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
transition: all $transition-speed ease;
} // Hover effect subtile
&:hover {
.logo-image {
.svgclass_animate { transform: scale(1.05);
transform: translateY(-70px); }
color: red;
}
#sun {
animation: gravity 5s infinite;
}
#logoimg {
height: 100px;
width: auto;
@media screen and (max-width: 600px) {
} }
} }
@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% { 0% {
transform: rotateY(0deg); 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 { #smile {
opacity: 0.1 !important; opacity: 0.1 !important;
fill: red; 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> <template>
<div id="logo"> <div class="logo-wrapper">
<img id="logoimg" :alt="logoalt()" :src=logoimg() :style="mystyle"> <img
class="logo-image"
:alt="logoalt()"
:src="logoimg()"
:style="mystyle"
>
</div> </div>
</template> </template>
<script lang="ts" src="./logo.ts">
</script> <script lang="ts" src="./logo.ts"></script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './logo.scss'; @import './logo.scss';
</style> </style>

View File

@@ -43,7 +43,7 @@ const msg_website_it = {
presentazione: 'Presentazione', presentazione: 'Presentazione',
presentazione2: 'Presentazione', presentazione2: 'Presentazione',
invita: 'Invita Persone', invita: 'Invita Persone',
SignUp: 'Modulo di Registrazione:', SignUp: 'Unisciti a {sitename}!',
SignUpCollettivo: 'Reg. Collettiva:', SignUpCollettivo: 'Reg. Collettiva:',
SignUpCollettivo2: 'Registrazione 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>', 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 idlink: string
} }
export interface IAmmetti {
token: string
username: string
}
export interface ICallResult { export interface ICallResult {
code?: string code?: string
msg?: string msg?: string

View File

@@ -666,6 +666,17 @@ function getRoutesAd(site: ISites) {
infooter: false, infooter: false,
separator: 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, active: true,
order: 1001, order: 1001,

View File

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

View File

@@ -6,6 +6,9 @@ export const serv_constants = {
RIS_CODE_EMAIL_ALREADY_VERIFIED: -5, RIS_CODE_EMAIL_ALREADY_VERIFIED: -5,
RIS_CODE_EMAIL_VERIFIED: 1, 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_DUPLICATED_DESCR_CITY_USER: -110,
RIS_CODE_REC_ALREADY_EXIST_SYMBOL: -102, RIS_CODE_REC_ALREADY_EXIST_SYMBOL: -102,
RIS_CODE_REC_ALREADY_EXIST_CODE: -101, RIS_CODE_REC_ALREADY_EXIST_CODE: -101,

View File

@@ -19,7 +19,7 @@ import type {
import { IFriends, ISettings } from '@src/model'; import { IFriends, ISettings } from '@src/model';
import { tools } from '@tools'; import { tools } from '@tools';
import translate from '@src/globalroutines/util'; 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 { ICallResult, IResult } from '@model/other';
import objectId from '@src/js/objectId'; import objectId from '@src/js/objectId';
@@ -1111,6 +1111,31 @@ export const useUserStore = defineStore('UserStore', {
return { code: this.getServerCode, msg: error.getMsgError() }; 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) { async unsubscribe(paramquery: any) {
return Api.SendReq('/news/unsubscribe', 'POST', paramquery) 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 { .signup {
width: 100%; width: 100%;
margin: 0 auto; 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> <template>
<form <div class="reset-password-container">
v-if="!emailinviata()" <!-- Card Reset Password -->
@submit.prevent.stop="submit" <q-card class="reset-card">
class="row justify-center text-center padding" <!-- Stato 1: Richiesta Email -->
> <form v-if="!emailinviata()" @submit.prevent.stop="submit">
<div class="q-gutter-sm q-ma-sm"> <!-- Header -->
<div> <div class="reset-header">
<q-banner <div class="icon-wrapper">
rounded <q-icon name="lock_reset" size="56px" color="primary" />
class="bg-primary text-white" </div>
style="text-align: center" <h1 class="reset-title">{{ t('reset.title_reset_pwd') }}</h1>
> <p class="reset-subtitle">Inserisci la tua email per recuperare l'accesso</p>
<span class="mybanner">{{ t('reset.title_reset_pwd') }}</span> </div>
</q-banner>
<br />
<!-- Form Section -->
<q-card-section class="reset-form">
<div class="form-fields">
<!-- Email Input -->
<q-input <q-input
ref="emailRef" ref="emailRef"
v-model="form.email" v-model="form.email"
rounded filled
outlined class="modern-input"
autocomplete="email" autocomplete="email"
maxlength="50" maxlength="50"
debounce="1000" debounce="1000"
tabindex="1"
:error="v$.email.$error" :error="v$.email.$error"
:error-message="tools.errorMsg('email', v$.email)" :error-message="tools.errorMsg('email', v$.email)"
:label="$t('reg.email')" :label="$t('reg.email')"
> >
<template v-slot:prepend> <template v-slot:prepend>
<q-icon name="email" /> <q-icon name="email" color="primary" />
</template> </template>
</q-input> </q-input>
<br /> <!-- Submit Button -->
<div class="center q-ma-sm">
<q-btn <q-btn
rounded type="submit"
unelevated
size="lg" size="lg"
color="primary" color="primary"
type="submit" icon="send"
:disable="v$.$error || v$.$invalid" :disable="v$.$error || v$.$invalid"
>{{ t('reset.send_reset_pwd') }} :label="t('reset.send_reset_pwd')"
</q-btn> class="submit-btn"
</div> 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>
</div> </div>
</q-card-section>
</form> </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> <!-- 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> <strong>{{ t('reset.check_email') }}</strong>
</p>
</div> </div>
<br> <!-- Code Verification Section -->
<q-card-section class="reset-form">
<div class="form-fields">
<!-- Code Input -->
<div class="code-input-wrapper">
<q-input <q-input
v-model="form.tokenforgot_code" v-model="form.tokenforgot_code"
rounded filled
outlined class="modern-input code-input"
label="Inserisci il codice a 6 cifre" label="Inserisci il codice a 6 cifre"
debounce="1000" debounce="1000"
:maxlength="6" :maxlength="6"
type="number" type="text"
inputmode="numeric"
pattern="[0-9]*"
tabindex="1"
> >
<template v-slot:prepend>
<q-icon name="pin" color="primary" />
</template>
</q-input> </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>
<br /><br /> <!-- Confirm Button -->
<div class="center q-ma-sm">
<q-btn <q-btn
@click="checkCode" @click="checkCode"
rounded unelevated
size="lg" size="lg"
color="primary" color="positive"
icon="check_circle"
type="submit" type="submit"
:disable="v$.$error || v$.$invalid" :disable="v$.$error || v$.$invalid"
>{{ t('reset.confirmcode_reset') }} :label="t('reset.confirmcode_reset')"
</q-btn> 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>
</div> </div>
</q-card-section>
</div>
</q-card>
</div>
</template> </template>
<script lang="ts" src="./requestresetpwd.ts"> <script lang="ts" src="./requestresetpwd.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './requestresetpwd'; @import './requestresetpwd';

View File

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