- Sistemato INVITI alla App

- Completamento Profilo
- Registrazione tramite Invito, senza richiedere conferma email.
This commit is contained in:
Surya Paolo
2025-11-18 23:56:08 +01:00
parent fc569192e7
commit 4985e7565d
98 changed files with 2209 additions and 3595720 deletions

View File

@@ -1,12 +1,14 @@
VITE_APP_ID="18"
VITE_APP_ID="13"
VITE_APP_URL="https://localhost"
VITE_MONGODB_HOST="https://localhost:3000"
VITE_LOGO_REG='gruppomacro-logo-full.png'
DEBUG_VITE_APP_URL="https://192.168.8.182"
DEBUG_VITE_MONGODB_HOST="https://192.168.8.182:3000"
VITE_LOGO_REG='riso-logo-full.png'
VITE_PUBLICKEY_PUSH='BDncvMiUZmjaCG2Kr1V9N0_33hOG-AuNSbHSvL24y2dzBiUjAxKm02emx5SeJvz2IGmtRf6YqCgopeQwCwUmZw8'
VITE_DEBUG="1"
VITE_VUE_APP_ISTEST=0
VITE_VUE_APP_INLOCALE=1
DIRECTORY_LOCAL="newfreeplanet"
DIRECTORY_LOCAL="myprojplanet_vite"
DIRECTORY_SERVER="freeplanet_serverside"
SERVERDIR_WEBSITE=""
SERVERPW_WEBSITE=""

View File

@@ -1,13 +1,11 @@
VITE_APP_ID="18"
VITE_APP_URL="https://gruppomacro.app"
VITE_MONGODB_HOST="https://api.gruppomacro.app"
VITE_LOGO_REG='gruppomacro-logo-full.png'
VITE_PUBLICKEY_PUSH="BJgo8XR_upbnbMLWgCAUELo6DK7dRXffYAnFOxbaMMz5favBgcQBKT-eISqouO-jRad4Sw8l5nd2wCF6KorGiTc"
VITE_APP_ID="13"
VITE_APP_URL="https://riso.app"
VITE_MONGODB_HOST="https://api.riso.app"
VITE_LOGO_REG='riso-logo-full.png'
VITE_PUBLICKEY_PUSH="BGXRf1TgcqocqD6J7qnRgCG7AvM2lxAoW7peb7UEzB4SxBb6DxGRdJ0UvD9ewnrB9KrSrh0-aDCODXBm7sZ1DDs"
VITE_DEBUG="0"
VITE_VUE_APP_ISTEST="0"
DIRECTORY_LOCAL="myprojplanet_vite"
DIRECTORY_SERVER="/var/www/nodejs_piuchebuono_server"
SERVERDIR_WEBSITE="/var/www/gruppomacro.app"
SERVERPW_WEBSITE="pwdadmin@1AOK"
PORT_SPA="8089"
PORT_PWA="8099"
DIRECTORY_LOCAL=myprojplanet_vite
DIRECTORY_SERVER=/var/www/nodejs_riso_server
SERVERDIR_WEBSITE="/var/www/riso.app"
SERVERPW_WEBSITE="pwdadmin@1AOK"

View File

@@ -1,28 +1,28 @@
{
"name": "gruppomacro",
"name": "riso",
"version": "1.2.78",
"productName": "Gruppo Macro",
"description": "Il Gruppo Editoriale Macro, attivo dal 1987, è leader europeo nella pubblicazione di libri per il benessere e la consapevolezza. Con oltre 1.500 titoli, promuove una visione armonica del mondo, offrendo opere di autori internazionali e italiani come Gregg Braden, Bruce Lipton, Joe Dispenza, Louise Hay, Eckhart Tolle e molti altri. Scopri un'editoria che abbraccia il corpo, la mente, lo spirito e l'ecologia.",
"productName": "Riso 💚 - Rete Italiana Scambi Orizzontali",
"description": "Progetto RISO (Rete Italiana Scambi Orizzontali) promuove una rete di comunità locali che favoriscono scambi di beni, servizi e ospitalità. Con l'App RISO, sviluppata per facilitare il baratto, il dono e l'uso di monete alternative come i RIS, il progetto crea legami autentici basati sulla fiducia e sostenibilità. Partecipa agli scambi e costruisci una comunità più consapevole e autosufficiente.",
"author": "Surya",
"private": true,
"keywords": [],
"license": "MIT",
"type": "module",
"scripts": {
"dev": "PORT=8089 APP_VERSION='1.2.78' quasar dev",
"dev": "APP_VERSION='1.2.78' PORT=8084 quasar dev",
"dev_noCheck": "SKIP_TSC=true quasar dev",
"build": "quasar build",
"buildpwa": "NODE_ENV=production APP_VERSION='1.2.78' quasar build -m pwa",
"buildpwatest": "NODE_ENV=production APP_VERSION='1.2.78' quasar build -m pwa",
"type-check": "vue-tsc --noEmit",
"type-check:watch": "vue-tsc --noEmit --watch",
"buildspa": "APP_VERSION='1.2.78' quasar build -m spa",
"buildspa": "quasar build -m spa",
"lint": "eslint -c ./eslint.config.js \"./src*/**/*.{ts,js,cjs,mjs,vue}\"",
"lintfile": "eslint --ext .js,.ts,.vue --ignore-path .gitignore ./ > file.out.txt",
"lintfileNoJS": "eslint --ext .ts,.vue --ignore-path .gitignore ./ > file.out.txt",
"fix": "eslint -c ./eslint.config.js \"./src*/**/*.{ts,js,cjs,mjs,vue}\" --ignore-pattern .gitignore ./ --fix > file.out.txt",
"pwa": "NODE_ENV=development PORT=8099 APP_VERSION='1.2.78' quasar dev -m pwa",
"spa": "NODE_ENV=development PORT=8089 APP_VERSION='1.2.78' quasar dev",
"pwa": "NODE_ENV=development PORT=8094 APP_VERSION='1.2.78' quasar dev -m pwa",
"spa": "NODE_ENV=development PORT=8084 APP_VERSION='1.2.78' quasar dev",
"debug": "quasar dev --mode debug",
"test": "echo \"No test specified\" && exit 0",
"generate-sw": "workbox generateSW workbox-config.js",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

BIN
public/images/layers-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/images/layers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

BIN
public/images/riso_home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -1,2 +0,0 @@
!function(){'use strict';try{self['workbox:sw:6.1.0']&&_()}catch(t){}const t={backgroundSync:'background-sync',broadcastUpdate:'broadcast-update',cacheableResponse:'cacheable-response',core:'core',expiration:'expiration',googleAnalytics:'offline-ga',navigationPreload:'navigation-preload',precaching:'precaching',rangeRequests:'range-requests',routing:'routing',strategies:'strategies',streams:'streams',recipes:'recipes'};self.workbox=new class{constructor(){return this.v={},this.Pt={debug:'localhost'===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.$t=this.Pt.debug?'dev':'prod',this.jt=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule('workbox-'+o),e[s]}})}setConfig(t={}){if(this.jt)throw new Error('Config must be set before accessing workbox.* modules');Object.assign(this.Pt,t),this.$t=this.Pt.debug?'dev':'prod'}loadModule(t){const e=this.St(t);try{importScripts(e),this.jt=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}St(t){if(this.Pt.modulePathCb)return this.Pt.modulePathCb(t,this.Pt.debug);let e=['https://storage.googleapis.com/workbox-cdn/releases/6.1.0'];const s=`${t}.${this.$t}.js`,o=this.Pt.modulePathPrefix;return o&&(e=o.split('/'),''===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join('/')}}}();
//# sourceMappingURL=workbox-sw.js.map

View File

@@ -1,2 +0,0 @@
!function(){"use strict";try{self["workbox:sw:5.1.4"]&&_()}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule("workbox-"+o),e[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(t){if(this.t.modulePathCb)return this.t.modulePathCb(t,this.t.debug);let e=["https://storage.googleapis.com/workbox-cdn/releases/5.1.4"];const s=`${t}.${this.s}.js`,o=this.t.modulePathPrefix;return o&&(e=o.split("/"),""===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join("/")}}}();
//# sourceMappingURL=workbox-sw.js.map

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,2 +1,2 @@
!function(){"use strict";try{self["workbox:sw:5.1.4"]&&_()}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule("workbox-"+o),e[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(t){if(this.t.modulePathCb)return this.t.modulePathCb(t,this.t.debug);let e=["https://storage.googleapis.com/workbox-cdn/releases/5.1.4"];const s=`${t}.${this.s}.js`,o=this.t.modulePathPrefix;return o&&(e=o.split("/"),""===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join("/")}}}();
!function(){"use strict";try{self["workbox:sw:7.3.0"]&&_()}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams",recipes:"recipes"};self.workbox=new class{constructor(){return this.v={},this.Pt={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.$t=this.Pt.debug?"dev":"prod",this.jt=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule(`workbox-${o}`),e[s]}})}setConfig(t={}){if(this.jt)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.Pt,t),this.$t=this.Pt.debug?"dev":"prod"}loadModule(t){const e=this.St(t);try{importScripts(e),this.jt=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}St(t){if(this.Pt.modulePathCb)return this.Pt.modulePathCb(t,this.Pt.debug);let e=["https://storage.googleapis.com/workbox-cdn/releases/7.3.0"];const s=`${t}.${this.$t}.js`,o=this.Pt.modulePathPrefix;return o&&(e=o.split("/"),""===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join("/")}}}();
//# sourceMappingURL=workbox-sw.js.map

View File

@@ -21,6 +21,7 @@ const msg_website_it = {
myservice2: 'myservice2',
myhosps2: 'myhosps2',
mygood2: 'mygood2',
InvitoReg: 'Invito',
fundraising: 'Sostieni il Progetto',
notifs: 'Configura le Notifiche',
unsubscribe: 'Disiscriviti',

View File

@@ -52,7 +52,6 @@
"eslint-plugin-n": "^17.19.0",
"eslint-plugin-quasar": "^1.1.0",
"gsap": "^3.13.0",
"html2pdf.js": "^0.10.3",
"jquery": "^3.7.1",
"js-cookie": "^3.0.5",
"jsbarcode": "^3.12.1",

View File

@@ -134,17 +134,6 @@ function getDynamicPages(site: ISites): IListRoutes[] {
inmenu: true,
infooter: true,
},
{
active: site.confpages && site.confpages.showProfile,
order: 120,
path: '/editprofile',
materialIcon: 'fas fa-user',
name: 'pages.profile3',
component: () => import('@src/views/user/editprofile/editprofile.vue'),
meta: { requiresAuth: true },
inmenu: false,
infooter: false,
},
{
active: site.confpages && site.confpages.showiscrittiMenu,
order: 130,

View File

@@ -1 +1 @@
TERMINA DI LAVORARE SU riso.app: (Sovrascrivo!)
TERMINA DI LAVORARE SU gruppomacro.app: (Sovrascrivo!)

View File

@@ -1,7 +1,7 @@
{
"name": "Gruppo Macro",
"short_name": "GruppoMacro",
"description": "",
"name": "Riso",
"short_name": "Riso",
"description": "Siamo la Rete Italiana di Scambio Orizzontale, abbiamo creato questa piattaforma per metterla al servizio di chi vuole riscoprire il valore della condivisione e della cooperazione. Valori semplici e profondi che ci aiutano a ritrovare il Senso della Vita, perduto in questa società consumista, e riporti quei Sani Pricìpi Naturali ed Umani di Fratellanza che intere popolazioni antiche conoscevano bene.",
"display": "standalone",
"orientation": "portrait",
"background_color": "#fff",
@@ -11,17 +11,47 @@
"start_url": "/?homescreen=1",
"icons": [
{
"src": "/images/gm-android-icon-512x512.png",
"src": "/images/riso-android-icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/images/gm-android-icon-192x192.png",
"src": "/images/riso-android-icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/images/riso-android-icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/gm-apple-touch-icon.png",
"src": "/images/riso-android-icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/images/riso-android-icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/images/riso-apple-icon-120x120.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "/images/riso-apple-icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/images/riso-apple-icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/images/riso-apple-icon-180x180.png",
"sizes": "180x180",
"type": "image/png"
}

View File

@@ -48,6 +48,7 @@ export default ({ router }) => {
'/signin',
'/signup',
'/registrati',
'/savepage',
];
if (ignoredPaths.includes(to.path)) {
return;

281
src/components/CCheckIfIsLogged/CCheckIfIsLogged.scss Executable file → Normal file
View File

@@ -1,3 +1,284 @@
.check-login-wrapper {
padding: 1.5rem;
max-width: 800px;
margin: 0 auto;
}
.login-cards-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.login-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 2rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.18);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
}
}
// Card principale di login
.main-card {
background: linear-gradient(135deg, #6b8e23 0%, #556b2f 100%);
color: white;
text-align: center;
.card-icon-wrapper {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
.card-icon {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
padding: 1rem;
border-radius: 50%;
color: white;
animation: pulse 2s ease-in-out infinite;
}
}
.card-content {
margin-bottom: 2rem;
.card-title {
font-size: 1.75rem;
font-weight: 700;
margin: 0 0 1rem 0;
color: white;
}
.card-description {
font-size: 1.05rem;
line-height: 1.6;
margin: 0;
opacity: 0.95;
}
}
.card-actions {
.login-btn {
padding: 0.75rem 2.5rem;
font-size: 1.1rem;
font-weight: 600;
background: white;
color: #6b8e23;
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
}
}
}
// Stile per le card inline (Telegram e Aiuto)
.card-content-inline {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1.5rem;
.telegram-info,
.help-info {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
.telegram-icon,
.help-icon {
flex-shrink: 0;
}
.telegram-text,
.help-text {
display: flex;
flex-direction: column;
gap: 0.25rem;
.telegram-title,
.help-title {
font-weight: 600;
font-size: 1.1rem;
color: #333;
}
.telegram-subtitle,
.help-subtitle {
font-size: 0.9rem;
color: #666;
}
}
}
.telegram-btn,
.help-btn {
flex-shrink: 0;
padding: 0.6rem 1.5rem;
font-weight: 600;
white-space: nowrap;
}
}
// Card Telegram
.telegram-card {
.telegram-icon {
color: #0088cc;
}
.telegram-btn {
background: #0088cc;
&:hover {
background: #006699;
}
}
}
// Card Aiuto
.help-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
.help-icon {
color: #6b8e23;
}
.help-btn {
border: 2px solid #6b8e23;
&:hover {
background: #6b8e23;
color: white !important;
}
}
}
// Animazioni
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
// Responsive
@media (max-width: 768px) {
.check-login-wrapper {
padding: 1rem;
}
.login-cards-container {
gap: 1rem;
}
.login-card {
padding: 1.5rem;
}
.main-card {
.card-content {
.card-title {
font-size: 1.5rem;
}
.card-description {
font-size: 1rem;
}
}
.card-actions {
.login-btn {
width: 100%;
padding: 0.75rem 1.5rem;
}
}
}
.card-content-inline {
flex-direction: column;
text-align: center;
.telegram-info,
.help-info {
flex-direction: column;
text-align: center;
}
.telegram-btn,
.help-btn {
width: 100%;
}
}
}
@media (max-width: 480px) {
.main-card {
.card-content {
.card-title {
font-size: 1.25rem;
}
.card-description {
font-size: 0.95rem;
}
}
}
.card-content-inline {
.telegram-text,
.help-text {
.telegram-title,
.help-title {
font-size: 1rem;
}
.telegram-subtitle,
.help-subtitle {
font-size: 0.85rem;
}
}
}
}
// Dark mode support
body.body--dark {
.login-card {
background: rgba(30, 30, 30, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.card-content-inline {
.telegram-text,
.help-text {
.telegram-title,
.help-title {
color: #fff;
}
.telegram-subtitle,
.help-subtitle {
color: #aaa;
}
}
}
.help-card {
background: linear-gradient(135deg, #2a2a2a 0%, #1a1a1a 100%);
}
}
#logoimg {
height: 300px;
width: auto;

146
src/components/CCheckIfIsLogged/CCheckIfIsLogged.vue Executable file → Normal file
View File

@@ -1,87 +1,91 @@
<template>
<div v-if="showalways || (!showalways && !isLogged)">
<div class="q-pa-md q-gutter-sm">
<div class=" text-center">
<transition
name="fade"
mode="out-in"
appear
enter-active-class="animazione fadeIn"
leave-active-class="animazione fadeOut"
>
<q-banner
:key="1"
rounded
class="text-white bg-red"
color="primary q-title"
style="text-align: center"
>
<template v-slot:avatar>
<q-icon :key="2" name="fas fa-sign-in-alt" color="white" />
</template>
<div :key="3">
<div v-if="showalways || (!showalways && !isLogged)" class="check-login-wrapper">
<transition
name="fade"
mode="out-in"
appear
enter-active-class="animazione fadeIn"
leave-active-class="animazione fadeOut"
>
<div class="login-cards-container">
<!-- Card principale di login -->
<div class="login-card main-card">
<div class="card-icon-wrapper">
<q-icon name="fas fa-sign-in-alt" size="48px" class="card-icon" />
</div>
<div class="card-content">
<h3 class="card-title">Benvenuto su RISO</h3>
<p class="card-description">
Accedi con le tue credenziali per utilizzare la APP e per unirti
al Circuito di scambio RIS del tuo territorio
</div>
<template v-slot:action>
<div>
<q-btn
flat
color="white"
@click="tools.openrighttoolbar()"
>{{ t('login.enter') }}
</q-btn>
</div>
<!--<CRegistration />-->
</template>
</q-banner>
</transition>
<q-separator inset />
<br>
<q-banner
v-if="false"
rounded
dense
size="lg"
class="shadow-5"
color="primary q-title"
style="text-align: center"
>
<div class="mybanner" :key="3">
👉🏻 Entra nel canale Telegram per unirti al gruppo Provinciale:
</p>
</div>
<template v-slot:action>
<div class="card-actions">
<q-btn
type="a"
unelevated
rounded
size="lg"
color="primary"
class="login-btn"
@click="tools.openrighttoolbar()"
>
<q-icon name="fas fa-sign-in-alt" class="q-mr-sm" />
{{ t('login.enter') }}
</q-btn>
</div>
</div>
<!-- Card Telegram (opzionale) -->
<div v-if="false" class="login-card telegram-card">
<div class="card-content-inline">
<div class="telegram-info">
<q-icon name="fab fa-telegram" size="32px" class="telegram-icon" />
<div class="telegram-text">
<span class="telegram-title">Entra nel canale Telegram</span>
<span class="telegram-subtitle">Unisciti al gruppo Provinciale</span>
</div>
</div>
<q-btn
rounded
unelevated
icon="fab fa-telegram"
color="primary"
label="Apri canale"
href="https://t.me/riso_canale/3"
target="_blank"
label="Progetto RISO"
>
</q-btn>
</template>
</q-banner>
</div>
<br />
<q-banner rounded class="bg-green-8 text-white">
<div class="text-h6 text-center">
Visualizza la pagina di Aiuto<br />
<div class="text-center">
<q-btn
:color="$q.dark.isActive ? `black` : `white`"
push
glossy
:text-color="$q.dark.isActive ? `white` : `black`"
label="Pagina Aiuto"
to="/istruzioni"
class="telegram-btn"
/>
</div>
</div>
</q-banner>
</div>
<!-- Card Aiuto -->
<div class="login-card help-card">
<div class="card-content-inline">
<div class="help-info">
<q-icon name="fas fa-question-circle" size="32px" class="help-icon" />
<div class="help-text">
<span class="help-title">Hai bisogno di aiuto?</span>
<span class="help-subtitle">Consulta la guida completa</span>
</div>
</div>
<q-btn
rounded
unelevated
icon="fas fa-book-open"
color="white"
text-color="primary"
label="Vai alla guida"
to="/istruzioni"
class="help-btn"
/>
</div>
</div>
</div>
</transition>
</div>
</template>

View File

@@ -1,5 +1,115 @@
.myflex{
.myflex {
display: flex;
flex: 1;
}
.regulation-container {
:deep(.regulation-content) {
max-width: 900px;
margin: 0 auto;
.reg-header {
text-align: center;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 2px solid #6b8e23;
h1 {
font-size: 2rem;
font-weight: 700;
color: #6b8e23;
margin: 0;
}
}
.reg-section {
margin-bottom: 2.5rem;
.section-title {
font-size: 1.5rem;
font-weight: 600;
color: #556b2f;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid rgba(107, 142, 35, 0.3);
}
.section-content {
font-size: 1rem;
line-height: 1.7;
color: #333;
text-align: justify;
margin-bottom: 1rem;
strong {
color: #6b8e23;
font-weight: 600;
}
}
.section-list {
list-style: none;
padding-left: 0;
scssli {
padding: 0.75rem 0 0.75rem 3rem; // aumenta da 2rem a 3rem
position: relative;
line-height: 1.6;
&:before {
content: "";
position: absolute;
left: 1rem; // aumenta da 0.5rem a 1rem
color: #6b8e23;
font-size: 1.5rem;
line-height: 1.6;
}
}
}
.highlight-box {
background: rgba(107, 142, 35, 0.08);
border-left: 4px solid #6b8e23;
padding: 1rem 1.5rem;
margin: 1rem 0;
border-radius: 4px;
strong {
display: block;
margin-bottom: 0.5rem;
}
}
}
.reg-footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(107, 142, 35, 0.3);
font-size: 0.95rem;
color: #666;
}
}
}
// Responsive
@media (max-width: 768px) {
.regulation-container :deep(.regulation-content) {
.reg-header h1 {
font-size: 1.5rem;
}
.reg-section {
.section-title {
font-size: 1.25rem;
}
.section-content {
text-align: left;
}
.section-list li {
padding-left: 1.5rem;
}
}
}
}

View File

@@ -1,21 +1,21 @@
import type { PropType } from 'vue';
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
import { useUserStore } from '@store/UserStore'
import { useCircuitStore } from '@store/CircuitStore'
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { useUserStore } from '@store/UserStore';
import { useCircuitStore } from '@store/CircuitStore';
import type { ICircuit, IMyCircuit, IMyGroup } from 'model';
import { IImgGallery, IUserFields, IUserProfile, IFriends, IAccount } from 'model'
import { costanti } from '@costanti'
import { shared_consts } from '@src/common/shared_vuejs'
import { tools } from '@tools'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { CUserNonVerif } from '@src/components/CUserNonVerif'
import { CSaldo } from '@src/components/CSaldo'
import { CTitleBanner } from '@src/components/CTitleBanner'
import { toolsext } from '@store/Modules/toolsext'
import { useGlobalStore } from '@store/globalStore'
import { IImgGallery, IUserFields, IUserProfile, IFriends, IAccount } from 'model';
import { costanti } from '@costanti';
import { shared_consts } from '@src/common/shared_vuejs';
import { tools } from '@tools';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { CUserNonVerif } from '@src/components/CUserNonVerif';
import { CSaldo } from '@src/components/CSaldo';
import { CTitleBanner } from '@src/components/CTitleBanner';
import { toolsext } from '@store/Modules/toolsext';
import { useGlobalStore } from '@store/globalStore';
import { userPanel } from 'app/src/rootgen/admin/userPanel';
export default defineComponent({
@@ -56,101 +56,237 @@ export default defineComponent({
type: Object as PropType<IUserFields | null>,
required: false,
default: null,
}
},
},
setup(props, { emit }) {
const userStore = useUserStore();
const circuitStore = useCircuitStore();
const $q = useQuasar();
const { t } = useI18n();
const $router = useRouter();
const $route = useRoute();
const userStore = useUserStore()
const circuitStore = useCircuitStore()
const $q = useQuasar()
const { t } = useI18n()
const $router = useRouter()
const $route = useRoute()
const globalStore = useGlobalStore();
const globalStore = useGlobalStore()
const circuit = ref(<IMyCircuit | ICircuit | null>null);
const account = computed(() =>
circuit.value ? userStore.getAccountByCircuitId(circuit.value._id) : null
);
const circuit = ref(<IMyCircuit | ICircuit | null>null)
const account = computed(() => circuit.value ? userStore.getAccountByCircuitId(circuit.value._id) : null)
const qtarem = computed(() =>
account.value ? circuitStore.getRemainingCoinsToSend(account.value) : 0
);
const saldo_pend = computed(() => (account.value ? account.value.saldo_pend : 0));
const saldo = computed(() => (account.value ? account.value.saldo : 0));
const qtarem = computed(() => account.value ? circuitStore.getRemainingCoinsToSend(account.value) : 0)
const saldo_pend = computed(() => account.value ? account.value.saldo_pend : 0)
const saldo = computed(() => account.value ? account.value.saldo : 0)
const fidoConcessoUtente = computed(() =>
circuitStore.getFidoConcessoByUsername(
props.myuser,
circuit.value._id,
props.username
)
);
const fidoConcessoUtente = computed(() => circuitStore.getFidoConcessoByUsername(props.myuser, circuit.value._id, props.username))
const table = ref(shared_consts.TABLES_CIRCUITS);
const table = ref(shared_consts.TABLES_CIRCUITS)
const showingtooltip = ref(false);
const showrules = ref(false);
const requestToEnterCircuit = ref(false);
const groupnameSel = ref(<IMyGroup | null>null);
const showingtooltip = ref(false)
const showrules = ref(false)
const requestToEnterCircuit = ref(false)
const groupnameSel = ref(<IMyGroup | null>null)
watch(() => props.mycircuit, (newval, oldval) => {
mounted()
})
watch(
() => props.mycircuit,
(newval, oldval) => {
mounted();
}
);
function getNameCircuit() {
if (circuit.value) {
if (tools.existProp(circuit.value, 'name')) {
return tools.getProp(circuit.value, 'name')
return tools.getProp(circuit.value, 'name');
} else if (tools.existProp(circuit.value, 'circuitname')) {
return tools.getProp(circuit.value, 'circuitname')
return tools.getProp(circuit.value, 'circuitname');
}
}
return ''
return '';
}
function mounted() {
groupnameSel.value = props.prop_groupnameSel
groupnameSel.value = props.prop_groupnameSel;
if (!props.mycircuit) {
if (props.circuitname) {
circuit.value = null
circuit.value = null;
}
} else {
if (props.mycircuit) {
circuit.value = props.mycircuit
circuit.value = props.mycircuit;
}
}
if (circuit.value) {
const rectofind = circuitStore.listcircuits.find((circ: ICircuit) => circ.name === getNameCircuit())
const rectofind = circuitStore.listcircuits.find(
(circ: ICircuit) => circ.name === getNameCircuit()
);
if (rectofind) {
// console.log('rectofind', rectofind)
circuit.value = rectofind
circuit.value = rectofind;
}
}
}
function getImgCircuit(circuit: ICircuit) {
return userStore.getImgByCircuit(circuit)
return userStore.getImgByCircuit(circuit);
}
function naviga(path: string) {
$router.push(path)
$router.push(path);
}
function setCmd(cmd: number, myusername: string, value: any = '') {
emit('setCmd', cmd, myusername, value)
emit('setCmd', cmd, myusername, value);
}
function myusername() {
return userStore.my.username
return userStore.my.username;
}
function getRegulation(reg: string) {
const strreg = reg + ''
let name = 'Provinciale';
if (!reg) {
let name = getNameCircuit()
const mystringa = t('circuit.regolamento', { nomecircuito: name })
return mystringa
} else {
return reg
name = getNameCircuit();
}
// Trasforma il vecchio HTML in uno moderno
return transformRegulationHTML(name);
}
onMounted(mounted)
function transformRegulationHTML(circuitName: string): string {
return `
<div class="regulation-content">
<div class="reg-header">
<h1>${circuitName}</h1>
</div>
<div class="reg-section">
<h2 class="section-title">Costituzione e scopo Comunitario</h2>
<p class="section-content">
La Comunità Territoriale "RIS ${circuitName}" spontanea (da ora "la Comunità") costituisce un circuito di scambio
tra i partecipanti, ciascuno dei quali dovrà indicare i beni e servizi che offre alla Comunità stessa in RIS.
Il circuito funziona come dettagliato qui di seguito.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Circuito di scambio</h2>
<p class="section-content">
La Comunità ha avviato un sistema di scambio di beni e servizi tra utenti, in cui il valore delle transazioni
si misura utilizzando una unità di conto denominata <strong>RIS</strong>, convenzionalmente considerata pari a 1 euro.
</p>
<p class="section-content">
Gli scambi tra utenti avvengono liberamente, dopo aver concordato il valore tra le parti.
</p>
<p class="section-content">
Tutti gli utenti partecipanti al circuito iniziano con un conto vuoto, cioè pari a 0 RIS.
</p>
<p class="section-content">
L'utente pagante che ha un conto RIS vuoto o con quantità non sufficiente a perfezionare lo scambio può utilizzare
la Fiducia Concessa andando in debito fino al limite massimo a lui permesso, denominato "fiducia concessa".
Un equivalente valore in RIS a credito sarà conseguentemente iscritto sul conto del ricevente.
</p>
<p class="section-content">
In ogni momento nel circuito la somma di tutte le esposizioni in positivo saranno perfettamente bilanciate dalla
somma di tutte le esposizioni in negativo, dimostrando così che ciascun detentore di un saldo positivo abbia
garantita la solvibilità del proprio credito. Ciascun detentore di un saldo negativo concorda che la sua
esposizione funge da garanzia della quantità di RIS equivalenti circolanti nel circuito e che può essere chiamato
a regolarizzare con equivalente pagamento in euro *, entro un lasso di tempo ragionevolmente adeguato, nel caso in cui:
</p>
<ul class="section-list">
<li>la Comunità, in accordo con il Gruppo Tecnico, deliberi il rientro di alcune posizioni con decisione motivata;</li>
<li>l'utente decida di uscire per motivi personali;</li>
<li>il circuito chiuda, per decisione deliberata o per motivi di forza maggiore.</li>
</ul>
<div class="highlight-box">
<strong>* Nota importante:</strong>
La regolarizzazione può avvenire con equivalente valore in beni e servizi, oppure, in ultima istanza,
con pagamento in euro.
</div>
</div>
<div class="reg-section">
<h2 class="section-title">Il Gruppo Tecnico</h2>
<p class="section-content">
La Comunità costituisce il Gruppo Tecnico di Gestione degli Scambi (da ora "Gruppo Tecnico"), partecipante esso
stesso agli scambi tramite uno o più delegati autorizzati. Il Gruppo Tecnico è l'organo che supporta la comunità
nella gestione degli scambi e quant'altro sia necessario all'ottimale svolgimento dello scopo comunitario.
</p>
<p class="section-content"><strong>In particolare ha il compito di:</strong></p>
<ul class="section-list">
<li>approvare le richieste di adesione, dopo aver verificato che il richiedente faccia parte della Comunità;</li>
<li>tenere aggiornato l'elenco dei partecipanti al circuito;</li>
<li>stabilire la soglia di massimo scoperto e massimo attivo, collettivo e individuale;</li>
<li>valutare la sostenibilità di eventuali stanziamenti di finanziamenti;</li>
<li>introdurre tassazioni sulle transazioni o una tantum;</li>
<li>adottare o meno il Deperimento dei conti inoperosi (riferimento sistema Si.Cre.Na.C.C.) e modularne i parametri di applicazione;</li>
<li>deliberare la chiusura del circuito;</li>
<li>deliberare la destinazione delle riserve comunitarie depositate sui Conti della Comunità per: finanziare opere varie;
corrispondere salari o agire con interventi di sussistenza a partecipanti in stato di necessità; rilevare lo stato
debitorio all'interno del circuito di utenti in difficoltà economica, irreperibili o venuti a mancare; attuare
interventi ritenuti utili, necessari o meritevoli;</li>
<li>redigere, emendare ed adeguare alle eventuali necessità il presente Regolamento.</li>
</ul>
<p class="section-content">
Il Gruppo Tecnico è formato da candidati scelti dalla Comunità sulla base delle competenze e dovrà sottoporre
all'approvazione della Comunità stessa resoconti periodici delle sue attività.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Sistemi di contabilizzazione</h2>
<p class="section-content">
I conti RIS e le transazioni conseguenti agli scambi sono registrati attraverso la piattaforma <strong>riso.app</strong>,
dove i partecipanti comunicano i beni e servizi proposti alla Comunità. Tutti i partecipanti possono visualizzare
in ogni momento la situazione del circuito.
</p>
<p class="section-content">
La Comunità, in accordo con il Gruppo Tecnico, può decidere di utilizzare altri strumenti di contabilizzazione,
che dovranno, in ogni caso, permettere l'inserimento delle singole transazioni effettuate tra gli utenti e la
visualizzazione da parte di ogni partecipante. Saranno dunque inseriti nel nuovo strumento tutti gli utenti
partecipanti con l'ultimo saldo rilevato al momento della transizione.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Conto Comunitario</h2>
<p class="section-content">
La Comunità può decidere di aprire uno o più Conti all'interno del circuito, che saranno gestiti dal Gruppo Tecnico,
allo scopo di interagire con i conti degli altri utenti e quindi recepire eventuali proventi da tassazioni o
finanziare iniziative autorizzate. I Conti Comunitari sono i conti della Comunità.
</p>
<p class="section-content">
Un conto a credito rappresenta una riserva monetaria da re-immettere nel circuito per finanziare opere o attività
di beneficio comune; un conto a debito rappresenta il "debito pubblico" della Comunità.
</p>
<p class="section-content">
I conti comunitari sono sottoposti alle stesse regole degli altri conti (conto vuoto, possibilità di scoperto di conto, ecc.).
Ogni operazione straordinaria viene deliberata appositamente, mentre l'ordinaria amministrazione può essere autorizzata
in via continuativa.
</p>
</div>
<div class="reg-section">
<h2 class="section-title">Riferimenti</h2>
<p class="section-content">
Per una migliore comprensione dei meccanismi che sottostanno allo scambio nel qui presentato circuito, si rimanda
alla lettura del testo disponibile gratuitamente su <strong>https://sicrenacc.info</strong>, da cui è stata tratta
libera ispirazione e condivisione dei principi generali del Sistema di Credito Naturale.
</p>
</div>
</div>
`;
}
onMounted(mounted);
return {
circuit,
@@ -176,6 +312,6 @@ export default defineComponent({
requestToEnterCircuit,
groupnameSel,
fidoConcessoUtente,
}
};
},
})
});

View File

@@ -564,12 +564,14 @@
></q-btn>
<div
v-if="showrules"
v-html="getRegulation(circuit.regulation)"
></div>
class="regulation-container q-mb-lg"
>
<div v-html="getRegulation(circuit.regulation)"></div>
</div>
</q-card-section>
<q-card-actions align="center">
<q-btn
class="centeritems"
class="centeritems q-ma-lg"
icon="fas fa-user-plus"
color="positive"
:label="$t('circuit.acceptregulation')"

View File

@@ -1201,7 +1201,7 @@
>
</CShareSocial>
</q-dialog>
<q-dialog v-model="mostraInviti">
<q-dialog v-model="mostraInviti" maximized>
<q-card style="min-width: 350px; max-width: 600px">
<!-- Header con bottone chiudi -->
<q-bar class="bg-primary text-white">

View File

@@ -223,7 +223,7 @@ export default defineComponent({
}
if (
mypathin.value === 'home_logout' &&
(mypathin.value === 'home_logout' || mypathin.value === 'presentazione') &&
globalStore.site.name === 'local' &&
!rec.value
) {

View File

@@ -111,7 +111,7 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-weight: 500;
@media (max-width: 600px) {
font-size: 13px;
font-size: 16px;
}
}
@@ -247,7 +247,7 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
line-height: 1.3;
@media (max-width: 600px) {
font-size: 15px;
font-size: 16px;
}
.step-completed & {
@@ -262,7 +262,7 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
margin-top: 2px;
@media (max-width: 600px) {
font-size: 12px;
font-size: 15px;
}
}
@@ -280,12 +280,12 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
.step-description {
margin: 0 0 16px 0;
font-size: 14px;
font-size: 16px;
line-height: 1.6;
color: #475569;
@media (max-width: 600px) {
font-size: 13px;
font-size: 15px;
margin: 0 0 12px 0;
}
}
@@ -305,23 +305,23 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
width: 100%;
height: 44px;
font-weight: 600;
font-size: 15px;
font-size: 16px;
text-transform: none;
letter-spacing: 0.3px;
@media (max-width: 600px) {
height: 42px;
font-size: 14px;
font-size: 16px;
}
}
.skip-btn {
align-self: center;
font-size: 13px;
font-size: 16px;
text-transform: none;
@media (max-width: 600px) {
font-size: 12px;
font-size: 15px;
}
}
@@ -330,12 +330,12 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(33, 186, 69, 0.1);
border-left: 4px solid $success-color;
border-radius: 8px;
font-size: 14px;
font-size: 16px;
line-height: 1.6;
color: #0d5c2a;
@media (max-width: 600px) {
font-size: 13px;
font-size: 15px;
padding: 10px 14px;
}
}
@@ -358,7 +358,7 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
:deep(.q-field__label) {
font-weight: 500;
font-size: 14px;
font-size: 16px;
}
}
@@ -397,12 +397,12 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.completion-text {
font-size: 16px;
font-size: 18px;
font-weight: 600;
color: $success-color;
@media (max-width: 600px) {
font-size: 15px;
font-size: 16px;
}
}
@@ -446,13 +446,13 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 12px;
transition: $transition;
flex-shrink: 0;
font-size: 14px;
font-size: 16px;
min-width: 120px;
@media (max-width: 600px) {
height: 42px;
min-width: 100px;
font-size: 13px;
font-size: 15px;
}
@media (max-width: 400px) {
@@ -510,17 +510,17 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
:deep(.q-banner__content) {
font-size: 14px;
font-size: 16px;
line-height: 1.6;
@media (max-width: 600px) {
font-size: 13px;
font-size: 15px;
}
}
:deep(.q-btn) {
@media (max-width: 600px) {
font-size: 13px;
font-size: 15px;
padding: 8px 12px;
}
}
@@ -564,13 +564,13 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
:deep(.q-badge) {
padding: 4px 10px;
font-size: 12px;
font-size: 14px;
font-weight: 600;
border-radius: 12px;
@media (max-width: 600px) {
padding: 3px 8px;
font-size: 11px;
font-size: 12px;
}
}

View File

@@ -189,7 +189,7 @@ export default defineComponent({
const stepCircuitItalia = computed(() => ({
step: STEP_CIRCUIT_ITALIA,
title: t('tutorial.step_circuito_italia_title') || 'Circuito Italia',
title: t('tutorial.step_circuito_italia_title') || 'Circuito RIS Italia',
extratitle: function () {
return circuititalia.value ? ': ' + circuititalia.value.name : '';
},
@@ -234,7 +234,7 @@ export default defineComponent({
// Step 3: Circuito Locale (solo se disponibile)
steps.push({
key: 'circuit',
name: 'Circuito Locale',
name: 'Circuito RIS Locale',
completed: stepCircuit.value.checkOk(),
step: STEP_CIRCUIT,
});
@@ -242,7 +242,7 @@ export default defineComponent({
// Step 4: Circuito Italia (solo se circuito locale completato)
steps.push({
key: 'circuitItalia',
name: 'Circuito Italia',
name: 'Circuito RIS Italia',
completed: stepCircuitItalia.value.checkOk(),
step: STEP_CIRCUIT_ITALIA,
});
@@ -272,9 +272,9 @@ export default defineComponent({
},
{
key: 'circuit',
visible: !!mycircuit.value,
visible: true,
disabled: false,
title: 'Circuito Locale',
title: 'Circuito RIS Locale',
description: 'Seleziona la tua provincia di residenza per connetterti con la community locale.',
completed: stepCircuit.value.checkOk(),
avatar: {
@@ -308,8 +308,8 @@ export default defineComponent({
? 'Completato!'
: 'Unisciti al circuito nazionale (opzionale)',
badge: {
color: stepCircuitItalia.value.checkOkReal() ? 'positive' : (isSalta(STEP_CIRCUIT_ITALIA) || isSalta(STEP_CIRCUIT)) ? 'red' : 'grey',
label: stepCircuitItalia.value.checkOkReal() ? 'Fatto' : (isSalta(STEP_CIRCUIT_ITALIA) || isSalta(STEP_CIRCUIT)) ? 'Saltato' : 'opzionale',
color: stepCircuitItalia.value.checkOkReal() ? 'positive' : (isSalta(STEP_CIRCUIT_ITALIA)) ? 'red' : 'grey',
label: stepCircuitItalia.value.checkOkReal() ? 'Fatto' : (isSalta(STEP_CIRCUIT_ITALIA)) ? 'Saltato' : 'opzionale',
},
},
]);
@@ -354,7 +354,8 @@ export default defineComponent({
const totalSteps = computed(() => {
let count = 0;
count++; // Telegram
if (mycircuit.value) count++; // Circuito Locale
count++; // Circuito Locale
// if (mycircuit.value)
if (circuititalia.value) count++; // Circuito Italia
return count;
});
@@ -571,8 +572,8 @@ export default defineComponent({
function isSalta(step: number) {
return (
(step === STEP_CIRCUIT && mycircuit.value && isAskedToCircuit()) ||
(step === STEP_CIRCUIT_ITALIA && circuititalia.value && isAskedToCircuitItalia())
(step === STEP_CIRCUIT && mycircuit.value && userStore.my.profile.noCircuit) ||
(step === STEP_CIRCUIT_ITALIA && circuititalia.value && userStore.my.profile.noCircIta)
);
}

View File

@@ -1,46 +0,0 @@
.signup {
width: 100%;
margin: 0 auto;
max-width: 450px;
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.clCellCode {
border-radius: 32px;
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
.clCell {
border-radius: 32px;
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
.vue-country-select{
border-radius: 32px;
}
.myuserinvitante{
font-weight: bold;
color: red;
font-size: 1.5rem;
}
.cosa_chiedere{
font-weight: bold;
color: blue;
font-size: 1rem;
padding: 10px;
}

View File

@@ -1,461 +0,0 @@
import { tools } from '@tools'
import type { ISignupOptions } from 'model'
import { Logo } from '@src/components/logo'
// import 'vue-country-code/dist/vue-country-code.css'
import { CTitleBanner } from '../CTitleBanner'
import { CCopyBtn } from '../CCopyBtn'
import { CRegistration } from '../CRegistration'
import { PagePolicy } from '../PagePolicy'
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
import { CSignIn } from '@src/components/CSignIn'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { DefaultProfile, useUserStore } from '@store/UserStore'
import useValidate from '@vuelidate/core'
import useVuelidate from '@vuelidate/core'
import { shared_consts } from '@src/common/shared_vuejs'
import { minLength, required, sameAs } from '@vuelidate/validators'
// import { ValidationRuleset } from 'vuelidate'
import { complexity, complexityUser, registereduser, aportadorexist } from '../../validation'
// import 'vue3-tel-input/dist/vue3-tel-input.css'
import { useRoute, useRouter } from 'vue-router'
import { static_data } from '@src/db/static_data'
import { useGlobalStore } from '@store/globalStore'
// import {Loading, QSpinnerFacebook, QSpinnerGears} from 'quasar'
export default defineComponent({
name: 'CSignUp',
components: { Logo, CTitleBanner, PagePolicy, CCopyBtn, CRegistration },
props: {
showadultcheck: {
type: Boolean,
required: false,
default: false,
},
showcell: {
type: Boolean,
required: false,
default: false,
},
showaportador: {
type: Boolean,
required: false,
default: false,
},
shownationality: {
type: Boolean,
required: false,
default: false,
},
show_namesurname: {
type: Boolean,
required: false,
default: true,
},
regexpire: {
type: String,
required: false,
default: '',
},
name_default: {
type: String,
required: false,
default: '',
},
username_default: {
type: String,
required: false,
default: '',
},
need_Telegram: {
type: Boolean,
required: false,
default: false,
},
collettivo: {
type: Boolean,
required: false,
default: false,
},
token: {
type: String,
required: false,
default: '',
},
},
setup(props, { emit }) {
const $q = useQuasar()
const { t } = useI18n()
const userStore = useUserStore()
const $route = useRoute()
const $router = useRouter()
const countryname = ref('')
const iamadult = ref(false)
const duplicate_email = ref(false)
const duplicate_username = ref(false)
const visureg = ref(false)
const showpolicy = ref(false)
const visubuttBOT = ref(false)
const isalreadyReg = ref(false)
const needTelegram = ref(false)
const slide = ref('1')
const inputAportador = ref(<any>null)
const inputEmail = ref(<any>null)
const inputUsername = ref(<any>null)
const inputName = ref(<any>null)
const inputSurname = ref(<any>null)
const inputPassword = ref(<any>null)
const inputPassword2 = ref(<any>null)
const checkifDisabled = computed(() => {
let ret = true
if (slide.value === '1') {
// Invitante + Email
ret = !signup.email || (tools.getAskToVerifyReg() && (!signup.aportador_solidario || inputAportador.value.hasError)) || (inputEmail.value && inputEmail.value.hasError)
} else if (slide.value === '2') {
// Username
ret = !signup.username || (inputUsername.value && inputUsername.value.hasError)
if (tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) {
ret = ret || (!signup.name || (inputName.value && inputName.value.hasError))
ret = ret || (!signup.surname || (inputSurname.value && inputSurname.value.hasError))
}
} else if (slide.value === '3') {
// Password
ret = !signup.password || (!inputPassword.value || (inputPassword.value && inputPassword.value.hasError)) || (!inputPassword2.value || (inputPassword2.value && inputPassword2.value.hasError))
}
return ret
})
const typePassword = ref('password')
const ap_iniziale = ref('')
const globalStore = useGlobalStore()
const site = computed(() => globalStore.site)
const signup = reactive(<ISignupOptions>{
email: '',
username: '',
name: '',
surname: '',
password: '',
repeatPassword: '',
terms: false,
profile: DefaultProfile,
aportador_solidario: '',
})
const validations: any = computed(() => {
const valid: any = {
repeatPassword: {
required,
repeatPassword: sameAs(signup.password),
},
password: {
required,
minLength: minLength(8),
complexity,
},
username: {
required,
minLength: minLength(4),
complexityUser,
registereduser,
},
name: {
required: (props.collettivo || tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) ? true : false,
},
surname: {
required: (tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) ? true : false,
},
terms: {
required,
},
aportador_solidario: {
aportadorexist,
required
}
}
if (props.show_namesurname) {
valid.name = {
}
valid.surname = {
}
}
return valid
})
// @ts-ignore
const v$ = useVuelidate(validations, signup)
const invited = ref($route.params.invited)
const usernameteleg = ref($route.params.usernameteleg)
const idteleg = ref($route.params.idteleg)
watch(() => slide.value, (to: any, from: any) => {
if (slide.value === '3') {
v$.value.$touch()
}
})
watch(() => invited, (to: any, from: any) => {
if (props.showaportador) {
console.log('changeaportador', $route.params.invited)
if (!signup.aportador_solidario) {
if ($route.params.invited) {
// @ts-ignore
signup.aportador_solidario = $route.params.invited
}
}
}
})
function allowSubmit() {
let error = v$.value.$error || v$.value.$invalid || globalStore.serverError
if (props.showadultcheck)
error = error || !iamadult.value
if (props.showcell) {
if (signup.profile)
error = error || signup.profile.cell!.length <= 6
else
error = true
}
if (tools.getAskToVerifyReg()) {
error = error || !signup.aportador_solidario
}
return !error
}
function env() {
return process.env
}
function changeemail() {
signup.email = tools.removespaces(signup.email!)
signup.email = signup.email.toLowerCase()
emit('update:value', signup.email)
}
function changeusername(value: string) {
signup.username = tools.removespaces(signup.username)
emit('update:value', signup.username)
}
function submitOk() {
v$.value.$touch()
signup.email = tools.removespaces(signup.email!)
signup.email = signup.email.toLowerCase()
signup.username = tools.removespaces(signup.username)
// remove @
signup.username = tools.removeAt(signup.username)
duplicate_email.value = false
duplicate_username.value = false
if (!signup.terms) {
tools.showNotif($q, t('reg.err.terms'))
return
}
/*if (v$.signup.$error) {
tools.showNotif($q, t('reg.err.errore_generico'))
return
} */
if (signup.name) {
signup.name = tools.CapitalizeAllWords(signup.name)
signup.surname = tools.CapitalizeAllWords(signup.surname)
}
$q.loading.show({ message: t('reg.incorso') })
console.log(signup)
return userStore.signup(tools.clone(signup))
.then((ris: any) => {
if (tools.SignUpcheckErrors($q, $router, ris.code, ris.msg))
$q.loading.hide()
}).catch((error: string) => {
console.log('ERROR = ' + error)
$q.loading.hide()
})
}
function intcode_change(coderec: any) {
// console.log('intcode', coderec)
if (signup.profile) {
signup.profile.intcode_cell = '+' + coderec.dialCode
signup.profile.iso2_cell = coderec.iso2
}
}
function selectcountry({ name, iso2, dialCode }: { name: string, iso2: string, dialCode: string }) {
// console.log(name, iso2, dialCode)
signup.profile.nationality = iso2
countryname.value = name
}
async function created() {
needTelegram.value = props.need_Telegram
console.log('$route.params', $route.params)
ap_iniziale.value = $route.params.invited ? $route.params.invited.toString() : ''
signup.aportador_solidario = $route.params.invited ? $route.params.invited.toString() : ''
signup.username = $route.params.usernameteleg ? $route.params.usernameteleg.toString() : ''
signup.regexpire = $route.params.regexpire ? $route.params.regexpire.toString() : props.regexpire
if (signup.username)
isalreadyReg.value = await tools.registeredusername(signup.username)
signup.profile.username_telegram = signup.username
if ($route.params.idteleg) {
signup.profile.teleg_id = $route.params.idteleg ? parseInt($route.params.idteleg.toString(), 10) : 0
}
if (props.collettivo) {
signup.username = props.username_default!
signup.name = props.name_default!
}
// console.log('1) aportador_solidario', signup.aportador_solidario)
if (!signup.aportador_solidario)
signup.aportador_solidario = tools.getCookie(tools.APORTADOR_SOLIDARIO, signup.aportador_solidario)
if (!signup.aportador_solidario || signup.aportador_solidario === 'undefined') {
if (!tools.getAskToVerifyReg()) {
signup.aportador_solidario = tools.APORTADOR_NONE
}
}
// console.log('signup.aportador_solidario', signup.aportador_solidario)
// console.log('getasktoverify', tools.getAskToVerifyReg())
if (tools.getAskToVerifyReg()) {
if (!signup.username || !signup.profile.teleg_id) {
// tools.copyStringToClipboard($q, signup.aportador_solidario, true)
visubuttBOT.value = true
// window.location.href = tools.getLinkBotTelegram()
}
}
}
function myRuleEmail(val: string) {
return new Promise((resolve, reject) => {
// call
// resolve(true)
// --> content is valid
// resolve(false)
// --> content is NOT valid, no error message
// resolve(error_message)
// --> content is NOT valid, we have error message
tools.registeredemail(val).then((emailOk) => {
let risp = !!emailOk || t('reg.err.duplicate_email')
if (emailOk) {
risp = tools.isEmail(val) || t('reg.err.invalid_email')
emailOk = emailOk && tools.isEmail(val)
}
if (emailOk) {
// risp = !tools.isEmailNoMicroZozz(val) || t('reg.err.invalid_email_micro')
}
resolve(risp)
})
// calling reject(...) will also mark the input
// as having an error, but there will not be any
// error message displayed below the input
// (only in browser console)
})
}
function showPassword() {
//
typePassword.value = typePassword.value === 'password' ? 'text' : 'password'
}
function regEventEmail(invited: boolean) {
console.log('EVENT RECEIVED: regEventEmail', invited)
// reg
visubuttBOT.value = false
needTelegram.value = false
}
onMounted(() => {
const token = props.token
// carica
})
created()
return {
changeemail,
changeusername,
submitOk,
selectcountry,
intcode_change,
tools,
countryname,
signup,
iamadult,
v$,
t,
allowSubmit,
myRuleEmail,
visureg,
showpolicy,
visubuttBOT,
isalreadyReg,
site,
showPassword,
typePassword,
ap_iniziale,
regEventEmail,
needTelegram,
slide,
checkifDisabled,
inputAportador,
inputEmail,
inputUsername,
inputName,
inputSurname,
inputPassword,
inputPassword2,
shared_consts,
}
},
})

View File

@@ -1,739 +0,0 @@
<template>
<div>
<div
v-if="tools.isLogged() && tools.getUsername() && !collettivo"
class="text-center"
>
<q-banner rounded class="bg-green text-white" style="text-align: center">
<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
class="q-ma-sm"
color="primary"
icon="fas fa-home"
label="Vai alla Home"
to="/"
></q-btn>
<q-btn
class="q-ma-sm"
color="accent"
icon="fas fa-sign"
label="Voglio vedere la pagina di Registrazione"
@click="visureg = true"
></q-btn>
<br />
</div>
</div>
<div v-if="!tools.isLogged() || visureg || collettivo" class="text-center">
<div>
<div>
<logo
mystyle="width: 40px !important; height: 40px !important; "
></logo>
<div v-if="!isalreadyReg && !(visubuttBOT && needTelegram)">
<CTitleBanner :title="$t('pages.SignUp')"></CTitleBanner>
</div>
</div>
</div>
<div
v-if="visubuttBOT && needTelegram && !collettivo"
class="q-gutter-md"
>
<div class="q-ma-md">
<CRegistration
:invited="signup.aportador_solidario"
:regexpire="regexpire"
@regEventEmail="regEventEmail"
:signupform="true"
/>
</div>
</div>
<div v-else-if="!isalreadyReg || collettivo" class="q-gutter-sm q-mt-sm">
<div v-if="signup.username === 'undefined'">
<br />
Vai su <b>BOT RISO</b> Telegram ed imposta l'Username di Telegram.<br /><br />
<q-btn
rounded
color="primary"
icon="fab fa-telegram"
label="Apri BOT"
type="a"
:href="
tools.getLinkBotTelegram(signup.aportador_solidario, regexpire)
"
target="_blank"
></q-btn>
<br /><br />
</div>
<div v-else>
<div v-if="signup.terms">
<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="
v$.aportador_solidario.$touch && !v$.aportador_solidario.$error
? $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
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)"
: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
v-if="signup.surname"
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="$t('reg.surname_opt')"
>
<template v-slot:prepend>
<q-icon name="person" />
</template>
</q-input>
<q-input
ref="inputPassword"
v-model="signup.password"
: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>
</div>
<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>
<div class="column">
<q-btn
rounded
size="lg"
color="positive"
@click="submitOk"
:label="$t('reg.submit')"
>
</q-btn>
<br />
</div>
</div>
<div v-else>
<q-carousel
v-model="slide"
ref="carousel"
transition-prev="slide-right"
transition-next="slide-left"
animated
swipeable
:class="`shadow-1`"
>
<template v-slot:control>
<q-carousel-control
position="bottom-right"
:offset="[18, 18]"
class="q-gutter-xs"
>
<q-btn
v-if="slide !== '1'"
push
text-color="black"
icon="arrow_left"
:label="$t('dialog.indietro')"
@click="$refs.carousel.previous()"
/>
<q-btn
v-if="slide !== '4'"
push
color="primary"
icon="arrow_right"
:label="$t('dialog.avanti')"
:disabled="checkifDisabled"
@click="!checkifDisabled ? $refs.carousel.next() : null"
/>
</q-carousel-control>
</template>
<q-carousel-slide name="1">
<div class="">
<q-input
v-if="
showaportador &&
signup.aportador_solidario !== tools.APORTADOR_NONE
"
ref="inputAportador"
bg-color="light-blue-4"
:readonly="!!ap_iniziale"
v-model="signup.aportador_solidario"
rounded
outlined
@keyup.enter="
v$.aportador_solidario.$touch &&
!v$.aportador_solidario.$error
? $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>
</div>
</q-carousel-slide>
<q-carousel-slide name="2">
<div class="cosa_chiedere">{{ t('reg.scegli_username') }}</div>
<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="
collettivo
? t('reg.username_reg_collettivo')
: 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
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)"
: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">
<q-input
v-if="shownationality"
v-model="countryname"
:readonly="true"
rounded
outlined
debounce="1000"
:label="$t('reg.nationality')"
>
<template v-slot:prepend>
<!--<vue-country-code
@onSelect="selectcountry"
:preferredCountries="tools.getprefCountries"
:dropdownOptions="{ disabledDialCode: true }"
>
</vue-country-code>-->
</template>
</q-input>
<!--<vue-tel-input
v-if="showcell"
@country-changed="intcode_change()"
:value="signup.profile.cell"
:placeholder="$t('reg.cell')"
maxlength="20"
:enabledCountryCode="true"
inputClasses="clCell"
wrapperClasses="clCellCode">
</vue-tel-input>-->
<div class="text-center">
<q-btn
label="Mostra Privacy"
@click="showpolicy = true"
></q-btn>
</div>
<q-dialog v-model="showpolicy">
<q-card class="dialog_card">
<q-toolbar class="bg-primary text-white">
<q-toolbar-title> Privacy Policy </q-toolbar-title>
<q-btn
flat
round
color="white"
icon="close"
v-close-popup
></q-btn>
</q-toolbar>
<q-card-section class="inset-shadow">
<PagePolicy
v-if="site.policy"
:owneremail="site.policy.owneremail"
:siteName="site.policy.siteName"
:ownerDataName="site.policy.ownerDataName"
:managerData="site.policy.managerData"
:includeData="site.policy.includeData"
:url="site.policy.url"
:lastdataupdate="site.policy.lastdataupdate"
:country="site.policy.country"
>
</PagePolicy>
</q-card-section>
</q-card>
</q-dialog>
<q-checkbox
v-model="signup.terms"
color="secondary"
@blur="v$.terms.$touch"
:error="v$.terms.$error"
:error-message="`${tools.errorMsg('terms', v$.terms)}`"
:label="$t('reg.terms')"
>
</q-checkbox>
<q-checkbox
v-if="showadultcheck"
v-model="iamadult"
color="secondary"
:label="$t('reg.onlyadult')"
>
</q-checkbox>
<div v-if="showadultcheck">
<br />
</div>
<!--
Già registrato?
<q-btn
class="q-ma-sm"
text-color="black"
color="white"
icon="fas fa-home"
label="Accedi"
to="/"
size="sm"
></q-btn>
<br /><br /><br />
-->
</q-carousel-slide>
</q-carousel>
</div>
<div class="row justify-center">
<q-btn-toggle
v-if="!signup.terms"
glossy
v-model="slide"
:options="[
{ label: 1, value: '1' },
{ label: 2, value: '2' },
{ label: 3, value: '3' },
{ label: 4, value: '4' },
]"
/>
</div>
</div>
</div>
<div v-else-if="isalreadyReg && !collettivo">
<q-banner
class="bg-negative text-white text-h5"
transition-show="jump-down"
>
Utente già registrato con l'username {{ signup.username }}
</q-banner>
</div>
</div>
</div>
</template>
<script lang="ts" src="./CSignUp.ts">
</script>
<style lang="scss" scoped>
@import './CSignUp.scss';
</style>

View File

@@ -1 +0,0 @@
export {default as CSignUp} from './CSignUp.vue'

View File

@@ -1,719 +1,24 @@
// ========================================
// 🎨 MODERN SIGNUP COMPONENT STYLES
// ========================================
// Variables
$primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
$success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
$error-gradient: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%);
$card-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
$card-shadow-hover: 0 15px 50px rgba(0, 0, 0, 0.15);
$border-radius: 20px;
$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
// ========================================
// CONTAINER & LAYOUT
// ========================================
.signup-container {
min-height: 100vh;
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px 15px 100px 15px; // Extra padding bottom for fixed buttons
position: relative;
@media (max-width: 768px) {
padding: 0px 10px 110px 10px;
}
}
.signup-content {
max-width: 500px;
width: 100%;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 0px;
}
// ========================================
// HEADER SECTION
// ========================================
.header-section {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
margin-bottom: 10px;
/* spazio tra logo e titolo */
animation: fadeInDown 0.6s ease-out;
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
.title-section {
flex: 1;
margin-top: 5px;
.signup-title {
font-size: 2rem;
font-weight: 700;
background: $primary-gradient;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 0 5px 0;
@media (max-width: 768px) {
font-size: 1.5rem;
}
}
.signup-subtitle {
color: #64748b;
font-size: 13px;
margin: 0;
font-weight: 400;
@media (max-width: 768px) {
font-size: 0.75rem;
}
}
}
// ========================================
// SUCCESS & ERROR CARDS
// ========================================
.already-logged,
.error-card {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
}
.success-card,
.error-card {
background: white;
border-radius: $border-radius;
padding: 48px 32px;
box-shadow: $card-shadow;
text-align: center;
animation: scaleIn 0.5s ease-out;
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@media (max-width: 768px) {
padding: 32px 24px;
}
}
.success-message {
font-size: 24px;
font-weight: 600;
color: #1e293b;
margin-bottom: 32px;
@media (max-width: 768px) {
font-size: 20px;
margin-bottom: 24px;
}
}
.error-message {
font-size: 20px;
color: #1e293b;
margin-top: 16px;
@media (max-width: 768px) {
font-size: 18px;
}
}
.action-buttons {
display: flex;
gap: 8px;
justify-content: center;
flex-wrap: wrap;
.action-btn {
min-width: 160px;
height: 48px;
font-weight: 600;
text-transform: none;
letter-spacing: 0.3px;
@media (max-width: 768px) {
min-width: 140px;
height: 44px;
}
}
}
// ========================================
// FORM CONTAINER
// ========================================
.form-container {
background: white;
border-radius: $border-radius;
padding: 36px 24px 24px 24px;
box-shadow: $card-shadow;
transition: $transition;
animation: fadeInUp 0.6s ease-out;
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
padding: 24px 16px 16px 16px;
border-radius: 16px;
}
}
.registration-form {
position: relative;
}
// ========================================
// PROGRESS INDICATOR
// ========================================
.progress-indicator {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40px;
position: relative;
padding: 0 20px;
@media (max-width: 768px) {
margin-bottom: 32px;
padding: 0 10px;
}
}
.progress-line {
position: absolute;
top: 20px;
left: 50px;
right: 50px;
height: 4px;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 0;
border-radius: 2px;
@media (max-width: 768px) {
left: 35px;
right: 35px;
top: 18px;
}
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
z-index: 1;
position: relative;
.step-circle {
width: 42px;
height: 42px;
border-radius: 50%;
background: #e2e8f0;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 16px;
color: #94a3b8;
transition: $transition;
border: 3px solid white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
@media (max-width: 768px) {
width: 36px;
height: 36px;
font-size: 14px;
}
}
.step-label {
font-size: 13px;
font-weight: 600;
color: #94a3b8;
transition: $transition;
text-align: center;
@media (max-width: 768px) {
font-size: 11px;
}
}
&.active {
.step-circle {
background: $primary-gradient;
color: white;
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.step-label {
color: #667eea;
font-weight: 700;
}
}
&.completed {
.step-circle {
background: $success-gradient;
color: white;
}
.step-label {
color: #11998e;
}
}
}
// ========================================
// CAROUSEL & SLIDES
// ========================================
.modern-carousel {
margin-bottom: 0;
min-height: 400px;
@media (max-width: 768px) {
min-height: 200px;
}
:deep(.q-carousel__slide) {
padding: 0;
}
}
.carousel-slide {
display: flex;
align-items: flex-start;
justify-content: center;
padding: 0 !important;
}
.slide-content {
width: 100%;
max-width: 100%;
animation: slideIn 0.4s ease-out;
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
}
.slide-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
margin-left: 6px;
margin-bottom: 32px;
@media (max-width: 768px) {
margin-bottom: 5px;
}
.q-icon {
margin-bottom: 16px;
opacity: 0;
animation: iconPop 0.6s ease-out 0.2s forwards;
@keyframes iconPop {
from {
opacity: 0;
transform: scale(0) rotate(-180deg);
}
to {
opacity: 1;
transform: scale(1) rotate(0deg);
}
}
@media (max-width: 768px) {
font-size: px !important;
margin-bottom: 0px;
}
}
.slide-title {
font-size: 26px;
font-weight: 700;
color: #1e293b;
margin: 0;
@media (max-width: 768px) {
font-size: 22px;
}
}
.slide-description {
font-size: 15px;
color: #64748b;
margin: 0;
font-weight: 400;
@media (max-width: 768px) {
font-size: 14px;
}
}
}
// ========================================
// FORM FIELDS
// ========================================
.form-fields {
display: flex;
flex-direction: column;
gap: 20px;
@media (max-width: 768px) {
gap: 15px;
}
}
.name-fields {
display: flex;
flex-direction: column;
gap: 20px;
@media (max-width: 768px) {
gap: 0px;
}
}
.modern-input {
:deep(.q-field__control) {
height: 56px;
border-radius: 14px;
background: #f8fafc;
transition: $transition;
&:hover {
background: #f1f5f9;
}
@media (max-width: 768px) {
height: 52px;
border-radius: 12px;
}
}
:deep(.q-field__label) {
font-weight: 500;
font-size: 14px;
}
:deep(.q-field__control)::before {
border-color: #e2e8f0;
}
:deep(.q-field__control):hover::before {
border-color: #cbd5e1;
}
:deep(.q-field--focused .q-field__control)::before {
border-color: #667eea;
border-width: 2px;
}
:deep(.q-field--filled .q-field__control) {
background: #f8fafc;
}
:deep(.q-field__prepend) {
.q-icon {
font-size: 22px;
margin-right: 4px;
}
}
:deep(.q-field__append) {
.q-btn {
opacity: 0.6;
transition: $transition;
&:hover {
opacity: 1;
}
}
}
}
// ========================================
// PRIVACY SECTION
// ========================================
.privacy-section {
margin-top: 24px;
@media (max-width: 768px) {
margin-top: 20px;
}
.q-separator {
background: #e2e8f0;
margin: 24px 0;
@media (max-width: 768px) {
margin: 20px 0;
}
}
}
.privacy-link {
text-align: center;
margin-bottom: 16px;
.q-btn {
font-weight: 500;
text-transform: none;
font-size: 14px;
&:hover {
background: rgba(102, 126, 234, 0.08);
}
}
}
.privacy-checkbox {
:deep(.q-checkbox__label) {
font-size: 14px;
line-height: 1.3;
color: #475569;
font-weight: 500;
}
:deep(.q-checkbox__inner) {
width: 22px;
height: 22px;
border-radius: 6px;
}
@media (max-width: 768px) {
:deep(.q-checkbox__label) {
font-size: 13px;
}
}
}
// ========================================
// FIXED BOTTOM ACTIONS
// ========================================
.fixed-bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 16px 20px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
z-index: 1000;
backdrop-filter: blur(10px);
border-top: 1px solid #e2e8f0;
@media (max-width: 768px) {
padding: 14px 16px;
}
}
.action-row {
max-width: 500px;
margin: 0 auto;
display: flex;
gap: 12px;
justify-content: space-between;
align-items: center;
@media (max-width: 768px) {
gap: 10px;
}
}
.nav-btn {
font-weight: 600;
text-transform: none;
letter-spacing: 0.3px;
height: 50px;
border-radius: 14px;
transition: $transition;
font-size: 15px;
@media (max-width: 768px) {
height: 46px;
border-radius: 12px;
font-size: 14px;
}
&:hover:not([disabled]) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
&:active:not([disabled]) {
transform: translateY(0);
}
&[disabled] {
opacity: 0.5;
}
}
.prev-btn {
flex: 0 0 auto;
min-width: 130px;
@media (max-width: 768px) {
min-width: 110px;
}
}
.next-btn {
flex: 1;
background: $primary-gradient;
&:hover:not([disabled]) {
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
}
.submit-btn {
flex: 1;
background: $success-gradient;
font-size: 16px;
font-weight: 700;
&:hover:not([disabled]) {
box-shadow: 0 6px 20px rgba(17, 153, 142, 0.4);
}
@media (max-width: 768px) {
font-size: 15px;
}
}
// ========================================
// TELEGRAM SETUP
// ========================================
.telegram-setup,
.telegram-registration {
text-align: center;
padding: 48px 24px;
background: white;
border-radius: $border-radius;
box-shadow: $card-shadow;
@media (max-width: 768px) {
padding: 32px 20px;
}
.telegram-message {
font-size: 18px;
color: #475569;
margin: 24px 0;
line-height: 1.3;
@media (max-width: 768px) {
font-size: 16px;
margin: 20px 0;
}
}
.telegram-btn {
min-width: 200px;
height: 54px;
font-size: 16px;
font-weight: 600;
text-transform: none;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
}
@media (max-width: 768px) {
min-width: 180px;
height: 50px;
font-size: 15px;
}
}
}
// ========================================
// UTILITIES
// ========================================
.signup {
width: 100%;
margin: 0 auto;
max-width: 500px;
max-width: 450px;
}
.wrapper {
display: flex;
align-items: center;
justify-content: center;
}
// Legacy classes (kept for compatibility)
.clCellCode,
.clCellCode {
border-radius: 32px;
border-right: #2d2260;
height: 50px;
font-size: 1rem;
padding: 8px;
}
.clCell {
border-radius: 32px;
border-right: #2d2260;
@@ -722,43 +27,20 @@ $transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 8px;
}
.vue-country-select {
.vue-country-select{
border-radius: 32px;
}
.myuserinvitante {
.myuserinvitante{
font-weight: bold;
color: #667eea;
color: red;
font-size: 1.5rem;
}
.cosa_chiedere {
font-weight: 600;
color: #667eea;
.cosa_chiedere{
font-weight: bold;
color: blue;
font-size: 1rem;
padding: 10px;
text-align: center;
}
// ========================================
// ANIMATIONS & TRANSITIONS
// ========================================
.q-carousel__slide {
animation: fadeSlide 0.3s ease-out;
@keyframes fadeSlide {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
// Smooth scroll behavior
* {
scroll-behavior: smooth;
}

View File

@@ -1,30 +1,42 @@
import { tools } from '@tools'
import { tools } from '@tools';
import type { ISignupOptions } from 'model'
import type { ISignupOptions } from 'model';
import { Logo } from '@src/components/logo'
import { Logo } from '@src/components/logo';
import { CTitleBanner } from '../CTitleBanner'
import { CCopyBtn } from '../CCopyBtn'
import { CRegistration } from '../CRegistration'
import { PagePolicy } from '../PagePolicy'
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
import { CSignIn } from '@src/components/CSignIn'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { DefaultProfile, useUserStore } from '@store/UserStore'
import useValidate from '@vuelidate/core'
import useVuelidate from '@vuelidate/core'
// import 'vue-country-code/dist/vue-country-code.css'
import { shared_consts } from '@src/common/shared_vuejs'
import { CTitleBanner } from '../CTitleBanner';
import { CCopyBtn } from '../CCopyBtn';
import { CRegistration } from '../CRegistration';
import { PagePolicy } from '../PagePolicy';
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue';
import { CSignIn } from '@src/components/CSignIn';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { DefaultProfile, useUserStore } from '@store/UserStore';
import useValidate from '@vuelidate/core';
import useVuelidate from '@vuelidate/core';
import { minLength, required, sameAs } from '@vuelidate/validators'
import { shared_consts } from '@src/common/shared_vuejs';
import { complexity, complexityUser, registereduser, aportadorexist } from '../../validation'
import { minLength, required, sameAs } from '@vuelidate/validators';
import { useRoute, useRouter } from 'vue-router'
import { static_data } from '@src/db/static_data'
import { useGlobalStore } from '@store/globalStore'
// import { ValidationRuleset } from 'vuelidate'
import {
complexity,
complexityUser,
registereduser,
aportadorexist,
} from '../../validation';
// import 'vue3-tel-input/dist/vue3-tel-input.css'
import { useRoute, useRouter } from 'vue-router';
import { static_data } from '@src/db/static_data';
import { useGlobalStore } from '@store/globalStore';
import { useInvitaAmicoStore } from 'app/src/stores/useInvitaAmicoStore';
// import {Loading, QSpinnerFacebook, QSpinnerGears} from 'quasar'
export default defineComponent({
name: 'CSignUp',
@@ -80,59 +92,78 @@ export default defineComponent({
required: false,
default: false,
},
token: {
type: String,
required: false,
default: '',
},
},
setup(props, { emit }) {
const $q = useQuasar()
const { t } = useI18n()
const userStore = useUserStore()
const $route = useRoute()
const $router = useRouter()
const $q = useQuasar();
const { t } = useI18n();
const userStore = useUserStore();
const $route = useRoute();
const $router = useRouter();
const countryname = ref('')
const iamadult = ref(false)
const duplicate_email = ref(false)
const duplicate_username = ref(false)
const visureg = ref(false)
const showpolicy = ref(false)
const visubuttBOT = ref(false)
const isalreadyReg = ref(false)
const needTelegram = ref(false)
const slide = ref('1')
const inputAportador = ref(<any>null)
const inputEmail = ref(<any>null)
const inputUsername = ref(<any>null)
const inputName = ref(<any>null)
const inputSurname = ref(<any>null)
const inputPassword = ref(<any>null)
const inputPassword2 = ref(<any>null)
const countryname = ref('');
const iamadult = ref(false);
const duplicate_email = ref(false);
const duplicate_username = ref(false);
const visureg = ref(false);
const showpolicy = ref(false);
const visubuttBOT = ref(false);
const isalreadyReg = ref(false);
const needTelegram = ref(false);
const slide = ref('1');
const inputAportador = ref(<any>null);
const inputEmail = ref(<any>null);
const inputUsername = ref(<any>null);
const inputName = ref(<any>null);
const inputSurname = ref(<any>null);
const inputPassword = ref(<any>null);
const inputPassword2 = ref(<any>null);
const invitaStore = useInvitaAmicoStore();
const checkifDisabled = computed(() => {
let ret = true
let ret = true;
if (slide.value === '1') {
// Email + Aportador
ret = !signup.email || (tools.getAskToVerifyReg() && (!signup.aportador_solidario || inputAportador.value?.hasError)) || (inputEmail.value && inputEmail.value.hasError)
// Invitante + Email
ret =
!signup.email ||
(tools.getAskToVerifyReg() &&
(!signup.aportador_solidario || inputAportador.value.hasError)) ||
(inputEmail.value && inputEmail.value.hasError);
} else if (slide.value === '2') {
// Username + Nome/Cognome
ret = !signup.username || (inputUsername.value && inputUsername.value.hasError)
// Username
ret = !signup.username || (inputUsername.value && inputUsername.value.hasError);
if (tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) {
ret = ret || (!signup.name || (inputName.value && inputName.value.hasError))
ret = ret || (!signup.surname || (inputSurname.value && inputSurname.value.hasError))
if (
tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)
) {
ret = ret || !signup.name || (inputName.value && inputName.value.hasError);
ret =
ret || !signup.surname || (inputSurname.value && inputSurname.value.hasError);
}
} else if (slide.value === '3') {
// Password + Ripeti Password
ret = !signup.password || (!inputPassword.value || (inputPassword.value && inputPassword.value.hasError)) || (!inputPassword2.value || (inputPassword2.value && inputPassword2.value.hasError))
// Password
ret =
!signup.password ||
!inputPassword.value ||
(inputPassword.value && inputPassword.value.hasError) ||
!inputPassword2.value ||
(inputPassword2.value && inputPassword2.value.hasError);
}
return ret
})
return ret;
});
const typePassword = ref('password')
const typePassword = ref('password');
const ap_iniziale = ref('')
const ap_iniziale = ref('');
const globalStore = useGlobalStore()
const site = computed(() => globalStore.site)
const globalStore = useGlobalStore();
const site = computed(() => globalStore.site);
const signup = reactive(<ISignupOptions>{
email: '',
@@ -141,10 +172,10 @@ export default defineComponent({
surname: '',
password: '',
repeatPassword: '',
terms: true, // ✅ GIÀ SPUNTATA DI DEFAULT
terms: true,
profile: DefaultProfile,
aportador_solidario: '',
})
});
const validations: any = computed(() => {
const valid: any = {
@@ -164,198 +195,278 @@ export default defineComponent({
registereduser,
},
name: {
required: (props.collettivo || tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) ? true : false,
required:
props.collettivo ||
tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)
? true
: false,
},
surname: {
required: (tools.getConfSiteOptionEnabled(shared_consts.ConfSite.regNameSurnameMandatory)) ? true : false,
required: tools.getConfSiteOptionEnabled(
shared_consts.ConfSite.regNameSurnameMandatory
)
? true
: false,
},
terms: {
required,
},
aportador_solidario: {
aportadorexist,
required
}
}
required,
},
};
if (props.show_namesurname) {
valid.name = {}
valid.surname = {}
valid.name = {};
valid.surname = {};
}
return valid
})
return valid;
});
// @ts-ignore
const v$ = useVuelidate(validations, signup)
const v$ = useVuelidate(validations, signup);
const invited = ref($route.params.invited)
const usernameteleg = ref($route.params.usernameteleg)
const idteleg = ref($route.params.idteleg)
const invited = ref($route.params.invited);
const usernameteleg = ref($route.params.usernameteleg);
const idteleg = ref($route.params.idteleg);
watch(() => slide.value, (to: any, from: any) => {
if (slide.value === '3') {
v$.value.$touch()
watch(
() => slide.value,
(to: any, from: any) => {
if (slide.value === '3') {
v$.value.$touch();
}
}
})
);
watch(() => invited, (to: any, from: any) => {
if (props.showaportador) {
console.log('changeaportador', $route.params.invited)
if (!signup.aportador_solidario) {
if ($route.params.invited) {
// @ts-ignore
signup.aportador_solidario = $route.params.invited
watch(
() => invited,
(to: any, from: any) => {
if (props.showaportador) {
console.log('changeaportador', $route.params.invited);
if (!signup.aportador_solidario) {
if ($route.params.invited) {
// @ts-ignore
signup.aportador_solidario = $route.params.invited;
}
}
}
}
})
);
function allowSubmit() {
let error = v$.value.$error || v$.value.$invalid || globalStore.serverError
let error = v$.value.$error || v$.value.$invalid || globalStore.serverError;
if (props.showadultcheck)
error = error || !iamadult.value
if (props.showadultcheck) error = error || !iamadult.value;
if (props.showcell) {
if (signup.profile)
error = error || signup.profile.cell!.length <= 6
else
error = true
if (signup.profile) error = error || signup.profile.cell!.length <= 6;
else error = true;
}
if (tools.getAskToVerifyReg()) {
error = error || !signup.aportador_solidario
error = error || !signup.aportador_solidario;
}
return !error
return !error;
}
function env() {
return process.env
return process.env;
}
function changeemail() {
signup.email = tools.removespaces(signup.email!)
signup.email = signup.email.toLowerCase()
emit('update:value', signup.email)
signup.email = tools.removespaces(signup.email!);
signup.email = signup.email.toLowerCase();
emit('update:value', signup.email);
}
function changeusername(value: string) {
signup.username = tools.removespaces(signup.username)
emit('update:value', signup.username)
signup.username = tools.removespaces(signup.username);
emit('update:value', signup.username);
}
function submitOk() {
v$.value.$touch()
v$.value.$touch();
signup.email = tools.removespaces(signup.email!)
signup.email = signup.email.toLowerCase()
signup.username = tools.removespaces(signup.username)
signup.email = tools.removespaces(signup.email!);
signup.email = signup.email.toLowerCase();
signup.username = tools.removespaces(signup.username);
// remove @
signup.username = tools.removeAt(signup.username)
signup.username = tools.removeAt(signup.username);
duplicate_email.value = false
duplicate_username.value = false
duplicate_email.value = false;
duplicate_username.value = false;
if (!signup.terms) {
tools.showNotif($q, t('reg.err.terms'))
return
tools.showNotif($q, t('reg.err.terms'));
return;
}
/*if (v$.signup.$error) {
tools.showNotif($q, t('reg.err.errore_generico'))
return
} */
if (signup.name) {
signup.name = tools.CapitalizeAllWords(signup.name)
signup.surname = tools.CapitalizeAllWords(signup.surname)
signup.name = tools.CapitalizeAllWords(signup.name);
signup.surname = tools.CapitalizeAllWords(signup.surname);
}
$q.loading.show({ message: t('reg.incorso') })
$q.loading.show({ message: t('reg.incorso') });
console.log(signup)
return userStore.signup(tools.clone(signup))
console.log(signup);
return userStore
.signup(tools.clone(signup))
.then((ris: any) => {
if (tools.SignUpcheckErrors($q, $router, ris.code, ris.msg))
$q.loading.hide()
}).catch((error: string) => {
console.log('ERROR = ' + error)
$q.loading.hide()
if (tools.SignUpcheckErrors($q, $router, ris.code, ris.msg)) $q.loading.hide();
})
.catch((error: string) => {
console.log('ERROR = ' + error);
$q.loading.hide();
});
}
function intcode_change(coderec: any) {
// console.log('intcode', coderec)
if (signup.profile) {
signup.profile.intcode_cell = '+' + coderec.dialCode
signup.profile.iso2_cell = coderec.iso2
signup.profile.intcode_cell = '+' + coderec.dialCode;
signup.profile.iso2_cell = coderec.iso2;
}
}
function selectcountry({ name, iso2, dialCode }: { name: string, iso2: string, dialCode: string }) {
signup.profile.nationality = iso2
countryname.value = name
function selectcountry({
name,
iso2,
dialCode,
}: {
name: string;
iso2: string;
dialCode: string;
}) {
// console.log(name, iso2, dialCode)
signup.profile.nationality = iso2;
countryname.value = name;
}
async function created() {
needTelegram.value = props.need_Telegram
needTelegram.value = props.need_Telegram;
console.log('$route.params', $route.params)
console.log('$route.params', $route.params);
ap_iniziale.value = $route.params.invited ? $route.params.invited.toString() : ''
ap_iniziale.value = $route.params.invited ? $route.params.invited.toString() : '';
signup.aportador_solidario = $route.params.invited ? $route.params.invited.toString() : ''
signup.username = $route.params.usernameteleg ? $route.params.usernameteleg.toString() : ''
signup.regexpire = $route.params.regexpire ? $route.params.regexpire.toString() : props.regexpire
signup.aportador_solidario = $route.params.invited
? $route.params.invited.toString()
: '';
signup.username = $route.params.usernameteleg
? $route.params.usernameteleg.toString()
: '';
signup.regexpire = $route.params.regexpire
? $route.params.regexpire.toString()
: props.regexpire;
if (signup.username)
isalreadyReg.value = await tools.registeredusername(signup.username)
signup.profile.username_telegram = signup.username
isalreadyReg.value = await tools.registeredusername(signup.username);
signup.profile.username_telegram = signup.username;
if ($route.params.idteleg) {
signup.profile.teleg_id = $route.params.idteleg ? parseInt($route.params.idteleg.toString(), 10) : 0
signup.profile.teleg_id = $route.params.idteleg
? parseInt($route.params.idteleg.toString(), 10)
: 0;
}
if (props.collettivo) {
signup.username = props.username_default!
signup.name = props.name_default!
signup.username = props.username_default!;
signup.name = props.name_default!;
}
// console.log('1) aportador_solidario', signup.aportador_solidario)
if (!signup.aportador_solidario)
signup.aportador_solidario = tools.getCookie(tools.APORTADOR_SOLIDARIO, signup.aportador_solidario)
signup.aportador_solidario = tools.getCookie(
tools.APORTADOR_SOLIDARIO,
signup.aportador_solidario
);
if (!signup.aportador_solidario || signup.aportador_solidario === 'undefined') {
if (!tools.getAskToVerifyReg()) {
signup.aportador_solidario = tools.APORTADOR_NONE
signup.aportador_solidario = tools.APORTADOR_NONE;
}
}
// console.log('signup.aportador_solidario', signup.aportador_solidario)
// console.log('getasktoverify', tools.getAskToVerifyReg())
if (tools.getAskToVerifyReg()) {
if (!signup.username || !signup.profile.teleg_id) {
visubuttBOT.value = true
// tools.copyStringToClipboard($q, signup.aportador_solidario, true)
visubuttBOT.value = true;
// window.location.href = tools.getLinkBotTelegram()
}
}
}
function myRuleEmail(val: string) {
return new Promise((resolve, reject) => {
// call
// resolve(true)
// --> content is valid
// resolve(false)
// --> content is NOT valid, no error message
// resolve(error_message)
// --> content is NOT valid, we have error message
tools.registeredemail(val).then((emailOk) => {
let risp = !!emailOk || t('reg.err.duplicate_email')
let risp = !!emailOk || t('reg.err.duplicate_email');
if (emailOk) {
risp = tools.isEmail(val) || t('reg.err.invalid_email')
emailOk = emailOk && tools.isEmail(val)
risp = tools.isEmail(val) || t('reg.err.invalid_email');
emailOk = emailOk && tools.isEmail(val);
}
resolve(risp)
})
})
if (emailOk) {
// risp = !tools.isEmailNoMicroZozz(val) || t('reg.err.invalid_email_micro')
}
resolve(risp);
});
// calling reject(...) will also mark the input
// as having an error, but there will not be any
// error message displayed below the input
// (only in browser console)
});
}
function showPassword() {
typePassword.value = typePassword.value === 'password' ? 'text' : 'password'
//
typePassword.value = typePassword.value === 'password' ? 'text' : 'password';
}
function regEventEmail(invited: boolean) {
console.log('EVENT RECEIVED: regEventEmail', invited)
visubuttBOT.value = false
needTelegram.value = false
console.log('EVENT RECEIVED: regEventEmail', invited);
// reg
visubuttBOT.value = false;
needTelegram.value = false;
}
created()
onMounted(async () => {
const token = props.token;
if (token) {
// carica le info della registrazione
const risinvite = await invitaStore.ottieniInvitoByToken(token);
if (risinvite && risinvite.email) {
signup.email = risinvite.email;
if (risinvite.usernameInvitante)
signup.aportador_solidario = risinvite.usernameInvitante
slide.value = '2'
}
}
});
created();
return {
changeemail,
@@ -391,6 +502,6 @@ export default defineComponent({
inputPassword,
inputPassword2,
shared_consts,
}
};
},
})
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,311 +0,0 @@
# 🎨 Componente CSignUp - Versione Migliorata
## ✨ Modifiche Principali
### 📊 Struttura Carousel (Da 4 a 3 Pagine)
#### **Pagina 1 - Dati Iniziali**
- ✉️ Email (campo principale)
- 👤 Aportador/Invitante (se necessario)
- 🎯 Focus: Raccolta dati di contatto
#### **Pagina 2 - Account**
- 👤 Username
- 📛 Nome e Cognome (se richiesto)
- 🎯 Focus: Identità utente
#### **Pagina 3 - Sicurezza e Privacy**
- 🔒 Password
- 🔒 Conferma Password
- ✅ Privacy Policy (GIÀ SPUNTATA)
- 🔞 Verifica Maggiore Età (se richiesto)
- 🎯 Focus: Sicurezza e consensi
---
## 🎨 Miglioramenti Estetici
### Design Moderno e Innovativo
- **Gradients Accattivanti**: Utilizzo di gradienti moderni per un look premium
- **Card con Shadow**: Effetti ombra eleganti per dare profondità
- **Animazioni Fluide**: Transizioni smooth tra le pagine
- **Icone Prominenti**: Icone grandi e colorate per guidare l'utente
- **Progress Indicator**: Barra di progresso visiva con step animati
### Color Scheme
```scss
Primary Gradient: #667eea #764ba2 (Viola/Blu)
Success Gradient: #11998e #38ef7d (Verde)
Error Gradient: #ee0979 #ff6a00 (Rosso/Arancio)
Background: #f5f7fa #c3cfe2 (Grigio chiaro)
```
---
## 📱 Responsive Design
### Breakpoints Ottimizzati
- **Desktop** (>768px): Layout completo con spaziature generose
- **Mobile** (<768px): Layout ottimizzato con dimensioni ridotte
### Caratteristiche Responsive
- Input con altezza adattiva (56px → 52px su mobile)
- Font size scalabili
- Padding e margini ottimizzati per ogni device
- Bottoni che si adattano alla larghezza disponibile
---
## 📍 Bottoni Fissi in Basso
### Implementazione
```scss
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
```
### Caratteristiche
- **Sempre Visibili**: I bottoni restano fissi durante lo scroll
- **Design Elegante**: Shadow e backdrop blur per un effetto premium
- **Azioni Contestuali**:
- Pagina 1-2: "Indietro" + "Continua"
- Pagina 3: "Indietro" + "Registrati" (verde, più grande)
### Animazioni Bottoni
- Hover: Movimento verso l'alto con shadow aumentata
- Click: Feedback visivo immediato
- Disabled: Opacità ridotta
---
## ☑️ Privacy Già Spuntata
### Implementazione nel TypeScript
```typescript
const signup = reactive(<ISignupOptions>{
// ... altri campi
terms: true, // ✅ GIÀ SPUNTATA DI DEFAULT
})
```
### User Experience
1. L'utente arriva alla pagina 3
2. La checkbox privacy è già selezionata
3. Link "Leggi la Privacy Policy" disponibile
4. L'utente può deselezionare se vuole (ma non può procedere)
---
## 👆 UX Ottimizzata
### Principi di Design Applicati
#### 1. **Minimo Effort**
- Campi ridotti al necessario
- Privacy pre-accettata
- 3 pagine invece di 4 (-25% di navigazione)
#### 2. **No Scroll Required**
- Altezza carousel ottimizzata (400px desktop, 360px mobile)
- Contenuto sempre visibile in una schermata
- Bottoni fissi eliminano necessità di scroll
#### 3. **Progressive Disclosure**
- Informazioni presentate gradualmente
- Step chiari e ben definiti
- Progress indicator sempre visibile
#### 4. **Error Prevention**
- Validazione real-time
- Bottoni disabilitati quando i dati non sono validi
- Messaggi di errore chiari e inline
#### 5. **Keyboard Navigation**
- Enter per passare al campo successivo
- Enter nell'ultimo campo per avanzare pagina
- Tab navigation ottimizzata
---
## 🎯 Caratteristiche Tecniche
### Progress Indicator Dinamico
```vue
<div class="progress-indicator">
<div v-for="step in 3" :key="step"
:class="{ active: parseInt(slide) >= step,
completed: parseInt(slide) > step }">
<!-- Step circle con animazioni -->
</div>
<div class="progress-line"
:style="{ width: `${(parseInt(slide) - 1) * 50}%` }">
</div>
</div>
```
### Validazione Intelligente
- Campo Email: Validazione asincrona per email duplicate
- Campo Username: Check real-time su disponibilità
- Password: Controllo complessità con feedback visivo
- Conferma Password: Match immediato
### Animazioni CSS
- **fadeInDown**: Header (0.6s)
- **fadeInUp**: Form container (0.6s)
- **scaleIn**: Success/Error cards (0.5s)
- **slideIn**: Contenuto slide (0.4s)
- **iconPop**: Icone header (0.6s con delay)
---
## 📦 File Modificati
### 1. CSignUp.vue
- ✅ Ridotto carousel a 3 slide
- ✅ Nuovo layout con progress indicator
- ✅ Bottoni fissi in basso
- ✅ Privacy section nell'ultima slide
- ✅ Header con icone e titoli descrittivi
- ✅ Animazioni fluide tra le slide
### 2. CSignUp.ts
-`terms: true` di default
- ✅ Logica di validazione per 3 slide
- ✅ Gestione keyboard navigation ottimizzata
### 3. CSignUp.scss
- ✅ Design system completo con variabili
- ✅ Gradients e colori moderni
- ✅ Animazioni e transizioni
- ✅ Responsive breakpoints
- ✅ Fixed bottom buttons styling
- ✅ Progress indicator styling
---
## 🚀 Come Usare
### Installazione
1. Sostituisci i file esistenti con le nuove versioni
2. Non sono richieste dipendenze aggiuntive
3. Il componente è retrocompatibile con le props esistenti
### Props Disponibili
Tutte le props originali sono mantenute:
- `showadultcheck`: Mostra checkbox maggiorenne
- `showcell`: Mostra campo telefono
- `showaportador`: Mostra campo invitante
- `shownationality`: Mostra campo nazionalità
- `show_namesurname`: Mostra campi nome/cognome
- `need_Telegram`: Richiede registrazione Telegram
- `collettivo`: Modalità collettivo
---
## 📊 Metriche di Miglioramento
| Metrica | Prima | Dopo | Miglioramento |
|---------|-------|------|---------------|
| Pagine Carousel | 4 | 3 | -25% |
| Click per Completamento | 6-8 | 4-5 | ~40% |
| Tempo Medio Registrazione | ~120s | ~80s | ~33% |
| Privacy Pre-accettata | ❌ | ✅ | +100% |
| Mobile Friendly | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +66% |
---
## 🎨 Design Tokens
### Spacing
```scss
Small: 8px, 12px
Medium: 16px, 20px, 24px
Large: 32px, 40px, 48px
```
### Typography
```scss
Title: 32px / 28px (mobile)
Subtitle: 16px / 14px (mobile)
Slide Title: 26px / 22px (mobile)
Body: 15px / 14px (mobile)
```
### Border Radius
```scss
Small: 12px
Medium: 14px
Large: 20px
Full: 50% (circles)
```
---
## 🔧 Personalizzazione
### Cambiare i Colori
Modifica le variabili in `CSignUp.scss`:
```scss
$primary-gradient: linear-gradient(135deg, #TUO_COLORE_1 0%, #TUO_COLORE_2 100%);
$success-gradient: linear-gradient(135deg, #TUO_COLORE_1 0%, #TUO_COLORE_2 100%);
```
### Cambiare le Animazioni
Modifica i timing delle animazioni:
```scss
$transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
```
### Cambiare l'Altezza del Carousel
```scss
.modern-carousel {
min-height: 400px; // Modifica qui
}
```
---
## 🐛 Note e Compatibilità
### Compatibilità
- ✅ Vue 3
- ✅ Quasar Framework
- ✅ TypeScript
- ✅ Vuelidate
- ✅ Mobile & Desktop
### Browser Support
- ✅ Chrome/Edge (ultime 2 versioni)
- ✅ Firefox (ultime 2 versioni)
- ✅ Safari (ultime 2 versioni)
- ✅ Mobile browsers
---
## 📝 Changelog
### Versione 2.0 (Attuale)
- ✨ Ridotto carousel da 4 a 3 pagine
- ✨ Design completamente rinnovato
- ✨ Progress indicator con animazioni
- ✨ Bottoni fissi in basso
- ✨ Privacy pre-accettata
- ✨ UX ottimizzata senza scroll
- ✨ Completamente responsive
- ✨ Nuove animazioni e transizioni
### Versione 1.0 (Precedente)
- Form di registrazione base
- 4 pagine carousel
- Design classico
---
## 📧 Supporto
Per domande o problemi, contatta il team di sviluppo.
**Made with ❤️ for better UX**

View File

@@ -37,6 +37,7 @@ $text-light: #666; // Grigio medio
align-items: center;
justify-content: center;
gap: 12px;
line-height: 1.4;
&.light {
color: white !important;
@@ -49,6 +50,14 @@ $text-light: #666; // Grigio medio
.title-icon {
color: $primary-color;
}
@media (max-width: 768px) {
font-size: 1.5rem;
margin-bottom: 1rem;
margin-top: 1rem !important;
line-height: 1.6rem;
}
}
// HomeRiso.scss - Aggiorna la sezione HERO
@@ -493,6 +502,9 @@ $text-light: #666; // Grigio medio
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2.5rem;
@media (max-width: 768px) {
gap: 1rem;
}
}
.value-item {
@@ -506,6 +518,10 @@ $text-light: #666; // Grigio medio
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
padding: 0.5rem;
}
}
.value-icon {
@@ -552,6 +568,11 @@ $text-light: #666; // Grigio medio
&:last-child {
margin-bottom: 0;
}
@media (max-width: 768px) {
margin-bottom: 1rem;
gap: 0rem;
}
}
.step-number {
@@ -607,12 +628,18 @@ $text-light: #666; // Grigio medio
text-align: center;
max-width: 800px;
margin: 0 auto 3rem;
@media (max-width: 768px) {
margin: 0 auto 1rem;
}
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
@media (max-width: 768px) {
gap: 0.5rem;
}
}
.feature-card {
@@ -712,6 +739,10 @@ $text-light: #666; // Grigio medio
color: $primary-dark;
text-align: center;
margin-bottom: 2rem;
@media (max-width: 768px) {
font-size: 1.6rem;
margin-bottom: 1rem;
}
}
.methods-grid {
@@ -1110,9 +1141,6 @@ $text-light: #666; // Grigio medio
font-size: 3.5rem;
}
.section-title {
font-size: 2rem;
}
}
@media (max-width: 768px) {
@@ -1130,10 +1158,6 @@ $text-light: #666; // Grigio medio
font-size: 1rem;
}
.section-title {
font-size: 1.8rem;
}
.cta-title {
font-size: 2rem;
}

View File

@@ -194,7 +194,7 @@
size="md"
class="title-icon"
/>
Guarda il Video di Presentazione
Video di Presentazione
</h2>
<div class="video-container">
@@ -793,7 +793,7 @@
<q-separator class="footer-separator" />
<div class="footer-bottom">
<p>{{ currentYear }} RISO - Rete Italiana di Scambio Orizzontale</p>
<p>(dal 2021) RISO - Rete Italiana di Scambio Orizzontale</p>
<p class="footer-values">
Comunità · Fiducia · Scambi Solidali · Sostenibilità
</p>

View File

@@ -2,17 +2,21 @@
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
min-height: 120vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@media (max-width: $breakpoint-sm-max) {
min-height: 100vh;
}
}
.invita-amico-card {
max-width: 600px;
max-width: 800px;
width: 100%;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
@media (max-width: $breakpoint-sm-max) {
max-width: 600px;
margin: 0;
border-radius: 0;
}
@@ -47,3 +51,50 @@
border-radius: 8px;
overflow: hidden;
}
// Bottoni selezione metodo
.selection-buttons {
display: flex;
gap: 16px;
flex-wrap: wrap;
@media (max-width: $breakpoint-xs-max) {
flex-direction: column;
}
}
.selection-btn {
flex: 1;
min-height: 180px;
border-radius: 12px;
border: 2px solid #e0e0e0;
background: white;
transition: all 0.3s ease;
&:hover {
border-color: var(--q-primary);
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.2);
transform: translateY(-4px);
}
&:active {
transform: translateY(-2px);
}
@media (max-width: $breakpoint-xs-max) {
min-height: 120px;
}
}
.selection-btn-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
width: 100%;
text-align: center;
@media (max-width: 768px) {
padding: 8px 24px;
}
}

View File

@@ -3,6 +3,7 @@ import { useQuasar } from 'quasar';
import { useInvitaAmicoStore } from '../../stores/useInvitaAmicoStore';
import type { InvitoAmicoForm } from '../../types/invita-amico.types.ts';
import { tools } from 'app/src/store/Modules/tools';
import { useI18n } from 'vue-i18n';
// Chiave localStorage
const MESSAGGIO_STORAGE_KEY = 'invita-amico-messaggio-personalizzato';
@@ -15,10 +16,12 @@ export default defineComponent({
setup(props, { emit }) {
// Composables
const $q = useQuasar();
const { t } = useI18n();
const invitaStore = useInvitaAmicoStore();
// State
const mostraCronologia = ref(false);
const metodoSelezionato = ref<'email' | 'telegram' | null>(null);
const form = reactive<InvitoAmicoForm & { usernameInvitante?: string }>({
email: '',
messaggio: '',
@@ -29,6 +32,20 @@ export default defineComponent({
// METHODS
// ==========================================
/**
* Seleziona il metodo di invio (email o telegram)
*/
const selezionaMetodo = (metodo: 'email' | 'telegram') => {
metodoSelezionato.value = metodo;
};
/**
* Torna alla schermata di scelta iniziale
*/
const tornaAllaScelta = () => {
metodoSelezionato.value = null;
};
/**
* Invia invito via email usando lo store Pinia
*/
@@ -51,7 +68,7 @@ export default defineComponent({
message: 'Invito inviato con successo! 🎉',
caption: `L'email è stata inviata a ${form.email}`,
icon: 'check_circle',
timeout: 3000,
timeout: 7000,
actions: [
{
label: 'Vedi cronologia',
@@ -86,14 +103,14 @@ export default defineComponent({
const onInviaTelegram = async () => {
emit('telegram-click');
const success = await invitaStore.inviaInvitoTelegram(form.messaggio);
const success = await invitaStore.inviaInvitoTelegram($q, t);
if (success) {
$q.notify({
type: 'positive',
message: 'Messaggio inviato via Telegram! ✈️',
icon: 'telegram',
timeout: 2000,
timeout: 4000,
});
} else {
$q.notify({
@@ -122,7 +139,7 @@ export default defineComponent({
type: 'info',
message: 'Cronologia cancellata',
icon: 'delete',
timeout: 2000,
timeout: 3000,
});
});
};
@@ -168,6 +185,9 @@ export default defineComponent({
// RETURN
return {
mostraCronologia,
metodoSelezionato,
selezionaMetodo,
tornaAllaScelta,
form,
onInviaEmail,
onInviaTelegram,

View File

@@ -12,47 +12,78 @@
/>
Invita un Amico
</div>
<div class="text-subtitle2">Condividi la nostra app con i tuoi amici!</div>
<!-- Stats Badge -->
<div
v-if="invitaStore.totaleInviti > 0"
class="stats-badge q-mt-md"
>
<q-chip
color="white"
text-color="primary"
icon="email"
class="q-mx-xs"
>
{{ invitaStore.contatoreInvitiRiusciti }} inviati
</q-chip>
<!--<q-chip
v-if="invitaStore.percentualeSuccesso > 0"
color="white"
text-color="primary"
icon="trending_up"
class="q-mx-xs"
>
{{ invitaStore.percentualeSuccesso }}% successo
</q-chip>-->
</div>
<div class="text-subtitle2">Condividi la app con i tuoi amici!</div>
</q-card-section>
<q-separator />
<!-- Form Section -->
<q-card-section>
<q-form
@submit="onInviaEmail"
class="q-gutter-md"
>
<!-- Schermata Selezione Metodo -->
<q-card-section v-if="!metodoSelezionato">
<div class="text-center q-mb-lg">
<div class="text-h6 text-grey-8 q-mb-xs">Come vuoi invitare?</div>
</div>
<div class="selection-buttons">
<q-btn
@click="selezionaMetodo('email')"
class="selection-btn"
unelevated
no-caps
>
<div class="selection-btn-content">
<q-icon
name="email"
size="48px"
color="primary"
/>
<div class="text-h6 q-mt-xs text-grey-9">Email</div>
<div class="text-caption text-grey-7">
Invia un invito diretto via email
</div>
</div>
</q-btn>
<q-btn
@click="selezionaMetodo('telegram')"
class="selection-btn"
unelevated
no-caps
>
<div class="selection-btn-content">
<q-icon
name="telegram"
size="48px"
color="blue-9"
/>
<div class="text-h6 q-mt-md text-grey-9">Telegram</div>
<div class="text-caption text-grey-7">
Condividi tramite Telegram
</div>
</div>
</q-btn>
</div>
</q-card-section>
<!-- Sezione Email (mostrata solo se selezionata) -->
<q-card-section v-if="metodoSelezionato === 'email'">
<div class="q-mb-md">
<q-btn
flat
dense
icon="arrow_back"
label="Cambia metodo"
color="grey-7"
size="sm"
@click="tornaAllaScelta"
/>
</div>
<q-form @submit="onInviaEmail">
<!-- Email Input -->
<q-input
v-model="form.email"
type="email"
label="Email del tuo amico *"
hint="Inserisci l'indirizzo email della persona che vuoi invitare"
label="Email della tua persona amica *"
lazy-rules
:rules="[
(val) => !!val || 'L\'email è obbligatoria',
@@ -86,10 +117,9 @@
<q-input
v-model="form.messaggio"
type="textarea"
label="Messaggio personalizzato (opzionale)"
hint="Aggiungi un messaggio personale al tuo invito"
label="Messaggio personale (opzionale)"
outlined
rows="3"
:rows="tools.isMobile() ? 6 : 9"
counter
maxlength="500"
:disable="invitaStore.loading"
@@ -113,17 +143,6 @@
<q-icon name="message" />
</template>
</q-input>
<!-- Info che viene salvato -->
<div
v-if="form.messaggio"
class="text-caption text-grey-6"
>
<q-icon
name="info"
size="xs"
/>
Questo messaggio sarà riutilizzato nei prossimi inviti
</div>
<!-- Alert errore -->
<q-banner
@@ -145,23 +164,31 @@
icon="email"
color="primary"
size="lg"
class="full-width"
class="full-width q-mt-md"
outline
:loading="invitaStore.loading"
:disable="invitaStore.loading || !form.email"
unelevated
/>
</q-form>
</q-card-section>
<q-separator inset />
<!-- Sezione Telegram -->
<q-card-section>
<!-- Sezione Telegram (mostrata solo se selezionata) -->
<q-card-section v-if="metodoSelezionato === 'telegram'">
<div class="q-mb-md">
<q-btn
dense
icon="arrow_back"
label="Cambia metodo"
size="sm"
outline
@click="tornaAllaScelta"
/>
</div>
<div class="text-center q-mb-md">
<div class="text-subtitle1 text-grey-8 q-mb-xs">
Oppure invita tramite Telegram
Invita tramite Telegram
</div>
<div class="text-caption text-grey-6">
<div class="text-caption text-grey-7">
Genera un messaggio da condividere su Telegram
</div>
</div>
@@ -178,8 +205,11 @@
/>
</q-card-section>
<!-- Info Section -->
<q-card-section class="bg-blue-1">
<!-- Info Section (solo per Telegram) -->
<q-card-section
v-if="metodoSelezionato === 'telegram'"
class="bg-blue-1"
>
<div class="text-center">
<q-icon
name="info"
@@ -188,7 +218,8 @@
class="q-mr-xs"
/>
<span class="text-grey-8">
Riceverai sul {{ tools.getBotName() }} il messaggio da inoltrare alla persona amica.
Riceverai sul {{ tools.getBotName() }} il messaggio da inoltrare alla
persona amica.
</span>
</div>
</q-card-section>
@@ -266,6 +297,29 @@
</div>
</q-card-section>
<!-- Stats Badge -->
<div
v-if="invitaStore.totaleInviti > 0"
class="stats-badge q-mt-md"
>
<q-chip
color="white"
text-color="primary"
icon="email"
class="q-mx-xs"
>
{{ invitaStore.contatoreInvitiRiusciti }} inviati
</q-chip>
<!--<q-chip
v-if="invitaStore.percentualeSuccesso > 0"
color="white"
text-color="primary"
icon="trending_up"
class="q-mx-xs"
>
{{ invitaStore.percentualeSuccesso }}% successo
</q-chip>-->
</div>
<!-- Bottone per mostrare cronologia -->
<q-card-section v-if="invitaStore.hasCronologia && !mostraCronologia">
<q-btn

View File

@@ -9,7 +9,7 @@
Invita un Amico
</div>
<div class="text-subtitle2">
Condividi la nostra app con i tuoi amici!
Condividi la app con i tuoi amici!
</div>
</q-card-section>

View File

@@ -376,7 +376,7 @@
v-if="!tools.isLogged()"
class="text-user text-italic bg-red"
>
{{ t('user.loggati') }}
{{ t('user.non_loggato') }}
</div>
<div

View File

@@ -22,6 +22,36 @@ body,
}
// Variabili per riusabilità
$heading-primary-size-mobile: 1.25rem;
$heading-primary-size-desktop: 1.5rem;
$heading-primary-weight: 700;
$heading-primary-letter-spacing: -0.02em;
$heading-primary-line-height: 1.3;
h1 {
// Font size fluido e responsive
font-size: clamp($heading-primary-size-mobile, 4vw, $heading-primary-size-desktop);
// Typography
font-weight: $heading-primary-weight;
letter-spacing: $heading-primary-letter-spacing;
line-height: $heading-primary-line-height;
// Migliora la leggibilità
color: var(--q-primary, #1976d2);
// Anti-aliasing per rendering più pulito
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
// Spacing opzionale
margin-bottom: 1rem;
// Transizione smooth per dark mode
transition: color 0.3s ease;
}
body {
font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-webkit-font-smoothing: antialiased;
@@ -85,13 +115,6 @@ li {
}
}
h1 {
font-size: 1.5rem;
font-weight: bold;
line-height: 3rem;
letter-spacing: -.01562em;
}
.logo {
margin-left: auto;
margin-right: auto;

View File

@@ -1,9 +1,9 @@
const msg_website_it = {
ws: {
sitename: 'Gruppo Macro',
siteshortname: 'Gruppo Macro',
description: '',
keywords: '',
sitename: 'RISO',
siteshortname: 'RISO',
description: 'Siamo la Rete Italiana di Scambio Orizzontale, abbiamo creato questa piattaforma per metterla al servizio di chi vuole riscoprire il valore della condivisione e della cooperazione. Valori semplici e profondi che ci aiutano a ritrovare il Senso della Vita, perduto in questa società consumista, e riporti quei Sani Pricìpi Naturali ed Umani di Fratellanza che intere popolazioni antiche conoscevano bene.',
keywords: 'riso, piattaforma di scambio, rete italiana scambio orizzontale, riso app, riso piattaforma, scambio e baratto, momenta RIS',
},
hours: {
descr: 'Descrizione',
@@ -16,20 +16,29 @@ const msg_website_it = {
pages: {
home: 'Home',
profile: 'Profilo',
install_site: 'Installa Sito',
profile2: 'ProfiloU',
mypage2: 'mypage2',
myservice2: 'myservice2',
myhosps2: 'myhosps2',
mygood2: 'mygood2',
catalogo: 'Catalogo',
fundraising: 'Sostieni il Progetto',
notifs: 'Configura le Notifiche',
unsubscribe: 'Disiscriviti',
unsubscribe_user: 'Disiscriviti User',
test: 'Test',
projects: 'Progetti',
report: 'Report Ore',
producer: 'Produttore',
orderinfo: 'Ordini Effettuati',
products: 'Prodotti',
productslist: 'Lista Prodotti',
collabora: 'Collabora',
storehouses: 'Magazzino',
departments: 'Uffici',
orders: 'Ordini Ricevuti',
orders2: 'Ordini Ricevuti',
sharewithus: 'Condividi con Noi',
checkout: 'Carrello',
payment: 'Pagamenti',
regok: 'Registrazione Confermata',
presentazione: 'Presentazione',
presentazione2: 'Presentazione',
@@ -75,12 +84,14 @@ const msg_website_it = {
eventodef: 'Evento:',
prova: 'prova',
dbop: 'Operazioni',
dbopmacro: 'Operazioni Macro',
projall: 'Comunitari',
groups: 'Lista Gruppi',
projectsShared: 'Condivisi da me',
myprojects: 'Privati',
favproj: 'Favoriti',
admin_ecommerce: 'ECommerce',
ecommerce: 'Prodotti',
ecommerce_menu: 'ECommerce1',
hours: 'Ore',
department: 'Uffici',
title: 'Titolo',
@@ -109,11 +120,16 @@ const msg_website_it = {
onlyif_logged: 'Solo se Loggati',
only_residenti: 'Solo Residenti',
only_consiglio: 'Solo Consiglieri',
only_collab: 'Solo Collaboratori',
color: 'Colore',
mainMenu: 'Menu Principale',
subtitle: 'Sottotitolo',
lang: 'Lingua',
keywords: 'Parole Chiave',
desctiption: 'Descrizione',
heightimg: 'Altezza Immagine',
},
msg: {
myAppName: 'Più che Buono',
myAppName: 'Riso',
myAppDescription: 'Il primo Vero Social Libero, Equo e Solidale, dove Vive Consapevolezza e Aiuto Comunitario. Gratuito',
underconstruction: 'App in costruzione...',
myDescriz: '',
@@ -172,18 +188,7 @@ const msg_website_it = {
descr: '<ul class="mylist" style="padding-left: 20px;">'
+ '<li>📱<strong>Condividendo la APP</strong> a tutti coloro che vogliono far parte insieme della crescita e sviluppo di una Nuova Era</li>'
+ '<li>👥 Aiutando a creare Gruppi Territoriali nella vostra città, impegnandosi a realizzare progetti per il Bene Comune, in onore ai principi Amorevoli e di condivisione.</li>'
+ '<li>🌱 Sostenendo le persone attorno a voi, e rispettando la nostra vera Casa: Madre Natura e Tutti gli Esseri Viventi. ❤️</li>'
+ '<li>👨🏻‍💻 Con una <strong>piccola donazione</strong> per le spese dei Server, manutenzione e per i continui sviluppi e miglioramenti</li></ul>' +
'1) Tramite <strong><a href="https://paypal.me/paoloarena" target="_blank">Paypal</a></strong>:<br>' +
'<br>2) Tramite <strong>Satispay</strong>: <a href="https://www.satispay.com/app/match/link/money-box/S6Y-SVN--62712D42-35B0-4BB9-8511-410C2AB8CD45" target="_blank">Clicca qui</a><br>' +
'<div style="font-size: 1rem; background-color: white; color: blue; border: solid 2px #f00; margin: 5px; padding: 5px; border-radius: 10px; " ' +
'class="row justify-around">' +
'Se ancora non hai Satispay <a href="https://www.satispay.com/promo/PAOLOARENA4">Richiedila cliccando qui</a></br>' +
'</div>' +
'<br>3) Tramite <strong>Bonifico Bancario</strong>:<br>' +
'(Scrivi a Surya (<a href="https://t.me/surya1977">@surya1977</a>) per le coordinate</br>' +
'' +
'4) In alternativa scegli tu una forma di Dono <br />' +
+ '<li>🌱 Sostenendo le persone attorno a voi, e rispettando la nostra vera Casa: Madre Natura e Tutti gli Esseri Viventi. ❤️</li>' +
'Grazie Mille per l\'Aiuto ed il Supporto' +
'<br>',
},

View File

@@ -1,6 +1,6 @@
/* GRUPPOMACRO APP
/* RISO APP
*/
import {
import type {
IListRoutes,
ILang,
IPreloadImages,
@@ -44,7 +44,6 @@ const firstPage = {
infooter: true,
}
function getDynamicPages(site: ISites): IListRoutes[] {
const baseroutes: IListRoutes[] = [
@@ -71,11 +70,34 @@ function getDynamicPages(site: ISites): IListRoutes[] {
},
{
active: true,
order: 400,
path: '/test-lungo',
materialIcon: 'fas fa-test',
name: 'mypages.test_lungo',
component: () => import('@src/views/testLungo/testLungo.vue'),
order: 12,
path: '/goods',
materialIcon: 'fas fa-tshirt',
name: 'mypages.goods',
component: () => import('@src/root/goods/goods.vue'),
meta: { requiresAuth: true },
inmenu: true,
infooter: true,
},
{
active: true,
order: 15,
path: '/services',
materialIcon: 'fas fa-house-user',
name: 'mypages.services',
component: () => import('@src/root/services/services.vue'),
meta: { requiresAuth: true },
inmenu: true,
infooter: true,
},
{
active: true,
order: 15,
path: '/activities',
materialIcon: 'fas fa-house-user',
name: 'mypages.activities',
component: () => import('@src/root/activities/activities.vue'),
meta: { requiresAuth: true },
inmenu: false,
infooter: false,
},
@@ -90,6 +112,17 @@ function getDynamicPages(site: ISites): IListRoutes[] {
inmenu: false,
infooter: false,
},
{
active: true,
order: 15,
path: '/hosps',
materialIcon: 'fas fa-bed',
name: 'mypages.hosp',
component: () => import('@src/root/hosp/hosp.vue'),
meta: { requiresAuth: true },
inmenu: true,
infooter: true,
},
{
active: site.confpages && site.confpages.enableCircuits,
order: 16,
@@ -102,7 +135,7 @@ function getDynamicPages(site: ISites): IListRoutes[] {
infooter: true,
},
{
active: site.confpages && site.confpages.enableEvents,
active: true,
order: 20,
path: '/events',
materialIcon: 'fas fa-bullhorn',
@@ -124,7 +157,7 @@ function getDynamicPages(site: ISites): IListRoutes[] {
infooter: false,
},
{
active: site.confpages && site.confpages.showProfile,
active: true,
order: 120,
path: '/myprofile',
materialIcon: 'fas fa-user',
@@ -134,8 +167,19 @@ function getDynamicPages(site: ISites): IListRoutes[] {
inmenu: true,
infooter: true,
},
/*{
active: true,
order: 120,
path: '/editprofile',
materialIcon: 'fas fa-user',
name: 'pages.profile3',
// component: () => import('app/src/components/editprofile/editprofile.vue'),
meta: { requiresAuth: true },
inmenu: false,
infooter: false,
},*/
{
active: site.confpages && site.confpages.showiscrittiMenu,
active: true,
order: 130,
path: '/friends',
materialIcon: 'fas fa-user-friends',
@@ -230,6 +274,16 @@ function getDynamicPages(site: ISites): IListRoutes[] {
inmenu: false,
infooter: false,
},
{
active: true,
order: 150,
path: '/sostieniilprogetto',
materialIcon: 'fas fa-hand-holding-heart',
name: 'pages.fundraising',
component: () => import('@src/root/fundraising/fundraising.vue'),
inmenu: false,
infooter: false,
},
{
active: true,
order: 80,
@@ -248,7 +302,7 @@ function getDynamicPages(site: ISites): IListRoutes[] {
export function firstimagehome() {
let img = 'statics/images/background.jpg'
const img = 'statics/images/background.jpg'
return img
}

View File

@@ -28,6 +28,13 @@
<div v-else>
<div v-if="isfinishLoading">
<CMyPageElem
v-if="tools.pageExist('/presentazione')"
title="Home"
mypath="presentazione"
>
</CMyPageElem>
<CMyPageElem
v-else
title="Home"
mypath="home_logout"
>

View File

@@ -658,7 +658,7 @@ function getRoutesAd(site: ISites) {
{
active: true,
order: 1005,
path: '/invitetoreg/:token',
path: '/invitetoreg/:tok',
materialIcon: 'how_to_reg',
name: 'pages.InvitoReg',
component: () => import('@src/views/login/invitoreg/invitoreg.vue'),

View File

@@ -1571,7 +1571,7 @@ const msg_it = {
acceptregulation: 'Accetta il Regolamento',
domanda_ask: 'Confermi di voler entrare nel {circuitname} accettando il suo Regolamento?',
domanda_revoke: 'Revocare la richiesta al {circuitname}?',
already_entered: 'Sei ora dentro al {circuitname}. Gli amministratori ti abiliteranno la "Fiducia Concessa" appena possibile (ove sia possibile).',
already_entered: 'Gli amministratori del {circuitname} ti abiliteranno appena possibile.',
askedto: 'La tua richiesta al {circuitname} è stata inviata. Gli amministratori ti abiliteranno non appena possibile.',
revoketo: 'Revocato la richiesta d\'invito al {circuitname}',
domanda_remove: 'Rimuovere dal Circuito {username} ?',

View File

@@ -150,6 +150,7 @@ export const tools = {
TIPOVIS_SHOW_INPAGE: 8,
APORTADOR_SOLIDARIO: 'apsol',
TOK_INV: 'tokinv',
IDAPP_AYNI: '7',
IDAPP_SIP: '9',
@@ -11280,6 +11281,14 @@ export const tools = {
return false;
}
},
pageExist(path: string) {
const globalStore = useGlobalStore();
const page = globalStore.getPage(path)
return !!page
}
// FINE !
// getLocale() {

View File

@@ -1479,7 +1479,9 @@ export const useGlobalStore = defineStore('GlobalStore', {
return Api.SendReq('/savepage', 'POST', { page })
.then((res) => {
if (res && res.data && res.data.mypage) {
const index = this.mypage.findIndex((rec) => rec.path === res.data.mypage.path);
let index = this.mypage.findIndex((rec) => rec._id === res.data.mypage._id);
if (index < 0)
index = this.mypage.findIndex((rec) => rec.path === res.data.mypage.path);
if (index >= 0) {
this.mypage[index] = res.data.mypage;
} else {

View File

@@ -5,9 +5,12 @@ import type {
InvitoAmicoForm,
InvitoAmicoRequest,
InvitoAmicoResponse,
InvitoGetResponse,
} from '../types/invita-amico.types.ts';
import { useUserStore } from '../store/index.js';
import { Api } from '../store/Api/index.js';
import { tools } from '@tools';
import { shared_consts } from '@src/common/shared_vuejs'
/**
* Store Pinia per la gestione degli inviti amici
@@ -190,23 +193,60 @@ export const useInvitaAmicoStore = defineStore('invitaAmico', () => {
}
};
const ottieniInvitoByToken = async (
token: string,
): Promise<InvitoGetResponse> => {
// Reset errori
error.value = null;
loading.value = true;
const userStore = useUserStore();
try {
if (!token) {
throw new Error('Token non valido');
}
// Chiamata API
const response = await Api.SendReq('/inviti/getinv', 'POST', { tok: token });
if (response.data.success) {
return response.data.rec;
}
} catch (err) {
// Gestione errori
const errorMessage = err instanceof Error ? err.message : 'Errore sconosciuto';
error.value = errorMessage;
// Ritorna risposta di errore
return null;
} finally {
loading.value = false;
}
};
/**
* Invia invito via Telegram
* (placeholder - integra con la tua logica esistente)
*/
const inviaInvitoTelegram = async (messaggio?: string): Promise<boolean> => {
const inviaInvitoTelegram = async ($q: any, t: any): Promise<boolean> => {
loading.value = true;
error.value = null;
try {
await tools.sendMsgTelegramCmd(
$q,
t,
shared_consts.MsgTeleg.SHARE_MSGREG,
true
);
// TODO: Integra con la tua logica Telegram esistente
// Esempio:
// await telegramBotService.sendMessage(messaggio);
console.log('Invio via Telegram:', messaggio);
// Simula invio
await new Promise((resolve) => setTimeout(resolve, 1000));
return true;
} catch (err) {
@@ -379,6 +419,7 @@ export const useInvitaAmicoStore = defineStore('invitaAmico', () => {
// Actions
inviaInvitoEmail,
ottieniInvitoByToken,
inviaInvitoTelegram,
aggiungiACronologia,
rimuoviDaCronologia,

View File

@@ -17,6 +17,10 @@ export interface InvitoAmicoResponse {
message: string;
emailInviata?: boolean;
}
export interface InvitoGetResponse {
email: string;
usernameInvitante: string;
}
export interface EmailInvitoTemplate {
to: string;

View File

@@ -13,27 +13,19 @@ export default defineComponent({
setup() {
const $route = useRoute()
const adult = ref(false)
const invited = computed(() => $route.params.invited)
const regexpire = computed(() => $route.params.regexpire)
// @ts-ignore
watch(() => invited, (newval, oldval) => {
console.log('$route.params.invited')
adult.value = !!$route.params.invited
})
const tok = computed(() => $route.params.tok)
function created() {
if (!tools.getCookie(tools.APORTADOR_SOLIDARIO, '')) {
if (!tools.getCookie(tools.TOK_INV, '')) {
// @ts-ignore
tools.setCookie(tools.APORTADOR_SOLIDARIO, $route.params.invited ? $route.params.invited : '')
tools.setCookie(tools.TOK_INV, tok.value)
}
}
created()
return {
regexpire
tok,
}
},
})

View File

@@ -1,6 +1,6 @@
<template>
<q-page padding class="signup">
<CSignUp :showcell="false" :showaportador="true" :show_namesurname="true" :need_Telegram="true" :regexpire="regexpire" :token="token">
<CSignUp :showcell="false" :showaportador="true" :show_namesurname="true" :token="tok">
</CSignUp>
</q-page>

View File

@@ -1,12 +1,14 @@
<template>
<!--<CNotifAtTop />-->
<div v-if="tools.isUserOk()" class="q-gutter-sm q-pa-sm q-pb-md">
<div class="q-gutter-sm q-pa-sm q-pb-md">
<div v-if="!circuit && !loading">
<div v-if="mystatus === 403">
<div v-if="mystatus === 401">
<h3>
Non hai i permessi per accedere al Circuito.
<br />Occorre prima registrarsi ed accedere alla App
</h3>
<div v-if="!tools.isLogged()">
<CCheckIfIsLogged></CCheckIfIsLogged>
</div>
</div>
<div v-else-if="tools.isLogged() && path">
<h3>Circuito non Esistente</h3>
@@ -17,12 +19,20 @@
</div>
</div>
</div>
<div v-else>
<div v-else-if="tools.isUserOk()">
<div v-if="!tools.isLogged()">
<CCheckIfIsLogged></CCheckIfIsLogged>
</div>
<q-dialog v-model="showPic" full-height full-width>
<img :src="getImgCircuit()" :alt="circuit.name" class="full-width" />
<q-dialog
v-model="showPic"
full-height
full-width
>
<img
:src="getImgCircuit()"
:alt="circuit.name"
class="full-width"
/>
</q-dialog>
<q-dialog
@@ -31,24 +41,37 @@
transition-show="slide-up"
transition-hide="slide-down"
>
<q-card v-if="circuit" class="dialog_card">
<q-toolbar class="bg-primary text-white" dense>
<q-card
v-if="circuit"
class="dialog_card"
>
<q-toolbar
class="bg-primary text-white"
dense
>
<!--<q-toolbar :class="tools.displayClasses(myevent)"-->
<!--:style="tools.displayStyles(myevent) + ` min-width: `+ tools.myheight_dialog() + `px;`">-->
<q-toolbar-title>
{{ circuit.name }}
<div v-if="groupnameSel">
Gruppo: {{ groupnameSel.groupname }}
</div>
<div v-if="groupnameSel">Gruppo: {{ groupnameSel.groupname }}</div>
</q-toolbar-title>
<q-btn flat round color="white" icon="close" v-close-popup></q-btn>
<q-btn
flat
round
color="white"
icon="close"
v-close-popup
></q-btn>
</q-toolbar>
<q-card-section v-if="circuit.symbol === 'RIS'" class="inset-shadow">
<q-card-section
v-if="circuit.symbol === 'RIS'"
class="inset-shadow"
>
<div v-html="t('circuit.disclaimer')"></div>
</q-card-section>
<q-card-section class="inset-shadow">
<div style="font-weight: bold; font-size: 1.25rem">
{{ t("circuit.regulation") }} {{ circuit.name }}
{{ t('circuit.regulation') }} {{ circuit.name }}
</div>
<q-btn
v-if="!showrules"
@@ -95,7 +118,7 @@
></CSendCoins>
</div>
</div>
<div v-if="circuit">
<div v-else-if="circuit">
<div
class="absolute-top-right q-mr-sm q-my-md"
style="margin-top: 50px !important"
@@ -109,7 +132,10 @@
style="z-index: 1"
>
<q-menu>
<q-list v-if="true" style="min-width: 150px">
<q-list
v-if="true"
style="min-width: 150px"
>
<q-item
clickable
v-close-popup
@@ -118,10 +144,9 @@
saldo < 0
? tools.showNegativeNotif(
$q,
t(
'circuit.per_uscire_dal_circuito_occorre_essere_a_zero',
{ symbol: circuit.symbol }
),
t('circuit.per_uscire_dal_circuito_occorre_essere_a_zero', {
symbol: circuit.symbol,
}),
30000
)
: tools.removeFromMyCircuits(
@@ -136,14 +161,20 @@
"
>
<q-item-section avatar>
<q-icon color="negative" name="fas fa-user-minus" />
<q-icon
color="negative"
name="fas fa-user-minus"
/>
</q-item-section>
<q-item-section>
{{ $t("circuit.exit_circuit") }}
{{ $t('circuit.exit_circuit') }}
</q-item-section>
</q-item>
<q-list v-if="userStore.isAdmin" style="min-width: 200px">
<q-list
v-if="userStore.isAdmin"
style="min-width: 200px"
>
<q-item
clickable
v-close-popup
@@ -158,9 +189,12 @@
"
>
<q-item-section avatar>
<q-icon color="negative" name="fas fa-trash-alt" />
<q-icon
color="negative"
name="fas fa-trash-alt"
/>
</q-item-section>
<q-item-section>{{ $t("circuit.delete") }}</q-item-section>
<q-item-section>{{ $t('circuit.delete') }}</q-item-section>
</q-item>
</q-list>
</q-list>
@@ -190,7 +224,10 @@
class="fit column no-wrap justify-evenly items-center content-start"
>
<div class="row justify-center">
<q-avatar v-if="getImgCircuit()" size="50px">
<q-avatar
v-if="getImgCircuit()"
size="50px"
>
<q-img
:src="getImgCircuit()"
:alt="circuit.name"
@@ -199,7 +236,10 @@
@click="showPic = true"
/>
</q-avatar>
<div class="q-mx-xs text-h6" style="align-self: center">
<div
class="q-mx-xs text-h6"
style="align-self: center"
>
<span v-if="checkifShow('name')">{{ circuit.name }}</span>
</div>
<div class="row no-wrap justify-center items-center">
@@ -225,10 +265,17 @@
></q-btn>
</div>
<q-dialog v-model="showMov" :maximized="$q.screen.lt.sm" permanent>
<q-dialog
v-model="showMov"
:maximized="$q.screen.lt.sm"
permanent
>
<q-card class="dialog_card">
<q-bar dense class="bg-primary text-white">
{{ t("circuit.movements") }}:
<q-bar
dense
class="bg-primary text-white"
>
{{ t('circuit.movements') }}:
<q-space />
<q-btn
flat
@@ -355,7 +402,7 @@
class="bg-red text-white"
style="text-align: center"
>
<em style="font-weight: bold">{{ $t("db.youarerefusedcircuit") }}</em>
<em style="font-weight: bold">{{ $t('db.youarerefusedcircuit') }}</em>
<br />
</q-banner>
@@ -371,11 +418,13 @@
class="bg-blue text-white"
>
<template v-slot:avatar>
<q-icon name="fas fa-info" color="red" size="sm" />
<q-icon
name="fas fa-info"
color="red"
size="sm"
/>
</template>
<span
v-html="$t('circuit.entra_italia_solo_dopo_aver_fido')"
></span>
<span v-html="$t('circuit.entra_italia_solo_dopo_aver_fido')"></span>
</q-banner>
<q-banner
v-if="circuitStore.IsNationalAndNotEnterInLocal(circuit.name)"
@@ -393,7 +442,7 @@
</template>
<em style="font-weight: bold">
{{ $t("circuit.beforeentertolocalcircuit") }}
{{ $t('circuit.beforeentertolocalcircuit') }}
</em>
<br />
</q-banner>
@@ -412,11 +461,15 @@
style="text-align: center"
>
<template v-slot:avatar>
<q-icon name="fas fa-info" color="white" size="sm" />
<q-icon
name="fas fa-info"
color="white"
size="sm"
/>
</template>
<em style="font-weight: bold">
{{ $t("db.insertgoodorservices_to_enter_circuit") }}
{{ $t('db.insertgoodorservices_to_enter_circuit') }}
</em>
<br />
</q-banner>
@@ -442,7 +495,7 @@
</template>
<em style="font-weight: bold">
{{ $t("db.insertgoodorservices_to_enter_circuit") }}
{{ $t('db.insertgoodorservices_to_enter_circuit') }}
</em>
<br />
</q-banner>
@@ -465,11 +518,7 @@
"
icon="fas fa-user-plus"
color="primary"
:label="
circuit.askManagerToEnter
? t('circuit.ask')
: t('circuit.enter')
"
:label="circuit.askManagerToEnter ? t('circuit.ask') : t('circuit.enter')"
@click="
requestToEnterCircuit = true;
groupnameSel = null;
@@ -485,9 +534,7 @@
flat
outline
:label="$t('shared.cancel_ask_short')"
@click="
tools.cancelReqCircuit($q, userStore.my.username, circuit.name)
"
@click="tools.cancelReqCircuit($q, userStore.my.username, circuit.name)"
/>
</div>
</div>
@@ -496,7 +543,11 @@
v-if="circuit.name"
class="no-wrap justify-evenly items-center content-start"
>
<q-tabs v-model="tabcircuit" class="text-blue" no-caps>
<q-tabs
v-model="tabcircuit"
class="text-blue"
no-caps
>
<q-tab
:label="t('shared.info1')"
name="info"
@@ -504,8 +555,7 @@
></q-tab>
<q-tab
v-if="
tools.iCanShowCircuitsMember(circuit) ||
tools.iAmAdminCircuit(circuit.name)
tools.iCanShowCircuitsMember(circuit) || tools.iAmAdminCircuit(circuit.name)
"
:label="t('shared.subscribes')"
name="members"
@@ -535,14 +585,22 @@
></q-tab>
</q-tabs>
<q-tab-panels v-model="tabcircuit" animated keep-alive>
<q-tab-panel name="annunci" style="max-width: 500px"> </q-tab-panel>
<q-tab-panels
v-model="tabcircuit"
animated
keep-alive
>
<q-tab-panel
name="annunci"
style="max-width: 500px"
>
</q-tab-panel>
<q-tab-panel
name="gruppicollettivi"
style="max-width: 500px"
v-if="tools.iCanShowCircuitsMember(circuit)"
>
<div class="text-h7">{{ t("circuit.contideigruppi") }}:</div>
<div class="text-h7">{{ t('circuit.contideigruppi') }}:</div>
<div class="text-h8 q-mb-sm">
<div v-html="$t('circuit.collettivi_info')"></div>
</div>
@@ -551,7 +609,11 @@
v-model="tabmembers"
class="text-blue"
>
<q-tab label="Iscritti" name="all" icon="fas fa-users"></q-tab>
<q-tab
label="Iscritti"
name="all"
icon="fas fa-users"
></q-tab>
<q-tab
v-if="tools.iAmAdminCircuit(circuit.name)"
label="Richieste"
@@ -567,10 +629,18 @@
></q-tab>
</q-tabs>
<q-tab-panels v-model="tabmembers" animated keep-alive>
<q-tab-panels
v-model="tabmembers"
animated
keep-alive
>
<q-tab-panel name="all">
<div class="row justify-center">
<q-btn rounded icon="fas fa-user-plus" class="text-center">
<q-btn
rounded
icon="fas fa-user-plus"
class="text-center"
>
<q-menu>
<q-item>Chiedi di Entrare nei Circuiti:</q-item>
<q-list
@@ -663,9 +733,7 @@
ref="tabGroups"
prop_mytable="mygroups"
prop_mytitle
:prop_mycolumns="
showsaldi ? colmyUserPeopleSaldi : colmyUserPeople
"
:prop_mycolumns="showsaldi ? colmyUserPeopleSaldi : colmyUserPeople"
prop_colkey="_id"
col_title="groupname"
:vertical="costanti.VISUTABLE_GROUP_CIRCUIT"
@@ -695,7 +763,7 @@
></CGridTableRec>
</q-tab-panel>
<q-tab-panel name="rich">
{{ $t("circuit.richieste_title") }}
{{ $t('circuit.richieste_title') }}
<CGridTableRec
v-if="!loading"
prop_mytable="circuits"
@@ -759,7 +827,7 @@
<div>
<q-card>
<q-card-section>
<div class="text-h7">{{ t("circuit.info") }}</div>
<div class="text-h7">{{ t('circuit.info') }}</div>
</q-card-section>
<q-separator />
@@ -772,7 +840,7 @@
<div v-html="$t('circuit.aggiuntive')"></div>
</div>
<q-card-section>
<div class="text-h7">{{ t("circuit.descr") }}:</div>
<div class="text-h7">{{ t('circuit.descr') }}:</div>
</q-card-section>
<q-separator />
@@ -786,19 +854,24 @@
</q-card-section>
<div class="container">
<q-icon name="fas fa-users" class="iconcirc"></q-icon>
<q-icon
name="fas fa-users"
class="iconcirc"
></q-icon>
<span class="element">{{ numUsers() }}</span>
{{
numUsers() === 1
? t("shared.member")
: t("shared.members")
}}
{{ numUsers() === 1 ? t('shared.member') : t('shared.members') }}
</div>
<div class="container" v-if="circuit.link_group">
<q-icon name="fab fa-telegram" class="iconcirc"></q-icon>
<div
class="container"
v-if="circuit.link_group"
>
<q-icon
name="fab fa-telegram"
class="iconcirc"
></q-icon>
<span class="element">
{{
$t("circuit.chat_gruppo_telegram_circuito", {
$t('circuit.chat_gruppo_telegram_circuito', {
name: circuit.name,
})
}}:
@@ -836,13 +909,17 @@
</span>
</div>
<div class="container">
<q-icon name="fas fa-user-cog" class="iconcirc"></q-icon>
<q-icon
name="fas fa-user-cog"
class="iconcirc"
></q-icon>
<span class="element">{{ numAdmins() }}</span>
{{
numAdmins() === 1 ? t("shared.admin") : t("shared.admins")
}}
{{ numAdmins() === 1 ? t('shared.admin') : t('shared.admins') }}
</div>
<div v-for="(user, index) of circuit.admins" :key="index">
<div
v-for="(user, index) of circuit.admins"
:key="index"
>
<CMyUser
:mycontact="user"
:visu="costanti.FIND_PEOPLE"
@@ -850,15 +927,15 @@
></CMyUser>
</div>
<div class="sezioni">
<q-icon name="fas fa-coins" class="iconcirc"></q-icon>
{{ t("circuit.symbol") }}:
<q-icon
name="fas fa-coins"
class="iconcirc"
></q-icon>
{{ t('circuit.symbol') }}:
<span class="text-h7">
<em
class="q-px-sm text-black rounded-borders"
:style="
`background-color: ` +
tools.getColorByCircuit(circuit)
"
:style="`background-color: ` + tools.getColorByCircuit(circuit)"
>{{ tools.getSymbolByCircuit(circuit) }}</em
>
</span>
@@ -924,13 +1001,22 @@
</div>
-->
</div>
<div v-if="circuit.totTransato" class="sezioni">
<q-icon name="fas fa-stats" class="iconcirc"></q-icon>
{{ t("circuit.stats") }}:
<div
v-if="circuit.totTransato"
class="sezioni"
>
<q-icon
name="fas fa-stats"
class="iconcirc"
></q-icon>
{{ t('circuit.stats') }}:
<br />
</div>
<div :class="$q.screen.lt.sm ? '' : 'row'">
<div v-if="circuit.totTransato" class="sezioni">
<div
v-if="circuit.totTransato"
class="sezioni"
>
<CCurrencyValue
:symbol="tools.getSymbolByCircuit(circuit)"
:color="tools.getColorByCircuit(circuit)"
@@ -941,7 +1027,10 @@
:tips="t('circuit.totTransato_tips')"
></CCurrencyValue>
</div>
<div v-if="circuit.totCircolante" class="sezioni">
<div
v-if="circuit.totCircolante"
class="sezioni"
>
<CCurrencyValue
:symbol="tools.getSymbolByCircuit(circuit)"
:color="tools.getColorByCircuit(circuit)"
@@ -954,14 +1043,13 @@
</div>
</div>
<div class="sezioni">
<q-icon name="fas fa-toggle-on" class="iconcirc"></q-icon>
{{ t("circuit.transactionsEnabled") }}:
<q-icon
name="fas fa-toggle-on"
class="iconcirc"
></q-icon>
{{ t('circuit.transactionsEnabled') }}:
<span class="text-section">
{{
circuit.transactionsEnabled
? t("dialog.yes")
: t("dialog.no")
}}
{{ circuit.transactionsEnabled ? t('dialog.yes') : t('dialog.no') }}
{{}}
</span>
</div>
@@ -990,7 +1078,7 @@
<q-card v-if="circuit.name">
<q-card-section>
<div class="text-h8">
{{ t("circuit.regulation") }} {{ circuit.name }}:
{{ t('circuit.regulation') }} {{ circuit.name }}:
</div>
</q-card-section>
<q-separator />
@@ -1010,10 +1098,16 @@
></span>
</div>
</q-card-section>
<div v-if="circuit.createdBy" class="container text-h8">
<q-icon name="fas fa-lightbulb" class="iconcirc"></q-icon>
<div
v-if="circuit.createdBy"
class="container text-h8"
>
<q-icon
name="fas fa-lightbulb"
class="iconcirc"
></q-icon>
{{
$t("shared.createddate", {
$t('shared.createddate', {
date: tools.getstrDateYY(circuit.date_created),
})
}}
@@ -1026,10 +1120,13 @@
"
class="container text-h8"
>
<q-icon name="fas fa-pencil-alt" class="iconcirc"></q-icon>
<q-icon
name="fas fa-pencil-alt"
class="iconcirc"
></q-icon>
<span class="element text-h8">
{{
$t("shared.lastmodify", {
$t('shared.lastmodify', {
date: tools.getstrDateYY(circuit.date_updated),
})
}}
@@ -1044,7 +1141,10 @@
<div v-if="circuit.note">
<br />
<div class="q-ma-sm q-gutter-sm q-pa-xs">
<div v-if="circuit.note" v-html="circuit.note"></div>
<div
v-if="circuit.note"
v-html="circuit.note"
></div>
</div>
</div>
</q-tab-panel>
@@ -1058,7 +1158,11 @@
v-model="tabmembers"
class="text-blue"
>
<q-tab label="Iscritti" name="all" icon="fas fa-users"></q-tab>
<q-tab
label="Iscritti"
name="all"
icon="fas fa-users"
></q-tab>
<q-tab
v-if="tools.iAmAdminCircuit(circuit.name)"
label="Richieste"
@@ -1074,7 +1178,10 @@
></q-tab>
</q-tabs>
<q-tab-panels v-model="tabmembers" keep-alive>
<q-tab-panels
v-model="tabmembers"
keep-alive
>
<q-tab-panel name="all">
<!--<q-toggle v-model="showsaldi" :label="t('movement.showsaldi')"></q-toggle>-->
</q-tab-panel>
@@ -1141,9 +1248,9 @@
<q-tab-panel name="comunitario">
<q-card v-if="circuit.name">
<q-card-section>
<div class="text-h7">{{ t("circuit.contocomunitario") }}:</div>
<div class="text-h7">{{ t('circuit.contocomunitario') }}:</div>
<div class="text-h8 q-mb-sm">
{{ $t("circuit.info_contocom") }}
{{ $t('circuit.info_contocom') }}
</div>
</q-card-section>
<q-separator />
@@ -1158,15 +1265,11 @@
:color="circuit.color"
:saldo="circuit.account.saldo_pend"
:valueextra="
circuit.account.saldo != circuit.account.saldo_pend
? `* `
: ''
circuit.account.saldo != circuit.account.saldo_pend ? `* ` : ''
"
:qtarem="
circuit.account
? circuitStore.getRemainingCoinsToSend(
circuit.account
)
? circuitStore.getRemainingCoinsToSend(circuit.account)
: 0
"
></CSaldo>
@@ -1266,7 +1369,11 @@
height="140px"
animation="fade"
/>
<q-card flat bordered style="width: 250px">
<q-card
flat
bordered
style="width: 250px"
>
<div class="text-h7">
<q-skeleton :animation="animation" />
</div>
@@ -1285,15 +1392,10 @@
</div>
</div>
</div>
<div v-else>
<CUserNonVerif></CUserNonVerif>
</div>
</template>
<script lang="ts" src="./mycircuit.ts">
</script>
<script lang="ts" src="./mycircuit.ts"></script>
<style lang="scss" scoped>
@import "./mycircuit.scss";
@import './mycircuit.scss';
</style>

View File

@@ -2,11 +2,14 @@
<CNotifAtTop />
<div class="q-gutter-sm q-pa-sm q-pb-md">
<div v-if="!mygrp && !loading">
<div v-if="mystatus === 403">
<div v-if="mystatus === 401">
<h3>
Non hai i permessi per accedere al Gruppo.
<br />Occorre prima registrarsi alla App
</h3>
<div v-if="!tools.isLogged()">
<CCheckIfIsLogged></CCheckIfIsLogged>
</div>
</div>
<div v-else>
<h3>Gruppo non Esistente</h3>