- verifica email se non è stata verificata (componente)

- altri aggiornamenti grafica PAGERIS.
- OLLAMA AI
This commit is contained in:
Surya Paolo
2025-12-11 18:34:39 +01:00
parent 6fdb101092
commit 89a8d10eae
44 changed files with 7915 additions and 3565 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -15,10 +15,10 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<h3 class="card-title">Benvenuto su RISO</h3> <h3 class="card-title">Benvenuto su {{ tools.sitename() }}</h3>
<p class="card-description"> <p class="card-description">
Accedi con le tue credenziali per utilizzare la APP e per unirti Accedi con le tue credenziali per utilizzare la APP e per unirti
al Circuito di scambio RIS del tuo territorio ai membri della Community
</p> </p>
</div> </div>

View File

@@ -3,7 +3,10 @@
<div class="key-label"> <div class="key-label">
{{ mykey }} {{ mykey }}
</div> </div>
<div class="value-content" :style="color ? `background-color: ${color}; color: white;` : ''"> <div
class="value-content"
:style="color ? `background-color: ${color}; color: white;` : ''"
>
<span v-if="mydate"> <span v-if="mydate">
<CDateTime <CDateTime
v-model:value="mydate" v-model:value="mydate"
@@ -11,10 +14,17 @@
:canEdit="false" :canEdit="false"
/> />
</span> </span>
<span v-else class="value-text"> <span
v-else
class="value-text"
>
{{ myvalue || '-' }} {{ myvalue || '-' }}
</span> </span>
</div>
<div
class="value-content"
:style="color ? `background-color: ${color}; color: white;` : ''"
>
<q-btn <q-btn
v-if="showSetButton && onSetValue" v-if="showSetButton && onSetValue"
rounded rounded
@@ -30,8 +40,7 @@
</div> </div>
</template> </template>
<script lang="ts" src="./CKeyAndValue.ts"> <script lang="ts" src="./CKeyAndValue.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import './CKeyAndValue.scss'; @import './CKeyAndValue.scss';

View File

@@ -32,6 +32,7 @@ import { shared_consts } from '@/common/shared_vuejs';
import { LandingFooter } from '@/components/LandingFooter'; import { LandingFooter } from '@/components/LandingFooter';
import { CMyActivities } from '@/components/CMyActivities'; import { CMyActivities } from '@/components/CMyActivities';
import { CECommerce } from '@/components/CECommerce'; import { CECommerce } from '@/components/CECommerce';
import { CheckEmail } from '@/components/CheckEmail';
import { HomeRiso } from '@/components/HomeRiso'; import { HomeRiso } from '@/components/HomeRiso';
import mycircuits from '@/views/user/mycircuits/mycircuits.vue'; import mycircuits from '@/views/user/mycircuits/mycircuits.vue';
import PageRis from '@/components/pageris/pageris.vue'; import PageRis from '@/components/pageris/pageris.vue';
@@ -118,6 +119,7 @@ export default defineComponent({
CCheckIfIsLogged, CCheckIfIsLogged,
CStatusReg, CStatusReg,
CDashboard, CDashboard,
CheckEmail,
CMainView, CMainView,
CNotifAtTop, CNotifAtTop,
CPresentazione, CPresentazione,

View File

@@ -904,14 +904,7 @@
> >
Msg di Controllo Verifica Email Msg di Controllo Verifica Email
</div> </div>
<div class="q-pa-xs q-gutter-md"> <CheckEmail />
<div
v-if="tools.isLogged() && !tools.isVerified()"
class="text-verified"
>
{{ t('components.authentication.email_verification.link_sent') }}
</div>
</div>
</div> </div>
<div v-else-if="myel.type === shared_consts.ELEMTYPE.PRESENTAZIONE"> <div v-else-if="myel.type === shared_consts.ELEMTYPE.PRESENTAZIONE">
<div <div
@@ -1090,10 +1083,10 @@
Check Sito di Test Check Sito di Test
</div> </div>
<q-banner <q-banner
v-if="tools.isTest() && false" v-if="tools.isTest()"
rounded rounded
dense dense
class="bg-negative text-white" class="bg-negative text-white q-ma-sm"
color="primary q-title" color="primary q-title"
style="text-align: center" style="text-align: center"
> >
@@ -1104,7 +1097,7 @@
size="xs" size="xs"
/> />
</template> </template>
<span class="mybanner"> TEST !</span> <span class="mybanner"> AMBIENTE DI TEST !</span>
</q-banner> </q-banner>
</div> </div>
<div v-else-if="myel.type === shared_consts.ELEMTYPE.CHECKNEWVERSION"> <div v-else-if="myel.type === shared_consts.ELEMTYPE.CHECKNEWVERSION">

View File

@@ -589,3 +589,47 @@
font-weight: 500; font-weight: 500;
} }
} }
.annuncio-location {
font-size: 1rem;
color: #718096;
align-items: center;
gap: 4px;
&::before {
content: '📍';
}
}
// Il contenitore padre (q-item o card) deve avere position relative
.q-item,
.event-card {
position: relative;
}
// Bottone overlay fisso a destra
.action-menu-btn-overlay {
position: absolute !important;
top: 8px;
right: 0px;
transform: translateY(-50%);
z-index: 10;
// Sfondo semi-trasparente per visibilità
background: rgba(255, 255, 255, 0.8) !important;
// Ombra leggera
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
&:hover {
background: rgba(255, 255, 255, 1) !important;
}
}
// Alternativa: in alto a destra
.action-menu-btn-overlay--top {
position: absolute !important;
top: 4px;
right: 4px;
z-index: 10;
}

View File

@@ -340,8 +340,9 @@
<span <span
v-for="(rec, ind) of myrec.mycities" v-for="(rec, ind) of myrec.mycities"
:key="ind" :key="ind"
class="annuncio-location"
> >
<span v-if="ind > 0">, </span> <span v-if="ind > 0"></span>
<span v-if="table === shared_consts.TABLES_MYHOSPS" class="cities-text-bold">{{ rec.comune }} ({{ rec.prov }})</span> <span v-if="table === shared_consts.TABLES_MYHOSPS" class="cities-text-bold">{{ rec.comune }} ({{ rec.prov }})</span>
<span v-else>{{ rec.comune }} ({{ rec.prov }})</span> <span v-else>{{ rec.comune }} ({{ rec.prov }})</span>
</span> </span>
@@ -353,12 +354,11 @@
<q-item-section <q-item-section
v-if="tools.canModifyThisRec(myrec, table) || editOn" v-if="tools.canModifyThisRec(myrec, table) || editOn"
side side
top
class="actions-section" class="actions-section"
> >
<q-btn <q-btn
round round
flat
dense
icon="more_vert" icon="more_vert"
size="sm" size="sm"
class="action-menu-btn" class="action-menu-btn"

View File

@@ -459,7 +459,7 @@
<q-item-label> <q-item-label>
<q-btn rounded icon="fas fa-ellipsis-h"> <q-btn rounded icon="fas fa-ellipsis-h">
<q-menu> <q-menu>
<q-list <!--<q-list
v-if="!userStore.IsMyFriendByUsername(contact.username)" v-if="!userStore.IsMyFriendByUsername(contact.username)"
style="min-width: 200px" style="min-width: 200px"
> >
@@ -481,7 +481,7 @@
t('friends.ask_friend') t('friends.ask_friend')
}}</q-item-section> }}</q-item-section>
</q-item> </q-item>
</q-list> </q-list>-->
<q-list style="min-width: 200px"> <q-list style="min-width: 200px">
<q-item <q-item
clickable clickable
@@ -515,7 +515,7 @@
> >
<q-menu> <q-menu>
<q-list style="min-width: 200px"> <q-list style="min-width: 200px">
<q-item <!--<q-item
v-if=" v-if="
!userStore.IsMyFriendByUsername(contact.username) && !userStore.IsMyFriendByUsername(contact.username) &&
!userStore.IsAskedFriendByUsername(contact.username) !userStore.IsAskedFriendByUsername(contact.username)
@@ -538,9 +538,9 @@
<q-item-section>{{ <q-item-section>{{
t('friends.ask_friend') t('friends.ask_friend')
}}</q-item-section> }}</q-item-section>
</q-item> </q-item>-->
<q-item <q-item
v-else-if=" v-if="
!userStore.IsMyFriendByUsername(contact.username) && !userStore.IsMyFriendByUsername(contact.username) &&
userStore.IsAskedFriendByUsername(contact.username) userStore.IsAskedFriendByUsername(contact.username)
" "

View File

@@ -35,6 +35,7 @@ export default defineComponent({
}); });
const handleInput = (value: string | number) => { const handleInput = (value: string | number) => {
// console.log('value', value)
if (value === '⌫') { if (value === '⌫') {
inputValue.value = inputValue.value.slice(0, -1); inputValue.value = inputValue.value.slice(0, -1);
} else if (value === '.' && !inputValue.value.includes('.')) { } else if (value === '.' && !inputValue.value.includes('.')) {
@@ -43,12 +44,14 @@ export default defineComponent({
inputValue.value += value.toString(); inputValue.value += value.toString();
} }
// console.log('inputValue.value', inputValue.value)
// Verifica se inputValue contiene più di due cifre decimali // Verifica se inputValue contiene più di due cifre decimali
const decimalPattern = /^\d+(\.\d{0,2})?$/; // Regex per validare il numero const decimalPattern = /^\d+(\.\d{0,2})?$/; // Regex per validare il numero
const newValue = inputValue.value; const newValue = inputValue.value;
// Se non rispetta il formato, tronca il numero a 2 cifre decimali // Se non rispetta il formato, tronca il numero a 2 cifre decimali
if (!decimalPattern.test(newValue)) { /*if (!decimalPattern.test(newValue)) {
// Se troviamo un punto decimale, manteniamo solo le prime 2 cifre // Se troviamo un punto decimale, manteniamo solo le prime 2 cifre
const parts = newValue.split('.'); // Dividi il numero in parte intera e decimale const parts = newValue.split('.'); // Dividi il numero in parte intera e decimale
if (parts.length > 1) { if (parts.length > 1) {
@@ -58,7 +61,7 @@ export default defineComponent({
// Nessuna parte decimale, quindi usa solo la parte intera // Nessuna parte decimale, quindi usa solo la parte intera
inputValue.value = parts[0]; inputValue.value = parts[0];
} }
} }*/
emit('update:modelValue', inputValue.value); emit('update:modelValue', inputValue.value);
}; };

View File

@@ -10,7 +10,7 @@
class="balance-text-saldo" class="balance-text-saldo"
>Saldo: >Saldo:
</span> </span>
{{ currentBalance > 0 ? '+' : '' }}{{ currentBalance }} RIS {{ currentBalance > 0 ? '+' : '' }}{{ currentBalance.toFixed(2) }} RIS
</span> </span>
</div> </div>
@@ -47,14 +47,14 @@
:style="{ '--zero-position': zeroPosition + '%' }" :style="{ '--zero-position': zeroPosition + '%' }"
> >
<div class="marker min-marker"> <div class="marker min-marker">
<span class="marker-value">{{ minLimit }}</span> <span class="marker-value">{{ minLimit.toFixed(2) }}</span>
<span class="marker-label">Fido</span> <span class="marker-label">Fido</span>
</div> </div>
<div class="marker zero-marker-label"> <div class="marker zero-marker-label">
<span class="marker-value">0</span> <span class="marker-value">0</span>
</div> </div>
<div class="marker max-marker"> <div class="marker max-marker">
<span class="marker-value">+{{ maxLimit }}</span> <span class="marker-value">+{{ maxLimit.toFixed(2) }}</span>
<span class="marker-label">Max</span> <span class="marker-label">Max</span>
</div> </div>
</div> </div>
@@ -69,7 +69,7 @@
color="negative" color="negative"
/> />
<span class="availability-text"> <span class="availability-text">
Puoi dare ancora: <strong>{{ canGive }} RIS</strong> Puoi dare ancora: <strong>{{ canGive.toFixed(2) }} RIS</strong>
</span> </span>
</div> </div>
<div class="availability-item"> <div class="availability-item">
@@ -79,7 +79,7 @@
color="positive" color="positive"
/> />
<span class="availability-text"> <span class="availability-text">
Puoi ricevere: <strong>{{ canReceive }} RIS</strong> Puoi ricevere: <strong>{{ canReceive.toFixed(2) }} RIS</strong>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,341 +1,575 @@
.c-send-coins { // Variables
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); $primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh; $orange-gradient: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
padding-bottom: 80px; $ris-color: #ff5500;
$border-radius-lg: 16px;
$border-radius-md: 12px;
$border-radius-sm: 8px;
$shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
$shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
// Main Dialog
.send-coins-dialog {
border-radius: $border-radius-lg $border-radius-lg 0 0;
overflow: hidden;
max-width: 420px;
width: 100%;
@media (min-width: 600px) {
border-radius: $border-radius-lg;
max-height: 90vh;
}
} }
.page-header { .mobile-fullheight {
height: 100vh;
max-height: 100vh;
display: flex;
flex-direction: column;
border-radius: 0;
}
// Header
.dialog-header {
position: relative;
}
.header-gradient {
background: $primary-gradient;
padding: 12px 16px 14px;
position: relative;
}
.header-top-bar {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 12px 16px; justify-content: space-between;
background: rgba(255, 255, 255, 0.1); margin-bottom: 12px;
}
.close-btn {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
.back-btn { &:hover {
color: white; background: rgba(255, 255, 255, 0.25);
margin-right: 12px;
}
.header-title {
font-size: 20px;
font-weight: 600;
color: white;
} }
} }
.content-section { .header-title-wrapper {
padding: 20px 16px; display: flex;
align-items: center;
gap: 10px;
} }
.section-title { .ris-coin-icon {
font-size: 24px; width: 28px;
height: 28px;
border-radius: 50%;
background: $orange-gradient;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.4);
.ris-logo {
width: 18px;
height: 18px;
object-fit: contain;
}
.coin-symbol {
color: white;
font-weight: 700; font-weight: 700;
color: white; font-size: 12px;
margin-bottom: 20px; }
} }
.section-subtitle { .header-title {
font-size: 16px; color: white;
font-size: 17px;
font-weight: 600; font-weight: 600;
color: rgba(255, 255, 255, 0.9); }
// Balance Card - Compatto
.balance-card {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(20px);
border-radius: $border-radius-sm;
padding: 10px 14px;
}
.balance-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.balance-main {
display: flex;
flex-direction: column;
gap: 2px;
}
.balance-label {
color: rgba(255, 255, 255, 0.7);
font-size: 11px;
}
.balance-value {
color: white;
font-size: 20px;
font-weight: 700;
line-height: 1.2;
.balance-symbol {
font-size: 14px;
font-weight: 500;
opacity: 0.9;
}
}
.balance-fido {
text-align: right;
}
.fido-label {
display: block;
color: rgba(255, 255, 255, 0.7);
font-size: 10px;
}
.fido-value {
color: #4ade80;
font-size: 14px;
font-weight: 600;
}
// Content
.dialog-content {
padding: 14px 16px;
flex: 1;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
}
// Section Block - Più compatto
.section-block {
margin-bottom: 12px; margin-bottom: 12px;
} }
.search-input { .section-label {
margin-bottom: 16px; display: block;
color: #6b7280;
font-size: 12px;
font-weight: 500;
margin-bottom: 6px;
}
// Modern Select
.modern-select {
:deep(.q-field__control) { :deep(.q-field__control) {
background: rgba(255, 255, 255, 0.95); border-radius: $border-radius-sm;
border-radius: 12px; background: #f9fafb;
border: 1.5px solid #e5e7eb;
min-height: 40px;
transition: all 0.2s ease;
&:hover {
border-color: #d1d5db;
} }
}
.circuit-selector {
margin-bottom: 24px;
}
.circuit-select {
:deep(.q-field__control) {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
}
}
.user-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.user-item {
display: flex;
align-items: center;
padding: 12px;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
&:active {
transform: scale(0.98);
background: rgba(255, 255, 255, 0.85);
} }
.user-info { :deep(.q-field__control-container) {
flex: 1;
margin-left: 12px;
} }
.user-name { :deep(.q-field--focused .q-field__control) {
font-size: 16px; border-color: #667eea;
font-weight: 600; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
color: #1a1a1a;
} }
.user-username { :deep(.q-field__native) {
font-weight: 500;
font-size: 14px; font-size: 14px;
color: #666; color: #374151;
}
:deep(.q-field__label) {
font-size: 13px;
} }
} }
.selected-user-card { .modern-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: #f9fafb;
border: 1.5px solid #e5e7eb;
min-height: 40px;
}
}
// Recipient Card - Compatto
.recipient-card {
background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
border: 1.5px solid #ddd6fe;
border-radius: $border-radius-md;
padding: 10px 12px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 16px; justify-content: space-between;
background: rgba(255, 255, 255, 0.95); gap: 10px;
border-radius: 16px; }
margin-bottom: 20px;
.user-info { .recipient-content {
margin-left: 12px;
flex: 1; flex: 1;
} min-width: 0;
}
.circuit-badge { .recipient-view {
display: inline-block; :deep(.q-item) {
padding: 0;
min-height: auto;
}
}
.circuit-badge {
color: white;
font-size: 11px;
font-weight: 600;
padding: 4px 10px;
border-radius: 16px;
flex-shrink: 0;
}
// Amount Input Row - Compatto
.amount-input-row {
cursor: pointer;
}
.amount-input {
:deep(.q-field__control) {
border-radius: $border-radius-sm;
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
border: none;
min-height: 48px;
padding: 4px 12px; padding: 4px 12px;
background: rgba(103, 126, 234, 0.1); }
color: #667eea;
border-radius: 12px; :deep(.q-field__native) {
font-size: 12px; color: white !important;
font-weight: 600; font-size: 22px !important;
margin-top: 4px; font-weight: 700 !important;
text-align: center;
}
:deep(.q-field__prepend) {
padding-right: 0;
}
:deep(.q-field__append) {
padding-left: 0;
} }
} }
.circuit-list { .currency-symbol {
display: flex; color: rgba(255, 255, 255, 0.5);
flex-direction: column; font-size: 18px;
gap: 12px; font-weight: 400;
margin-bottom: 24px;
} }
.circuit-item { .coin-badge {
display: flex; width: 32px;
align-items: center; height: 32px;
padding: 16px; border-radius: 20%;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
&.selected {
background: rgba(103, 126, 234, 0.15);
border: 2px solid #667eea;
}
&:active {
transform: scale(0.98);
}
.circuit-info {
flex: 1;
}
.circuit-name {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 4px;
}
.circuit-balance {
font-size: 14px;
color: #666;
}
}
.amount-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
padding: 20px;
margin-bottom: 20px;
}
.amount-display {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 24px; color: white;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
margin-bottom: 16px;
cursor: pointer;
position: relative;
.currency {
font-size: 24px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
margin-right: 8px;
}
.amount-value {
font-size: 42px;
font-weight: 700; font-weight: 700;
color: white;
}
.keyboard-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: white;
}
}
.limits-info {
display: flex;
justify-content: space-around;
padding: 12px 0;
margin-bottom: 16px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.limit-item {
display: flex;
flex-direction: column;
align-items: center;
.limit-label {
font-size: 12px; font-size: 12px;
color: #666; box-shadow: 0 2px 8px rgba(255, 107, 53, 0.3);
margin-bottom: 4px; }
}
.limit-value { .keyboard-btn {
font-size: 16px; color: rgba(255, 255, 255, 0.7);
font-weight: 600;
color: #1a1a1a; &:hover {
color: white;
} }
} }
.description-input { // Banners
.warning-banner {
background: $orange-gradient;
color: white;
font-weight: 500;
font-size: 13px;
}
.error-banner {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
color: white;
font-weight: 500;
font-size: 13px;
}
// Note Input - Compatto
.modern-textarea {
:deep(.q-field__control) { :deep(.q-field__control) {
border-radius: 12px; border-radius: $border-radius-sm;
background: #f9fafb;
border: 1.5px solid #e5e7eb;
transition: all 0.2s ease;
} }
}
.action-btn { :deep(.q-field--focused .q-field__control) {
border-radius: 12px; border-color: #667eea;
padding: 12px; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
font-size: 16px;
font-weight: 600;
text-transform: none;
}
.confirmation-card {
border-radius: 24px;
max-width: 400px;
width: 90vw;
}
.confirmation-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
.header-title {
font-size: 20px;
font-weight: 600;
color: white;
text-align: center;
} }
}
.confirmation-content { :deep(.q-field__native) {
padding: 24px; resize: none;
text-align: center;
}
.amount-label {
font-size: 14px; font-size: 14px;
color: #666; }
margin-bottom: 8px;
}
.amount-display-large { :deep(.q-field__counter) {
font-size: 36px; color: #9ca3af;
font-weight: 700; font-size: 11px;
color: #1a1a1a; }
margin-bottom: 24px;
}
.users-row { :deep(.q-field__label) {
display: flex; font-size: 13px;
align-items: center;
justify-content: center;
gap: 24px;
margin-bottom: 32px;
}
.user-col {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.user-name-small {
font-size: 14px;
color: #666;
}
.verify-title {
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 12px;
}
.verify-text {
font-size: 15px;
color: #666;
line-height: 1.5;
.amount-highlight {
color: #667eea;
font-weight: 700;
} }
} }
.confirmation-actions { // Actions
.dialog-actions {
display: flex; display: flex;
gap: 12px; gap: 10px;
padding: 16px 24px; padding: 12px 16px;
background: white;
border-top: 1px solid #f3f4f6;
} }
.cancel-btn, .fixed-bottom-actions {
.confirm-btn { position: fixed;
flex: 1; bottom: 0;
border-radius: 12px; left: 0;
padding: 12px; right: 0;
font-size: 16px; z-index: 100;
font-weight: 600; box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
text-transform: none; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
} }
.cancel-btn { .cancel-btn {
background: rgba(0, 0, 0, 0.05); flex: 1;
background: #f3f4f6;
color: #6b7280;
font-weight: 600;
font-size: 14px;
padding: 10px 16px;
border-radius: $border-radius-sm;
text-transform: none;
&:hover {
background: #e5e7eb;
}
}
.send-btn {
flex: 2;
background: $orange-gradient;
color: white;
font-weight: 600;
font-size: 14px;
padding: 10px 20px;
border-radius: $border-radius-sm;
text-transform: none;
box-shadow: 0 2px 8px rgba(249, 115, 22, 0.3);
transition: all 0.2s ease;
&:hover:not(.btn-disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
}
&.btn-disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.send-btn-text {
margin-right: 6px;
}
.send-btn-icon {
width: 20px;
height: 20px;
object-fit: contain;
}
// Keyboard Dialog
.keyboard-dialog {
width: 100%;
max-width: 420px;
border-radius: $border-radius-lg $border-radius-lg 0 0;
margin: 0;
}
.keyboard-header {
background: #f9fafb;
border-bottom: 1px solid #e5e7eb;
padding: 12px 16px;
}
.keyboard-header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.keyboard-title {
color: #6b7280;
font-weight: 500;
font-size: 14px;
}
.done-btn {
font-weight: 600;
font-size: 14px;
}
.keyboard-display {
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
border-radius: $border-radius-sm;
padding: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.keyboard-amount {
color: white;
font-size: 28px;
font-weight: 700;
}
.keyboard-coin-badge {
width: 32px;
height: 32px;
border-radius: 20%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 12px;
}
.keyboard-section {
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)) !important;
}
// Dark mode support
.body--dark {
.send-coins-dialog {
background: #1f2937;
}
.dialog-content {
background: #1f2937;
}
.section-label {
color: #9ca3af;
}
.modern-select,
.modern-input {
:deep(.q-field__control) {
background: #374151;
border-color: #4b5563;
}
:deep(.q-field__native) {
color: #f3f4f6;
}
}
.recipient-card {
background: linear-gradient(135deg, #374151 0%, #4b5563 100%);
border-color: #6b7280;
}
.modern-textarea {
:deep(.q-field__control) {
background: #374151;
border-color: #4b5563;
}
:deep(.q-field__native) {
color: #f3f4f6;
}
}
.dialog-actions {
background: #1f2937;
border-top-color: #374151;
}
.fixed-bottom-actions {
background: #1f2937;
}
.cancel-btn {
background: #374151;
color: #d1d5db;
}
.keyboard-dialog {
background: #1f2937;
}
.keyboard-header {
background: #374151;
border-color: #4b5563;
}
.keyboard-title {
color: #d1d5db;
}
}
// Responsive - Ultra compatto per mobile piccoli
@media (max-width: 360px) {
.header-gradient {
padding: 10px 12px 12px;
}
.balance-value {
font-size: 18px;
}
.dialog-content {
padding: 12px;
}
.section-block {
margin-bottom: 10px;
}
.amount-input {
:deep(.q-field__native) {
font-size: 20px !important;
}
}
} }

View File

@@ -1,43 +1,47 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { computed, defineComponent, onMounted, ref, watch } from 'vue' import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import type { IAccount, ICircuit, IMyGroup, ISendCoin, IUserFields } from '../../model'; import type { IAccount, ICircuit, IMyGroup, ISendCoin, IUserFields } from '../../model';
import { IOperators, ISpecialField } from '../../model' import { IOperators, ISpecialField } from '../../model';
import { tools } from '@tools' import { tools } from '@tools';
import { CSaldo } from '@/components/CSaldo' import { CSaldo } from '@/components/CSaldo';
import { useUserStore } from '@store/UserStore' import { useUserStore } from '@store/UserStore';
import { useCircuitStore } from '@store/CircuitStore' import { useCircuitStore } from '@store/CircuitStore';
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n';
import { CNumericKeyboard } from '@/components/CNumericKeyboard' import { CNumericKeyboard } from '@/components/CNumericKeyboard';
import { CMyUserOnlyView } from '@/components/CMyUserOnlyView' import { CMyUserOnlyView } from '@/components/CMyUserOnlyView';
import { CMyGroupOnlyView } from '@/components/CMyGroupOnlyView' import { CMyGroupOnlyView } from '@/components/CMyGroupOnlyView';
import { CCheckCircuitsEnabled } from '@/components/CCheckCircuitsEnabled' import { CCheckCircuitsEnabled } from '@/components/CCheckCircuitsEnabled';
import { costanti } from '@costanti' import { costanti } from '@costanti';
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router';
import { shared_consts } from '@/common/shared_vuejs' import { shared_consts } from '@/common/shared_vuejs';
export default defineComponent({ export default defineComponent({
name: 'CSendCoins', name: 'CSendCoins',
emits: ['close', 'showed'], emits: ['close', 'showed'],
props: { props: {
loadprofile: {
type: Boolean,
default: false,
},
showprop: { showprop: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
circuitname: { circuitname: {
type: String, type: String,
default: '' default: '',
}, },
qtydefault: { qtydefault: {
type: String, type: String,
required: false, required: false,
default: '' default: '',
}, },
to_user: { to_user: {
type: Object as PropType<IUserFields>, type: Object as PropType<IUserFields>,
required: false, required: false,
default: null default: null,
}, },
to_group: { to_group: {
type: Object as PropType<IMyGroup>, type: Object as PropType<IMyGroup>,
@@ -65,99 +69,133 @@ export default defineComponent({
default: '', default: '',
}, },
}, },
components: { CSaldo, CMyUserOnlyView, CMyGroupOnlyView, CCheckCircuitsEnabled, CNumericKeyboard }, components: {
CSaldo,
CMyUserOnlyView,
CMyGroupOnlyView,
CCheckCircuitsEnabled,
CNumericKeyboard,
},
setup(props, { emit }) { setup(props, { emit }) {
const $q = useQuasar() const $q = useQuasar();
const { t } = useI18n() const { t } = useI18n();
const showpage = ref(false) const showpage = ref(false);
const userStore = useUserStore() const userStore = useUserStore();
const circuitStore = useCircuitStore() const circuitStore = useCircuitStore();
const $router = useRouter() const $router = useRouter();
const from_username = ref(userStore.my.username) const to_user_real = ref(<IUserFields>{});
const from_groupname = ref('')
const from_contocom = ref('')
const circuitsel = ref('')
const qty = ref(<string | number>'')
const causal = ref('')
const loading = ref(false)
const visubanner = ref(true)
const bothcircuits = ref(<any>[])
const showProvinceToSelect = ref(false) const from_username = ref(userStore.my.username);
const from_groupname = ref('');
const from_contocom = ref('');
const circuitsel = ref('');
const qty = ref(<string | number>'');
const causal = ref('');
const loading = ref(false);
const visubanner = ref(true);
const bothcircuits = ref(<any>[]);
const groupSel = ref(<IMyGroup | null | undefined>null) const showProvinceToSelect = ref(false);
const showKeyboard = ref(false);
const datasaved = ref(<any>null) const groupSel = ref(<IMyGroup | null | undefined>null);
const step = ref(0)
const sendCoinDialog = ref(null)
const circuittoload = ref(<ICircuit | undefined>undefined) const datasaved = ref(<any>null);
const circuitloaded = ref(<ICircuit>{}) const step = ref(0);
const circuitdest = ref(<ICircuit | undefined>undefined) const sendCoinDialog = ref(null);
const accountloaded = ref(<IAccount | undefined | null>undefined)
const accountdest = ref(<IAccount | undefined>undefined)
const remainingCoins = ref(0)
const maxsendable = ref(0)
const numstep = ref(0)
const arrTypesAccounts = ref(<any>[])
const tipoConto = ref(shared_consts.AccountType.USER)
const priceLabel = computed(() => circuitloaded.value ? `${qty.value} ` + circuitloaded.value.symbol : '') const circuittoload = ref(<ICircuit | undefined>undefined);
const arrayMarkerLabel = ref(<any>[]) const circuitloaded = ref(<ICircuit>{});
const circuitdest = ref(<ICircuit | undefined>undefined);
const accountloaded = ref(<IAccount | undefined | null>undefined);
const accountdest = ref(<IAccount | undefined>undefined);
const remainingCoins = ref(0);
const maxsendable = ref(0);
const numstep = ref(0);
const arrTypesAccounts = ref(<any>[]);
const tipoConto = ref(shared_consts.AccountType.USER);
const qtyRef = ref(<any>null) const priceLabel = computed(() =>
const causalRef = ref(<any>null) circuitloaded.value ? `${qty.value} ` + circuitloaded.value.symbol : ''
);
const arrayMarkerLabel = ref(<any>[]);
const groupsListAdmin = ref(<IMyGroup[]>[]) const qtyRef = ref(<any>null);
const causalRef = ref(<any>null);
const arrGroupsList = ref(<any[]>[]) const groupsListAdmin = ref(<IMyGroup[]>[]);
watch(() => circuitsel.value, (newval, oldval) => { const arrGroupsList = ref(<any[]>[]);
tools.setCookie(tools.CIRCUIT_USE, newval)
aggiorna()
})
watch(() => tipoConto.value, (newval, oldval) => { watch(
() => circuitsel.value,
(newval, oldval) => {
tools.setCookie(tools.CIRCUIT_USE, newval);
aggiorna(true);
}
);
watch(
() => tipoConto.value,
(newval, oldval) => {
if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) { if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) {
if (arrGroupsList.value.length >= 1) if (arrGroupsList.value.length >= 1)
from_groupname.value = arrGroupsList.value[0].value from_groupname.value = arrGroupsList.value[0].value;
} }
tools.setCookie(tools.COOK_TIPOCONTO, tipoConto.value.toString()) tools.setCookie(tools.COOK_TIPOCONTO, tipoConto.value.toString());
aggiorna() aggiorna(true);
}) }
);
watch(() => from_groupname.value, (newval, oldval) => { watch(
aggiorna() () => from_groupname.value,
}) (newval, oldval) => {
aggiorna(true);
}
);
watch(() => from_username.value, (newval, oldval) => { watch(
aggiorna() () => from_username.value,
}) (newval, oldval) => {
aggiorna(true);
}
);
watch(() => props.showprop, (newval, oldval) => { watch(
console.log('props.showprop', props.showprop, newval) () => props.showprop,
showpage.value = newval (newval, oldval) => {
}) showpage.value = newval;
}
);
async function aggiorna() { async function aggiorna(load: boolean = false) {
if (load) {
inizio_caricamento();
}
groupSel.value = null groupSel.value = null;
from_contocom.value = '' from_contocom.value = '';
accountloaded.value = null accountloaded.value = null;
if (!circuittoload.value || (circuittoload.value && circuittoload.value.name !== circuitsel.value)) { if (
circuittoload.value = circuitStore.listcircuits.find((rec: ICircuit) => rec.name === circuitsel.value) !circuittoload.value ||
(circuittoload.value && circuittoload.value.name !== circuitsel.value)
) {
circuittoload.value = circuitStore.listcircuits.find(
(rec: ICircuit) => rec.name === circuitsel.value
);
if (circuittoload.value) { if (circuittoload.value) {
loading.value = true loading.value = true;
await userStore.loadCircuit(circuittoload.value.path, '').then(({ data, status }: { data: any, status: number }) => { await userStore
datasaved.value = data .loadCircuit(circuittoload.value.path, '')
}) .then(({ data, status }: { data: any; status: number }) => {
datasaved.value = data;
});
} }
} }
@@ -165,201 +203,232 @@ export default defineComponent({
try { try {
arrTypesAccounts.value = [ arrTypesAccounts.value = [
{ {
label: t('circuit.personale') + ' ' + from_username.value, label: '👤 ' + t('circuit.personale') + ' - ' + from_username.value,
value: shared_consts.AccountType.USER, value: shared_consts.AccountType.USER,
}, },
] ];
if (datasaved.value.circuit) { if (datasaved.value.circuit) {
circuitloaded.value = datasaved.value.circuit circuitloaded.value = datasaved.value.circuit;
if (tipoConto.value === shared_consts.AccountType.USER) { if (tipoConto.value === shared_consts.AccountType.USER) {
accountloaded.value = userStore.getAccountByCircuitId(circuitloaded.value._id) accountloaded.value = userStore.getAccountByCircuitId(
circuitloaded.value._id
);
} else if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) { } else if (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO) {
groupSel.value = userStore.my.profile.manage_mygroups.find((group: IMyGroup) => from_groupname.value === group.groupname) groupSel.value = userStore.my.profile.manage_mygroups.find(
(group: IMyGroup) => from_groupname.value === group.groupname
);
accountloaded.value = groupSel.value ? groupSel.value.account : null accountloaded.value = groupSel.value ? groupSel.value.account : null;
} else if (tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT) { } else if (tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT) {
from_contocom.value = circuitloaded.value.path from_contocom.value = circuitloaded.value.path;
accountloaded.value = circuitloaded.value ? circuitloaded.value.account : null accountloaded.value = circuitloaded.value
? circuitloaded.value.account
: null;
} }
groupsListAdmin.value = userStore.GroupsListWhereIAmAdminInTheCircuit(circuitloaded.value.name) groupsListAdmin.value = userStore.GroupsListWhereIAmAdminInTheCircuit(
// console.log('groupsListAdmin.value', groupsListAdmin.value) circuitloaded.value.name
);
arrGroupsList.value = [] arrGroupsList.value = [];
if (groupsListAdmin.value) { if (groupsListAdmin.value) {
for (const group of groupsListAdmin.value) { for (const group of groupsListAdmin.value) {
let aggiungi = true let aggiungi = true;
if (props.to_group && props.to_group.groupname === group.groupname) if (props.to_group && props.to_group.groupname === group.groupname)
aggiungi = false aggiungi = false;
if (aggiungi) if (aggiungi)
arrGroupsList.value.push({ label: group.groupname, value: group.groupname }); arrGroupsList.value.push({
label: group.groupname,
value: group.groupname,
});
} }
} }
if (arrGroupsList.value.length > 0) { if (arrGroupsList.value.length > 0) {
arrTypesAccounts.value.push(
{
label: t('circuit.conticollettivi'),
value: shared_consts.AccountType.CONTO_DI_GRUPPO,
})
}
if (tools.iCanSendCoinsSuperUserCircuit(circuitsel.value) && (!props.to_contocom)) {
arrTypesAccounts.value.push({ arrTypesAccounts.value.push({
label: t('circuit.contocom'), label: '👥 ' + t('circuit.conticollettivi'),
value: shared_consts.AccountType.COMMUNITY_ACCOUNT, value: shared_consts.AccountType.CONTO_DI_GRUPPO,
}) });
} }
// if (tools.iAmAdminCircuit(circuitloaded.value.name)) if (
// arrGroupsList.value.push({ label: circuitloaded.value.name, value: circuitloaded.value.path }); tools.iCanSendCoinsSuperUserCircuit(circuitsel.value) &&
!props.to_contocom
) {
arrTypesAccounts.value.push({
label: '🏛️ ' + t('circuit.contocom'),
value: shared_consts.AccountType.COMMUNITY_ACCOUNT,
});
}
// accountdest.value = userStore.getAccountByCircuitId(circuitloaded.value._id)
if (accountloaded.value) { if (accountloaded.value) {
remainingCoins.value = circuitStore.getRemainingCoinsToSend(accountloaded.value) remainingCoins.value = circuitStore.getRemainingCoinsToSend(
accountloaded.value
);
if (accountloaded.value.saldo > 0) { if (accountloaded.value.saldo > 0) {
maxsendable.value = accountloaded.value.saldo + accountloaded.value.fidoConcesso maxsendable.value =
accountloaded.value.saldo + accountloaded.value.fidoConcesso;
} else { } else {
maxsendable.value = accountloaded.value.fidoConcesso maxsendable.value = accountloaded.value.fidoConcesso;
} }
if (remainingCoins.value < 10) { if (remainingCoins.value < 10) {
remainingCoins.value = 10 remainingCoins.value = 10;
} }
numstep.value = Math.round(maxsendable.value / 10) numstep.value = Math.round(maxsendable.value / 10);
if (numstep.value < 1) { if (numstep.value < 1) {
numstep.value = 1 numstep.value = 1;
} }
const quanti = [...Array(20).keys()].map(i => i + 1) const quanti = [...Array(20).keys()].map((i) => i + 1);
for (const ind of quanti) { for (const ind of quanti) {
const valuenorm = ind * numstep.value const valuenorm = ind * numstep.value;
const value = ind * numstep.value const value = ind * numstep.value;
if (value > remainingCoins.value) { if (value > remainingCoins.value) {
break break;
} else { } else {
const label = valuenorm.toString() const label = valuenorm.toString();
arrayMarkerLabel.value.push({ value, label }) arrayMarkerLabel.value.push({ value, label });
} }
} }
} }
} }
} finally { } finally {
loading.value = false loading.value = false;
if (load) {
fine_caricamento();
} }
} }
} }
}
function inizio_caricamento() {
$q.loading.show({
message: 'Caricamento in corso...',
spinnerColor: 'white',
backgroundColor: 'primary',
messageColor: 'white',
});
}
function fine_caricamento() {
$q.loading.hide();
}
async function mounted() { async function mounted() {
inizio_caricamento();
loading.value = true to_user_real.value = props.to_user;
loading.value = true;
arrTypesAccounts.value = [ arrTypesAccounts.value = [
{ {
label: t('circuit.personale') + ' ' + from_username.value, label: '👤 ' + t('circuit.personale') + ' - ' + from_username.value,
value: shared_consts.AccountType.USER, value: shared_consts.AccountType.USER,
}, },
] ];
let salvatoprec = tools.getCookie(tools.COOK_TIPOCONTO, -2, true) let salvatoprec = tools.getCookie(tools.COOK_TIPOCONTO, -2, true);
// .... if (to_user_real.value) {
if (props.to_user) { if (props.loadprofile) {
console.log('user', props.to_user) await userStore
bothcircuits.value = userStore.getMyCircuitsInCommonByUser(props.to_user) .loadUserProfile({ username: to_user_real.value.username })
.then((ris) => {
to_user_real.value = ris;
});
}
bothcircuits.value = userStore.getMyCircuitsInCommonByUser(to_user_real.value);
if (props.circuitname) { if (props.circuitname) {
circuitsel.value = props.circuitname circuitsel.value = props.circuitname;
} else { } else {
const circcookie = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]) const circcookie = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]);
if (circcookie && bothcircuits.value.findIndex((circ: ICircuit) => circ.name === circcookie) >= 0) if (
circuitsel.value = circcookie circcookie &&
bothcircuits.value.findIndex((circ: ICircuit) => circ.name === circcookie) >=
0
)
circuitsel.value = circcookie;
} }
if (bothcircuits.value && bothcircuits.value.find((name: any) => name !== circuitsel.value)) { if (
circuitsel.value = bothcircuits.value[0] bothcircuits.value &&
bothcircuits.value.find((name: any) => name !== circuitsel.value)
) {
circuitsel.value = bothcircuits.value[0];
} }
if (props.qtydefault) if (props.qtydefault) qty.value = props.qtydefault;
qty.value = props.qtydefault
if (props.sendRIS) { if (props.sendRIS) {
if (props.sendRIS !== '0') if (props.sendRIS !== '0') qty.value = props.sendRIS;
qty.value = props.sendRIS
} }
await aggiorna() await aggiorna();
// Se quello scelto c'è ancora sulla lista, allora lo imposto if (
arrTypesAccounts.value.findIndex((rec: any) => rec.value === salvatoprec) >= 0
if (arrTypesAccounts.value.findIndex((rec: any) => rec.value === salvatoprec) >= 0) { ) {
tipoConto.value = salvatoprec tipoConto.value = salvatoprec;
} else { } else {
tipoConto.value = shared_consts.AccountType.USER tipoConto.value = shared_consts.AccountType.USER;
} }
// tipoConto.value = tools.getCookie(tools.COOK_TIPOCONTO, shared_consts.AccountType.USER, true) showpage.value = true;
fine_caricamento();
showpage.value = true
} }
if (props.to_group) { if (props.to_group) {
console.log('group', props.to_group) bothcircuits.value = userStore.getMyCircuitsInCommonByGroup(props.to_group);
bothcircuits.value = userStore.getMyCircuitsInCommonByGroup(props.to_group)
console.log('bothcircuits', bothcircuits.value)
if (props.circuitname) { if (props.circuitname) {
circuitsel.value = props.circuitname circuitsel.value = props.circuitname;
} else { } else {
circuitsel.value = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]) circuitsel.value = tools.getCookie(tools.CIRCUIT_USE, bothcircuits.value[0]);
} }
if (!userStore.IsMyCircuitByName(circuitsel.value)) { if (!userStore.IsMyCircuitByName(circuitsel.value)) {
circuitsel.value = bothcircuits.value[0] circuitsel.value = bothcircuits.value[0];
} }
await aggiorna() await aggiorna();
showpage.value = true showpage.value = true;
} }
if (props.to_contocom) { if (props.to_contocom) {
bothcircuits.value = userStore.getMyCircuits() bothcircuits.value = userStore.getMyCircuits();
console.log('to_contocom', props.to_contocom) circuitsel.value = props.circuitname;
circuitsel.value = props.circuitname
if (!userStore.IsMyCircuitByName(circuitsel.value)) { if (!userStore.IsMyCircuitByName(circuitsel.value)) {
circuitsel.value = bothcircuits.value[0] circuitsel.value = bothcircuits.value[0];
} }
await aggiorna() await aggiorna();
showpage.value = true showpage.value = true;
} }
loading.value = false loading.value = false;
emit('showed') emit('showed');
} }
function hide() { function hide() {
emit('close', true) emit('close', true);
showpage.value = false showpage.value = false;
} }
function sendCoin() { function sendCoin() {
console.log('sendcoin', qty.value, props.to_group ? props.to_group.groupname : (props.to_user ? props.to_user.username : props.to_contocom)) const ok =
(to_user_real.value && to_user_real.value.username) ||
const ok = (props.to_user && props.to_user.username) ||
(props.to_group && props.to_group.groupname) || (props.to_group && props.to_group.groupname) ||
(props.to_contocom) props.to_contocom;
if (ok && qty.value && circuitloaded.value) { if (ok && qty.value && circuitloaded.value) {
const myrecsendcoin: ISendCoin = { const myrecsendcoin: ISendCoin = {
@@ -371,122 +440,104 @@ export default defineComponent({
causal: causal.value, causal: causal.value,
symbol: circuitloaded.value.symbol, symbol: circuitloaded.value.symbol,
causalDest: props.causalDest, causalDest: props.causalDest,
} };
myrecsendcoin.groupdest = props.to_group ? props.to_group.groupname : '' myrecsendcoin.groupdest = props.to_group ? props.to_group.groupname : '';
myrecsendcoin.contoComDest = props.to_contocom myrecsendcoin.contoComDest = props.to_contocom;
myrecsendcoin.grouporig = tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO ? from_groupname.value : '' myrecsendcoin.grouporig =
myrecsendcoin.contoComOrig = tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT ? from_contocom.value : '' tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO
? from_groupname.value
myrecsendcoin.dest = props.to_user ? props.to_user.username : '' : '';
myrecsendcoin.contoComOrig =
tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT
? from_contocom.value
: '';
myrecsendcoin.dest = to_user_real.value ? to_user_real.value.username : '';
if (myrecsendcoin) { if (myrecsendcoin) {
tools.sendCoinsByCircuit($q, $router, circuitloaded.value, myrecsendcoin) tools
.sendCoinsByCircuit($q, $router, circuitloaded.value, myrecsendcoin)
.then((ris: any) => { .then((ris: any) => {
if (ris) { if (ris) {
showpage.value = false showpage.value = false;
} }
}) });
} }
} }
} }
function ifNextCheck(actualstep: number) { function ifNextCheck(actualstep: number) {
let fase1ok = false;
let fase1ok = false
if (circuitloaded.value && !!circuitloaded.value._id) { if (circuitloaded.value && !!circuitloaded.value._id) {
fase1ok = !( fase1ok = !(
!circuitloaded.value.transactionsEnabled || !circuitloaded.value.transactionsEnabled ||
(tipoConto.value === shared_consts.AccountType.USER && (tipoConto.value === shared_consts.AccountType.USER &&
props.to_user && to_user_real.value &&
from_username.value === props.to_user.username) || from_username.value === to_user_real.value.username) ||
(tipoConto.value === (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO &&
shared_consts.AccountType.CONTO_DI_GRUPPO &&
!from_groupname.value) || !from_groupname.value) ||
(tipoConto.value === (tipoConto.value === shared_consts.AccountType.CONTO_DI_GRUPPO &&
shared_consts.AccountType.CONTO_DI_GRUPPO &&
props.to_group && props.to_group &&
from_groupname.value && from_groupname.value &&
props.to_group.groupname === from_groupname.value) || props.to_group.groupname === from_groupname.value) ||
(tipoConto.value === (tipoConto.value === shared_consts.AccountType.COMMUNITY_ACCOUNT &&
shared_consts.AccountType.COMMUNITY_ACCOUNT &&
!from_contocom.value) !from_contocom.value)
) );
}
if (actualstep === 0) {
return fase1ok
} else if (actualstep === 1) {
return fase1ok && checkRisValid()
} else if (actualstep === 2) {
return fase1ok && checkRisValid()
} }
return fase1ok && checkRisValid() && causal.value;
} }
function checkRisValid() { function checkRisValid() {
return qty.value ? qty.value && getQty() >= 0.01 && accountloaded.value return qty.value
&& getQty() <= circuitStore.getRemainingCoinsToSend(accountloaded.value) : false ? qty.value &&
getQty() >= 0.01 &&
accountloaded.value &&
getQty() <= circuitStore.getRemainingCoinsToSend(accountloaded.value)
: false;
} }
function getQty(): number { function getQty(): number {
let myqty: number | null = null let myqty: number | null = null;
try { try {
if (qty.value) { if (qty.value) {
myqty = parseFloat(String(qty.value)) myqty = parseFloat(String(qty.value));
} }
} catch (e) { } catch (e) {
return 0 return 0;
} }
return myqty ? myqty : 0 return myqty ? myqty : 0;
} }
function getTitle(step: number) { function getTitle(step: number) {
if (step === 0) { if (step === 0) {
return 'Circuito' return 'Circuito';
} else if (step === 1) { } else if (step === 1) {
return 'Quantità' return 'Quantità';
} else if (step === 2) { } else if (step === 2) {
return 'Causale' return 'Causale';
} }
} }
function getIcon(step: number) { function getIcon(step: number) {
if (step === 0) { if (step === 0) {
return 'circuit' return 'circuit';
} else if (step === 1) { } else if (step === 1) {
return 'attach_money' return 'attach_money';
} else if (step === 2) { } else if (step === 2) {
return 'description' return 'description';
} }
} }
function clickAvanti(actualstep: number) { function setQty(value: string | number) {
if (actualstep === 0) { qty.value = value;
step.value = 1
} else if (actualstep === 1) {
step.value = 2
} else if (actualstep === 2) {
sendCoin()
}
}
function clickIndietro(actualstep: number) {
if (actualstep === 1) {
step.value = 0
} else if (actualstep === 2) {
step.value = 1
} else if (actualstep === 0) {
hide()
}
} }
onMounted(mounted);
onMounted(mounted)
return { return {
t, t,
@@ -522,12 +573,13 @@ export default defineComponent({
shared_consts, shared_consts,
step, step,
ifNextCheck, ifNextCheck,
clickIndietro,
clickAvanti,
getTitle, getTitle,
getIcon, getIcon,
sendCoinDialog, sendCoinDialog,
visubanner, visubanner,
} showKeyboard,
setQty,
to_user_real,
};
}, },
}) });

View File

@@ -5,375 +5,351 @@
:maximized="$q.screen.lt.sm" :maximized="$q.screen.lt.sm"
@hide="hide" @hide="hide"
@show="qtyRef ? qtyRef.focus() : ''" @show="qtyRef ? qtyRef.focus() : ''"
transition-show="slide-up"
transition-hide="slide-down"
> >
<q-card <q-card
class="dialog_card" class="send-coins-dialog"
style="display: flex; flex-direction: column; height: 100%" :class="{ 'mobile-fullheight': $q.screen.lt.sm }"
> >
<q-bar class="bg-primary text-white"> <!-- Header con gradiente -->
{{ $t("circuit.sendcoins") }} <q-card-section class="dialog-header q-pa-none">
<q-space /> <div class="header-gradient">
<q-btn flat round color="white" icon="close" v-close-popup></q-btn> <!-- Top bar -->
</q-bar> <div class="header-top-bar">
<div class="header-title-wrapper">
<q-card-section> <div class="ris-coin-icon">
<q-stepper <img
v-model="step" v-if="circuitloaded.symbol === 'RIS'"
ref="stepper" src="/images/1ris_rosso_100.png"
color="primary" alt="RIS"
animated class="ris-logo"
class="mystepper" />
> <span v-else class="coin-symbol">{{ circuitloaded.symbol }}</span>
<q-step </div>
:name="0" <span class="header-title">Invia {{ circuitloaded.symbol || 'Crediti' }}</span>
:title="getTitle(0)" </div>
:icon="getIcon(0)" <q-btn
:done="ifNextCheck(0)" flat
> round
<CCheckCircuitsEnabled :to_user="to_user" :to_group="to_group">
</CCheckCircuitsEnabled>
<div v-if="circuitloaded.symbol">
<q-select
v-if="circuitname === ''"
:behavior="$q.platform.is.ios === true ? 'dialog' : 'menu'"
rounded
dense dense
outlined icon="close"
v-model="circuitsel" color="white"
:options="bothcircuits" class="close-btn"
label="Circuito" v-close-popup
> />
</q-select>
<div v-else>{{ circuitname }}</div>
</div> </div>
<!-- Balance Card compatto -->
<div class="balance-card">
<div class="balance-info">
<div class="balance-main">
<span class="balance-label">Saldo disponibile</span>
<span class="balance-value">
{{ accountloaded ? circuitStore.getRemainingCoinsToSend(accountloaded).toFixed(2) : '0.00' }}
<span class="balance-symbol">{{ circuitloaded.symbol }}</span>
</span>
</div>
<div class="balance-fido" v-if="accountloaded?.fidoConcesso > 0">
<span class="fido-label">Fido</span>
<span class="fido-value">+{{ accountloaded.fidoConcesso.toFixed(2) }}</span>
</div>
</div>
</div>
</div>
</q-card-section>
<!-- Content -->
<q-card-section
class="dialog-content scroll"
:style="$q.screen.lt.sm ? 'padding-bottom: 80px;' : ''"
>
<!-- Circuit Check -->
<CCheckCircuitsEnabled
:to_user="to_user_real"
:to_group="to_group"
/>
<!-- Circuit Selector -->
<div v-if="circuitloaded.symbol && circuitname === ''" class="section-block">
<q-select
v-model="circuitsel"
:options="bothcircuits"
:behavior="$q.platform.is.ios === true ? 'dialog' : 'menu'"
outlined
dense
label="Circuito"
class="modern-select"
popup-content-class="modern-select-popup"
>
<template v-slot:prepend>
<q-icon name="account_balance_wallet" color="primary" />
</template>
</q-select>
</div>
<!-- Province Banner -->
<q-banner <q-banner
v-if="showProvinceToSelect" v-if="showProvinceToSelect"
rounded rounded
class="bg-red text-white" class="warning-banner q-mb-sm"
style="text-align: center"
> >
<div> <template v-slot:avatar>
<em style="font-weight: bold">{{ <q-icon name="warning" color="white" />
$t("circuit.insertprovince_text") </template>
}}</em> {{ $t('circuit.insertprovince_text') }}
</div>
<br />
</q-banner> </q-banner>
<br />
<div v-if="circuitsel"> <!-- Sender Section -->
<q-banner <div v-if="circuitsel" class="section-block">
rounded
dense
class="shadow-5 q-my-sm"
color="primary q-title"
style="text-align: center"
>
<div class="mybanner_left q-mb-sm">
<q-select <q-select
v-if="arrTypesAccounts.length > 0" v-if="arrTypesAccounts.length > 0"
v-model="tipoConto" v-model="tipoConto"
class="my-custom-select" :options="arrTypesAccounts"
outlined outlined
bg-color="light-blue-2" dense
emit-value emit-value
map-options map-options
:options="arrTypesAccounts" label="Mittente"
:label="$t('circuit.sender')" class="modern-select"
></q-select> popup-content-class="modern-select-popup"
</div>
<div
v-if="
tipoConto === shared_consts.AccountType.CONTO_DI_GRUPPO
"
> >
<template v-slot:prepend>
<q-icon name="person" color="primary" />
</template>
</q-select>
<!-- Group Account Selector -->
<q-select <q-select
v-if="tipoConto === shared_consts.AccountType.CONTO_DI_GRUPPO"
v-model="from_groupname" v-model="from_groupname"
:options="arrGroupsList" :options="arrGroupsList"
:label="$t('circuit.choosecontocom')" :label="$t('circuit.choosecontocom')"
rounded outlined
dense
emit-value emit-value
map-options map-options
class="modern-select q-mt-sm"
> >
<!-- Mostra i gruppi su cui sei Admin --> <template v-slot:prepend>
<q-icon name="groups" color="primary" />
</template>
</q-select> </q-select>
</div>
<div <!-- Community Account -->
v-else-if="
tipoConto === shared_consts.AccountType.COMMUNITY_ACCOUNT
"
>
<q-input <q-input
v-if="tipoConto === shared_consts.AccountType.COMMUNITY_ACCOUNT"
v-model="from_contocom" v-model="from_contocom"
:label="$t('circuit.contocom')" :label="$t('circuit.contocom')"
outlined
dense
readonly readonly
class="q-my-sm" class="modern-input q-mt-sm"
> >
<template v-slot:prepend>
<q-icon name="account_balance" color="primary" />
</template>
</q-input> </q-input>
</div> </div>
<CSaldo <!-- Recipient Section -->
v-if="circuitloaded && circuitloaded.symbol" <div v-if="circuitsel" class="section-block">
:symbol="circuitloaded.symbol" <label class="section-label">Destinatario</label>
:color="circuitloaded.color" <div class="recipient-card">
:saldo="accountloaded ? accountloaded.saldo : 0" <div class="recipient-content">
:qtarem=" <!-- User Recipient -->
accountloaded
? circuitStore.getRemainingCoinsToSend(accountloaded)
: 0
"
>
</CSaldo>
</q-banner>
<q-banner
rounded
dense
class="shadow-5 q-my-sm"
color="primary q-title"
>
<div class="mybanner_left bg-green text-white q-mb-sm">
{{ $t("circuit.dest") }}
</div>
<!-- Destination -->
<CMyUserOnlyView <CMyUserOnlyView
v-if="to_user" v-if="to_user_real"
:mycontact="to_user" :mycontact="to_user_real"
:visu="costanti.FIND_PEOPLE" :visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd" @setCmd="tools.setCmd"
> class="recipient-view"
</CMyUserOnlyView> />
<!-- Group Recipient -->
<CMyGroupOnlyView <CMyGroupOnlyView
v-if="to_group" v-if="to_group"
:mygrp="to_group" :mygrp="to_group"
:visu="costanti.USER_GROUPS" :visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name" :circuitname="circuitloaded.name"
> class="recipient-view"
</CMyGroupOnlyView> />
<!-- Community Account Recipient -->
<CMyGroupOnlyView <CMyGroupOnlyView
v-if="circuitloaded && !!circuitloaded._id && to_contocom" v-if="circuitloaded && !!circuitloaded._id && to_contocom"
:mygrp="{ groupname: to_contocom }" :mygrp="{ groupname: to_contocom }"
:visu="costanti.USER_GROUPS" :visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name" :circuitname="circuitloaded.name"
> class="recipient-view"
</CMyGroupOnlyView> />
</q-banner> </div>
</div>
</div> </div>
<q-inner-loading id="spinner" :showing="loading"> <!-- Amount Section - Compatto -->
<q-spinner-tail size="6em" color="primary" /> <div v-if="circuitsel" class="section-block">
</q-inner-loading> <label class="section-label">Importo</label>
</q-step>
<q-step
:name="1"
:title="getTitle(1)"
:icon="getIcon(1)"
:done="ifNextCheck(1)"
>
<div v-if="circuitloaded && !!circuitloaded._id">
<q-banner
v-if="!circuitloaded.transactionsEnabled"
rounded
class="bg-red text-white"
style="text-align: center"
>
<em style="font-weight: bold">{{
$t("circuit.transactionsEnabled_text")
}}</em
><br />
</q-banner>
<div class="amount-input-row" @click="$q.screen.lt.sm ? showKeyboard = true : qtyRef?.focus()">
<q-input <q-input
ref="qtyRef" ref="qtyRef"
class="q-py-sm text-h5"
outlined
v-model="qty" v-model="qty"
:type="$q.platform.is.mobile ? 'string' : 'number'" :type="$q.screen.lt.sm ? 'text' : 'number'"
outlined
dense
input-class="amount-input-field"
:readonly="$q.screen.lt.sm"
:rules="[ :rules="[
(val) => (val) => !isNaN(parseFloat(val)) || t('circuit.qta_not_valid'),
val <= (val) => parseFloat(val) <= circuitStore.getRemainingCoinsToSend(accountloaded) || t('circuit.qta_remaining_to_send', { maxqta: circuitStore.getRemainingCoinsToSend(accountloaded), symbol: circuitloaded.symbol }),
circuitStore.getRemainingCoinsToSend(accountloaded) || (val) => parseFloat(val) > 0 || t('circuit.qta_not_valid'),
t('circuit.qta_remaining_to_send', {
maxqta:
circuitStore.getRemainingCoinsToSend(accountloaded),
symbol: circuitloaded.symbol,
}),
(val) => val > 0 || t('circuit.qta_not_valid'),
]" ]"
:label=" hide-bottom-space
t('movement.amount_to_send', { class="amount-input"
qtamax: circuitStore.getRemainingCoinsToSend(accountloaded) @keyup.enter="causalRef?.focus()"
? circuitStore
.getRemainingCoinsToSend(accountloaded)
.toFixed(2)
: 0 + ` ` + circuitloaded.symbol,
})
"
input-class="text-right"
input-style="padding-bottom: 24px !important;"
v-on:keyup.enter="$event.target.nextElementSibling.focus()"
> >
<!--val => val > circuitStore.getMaxCoinsToSend(accountloaded) || t('circuit.qta_max_to_send', { maxqta: tools.getRemainingCoinsToSend(accountloaded), symbol: circuitloaded.symbol })]" --> <template v-slot:prepend>
<span class="currency-symbol"></span>
</template>
<template v-slot:append> <template v-slot:append>
<div class="text-h5"> <div class="coin-badge" :style="`background: ${circuitloaded.color || '#ff5500'}`">
<em {{ circuitloaded.symbol }}
class="q-px-sm text-white rounded-borders"
:style="
`background-color: ` +
(circuitloaded.color ? circuitloaded.color : '#ff5500')
"
>{{ circuitloaded.symbol }}</em
>
</div> </div>
<q-btn
v-if="$q.screen.lt.sm"
flat
round
dense
icon="keyboard"
size="sm"
class="keyboard-btn q-ml-xs"
@click.stop="showKeyboard = true"
/>
</template> </template>
</q-input> </q-input>
<div class="q-mt-md">
<CNumericKeyboard v-model="qty" :showInput="false" />
</div> </div>
</div> </div>
</q-step>
<q-step
:name="2"
:title="getTitle(2)"
:icon="getIcon(2)"
:done="ifNextCheck(2)"
>
<!-- Destination -->
<CMyUserOnlyView
v-if="to_user"
:mycontact="to_user"
:visu="costanti.FIND_PEOPLE"
@setCmd="tools.setCmd"
>
</CMyUserOnlyView>
<CMyGroupOnlyView
v-if="to_group"
:mygrp="to_group"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
<CMyGroupOnlyView
v-if="circuitloaded && !!circuitloaded._id && to_contocom"
:mygrp="{ groupname: to_contocom }"
:visu="costanti.USER_GROUPS"
:circuitname="circuitloaded.name"
>
</CMyGroupOnlyView>
<div v-if="causalDest"> <!-- Transactions Disabled Banner -->
<q-field <q-banner
v-if="circuitloaded && !!circuitloaded._id && !circuitloaded.transactionsEnabled"
rounded rounded
filled class="error-banner q-mb-sm"
:label="t('circuit.descrizione_destinatario')"
stack-label
> >
<template v-slot:append> <template v-slot:avatar>
<q-icon name="description" /> <q-icon name="error" color="white" />
</template> </template>
<template v-slot:control> {{ $t('circuit.transactionsEnabled_text') }}
<div class="self-center full-width no-outline" tabindex="0"> </q-banner>
{{ causalDest }}
</div>
</template>
</q-field>
</div>
<!--<q-banner v-if="visubanner" rounded class="text-center text-bold"> <!-- Note Section - Compatto -->
{{ t('circuit.descr_casuale') }} <div v-if="circuitsel" class="section-block">
<template v-slot:action>
<q-btn label="Chiudi" flat @click="visubanner = false"></q-btn>
</template>
</q-banner>-->
<div class="q-mt-sm text-italic text-blue text-bold">
{{ $t("circuit.descr_casuale") }}
</div>
<q-input <q-input
ref="causalRef" ref="causalRef"
v-model="causal" v-model="causal"
autogrow outlined
rounded dense
filled type="textarea"
rows="3"
maxlength="200" maxlength="200"
counter counter
:label="$t('circuit.note')" label="Nota per il destinatario"
class="q-my-sm full-width" placeholder="Scrivi un messaggio..."
class="modern-textarea"
:rules="[
(val) => !!val?.trim() || 'Inserisci un messaggio',
(val) => val.trim().length >= 2 || 'Minimo 2 caratteri',
]"
lazy-rules
> >
<template v-slot:prepend>
<q-icon name="message" color="grey-6" class="self-start q-mt-xs" />
</template>
<template v-slot:after> <template v-slot:after>
<q-avatar> <q-avatar size="32px">
<img <img
:src=" :src="userStore.my.profile ? userStore.getImgByProfile(userStore.my) : ''"
userStore.my.profile
? userStore.getImgByProfile(userStore.my)
: ''
"
:alt="userStore.my.username" :alt="userStore.my.username"
/> />
</q-avatar> </q-avatar>
</template> </template>
<template v-slot:prepend>
<q-icon name="comment" />
</template>
</q-input> </q-input>
<div class="sendris">
{{
$t("circuit.sendcoinsto", {
qty,
coin: circuitloaded.symbol,
dest: to_group
? to_group.groupname
: to_user
? tools.getNomeUtenteByRecUser(to_user)
: to_contocom,
})
}}
sul {{ circuitsel }}
</div> </div>
</q-step>
</q-stepper>
</q-card-section> </q-card-section>
<q-card-actions align="center" style="row justify-between">
<!-- Fixed Bottom Actions -->
<q-card-actions
class="dialog-actions"
:class="{ 'fixed-bottom-actions': $q.screen.lt.sm }"
>
<q-btn <q-btn
class="col"
flat flat
:label="$t('dialog.indietro')" :label="t('dialog.cancel')"
color="primary" class="cancel-btn"
icon="navigate_before" v-close-popup
@click="clickIndietro(step)" />
></q-btn>
<q-btn <q-btn
class="col"
:disable="!ifNextCheck(step)" :disable="!ifNextCheck(step)"
:label=" class="send-btn"
step === 0 || step === 1 :class="{ 'btn-disabled': !ifNextCheck(step) }"
? t('dialog.avanti') @click="sendCoin"
: t('circuit.sendcoins', { >
qty, <span class="send-btn-text">Invia {{ qty || 0 }} {{ circuitloaded.symbol }}</span>
coin: circuitloaded.symbol, <img
dest: to_group v-if="circuitloaded.symbol === 'RIS'"
? to_group.groupname src="/images/1ris_rosso_100.png"
: to_user alt="RIS"
? tools.getNomeUtenteByRecUser(to_user) class="send-btn-icon"
: to_contocom, />
}) <q-icon v-else name="send" class="q-ml-sm" />
" </q-btn>
color="positive"
:icon-right="
step === 2 ? 'img: /images/1ris_rosso_100.png' : 'navigate_next'
"
@click="clickAvanti(step)"
></q-btn>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-dialog> </q-dialog>
<!-- Numeric Keyboard Dialog -->
<q-dialog
v-model="showKeyboard"
position="bottom"
seamless
no-backdrop-dismiss
>
<q-card class="keyboard-dialog">
<q-card-section class="keyboard-header">
<div class="keyboard-header-content">
<span class="keyboard-title">Inserisci importo</span>
<q-btn
flat
dense
label="Fatto"
color="primary"
class="done-btn"
v-close-popup
/>
</div>
<div class="keyboard-display">
<span class="keyboard-amount">{{ qty || '0' }}</span>
<div class="keyboard-coin-badge" :style="`background: ${circuitloaded.color || '#ff5500'}`">
{{ circuitloaded.symbol }}
</div>
</div>
</q-card-section>
<q-card-section class="keyboard-section q-pa-sm">
<CNumericKeyboard
v-model="qty"
:showInput="false"
@update:model-value="setQty"
/>
</q-card-section>
</q-card>
</q-dialog>
</template> </template>
<script lang="ts" src="./CSendCoins.ts"> <script lang="ts" src="./CSendCoins.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./CSendCoins.scss"; @import './CSendCoins.scss';
</style> </style>

View File

@@ -0,0 +1,62 @@
.check-email-page {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.check-email-container {
max-width: 500px;
margin: 0 auto;
flex: 1;
}
.email-icon-wrapper {
text-align: center;
margin-bottom: 24px;
.q-icon {
animation: pulse 2s ease-in-out infinite;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.7;
transform: scale(1.05);
}
}
.main-banner,
.info-banner {
.banner-content {
font-size: 15px;
line-height: 1.5;
}
}
.resend-card {
border-radius: 12px;
.countdown-section {
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
strong {
font-family: monospace;
font-size: 18px;
margin-left: 4px;
}
}
}
.helpful-links {
padding: 16px 0;
}

View File

@@ -0,0 +1,140 @@
import { defineComponent, ref, computed, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { LandingFooter } from '@/components/LandingFooter';
import { useUserStore } from '@store/UserStore';
import { tools } from '@tools';
import { Api } from '@api';
const RESEND_COOLDOWN_MINUTES = 1;
const RESEND_COOLDOWN_MS = RESEND_COOLDOWN_MINUTES * 60 * 1000;
const STORAGE_KEY = 'lastVerificationEmailSent';
export default defineComponent({
name: 'CheckEmail',
components: { LandingFooter },
setup() {
const { t } = useI18n();
const router = useRouter();
const userStore = useUserStore();
// State
const sending = ref(false);
const emailSent = ref(false);
const errorMessage = ref('');
const timeLeft = ref(0);
let countdownInterval: ReturnType<typeof setInterval> | null = null;
// Computed
const canResend = computed(() => timeLeft.value <= 0);
const formattedTimeLeft = computed(() => {
const minutes = Math.floor(timeLeft.value / 60);
const seconds = timeLeft.value % 60;
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
});
// Methods
const getLastSentTime = (): number => {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? parseInt(stored, 10) : 0;
};
const setLastSentTime = () => {
localStorage.setItem(STORAGE_KEY, Date.now().toString());
};
const calculateTimeLeft = (): number => {
const lastSent = getLastSentTime();
if (!lastSent) return 0;
const elapsed = Date.now() - lastSent;
const remaining = RESEND_COOLDOWN_MS - elapsed;
return remaining > 0 ? Math.ceil(remaining / 1000) : 0;
};
const startCountdown = () => {
timeLeft.value = calculateTimeLeft();
if (timeLeft.value > 0) {
countdownInterval = setInterval(() => {
timeLeft.value = calculateTimeLeft();
if (timeLeft.value <= 0 && countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
}, 1000);
}
};
const resendVerificationEmail = async () => {
if (!canResend.value || sending.value) return;
sending.value = true;
emailSent.value = false;
errorMessage.value = '';
try {
await userStore.resendVerificationEmail();
setLastSentTime();
emailSent.value = true;
startCountdown();
// Nascondi messaggio successo dopo 5 secondi
setTimeout(() => {
emailSent.value = false;
}, 5000);
} catch (error: any) {
errorMessage.value =
error?.message ||
t('components.authentication.email_verification.errore_invio');
// Nascondi messaggio errore dopo 5 secondi
setTimeout(() => {
errorMessage.value = '';
}, 5000);
} finally {
sending.value = false;
}
};
const goToChangeEmail = () => {
router.push({ name: 'ChangeEmail' });
};
const logout = async () => {
await userStore.logout();
router.push({ name: 'Home' });
};
// Lifecycle
onMounted(() => {
startCountdown();
});
onUnmounted(() => {
if (countdownInterval) {
clearInterval(countdownInterval);
}
});
return {
t,
tools,
sending,
emailSent,
errorMessage,
canResend,
timeLeft,
formattedTimeLeft,
resendVerificationEmail,
goToChangeEmail,
logout,
};
},
});

View File

@@ -0,0 +1,156 @@
<template>
<q-page
v-if="tools.isLogged() && !tools.isEmailVerified()"
padding
class="check-email-page"
>
<div class="check-email-container">
<!-- Icona principale -->
<div class="email-icon-wrapper">
<q-icon
name="mark_email_unread"
size="80px"
color="warning"
/>
</div>
<!-- Banner principale -->
<q-banner
rounded
class="bg-warning text-black main-banner"
>
<template v-slot:avatar>
<q-icon
name="info"
color="black"
/>
</template>
<div
class="banner-content"
v-html="
$t('components.authentication.email_verification2.link_sent', {
botname: tools.getBotName(),
})
"
></div>
</q-banner>
<!-- Sezione Reinvio Email -->
<q-card
flat
bordered
class="resend-card q-mt-lg"
>
<q-card-section>
<div class="text-subtitle1 text-weight-medium q-mb-sm">
{{ t('components.authentication.email_verification2.non_ricevuta') }}
</div>
<!-- Countdown attivo -->
<div
v-if="!canResend"
class="countdown-section"
>
<q-icon
name="schedule"
size="20px"
color="grey-6"
class="q-mr-sm"
/>
<span class="text-grey-7">
{{ t('components.authentication.email_verification2.attendi') }}
<strong>{{ formattedTimeLeft }}</strong>
</span>
</div>
<!-- Bottone Reinvio -->
<q-btn
v-else
:loading="sending"
:disable="sending"
color="primary"
icon="send"
:label="t('components.authentication.email_verification2.reinvia')"
class="full-width q-mt-sm"
@click="resendVerificationEmail"
/>
<!-- Messaggio successo -->
<q-banner
v-if="emailSent"
rounded
dense
class="bg-positive text-white q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="check_circle"
color="white"
/>
</template>
{{ t('components.authentication.email_verification2.inviata_successo') }}
</q-banner>
<!-- Messaggio errore -->
<q-banner
v-if="errorMessage"
rounded
dense
class="bg-negative text-white q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="error"
color="white"
/>
</template>
{{ errorMessage }}
</q-banner>
</q-card-section>
<!-- Banner istruzioni -->
<q-banner
rounded
class="bg-grey-3 text-black info-banner q-mt-md"
>
<template v-slot:avatar>
<q-icon
name="help_outline"
color="grey-7"
/>
</template>
<div
class="banner-content"
v-html="$t('components.authentication.email_verification2.se_non_ricevo')"
></div>
</q-banner>
</q-card>
<!-- Link utili -->
<div class="helpful-links q-mt-lg text-center">
<q-btn
flat
dense
color="primary"
:label="t('components.authentication.email_verification2.cambia_email')"
@click="goToChangeEmail"
/>
<span class="q-mx-sm text-grey-5">|</span>
<q-btn
flat
dense
color="grey-7"
:label="t('components.authentication.email_verification2.logout')"
@click="logout"
/>
</div>
</div>
<LandingFooter />
</q-page>
</template>
<script lang="ts" src="./CheckEmail.ts"></script>
<style lang="scss" scoped>
@import './CheckEmail.scss';
</style>

View File

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

View File

@@ -0,0 +1,569 @@
<template>
<q-card
class="ollama-chat"
:style="{ height: height }"
>
<!-- Header -->
<q-card-section class="q-py-sm bg-primary text-white">
<div class="row items-center justify-between">
<div class="row items-center q-gutter-sm">
<q-badge
:color="isConnected ? 'green' : 'red'"
rounded
/>
<span class="text-subtitle1 text-weight-medium"
>{{ title }} - {{ settings.model }}</span
>
</div>
<div class="row q-gutter-xs">
<q-btn
flat
dense
round
icon="settings"
@click="showSettings = true"
>
<q-tooltip>Impostazioni</q-tooltip>
</q-btn>
<q-btn
flat
dense
round
icon="delete_sweep"
@click="clearChat"
>
<q-tooltip>Pulisci Chat</q-tooltip>
</q-btn>
</div>
</div>
</q-card-section>
<!-- Messages -->
<q-card-section
ref="messagesContainer"
class="messages-container q-pa-md"
>
<div
v-if="messages.length === 0"
class="text-center text-grey-6 q-py-xl"
>
<q-icon
name="chat"
size="64px"
color="grey-4"
/>
<p class="q-mt-md">Inizia una conversazione!</p>
</div>
<div
v-for="(msg, index) in messages"
:key="index"
class="message-wrapper q-mb-md"
:class="msg.role === 'user' ? 'text-right' : 'text-left'"
>
<div
class="message-bubble q-pa-sm q-px-md"
:class="msg.role === 'user' ? 'bg-primary text-white' : 'bg-grey-3 text-dark'"
>
<!-- Puntini solo se streaming E contenuto vuoto -->
<div
v-if="msg.role === 'assistant' && msg.isStreaming && !msg.content"
class="typing-indicator"
>
<span></span><span></span><span></span>
</div>
<!-- Contenuto messaggio (anche durante streaming se già arrivato) -->
<div
v-else
class="message-content"
>
<div v-html="formatMessage(msg.content)"></div>
</div>
</div>
<div class="text-caption text-grey-6 q-mt-xs">
{{ msg.role === 'user' ? 'Tu' : modelName }} {{ formatTime(msg.timestamp) }}
</div>
</div>
</q-card-section>
<!-- Input -->
<q-card-section class="q-pa-sm border-top">
<div class="row q-gutter-sm items-end">
<q-input
v-model="inputMessage"
:disable="isGenerating"
outlined
dense
autogrow
class="col"
placeholder="Scrivi un messaggio..."
@keydown.enter.prevent="handleEnter"
>
<template v-slot:append>
<q-btn
v-if="isGenerating"
flat
dense
round
icon="stop"
color="negative"
@click="stopGeneration"
>
<q-tooltip>Ferma generazione</q-tooltip>
</q-btn>
</template>
</q-input>
<q-btn
:loading="isGenerating"
:disable="!inputMessage.trim() || isGenerating"
color="primary"
icon="send"
@click="sendMessage"
>
<q-tooltip>Invia messaggio</q-tooltip>
</q-btn>
</div>
</q-card-section>
<!-- Settings Dialog -->
<q-dialog v-model="showSettings">
<q-card style="min-width: 350px">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">Impostazioni</div>
<q-space />
<q-btn
icon="close"
flat
round
dense
v-close-popup
/>
</q-card-section>
<q-card-section class="q-gutter-md">
<q-input
v-model="settings.baseUrl"
outlined
label="Ollama URL"
hint="URL del server Ollama"
/>
<q-select
v-model="settings.model"
:options="availableModels"
outlined
label="Modello"
emit-value
map-options
/>
<div>
<div class="text-caption q-mb-sm">
Temperatura: {{ settings.temperature }}
</div>
<q-slider
v-model="settings.temperature"
:min="0"
:max="2"
:step="0.1"
label
color="primary"
/>
</div>
<q-input
v-model="settings.systemPrompt"
outlined
type="textarea"
label="System Prompt (opzionale)"
hint="Istruzioni per il comportamento dell'AI"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn
flat
label="Annulla"
color="grey"
v-close-popup
/>
<q-btn
flat
label="Salva"
color="primary"
@click="saveSettings"
v-close-popup
/>
</q-card-actions>
</q-card>
</q-dialog>
</q-card>
</template>
<script>
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue';
import OllamaService from './OllamaService.js';
import { tools } from '@tools';
export default {
name: 'OllamaChat',
props: {
title: {
type: String,
default: 'Chat AI',
},
height: {
type: String,
default: '600px',
},
baseUrl: {
type: String,
default: 'http://localhost:11434',
},
model: {
type: String,
default: '',
},
temperature: {
type: Number,
default: 0.7,
},
systemPrompt: {
type: String,
default: '',
},
initialMessages: {
type: Array,
default: () => [],
},
},
emits: ['message-sent', 'response-received', 'error', 'settings-changed'],
setup(props, { emit }) {
// State
const messages = ref([...props.initialMessages]);
const inputMessage = ref('');
const isGenerating = ref(false);
const isConnected = ref(false);
const showSettings = ref(false);
const messagesContainer = ref(null);
const abortController = ref(null);
const availableModels = ref([]);
// Settings
const settings = reactive({
baseUrl: props.baseUrl,
model: props.model,
temperature: props.temperature,
systemPrompt: props.systemPrompt,
});
// Ollama service instance
const ollama = new OllamaService(settings.baseUrl);
// Computed
const modelName = computed(() => settings.model);
// Methods
const scrollToBottom = async () => {
await nextTick();
if (messagesContainer.value) {
const container = messagesContainer.value.$el || messagesContainer.value;
container.scrollTop = container.scrollHeight;
}
};
const formatMessage = (content) => {
if (!content) return '';
return content
.replace(
/```(\w+)?\n([\s\S]*?)```/g,
'<pre class="code-block"><code>$2</code></pre>'
)
.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
.replace(/\n/g, '<br>');
};
const formatTime = (timestamp) => {
if (!timestamp) return '';
return new Date(timestamp).toLocaleTimeString('it-IT', {
hour: '2-digit',
minute: '2-digit',
});
};
const handleEnter = (event) => {
if (!event.shiftKey) {
sendMessage();
}
};
const sendMessage = async () => {
const content = inputMessage.value.trim();
if (!content || isGenerating.value) return;
// Add user message
const userMessage = {
role: 'user',
content,
timestamp: new Date(),
};
messages.value.push(userMessage);
inputMessage.value = '';
emit('message-sent', userMessage);
// Add placeholder for assistant
const assistantMessage = {
role: 'assistant',
content: '',
timestamp: new Date(),
isStreaming: true,
};
messages.value.push(assistantMessage);
const assistantIndex = messages.value.length - 1;
isGenerating.value = true;
abortController.value = new AbortController();
try {
ollama.setBaseUrl(settings.baseUrl);
const chatMessages = messages.value
.filter((m) => !m.isStreaming)
.map((m) => ({ role: m.role, content: m.content }));
await ollama.streamChat(
{
model: settings.model,
messages: chatMessages,
temperature: settings.temperature,
system: settings.systemPrompt || undefined,
},
(chunk, full) => {
messages.value[assistantIndex] = {
...messages.value[assistantIndex],
content: full,
isStreaming: true,
};
assistantMessage.isStreaming = true;
scrollToBottom();
},
(fullContent) => {
assistantMessage.content = fullContent;
assistantMessage.isStreaming = false;
assistantMessage.timestamp = new Date();
emit('response-received', assistantMessage);
}
);
isConnected.value = true;
} catch (error) {
assistantMessage.content = `❌ Errore: ${error.message}`;
assistantMessage.isStreaming = false;
isConnected.value = false;
emit('error', error);
}
isGenerating.value = false;
scrollToBottom();
};
const stopGeneration = () => {
if (abortController.value) {
abortController.value.abort();
isGenerating.value = false;
}
};
const clearChat = () => {
messages.value = [];
};
const loadModels = async () => {
try {
ollama.setBaseUrl(settings.baseUrl);
const models = await ollama.listModels();
availableModels.value = models.map((m) => ({
label: m.name,
value: m.name,
}));
isConnected.value = true;
// prende dal cookie salvato
const savedModel = tools.getCookie('ollama_model');
if (savedModel) {
settings.model = savedModel;
}
const modelIndex = availableModels.value.findIndex(
(m) => m.value === settings.model
);
if (modelIndex === -1) {
settings.model = availableModels.value[0].value;
}
scrollToBottom();
} catch (error) {
console.error('Error loadModels', error);
isConnected.value = false;
availableModels.value = [
{ label: 'llama3.2', value: 'llama3.2' },
{ label: 'llama3.1', value: 'llama3.1' },
{ label: 'mistral', value: 'mistral' },
{ label: 'codellama', value: 'codellama' },
];
}
};
const saveSettings = () => {
emit('settings-changed', { ...settings });
};
// Lifecycle
onMounted(() => {
try {
loadModels();
} catch (e) {
console.error('Error Mounted Chat:', e);
}
});
watch(
() => settings.model,
(newModel) => {
tools.setCookie('ollama_model', newModel);
},
{ deep: true }
);
// Watch messages for scroll
watch(messages, scrollToBottom, { deep: true });
return {
messages,
inputMessage,
isGenerating,
isConnected,
showSettings,
messagesContainer,
settings,
availableModels,
modelName,
formatMessage,
formatTime,
handleEnter,
sendMessage,
stopGeneration,
clearChat,
saveSettings,
tools,
};
},
};
</script>
<style scoped>
.ollama-chat {
display: flex;
flex-direction: column;
}
.messages-container {
flex: 1;
overflow-y: auto;
}
.message-bubble {
display: inline-block;
max-width: 80%;
border-radius: 16px;
word-break: break-word;
}
.text-right .message-bubble {
border-bottom-right-radius: 4px;
}
.text-left .message-bubble {
border-bottom-left-radius: 4px;
}
.message-content :deep(pre.code-block) {
background: #1e1e1e;
color: #d4d4d4;
padding: 12px;
border-radius: 8px;
overflow-x: auto;
margin: 8px 0;
}
.message-content :deep(.inline-code) {
background: rgba(0, 0, 0, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 8px 0;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: currentColor;
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}
.typing-indicator span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes typing {
0%,
60%,
100% {
opacity: 0.3;
transform: scale(0.8);
}
30% {
opacity: 1;
transform: scale(1);
}
}
.border-top {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.streaming-cursor {
display: inline-block;
animation: blink-cursor 0.8s infinite;
color: var(--q-primary);
margin-left: 2px;
}
@keyframes blink-cursor {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
</style>

View File

@@ -0,0 +1,388 @@
/**
* OllamaService - Servizio per interagire con Ollama API
* Usalo in Quasar/Vue.js per tutte le chiamate AI
*/
class OllamaService {
constructor(baseUrl = 'http://localhost:11434') {
this.baseUrl = baseUrl.replace(/\/$/, '');
this.defaultModel = '';
this.defaultTemperature = 0.7;
}
// Configura l'URL base
setBaseUrl(url) {
this.baseUrl = url.replace(/\/$/, '');
}
// Configura il modello di default
setDefaultModel(model) {
this.defaultModel = model;
}
/**
* Chat con cronologia messaggi
* @param {Object} options - Opzioni della chat
* @param {string} options.model - Nome del modello
* @param {Array} options.messages - Array di messaggi [{role: 'user'|'assistant', content: '...'}]
* @param {boolean} options.stream - Abilita streaming
* @param {number} options.temperature - Temperatura (0-2)
* @param {string} options.system - System prompt opzionale
* @returns {Promise<string|Response>}
*/
async chat(options) {
const {
model = this.defaultModel,
messages,
stream = false,
temperature = this.defaultTemperature,
system = null,
maxTokens = null,
topP = null,
topK = null,
} = options;
const payload = {
model,
messages,
stream,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
...(topP && { top_p: topP }),
...(topK && { top_k: topK }),
},
};
if (system) {
payload.system = system;
}
const response = await fetch(`${this.baseUrl}/api2/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
}
if (stream) return response;
const data = await response.json();
return data.message.content;
}
/**
* Generazione testo semplice (senza cronologia)
* @param {Object} options - Opzioni di generazione
* @param {string} options.model - Nome del modello
* @param {string} options.prompt - Prompt di input
* @param {boolean} options.stream - Abilita streaming
* @param {number} options.temperature - Temperatura (0-2)
* @param {string} options.system - System prompt opzionale
* @returns {Promise<string|Response>}
*/
async generate(options) {
const {
model = this.defaultModel,
prompt,
stream = false,
temperature = this.defaultTemperature,
system = null,
maxTokens = null,
} = options;
const payload = {
model,
prompt,
stream,
options: {
temperature,
...(maxTokens && { num_predict: maxTokens }),
},
};
if (system) {
payload.system = system;
}
const response = await fetch(`${this.baseUrl}/api2/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
}
if (stream) return response;
const data = await response.json();
return data.response;
}
/**
* Chat con streaming e callback
* @param {Object} options - Opzioni della chat
* @param {Function} onChunk - Callback per ogni chunk (chunk, fullContent)
* @param {Function} onComplete - Callback al completamento (fullContent)
* @returns {Promise<string>}
*/
async streamChat(options, onChunk, onComplete = null) {
const response = await this.chat({ ...options, stream: true });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
if (json.message?.content) {
fullContent += json.message.content;
if (onChunk) onChunk(json.message.content, fullContent);
}
} catch (e) {
// Ignora linee non JSON
}
}
}
} finally {
reader.releaseLock();
}
if (onComplete) onComplete(fullContent);
return fullContent;
}
/**
* Generazione con streaming e callback
* @param {Object} options - Opzioni di generazione
* @param {Function} onChunk - Callback per ogni chunk
* @returns {Promise<string>}
*/
async streamGenerate(options, onChunk, onComplete = null) {
const response = await this.generate({ ...options, stream: true });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullContent = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter((line) => line.trim());
for (const line of lines) {
try {
const json = JSON.parse(line);
if (json.response) {
fullContent += json.response;
if (onChunk) onChunk(json.response, fullContent);
}
} catch (e) {
// Ignora linee non JSON
}
}
}
} finally {
reader.releaseLock();
}
if (onComplete) onComplete(fullContent);
return fullContent;
}
/**
* Lista modelli disponibili
* @returns {Promise<Array>}
*/
async listModels() {
const response = await fetch(`${this.baseUrl}/api2/models`);
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
const data = await response.json();
return data.models || [];
}
/**
* Informazioni su un modello
* @param {string} model - Nome del modello
* @returns {Promise<Object>}
*/
async showModel(model) {
const response = await fetch(`${this.baseUrl}/api2/show`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: model }),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
return response.json();
}
/**
* Genera embeddings
* @param {string} model - Nome del modello
* @param {string|Array} prompt - Testo o array di testi
* @returns {Promise<Array>}
*/
async embeddings(model, prompt) {
const response = await fetch(`${this.baseUrl}/api2/embeddings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, prompt }),
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.status}`);
}
const data = await response.json();
return data.embedding;
}
// ============================================
// METODI HELPER PER USI COMUNI
// ============================================
/**
* Genera testo creativo
*/
async generateText(prompt, options = {}) {
return this.generate({
prompt,
temperature: 0.8,
...options,
});
}
/**
* Genera codice
*/
async generateCode(prompt, language = 'javascript', options = {}) {
return this.generate({
prompt: `Scrivi codice ${language} per: ${prompt}\n\nRispondi solo con il codice, senza spiegazioni.`,
temperature: 0.3,
...options,
});
}
/**
* Traduci testo
*/
async translate(text, targetLang = 'english', options = {}) {
return this.generate({
prompt: `Traduci il seguente testo in ${targetLang}. Rispondi solo con la traduzione:\n\n${text}`,
temperature: 0.3,
...options,
});
}
/**
* Riassumi testo
*/
async summarize(text, options = {}) {
return this.generate({
prompt: `Riassumi il seguente testo in modo conciso:\n\n${text}`,
temperature: 0.5,
...options,
});
}
/**
* Rispondi a una domanda basata su un contesto
*/
async answerQuestion(question, context, options = {}) {
return this.generate({
prompt: `Contesto:\n${context}\n\nDomanda: ${question}\n\nRispondi basandoti solo sul contesto fornito.`,
temperature: 0.3,
...options,
});
}
/**
* Estrai informazioni strutturate (JSON)
*/
async extractJSON(text, schema, options = {}) {
const response = await this.generate({
prompt: `Estrai le informazioni dal seguente testo e restituisci un JSON valido con questa struttura: ${JSON.stringify(schema)}\n\nTesto:\n${text}\n\nRispondi SOLO con il JSON, senza altro testo.`,
temperature: 0.1,
...options,
});
try {
// Cerca di estrarre JSON dalla risposta
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
return JSON.parse(response);
} catch (e) {
throw new Error('Failed to parse JSON response: ' + response);
}
}
/**
* Analisi del sentiment
*/
async analyzeSentiment(text, options = {}) {
const response = await this.generate({
prompt: `Analizza il sentiment del seguente testo e rispondi SOLO con un JSON nel formato {"sentiment": "positive"|"negative"|"neutral", "confidence": 0.0-1.0, "explanation": "breve spiegazione"}\n\nTesto: ${text}`,
temperature: 0.1,
...options,
});
try {
const jsonMatch = response.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
return JSON.parse(response);
} catch (e) {
return { sentiment: 'unknown', confidence: 0, raw: response };
}
}
/**
* Correggi grammatica
*/
async correctGrammar(text, language = 'italiano', options = {}) {
return this.generate({
prompt: `Correggi gli errori grammaticali nel seguente testo in ${language}. Rispondi solo con il testo corretto:\n\n${text}`,
temperature: 0.2,
...options,
});
}
/**
* Genera lista/bullet points
*/
async generateList(topic, count = 5, options = {}) {
return this.generate({
prompt: `Genera una lista di ${count} elementi su: ${topic}\n\nFormatta come lista puntata.`,
temperature: 0.7,
...options,
});
}
}
// Export per ES6 modules
export default OllamaService;
// Export per CommonJS
if (typeof module !== 'undefined' && module.exports) {
module.exports = OllamaService;
}

View File

@@ -0,0 +1,178 @@
/**
* useOllama - Composable Vue 3 per usare Ollama nelle tue applicazioni Quasar
*
* Esempio d'uso:
*
* import { useOllama } from './useOllama';
*
* const { generate, chat, isLoading, error } = useOllama({
* baseUrl: 'http://localhost:11434',
* model: 'llama3.2'
* });
*
* const result = await generate('Scrivi una poesia');
*/
import { ref, reactive } from 'vue';
import OllamaService from './OllamaService.js';
export function useOllama(options = {}) {
const {
baseUrl = 'http://localhost:11434',
model = 'llama3.2',
temperature = 0.7,
} = options;
// State
const isLoading = ref(false);
const error = ref(null);
const streamingContent = ref('');
const models = ref([]);
// Service instance
const service = new OllamaService(baseUrl);
service.setDefaultModel(model);
/**
* Genera testo
*/
const generate = async (prompt, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.generate({
prompt,
temperature,
...opts,
});
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Genera testo con streaming
*/
const generateStream = async (prompt, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.streamGenerate(
{ prompt, temperature, ...opts },
(chunk, full) => {
streamingContent.value = full;
}
);
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Chat con messaggi
*/
const chat = async (messages, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.chat({
messages,
temperature,
...opts,
});
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Chat con streaming
*/
const chatStream = async (messages, opts = {}) => {
isLoading.value = true;
error.value = null;
streamingContent.value = '';
try {
const result = await service.streamChat(
{ messages, temperature, ...opts },
(chunk, full) => {
streamingContent.value = full;
}
);
return result;
} catch (e) {
error.value = e.message;
throw e;
} finally {
isLoading.value = false;
}
};
/**
* Carica modelli disponibili
*/
const loadModels = async () => {
try {
models.value = await service.listModels();
return models.value;
} catch (e) {
error.value = e.message;
return [];
}
};
// Helper methods
const generateText = (prompt, opts) => service.generateText(prompt, opts);
const generateCode = (prompt, lang, opts) => service.generateCode(prompt, lang, opts);
const translate = (text, lang, opts) => service.translate(text, lang, opts);
const summarize = (text, opts) => service.summarize(text, opts);
const extractJSON = (text, schema, opts) => service.extractJSON(text, schema, opts);
const analyzeSentiment = (text, opts) => service.analyzeSentiment(text, opts);
return {
// State
isLoading,
error,
streamingContent,
models,
// Core methods
generate,
generateStream,
chat,
chatStream,
loadModels,
// Helper methods
generateText,
generateCode,
translate,
summarize,
extractJSON,
analyzeSentiment,
// Service access
service,
};
}
export default useOllama;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,7 @@ body {
color: #2c3e50; color: #2c3e50;
line-height: 1.7; line-height: 1.7;
/*background: linear-gradient(135deg, #f5f7fa 0%, #f3fff2 100%);*/ /*background: linear-gradient(135deg, #f5f7fa 0%, #f3fff2 100%);*/
background: linear-gradient(180deg, #f1f5f9 0%, #e2e8f0 100%);
//font-size: 1rem; //font-size: 1rem;
@media (max-width: 600px) { @media (max-width: 600px) {

View File

@@ -63,6 +63,7 @@ const msg_website_it = {
nextzoom: 'Conferenze', nextzoom: 'Conferenze',
requestresetpwd: 'Richiesta Reset Password', requestresetpwd: 'Richiesta Reset Password',
vreg: 'Verifica Reg', vreg: 'Verifica Reg',
reverif_email: 'Riverifica Email',
dashboard: 'Lavagna', dashboard: 'Lavagna',
statoattuale: 'Stato Attuale', statoattuale: 'Stato Attuale',
posizione_in_programmazione: 'Lista d\'Imbarco', posizione_in_programmazione: 'Lista d\'Imbarco',

View File

@@ -1,6 +1,6 @@
import type { IToken } from '@model/other' import type { IToken } from '@model/other'
import type { ICart, IOrderCart, IShareWithUs } from '@/model/Products' import type { ICart, IOrderCart, IShareWithUs } from '@/model/Products'
import type { IAccount, ICatGrp, ICircuit, IImgGallery, IMovement, IMyCircuit } from '@model/GlobalStore'; import type { IAccount, ICatGrp, ICircuit, IImgGallery, IMovement, IMovVisu, IMyCircuit } from '@model/GlobalStore';
import { IGallery } from '@model/GlobalStore' import { IGallery } from '@model/GlobalStore'
import type { IBookedEvent } from './Calendar' import type { IBookedEvent } from './Calendar'
@@ -197,7 +197,7 @@ export interface IUserProfile {
refused_circuits: any[] refused_circuits: any[]
manage_mycircuits: ICircuit[] manage_mycircuits: ICircuit[]
useraccounts: IAccount[] useraccounts: IAccount[]
last_my_transactions: IMovement[] last_my_transactions: IMovVisu[] // IMovement[]
total_transactions?: number total_transactions?: number
calc: ICalc calc: ICalc
} }

View File

@@ -26,6 +26,8 @@ import { shared_consts } from '@/common/shared_vuejs'
import { CPresentazione } from '@/components' import { CPresentazione } from '@/components'
import MixinMetaTags from '@/mixins/mixin-metatags' import MixinMetaTags from '@/mixins/mixin-metatags'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { watch } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'mainview', name: 'mainview',
components: { components: {
@@ -39,9 +41,21 @@ export default defineComponent({
const userStore = useUserStore() const userStore = useUserStore()
const { getValDb } = MixinBase() const { getValDb } = MixinBase()
const notifStore = useNotifStore() const notifStore = useNotifStore()
const $q = useQuasar()
const isfinishLoading = computed(() => globalStore.finishLoading) const isfinishLoading = computed(() => globalStore.finishLoading)
watch(() => globalStore.finishLoading, (newValue, oldValue) => {
if (!newValue) {
$q.loading.show({
message: 'Caricamento sito ...',
spinnerColor: 'primary',
})
} else {
$q.loading.hide()
}
})
const { setmeta } = MixinMetaTags() const { setmeta } = MixinMetaTags()
const { getRefLink } = MixinUsers() const { getRefLink } = MixinUsers()

View File

@@ -23,8 +23,10 @@
</CMyPageElem> </CMyPageElem>
</div> </div>
</div> </div>
<div v-if="isfinishLoading">
<LandingFooter></LandingFooter> <LandingFooter></LandingFooter>
</div> </div>
</div>
<div v-else> <div v-else>
<div v-if="isfinishLoading"> <div v-if="isfinishLoading">
<CMyPageElem <CMyPageElem

View File

@@ -196,6 +196,15 @@
mykey="Email" mykey="Email"
:myvalue="myuser.email" :myvalue="myuser.email"
/> />
<CKeyAndValue
mykey="Email Verificata"
:myvalue="myuser.verified_email"
:show-set-button="true"
:on-set-value="userStore.setVerifiedEmail"
:valuetoSet="true"
:param2="myuser._id"
button-tooltip="Verifica Email"
/>
<CKeyAndValue <CKeyAndValue
mykey="Versione" mykey="Versione"
:myvalue="myuser.profile.version" :myvalue="myuser.profile.version"

View File

@@ -15,6 +15,18 @@ function getRoutesAI(site: ISites) {
level_parent: 0, level_parent: 0,
level_child: 0.5, level_child: 0.5,
}, },
{
active: true,
order: 30,
path: '/ai-ollama',
materialIcon: 'fas fa-robot',
name: 'mypages.ollamaVPS',
component: () => import('@/views/toolsAI/ollama/ollama.vue'),
inmenu: true,
submenu: true,
level_parent: 0,
level_child: 0.5,
},
] ]
const routes_admin_ai: IListRoutes[] = [ const routes_admin_ai: IListRoutes[] = [

View File

@@ -805,6 +805,13 @@ function getRoutesAd(site: ISites) {
name: 'pages.vreg', name: 'pages.vreg',
component: () => import('@/views/login/vreg/vreg.vue') component: () => import('@/views/login/vreg/vreg.vue')
}, },
{
active: true,
order: 1000,
path: '/reverif_email',
name: 'pages.reverif_email',
component: () => import('@/views/login/reverif_email/reverif_email.vue')
},
{ {
active: true, active: true,
order: 1000, order: 1000,

File diff suppressed because it is too large Load Diff

View File

@@ -50,7 +50,7 @@ export const useCircuitStore = defineStore('CircuitStore', {
const account = userStore.my.profile.useraccounts.find( const account = userStore.my.profile.useraccounts.find(
(rec: IAccount) => rec.circuitId === circuitId (rec: IAccount) => rec.circuitId === circuitId
); );
if (account) return account.saldo; if (account) return tools.roundDec2(account.saldo, 2);
else return 0; else return 0;
}, },
@@ -164,6 +164,24 @@ export const useCircuitStore = defineStore('CircuitStore', {
return ''; return '';
} }
}, },
getMaxAccumuloByUsername(
myuser: IUserFields,
circuitId: string,
username: string,
groupname?: string
): number | string {
if (myuser && myuser.profile.useraccounts) {
const account = myuser.profile.useraccounts.find(
(rec: IAccount) =>
(rec.username === username ||
(rec.groupname === groupname && groupname !== '')) &&
rec.circuitId === circuitId
);
return account ? account.qta_maxConcessa : 0;
} else {
return '';
}
},
SonoDentroAdAlmeno1CircuitoConFido(): boolean { SonoDentroAdAlmeno1CircuitoConFido(): boolean {
const userStore = useUserStore(); const userStore = useUserStore();
@@ -334,7 +352,7 @@ export const useCircuitStore = defineStore('CircuitStore', {
return false; return false;
}, },
updateListCircuit(visu: number) { updateListCircuit(visu: number, sortbyuser?: boolean) {
const userStore = useUserStore(); const userStore = useUserStore();
let arr: any[] = []; let arr: any[] = [];
@@ -367,6 +385,31 @@ export const useCircuitStore = defineStore('CircuitStore', {
} }
} }
if (sortbyuser) {
// Ordina i circuiti prima per quello provinciale, poi quello Italia, poi tutti gli altri
const circuitiMiaProvincia = this.getCircuitsByProvince(
userStore.my.profile.resid_province
);
const circuitiNazionali = this.getCircuitsNational();
// Crea Set di ID per lookup veloce
const provinciaIds = new Set(circuitiMiaProvincia.map((c) => c._id));
const nazionaliIds = new Set(circuitiNazionali.map((c) => c._id));
// Filtra arr in tre gruppi (mantenendo solo elementi già presenti in arr)
const fromProvincia = arr.filter((rec) => provinciaIds.has(rec._id));
const fromNazionali = arr.filter(
(rec) => nazionaliIds.has(rec._id) && !provinciaIds.has(rec._id)
);
const others = arr.filter(
(rec) => !provinciaIds.has(rec._id) && !nazionaliIds.has(rec._id)
);
// Ricomponi l'array nell'ordine desiderato
arr = [...fromProvincia, ...fromNazionali, ...others];
}
return arr; return arr;
}, },
@@ -1420,5 +1463,15 @@ export const useCircuitStore = defineStore('CircuitStore', {
return str; return str;
}, },
getTypeCircuit(circuit: ICircuit) {
if (circuit.isCircItalia) {
return 'Nazionale';
} else if (circuit.circuitoIndipendente) {
return 'Indipendente';
} else if (!circuit.circuitiExtraProv) {
return 'Provinciale';
}
},
}, },
}); });

View File

@@ -7464,55 +7464,47 @@ export const tools = {
$router: any, $router: any,
circuit: ICircuit, circuit: ICircuit,
sendcoinrec: ISendCoin sendcoinrec: ISendCoin
) { ): Promise<void> {
const userStore = useUserStore(); const userStore = useUserStore();
const notifStore = useNotifStore(); const notifStore = useNotifStore();
//T_TOLTO const { username } = userStore.my;
const username = userStore.my.username; // Origine e destinazione con fallback chain più leggibile
const orig = sendcoinrec.grouporig || sendcoinrec.contoComOrig || '';
const dest = sendcoinrec.groupdest || sendcoinrec.contoComDest || sendcoinrec.dest;
const orig = sendcoinrec.grouporig // Parametri comuni per i messaggi
? sendcoinrec.grouporig const msgParams = {
: sendcoinrec.contoComOrig
? sendcoinrec.contoComOrig
: '';
const dest = sendcoinrec.groupdest
? sendcoinrec.groupdest
: sendcoinrec.contoComDest
? sendcoinrec.contoComDest
: sendcoinrec.dest;
let msg = '';
if (orig) {
msg = t('circuit.question_sendcoinsto_from', {
coin: circuit.symbol,
from: orig,
dest,
qty: sendcoinrec.qty,
});
} else {
msg = t('circuit.question_sendcoinsto', {
coin: circuit.symbol, coin: circuit.symbol,
dest, dest,
qty: sendcoinrec.qty, qty: sendcoinrec.qty,
}); circuit: circuit.name,
} ...(orig && { from: orig }), // Aggiunge 'from' solo se esiste
};
return $q // Seleziona il messaggio appropriato
.dialog({ const messageKey = orig
message: msg, ? 'circuit.question_sendcoinsto_from'
: 'circuit.question_sendcoinsto';
// Mostra dialog di conferma
return new Promise((resolve, reject) => {
$q.dialog({
title: t('db.domanda'),
message: t(messageKey, msgParams),
ok: { ok: {
label: t('dialog.yes'), label: t('dialog.yes'),
color: 'primary',
push: true, push: true,
}, },
cancel: { cancel: {
label: t('dialog.cancel'), label: t('dialog.cancel'),
color: 'secondary',
}, },
title: t('db.domanda'),
}) })
.onOk(() => { .onOk(async () => {
return userStore try {
.setCircuitCmd( const res = await userStore.setCircuitCmd(
$q, $q,
t, t,
username, username,
@@ -7520,14 +7512,24 @@ export const tools = {
shared_consts.CIRCUITCMD.SENDCOINS_REQ, shared_consts.CIRCUITCMD.SENDCOINS_REQ,
true, true,
sendcoinrec sendcoinrec
) );
.then((res: any) => {
console.log('setCircuitCmd ', res); console.log('setCircuitCmd', res);
if (res && res.cansend) {
if (res.useraccounts && res.useraccounts.length > 0) { if (!res?.cansend) {
tools.showNegativeNotif($q, res?.errormsg || t('errors.generic'));
return resolve();
}
// Aggiorna gli account utente se presenti
if (res.useraccounts?.length > 0) {
userStore.my.profile.useraccounts = res.useraccounts; userStore.my.profile.useraccounts = res.useraccounts;
} }
$router.push('/circuits');
// Aggiorna dati e naviga
tools.updateMyData(res);
notifStore.updateNotification = true;
tools.showPositiveNotif( tools.showPositiveNotif(
$q, $q,
t('circuit.coins_sent', { t('circuit.coins_sent', {
@@ -7536,20 +7538,23 @@ export const tools = {
dest, dest,
}) })
); );
//tools.showPositiveNotif($q, t('circuit.coins_sendrequest_sent'))
} else {
tools.showNegativeNotif($q, res.errormsg);
}
notifStore.updateNotification = true;
tools.updateMyData(res);
});
});
$router.push('/circuits');
resolve();
} catch (error) {
console.error('Errore invio coins:', error);
tools.showNegativeNotif($q, t('errors.generic'));
reject(error);
}
})
.onCancel(() => resolve())
.onDismiss(() => resolve());
});
},
/* return await new Promise((resolve, reject) => { /* return await new Promise((resolve, reject) => {
resolve(false) resolve(false)
}) })
*/ */
},
cancelReqCircuit($q: any, username: string, circuitname: string, groupname?: string) { cancelReqCircuit($q: any, username: string, circuitname: string, groupname?: string) {
const userStore = useUserStore(); const userStore = useUserStore();

View File

@@ -15,6 +15,7 @@ import type {
ISignupIscrizioneConacreisOptions, ISignupIscrizioneConacreisOptions,
IMovQuery, IMovQuery,
IGroup, IGroup,
IMovVisu,
} from '@/model'; } from '@/model';
import { IFriends, ISettings } from '@/model'; import { IFriends, ISettings } from '@/model';
import { tools } from '@tools'; import { tools } from '@tools';
@@ -805,6 +806,10 @@ export const useUserStore = defineStore('UserStore', {
return this.my.profile.teleg_id! > 0 || this.my.profile.teleg_id_old! > 0; return this.my.profile.teleg_id! > 0 || this.my.profile.teleg_id_old! > 0;
}, },
isEmailVerified() {
return this.my.verified_email;
},
isUserOk(anchesenonammesso: boolean = false): boolean { isUserOk(anchesenonammesso: boolean = false): boolean {
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
@@ -828,7 +833,7 @@ export const useUserStore = defineStore('UserStore', {
this.isUsernameTelegOk() this.isUsernameTelegOk()
); );
} else { } else {
return this.my.verified_email!; return this.my.verified_email! || this.isTelegIdOk();
} }
} }
@@ -1028,7 +1033,7 @@ export const useUserStore = defineStore('UserStore', {
mydata.password = String(hashedPassword); mydata.password = String(hashedPassword);
return Api.SendReq('/updatepwd', 'POST', mydata, true, false, 1) return Api.SendReq('/updatepwd', 'POST', mydata, true, false, 1)
.then((res) => { .then((res: any) => {
return { code: res.data.code, msg: res.data.msg }; return { code: res.data.code, msg: res.data.msg };
}) })
.catch((error: Types.AxiosError) => { .catch((error: Types.AxiosError) => {
@@ -1057,7 +1062,7 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/setlang', 'PATCH', { data: mydata }) return Api.SendReq('/setlang', 'PATCH', { data: mydata })
.then((res) => { .then((res: any) => {
if (res) { if (res) {
return res.data.code === serv_constants.RIS_CODE_OK; return res.data.code === serv_constants.RIS_CODE_OK;
} }
@@ -1076,8 +1081,8 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING); this.setServerCode(tools.CALLING);
return Api.SendReq('/requestnewpwd', 'POST', usertosend) return Api.SendReq('/requestnewpwd', 'POST', usertosend)
.then((res) => ({ code: res.data.code, msg: res.data.msg, link: res.data.link })) .then((res: any) => ({ code: res.data.code, msg: res.data.msg, link: res.data.link }))
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return this.getServerCode; return this.getServerCode;
}); });
@@ -1094,12 +1099,12 @@ export const useUserStore = defineStore('UserStore', {
paramquery.password = String(hashedPassword); paramquery.password = String(hashedPassword);
return Api.SendReq('/addNewSite', 'POST', paramquery) return Api.SendReq('/addNewSite', 'POST', paramquery)
.then((res) => ({ .then((res: any) => ({
code: res.data.code, code: res.data.code,
msg: res.data.msg, msg: res.data.msg,
idapp: res.data.idapp, idapp: res.data.idapp,
})) }))
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return this.getServerCode; return this.getServerCode;
}); });
@@ -1115,7 +1120,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING); this.setServerCode(tools.CALLING);
return Api.SendReq('/vreg', 'POST', usertosend) return Api.SendReq('/vreg', 'POST', usertosend)
.then((res) => { .then((res: any) => {
// console.log("RITORNO 2 "); // console.log("RITORNO 2 ");
// mutations.setServerCode(myres); // mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_EMAIL_VERIFIED) { if (res.data.code === serv_constants.RIS_CODE_EMAIL_VERIFIED) {
@@ -1126,7 +1131,30 @@ export const useUserStore = defineStore('UserStore', {
} }
return { code: res.data.code, msg: res.data.msg }; return { code: res.data.code, msg: res.data.msg };
}) })
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() };
});
},
async reverif_email(paramquery: ILinkReg) {
const usertosend = {
idlink: paramquery.idlink,
};
console.log(usertosend);
this.setServerCode(tools.CALLING);
return Api.SendReq('/reverif_email', 'POST', usertosend)
.then((res: any) => {
if (res.data.code === serv_constants.RIS_CODE_EMAIL_VERIFIED) {
console.log('VERIFICATO !!');
tools.localStSetItem(toolsext.localStorage.verified_email, String(true));
} else {
console.log('Risultato di vreg: ', res.data.code);
}
return { code: res.data.code, msg: res.data.msg };
})
.catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() }; return { code: this.getServerCode, msg: error.getMsgError() };
}); });
@@ -1141,7 +1169,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING); this.setServerCode(tools.CALLING);
return Api.SendReq('/ammetti', 'POST', usertosend) return Api.SendReq('/ammetti', 'POST', usertosend)
.then((res) => { .then((res: any) => {
// console.log("RITORNO 2 "); // console.log("RITORNO 2 ");
// mutations.setServerCode(myres); // mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_AMMESSO) { if (res.data.code === serv_constants.RIS_CODE_AMMESSO) {
@@ -1151,7 +1179,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return { code: res.data.code, msg: res.data.msg }; return { code: res.data.code, msg: res.data.msg };
}) })
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() }; return { code: this.getServerCode, msg: error.getMsgError() };
}); });
@@ -1170,7 +1198,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING); this.setServerCode(tools.CALLING);
return Api.SendReq('/abcirc', 'POST', usertosend) return Api.SendReq('/abcirc', 'POST', usertosend)
.then((res) => { .then((res: any) => {
// console.log("RITORNO 2 "); // console.log("RITORNO 2 ");
// mutations.setServerCode(myres); // mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_CODE_AMMESSO) { if (res.data.code === serv_constants.RIS_CODE_AMMESSO) {
@@ -1178,9 +1206,13 @@ export const useUserStore = defineStore('UserStore', {
} else { } else {
console.log('Risultato di abilita: ', res.data.code); console.log('Risultato di abilita: ', res.data.code);
} }
return { code: res.data.code, msg: res.data.msg, circuitName: res.data.circuitName }; return {
code: res.data.code,
msg: res.data.msg,
circuitName: res.data.circuitName,
};
}) })
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return { code: this.getServerCode, msg: error.getMsgError() }; return { code: this.getServerCode, msg: error.getMsgError() };
}); });
@@ -1188,7 +1220,7 @@ export const useUserStore = defineStore('UserStore', {
async unsubscribe(paramquery: any) { async unsubscribe(paramquery: any) {
return Api.SendReq('/news/unsubscribe', 'POST', paramquery) return Api.SendReq('/news/unsubscribe', 'POST', paramquery)
.then((res) => { .then((res: any) => {
// console.log("RITORNO 2 "); // console.log("RITORNO 2 ");
// mutations.setServerCode(myres); // mutations.setServerCode(myres);
if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) { if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) {
@@ -1203,7 +1235,7 @@ export const useUserStore = defineStore('UserStore', {
async unsubscribe_news_on_fielduser(paramquery: any) { async unsubscribe_news_on_fielduser(paramquery: any) {
return Api.SendReq('/news/unsubscribe_user', 'POST', paramquery) return Api.SendReq('/news/unsubscribe_user', 'POST', paramquery)
.then((res) => { .then((res: any) => {
if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) { if (res.data.code === serv_constants.RIS_UNSUBSCRIBED_OK) {
console.log('DESOTTOSCRITTO ALLA NEWSLETTER !!'); console.log('DESOTTOSCRITTO ALLA NEWSLETTER !!');
} else { } else {
@@ -1216,16 +1248,16 @@ export const useUserStore = defineStore('UserStore', {
async importemail(paramquery: any) { async importemail(paramquery: any) {
return Api.SendReq('/news/import', 'POST', paramquery) return Api.SendReq('/news/import', 'POST', paramquery)
.then((res) => res) .then((res: any) => res)
.catch((error) => ({ numtot: 0, numadded: 0, numalreadyexisted: 0 })); .catch((error) => ({ numtot: 0, numadded: 0, numalreadyexisted: 0 }));
}, },
async importExtraList(paramquery: any) { async importExtraList(paramquery: any) {
return Api.SendReq('/users/import_extralist', 'POST', paramquery) return Api.SendReq('/users/import_extralist', 'POST', paramquery)
.then((res) => { .then((res: any) => {
return res; return res;
}) })
.catch((error) => { .catch((error: any) => {
return { numtot: 0, numadded: 0, numalreadyexisted: 0 }; return { numtot: 0, numadded: 0, numalreadyexisted: 0 };
}); });
}, },
@@ -1244,22 +1276,22 @@ export const useUserStore = defineStore('UserStore', {
null, null,
{ timeout: 300000 } { timeout: 300000 }
) )
.then((res) => { .then((res: any) => {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return false; return false;
}); });
}, },
async execDbOpUser(paramquery: any) { async execDbOpUser(paramquery: any) {
return Api.SendReq('/users/dbopuser', 'POST', paramquery) return Api.SendReq('/users/dbopuser', 'POST', paramquery)
.then((res) => { .then((res: any) => {
tools.updateMyData(res.data.ris); tools.updateMyData(res.data.ris);
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return false; return false;
}); });
}, },
@@ -1327,6 +1359,31 @@ export const useUserStore = defineStore('UserStore', {
} }
return await this.execDbOpUser({ mydata }); return await this.execDbOpUser({ mydata });
}, },
async setVerifiedEmail(val: boolean, userId?: string) {
const mydata = {
_id: userId ? userId : this.my._id,
dbop: 'verifiedemail',
value: val,
};
if (userId) {
} else {
if (this.my.verified_email !== val) {
this.my.verified_email = val;
}
}
return await this.execDbOpUser({ mydata });
},
async resendVerificationEmail() {
const mydata = {
_id: this.my._id,
dbop: 'resendVerificationEmail',
value: this.my.email,
};
return await this.execDbOpUser({ mydata });
},
async setPwdComeQuellaDellAdmin(val: boolean, userId?: string) { async setPwdComeQuellaDellAdmin(val: boolean, userId?: string) {
const mydata = { const mydata = {
_id: userId, _id: userId,
@@ -1388,10 +1445,11 @@ export const useUserStore = defineStore('UserStore', {
async getProvincesForMap() { async getProvincesForMap() {
return Api.SendReq('/users/infomap', 'POST', null) return Api.SendReq('/users/infomap', 'POST', null)
.then((res) => { .then((res: any) => {
return res ? res.data.ris : []; return res ? res.data.ris : [];
}) })
.catch((error) => { .catch((e: any) => {
console.error('Err getProvincesForMap:', e);
return null; return null;
}); });
}, },
@@ -1417,29 +1475,29 @@ export const useUserStore = defineStore('UserStore', {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return null; return null;
}); });
}, },
async reportload(paramquery: any) { async reportload(paramquery: any) {
return Api.SendReq('/report/load', 'POST', paramquery) return Api.SendReq('/report/load', 'POST', paramquery)
.then((res) => { .then((res: any) => {
// console.log('res', res) // console.log('res', res)
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return null; return null;
}); });
}, },
async newsletter_setactivate(paramquery: any) { async newsletter_setactivate(paramquery: any) {
return Api.SendReq('/news/setactivate', 'POST', paramquery) return Api.SendReq('/news/setactivate', 'POST', paramquery)
.then((res) => { .then((res: any) => {
// console.log('res', res) // console.log('res', res)
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return null; return null;
}); });
}, },
@@ -1599,7 +1657,7 @@ export const useUserStore = defineStore('UserStore', {
this.setServerCode(tools.CALLING); this.setServerCode(tools.CALLING);
return Api.SendReq('/users', 'POST', authData) return Api.SendReq('/users', 'POST', authData)
.then((res) => { .then((res: any) => {
const newuser = res.data; const newuser = res.data;
// console.log('newuser', newuser) // console.log('newuser', newuser)
@@ -1652,7 +1710,7 @@ export const useUserStore = defineStore('UserStore', {
return { code: toolsext.ERR_GENERICO, msg: '' }; return { code: toolsext.ERR_GENERICO, msg: '' };
} }
}) })
.catch((error) => { .catch((error: any) => {
console.log('Err', error); console.log('Err', error);
this.setErrorCatch(error); this.setErrorCatch(error);
return { code: this.getServerCode, msg: this.getMsg }; return { code: this.getServerCode, msg: this.getMsg };
@@ -1723,14 +1781,14 @@ export const useUserStore = defineStore('UserStore', {
authData.userId = this.my._id; authData.userId = this.my._id;
return Api.SendReq('/iscritti_conacreis', 'POST', authData) return Api.SendReq('/iscritti_conacreis', 'POST', authData)
.then((res) => { .then((res: any) => {
if (res.status === 200) { if (res.status === 200) {
return { code: serv_constants.RIS_ISCRIZIONE_OK, msg: '' }; return { code: serv_constants.RIS_ISCRIZIONE_OK, msg: '' };
} else { } else {
return { code: toolsext.ERR_GENERICO, msg: '' }; return { code: toolsext.ERR_GENERICO, msg: '' };
} }
}) })
.catch((error) => { .catch((error: any) => {
console.log('Err', error); console.log('Err', error);
this.setErrorCatch(error); this.setErrorCatch(error);
return { code: this.getServerCode, msg: this.getMsg }; return { code: this.getServerCode, msg: this.getMsg };
@@ -1771,7 +1829,7 @@ export const useUserStore = defineStore('UserStore', {
// console.log('executing login...') // console.log('executing login...')
return await Api.SendReq('/users/login', 'POST', usertosend, true, false, 0) return await Api.SendReq('/users/login', 'POST', usertosend, true, false, 0)
.then((res) => { .then((res: any) => {
myres = res; myres = res;
if (myres.status !== 200) { if (myres.status !== 200) {
@@ -1779,7 +1837,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return myres; return myres;
}) })
.then((res) => { .then((res: any) => {
console.log(' Login res', res); console.log(' Login res', res);
if (res.success) { if (res.success) {
@@ -1808,7 +1866,7 @@ export const useUserStore = defineStore('UserStore', {
return code; return code;
} }
}) })
.catch((error) => { .catch((error: any) => {
console.log('error', error); console.log('error', error);
this.setErrorCatch(error); this.setErrorCatch(error);
return this.getServerCode; return this.getServerCode;
@@ -1848,11 +1906,11 @@ export const useUserStore = defineStore('UserStore', {
this.clearAuthData(); this.clearAuthData();
return await Api.SendReq('/users/me/token', 'DELETE', null) return await Api.SendReq('/users/me/token', 'DELETE', null)
.then((res) => { .then((res: any) => {
console.log(res); console.log(res);
}) })
.then(() => this.clearAuthData()) .then(() => this.clearAuthData())
.catch((error) => { .catch((error: any) => {
this.setErrorCatch(error); this.setErrorCatch(error);
return this.getServerCode; return this.getServerCode;
}); });
@@ -1977,14 +2035,14 @@ export const useUserStore = defineStore('UserStore', {
}; };
return await Api.SendReq('/users/profile', 'POST', data) return await Api.SendReq('/users/profile', 'POST', data)
.then((ris) => { .then((ris: any) => {
if (this.my.username === ris.data.user.username) { if (this.my.username === ris.data.user.username) {
// this.updateDataFr(ris.data.friends) // this.updateDataFr(ris.data.friends)
} }
return ris.data.user; return ris.data.user;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2002,14 +2060,14 @@ export const useUserStore = defineStore('UserStore', {
}; };
return await Api.SendReq('/users/activities', 'POST', data) return await Api.SendReq('/users/activities', 'POST', data)
.then((ris) => { .then((ris: any) => {
if (this.my.username === ris.data.user.username) { if (this.my.username === ris.data.user.username) {
// this.updateDataFr(ris.data.friends) // this.updateDataFr(ris.data.friends)
} }
return ris.data.user; return ris.data.user;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2020,10 +2078,10 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/users/notifs', 'POST', data) return Api.SendReq('/users/notifs', 'POST', data)
.then((ris) => { .then((ris: any) => {
return ris.data; return ris.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2034,11 +2092,11 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/users/panel', 'POST', data) return Api.SendReq('/users/panel', 'POST', data)
.then((ris) => { .then((ris: any) => {
console.log('out:', ris); console.log('out:', ris);
return ris.data; return ris.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2050,11 +2108,11 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/users/receiveris', 'POST', data) return Api.SendReq('/users/receiveris', 'POST', data)
.then((ris) => { .then((ris: any) => {
console.log('out:', ris); console.log('out:', ris);
return ris.data; return ris.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2065,11 +2123,11 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/users/listlinkreg', 'POST', data) return Api.SendReq('/users/listlinkreg', 'POST', data)
.then((ris) => { .then((ris: any) => {
console.log('out:', ris); console.log('out:', ris);
return ris.data; return ris.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2081,10 +2139,10 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/mygroup/load', 'POST', data) return Api.SendReq('/mygroup/load', 'POST', data)
.then((res) => { .then((res: any) => {
return { data: res.data, status: res.status }; return { data: res.data, status: res.status };
}) })
.catch((error) => { .catch((error: any) => {
return { data: null, status: error.status }; return { data: null, status: error.status };
}); });
}, },
@@ -2098,7 +2156,7 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/circuit/load', 'POST', data) return Api.SendReq('/circuit/load', 'POST', data)
.then((res) => { .then((res: any) => {
this.my.profile.last_circuitpath = path; this.my.profile.last_circuitpath = path;
if (res && res.data.arrrecnotif) { if (res && res.data.arrrecnotif) {
notifStore.updateArrRecNotifFromServer(res.data.arrrecnotif); notifStore.updateArrRecNotifFromServer(res.data.arrrecnotif);
@@ -2111,7 +2169,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return { data: res.data, status: res.status }; return { data: res.data, status: res.status };
}) })
.catch((error) => { .catch((error: any) => {
return { data: null, status: error.status }; return { data: null, status: error.status };
}); });
}, },
@@ -2122,11 +2180,11 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/myskills/page', 'POST', data) return Api.SendReq('/myskills/page', 'POST', data)
.then((res) => { .then((res: any) => {
console.log('res.data', res); console.log('res.data', res);
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2139,10 +2197,10 @@ export const useUserStore = defineStore('UserStore', {
}; };
return Api.SendReq('/mygen/page', 'POST', data) return Api.SendReq('/mygen/page', 'POST', data)
.then((res) => { .then((res: any) => {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
console.error('err', error); console.error('err', error);
return null; return null;
}); });
@@ -2163,41 +2221,41 @@ export const useUserStore = defineStore('UserStore', {
async loadFriends() { async loadFriends() {
return Api.SendReq('/users/friends', 'POST', null) return Api.SendReq('/users/friends', 'POST', null)
.then((ris) => { .then((ris: any) => {
this.updateDataFr(ris.data); this.updateDataFr(ris.data);
return ris.data; return ris.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
async loadGroups(username: string) { async loadGroups(username: string) {
return Api.SendReq('/users/groups', 'POST', null) return Api.SendReq('/users/groups', 'POST', null)
.then((res) => { .then((res: any) => {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
async loadCircuits(nummovTodownload: number) { async loadCircuits(nummovTodownload: number) {
return Api.SendReq('/users/circuits', 'POST', { nummovTodownload }) return Api.SendReq('/users/circuits', 'POST', { nummovTodownload })
.then((res) => { .then((res: any) => {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
async loadAllAccounts() { async loadAllAccounts() {
return Api.SendReq('/account/loadall', 'POST', null) return Api.SendReq('/account/loadall', 'POST', null)
.then((res) => { .then((res: any) => {
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
return {}; return {};
}); });
}, },
@@ -2216,11 +2274,11 @@ export const useUserStore = defineStore('UserStore', {
cmd, cmd,
value, value,
}) })
.then((res) => { .then((res: any) => {
this.updateTables = true; this.updateTables = true;
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2240,11 +2298,11 @@ export const useUserStore = defineStore('UserStore', {
cmd, cmd,
value, value,
}) })
.then((res) => { .then((res: any) => {
this.updateTables = true; this.updateTables = true;
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2264,14 +2322,14 @@ export const useUserStore = defineStore('UserStore', {
cmd, cmd,
value, value,
}) })
.then((res) => { .then((res: any) => {
this.updateTables = true; this.updateTables = true;
// const notifStore = useNotifStore() // const notifStore = useNotifStore()
// notifStore.updateNotification = true // notifStore.updateNotification = true
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2294,7 +2352,7 @@ export const useUserStore = defineStore('UserStore', {
false, false,
0 0
) )
.then((res) => { .then((res: any) => {
this.updateTables = true; this.updateTables = true;
const notifStore = useNotifStore(); const notifStore = useNotifStore();
if (res.data.recnotif) { if (res.data.recnotif) {
@@ -2308,7 +2366,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return res.data; return res.data;
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2323,7 +2381,7 @@ export const useUserStore = defineStore('UserStore', {
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
return Api.SendReq('/users/mgt', 'POST', { mydata }) return Api.SendReq('/users/mgt', 'POST', { mydata })
.then((res) => { .then((res: any) => {
console.log('res', res); console.log('res', res);
let msgok = let msgok =
@@ -2354,7 +2412,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return false; return false;
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('cal.err_sendmsg')); tools.showNegativeNotif($q, t('cal.err_sendmsg'));
return {}; return {};
}); });
@@ -2399,7 +2457,7 @@ export const useUserStore = defineStore('UserStore', {
} }
$q.loading.hide(); $q.loading.hide();
}) })
.catch((error) => { .catch((error: any) => {
$q.loading.hide(); $q.loading.hide();
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
@@ -2421,7 +2479,7 @@ export const useUserStore = defineStore('UserStore', {
tab, tab,
value, value,
}) })
.then((res) => { .then((res: any) => {
if (res && res.data.state === 1) { if (res && res.data.state === 1) {
if (myrec) { if (myrec) {
if (!myrec.numfav) { if (!myrec.numfav) {
@@ -2451,7 +2509,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.favorite_unset')); tools.showNegativeNotif($q, t('cmd.favorite_unset'));
} }
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2472,7 +2530,7 @@ export const useUserStore = defineStore('UserStore', {
tab, tab,
value, value,
}) })
.then((res) => { .then((res: any) => {
if (res && res.data.state === 1) { if (res && res.data.state === 1) {
if (!myrec.numattend) { if (!myrec.numattend) {
myrec.numattend = 0; myrec.numattend = 0;
@@ -2498,7 +2556,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.attend_unset')); tools.showNegativeNotif($q, t('cmd.attend_unset'));
} }
}) })
.catch((error) => { .catch((error: any) => {
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
}); });
@@ -2546,7 +2604,7 @@ export const useUserStore = defineStore('UserStore', {
tab, tab,
value, value,
}) })
.then((res) => { .then((res: any) => {
if (res && res.data.state === 1) { if (res && res.data.state === 1) {
if (!myrec.numbook) { if (!myrec.numbook) {
myrec.numbook = 0; myrec.numbook = 0;
@@ -2572,7 +2630,7 @@ export const useUserStore = defineStore('UserStore', {
tools.showNegativeNotif($q, t('cmd.bookmark_unset')); tools.showNegativeNotif($q, t('cmd.bookmark_unset'));
} }
}) })
.catch((error) => { .catch((error: any) => {
console.error('error', error); console.error('error', error);
tools.showNegativeNotif($q, t('db.recfailed')); tools.showNegativeNotif($q, t('db.recfailed'));
return {}; return {};
@@ -2624,7 +2682,7 @@ export const useUserStore = defineStore('UserStore', {
tab, tab,
value, value,
}) })
.then((res) => { .then((res: any) => {
if (res && res.data.state === 1) { if (res && res.data.state === 1) {
if (value) { if (value) {
if (!myrec) { if (!myrec) {
@@ -2651,7 +2709,7 @@ export const useUserStore = defineStore('UserStore', {
} }
return null; return null;
}) })
.catch((error) => { .catch((error: any) => {
console.error('error', error); console.error('error', error);
return null; return null;
}); });
@@ -2671,7 +2729,7 @@ export const useUserStore = defineStore('UserStore', {
}, },
async eseguiFunzSulServer(mydata: {}) { async eseguiFunzSulServer(mydata: {}) {
return await this.execDbOp({ mydata }).then((ris) => { return await this.execDbOp({ mydata }).then((ris: any) => {
return ris?.data; return ris?.data;
}); });
}, },
@@ -2739,5 +2797,25 @@ export const useUserStore = defineStore('UserStore', {
} }
}); });
}, },
getLastRecentUserTransactions() {
const arr = [];
for (const rec of this.my.profile.last_my_transactions) {
let myuser = null;
if (rec.userfrom && rec.userfrom.username !== this.my.username) {
myuser = rec.userfrom;
} else if (rec.userto && rec.userto.username !== this.my.username) {
myuser = rec.userto;
}
if (myuser) {
arr.push(myuser);
}
}
const uniques = arr.filter((v, i, a) => a.findIndex(t => (t.username === v.username)) === i);
return uniques;
},
}, },
}); });

View File

@@ -0,0 +1,6 @@
.mypanel {
padding: 10px;
margin: 10px;
}

View File

@@ -0,0 +1,77 @@
import { defineComponent, ref } from 'vue'
import { serv_constants } from '../../../store/Modules/serv_constants'
import type { ILinkReg } from '../../../model/other';
import { ICallResult } from '../../../model/other'
import { CSigninNoreg } from '../../../components/CSigninNoreg'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { useGlobalStore } from '@store/globalStore'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@store/UserStore'
import { tools } from '@tools'
export default defineComponent({
name: 'reverif_email',
components: { CSigninNoreg },
setup(props) {
const $q = useQuasar()
const route = useRoute()
const $router = useRouter()
const { t } = useI18n()
const globalStore = useGlobalStore()
const userStore = useUserStore()
const risultato = ref('---')
const riscode = ref(0)
function myrisultato() {
return risultato
}
function giaverificato() {
return riscode.value !== serv_constants.RIS_CODE_EMAIL_VERIFIED
}
function verificatook() {
return riscode.value === serv_constants.RIS_CODE_EMAIL_VERIFIED
}
function load() {
console.log('load REFERIFY')
let param: ILinkReg = { idlink: '' }
if (route.query.idlink)
param = { idlink: route.query.idlink.toString() }
return userStore.reverif_email(param)
.then((ris: any) => {
riscode.value = ris.code
risultato.value = ris.msg
console.log('RIS = ', ris)
if (verificatook()) {
setTimeout(() => {
$router.replace('/signin')
}, 2000)
}
}).catch((err: any) => {
console.log('ERR = ' + err)
})
}
load()
return {
tools,
verificatook,
giaverificato,
myrisultato,
t,
}
},
})

View File

@@ -0,0 +1,58 @@
<template>
<q-page
padding
class="vreg"
>
<div class="q-pa-md q-gutter-sm">
<q-banner
rounded
class="bg-primary text-white"
color="primary q-title"
style="text-align: center"
>
<span class="mybanner">{{ t('reg.title_verif_email') }}</span>
</q-banner>
<br />
<transition
enter-active-class="animated fadeIn"
leave-active-class="animated fadeOut"
appear
>
<div>
<q-banner
rounded
class="bg-warning text-black"
style="text-align: center"
v-if="giaverificato()"
>
<span class="mybanner">{{ myrisultato() }}</span>
</q-banner>
<q-banner
class="bg-positive text-white"
style="text-align: center"
rounded
v-if="verificatook()"
>
<span class="mybanner">{{ myrisultato() }}</span>
</q-banner>
</div>
</transition>
<div class="text-center q-mt-md">
<q-btn
rounded
size="lg"
color="primary"
@click="tools.openrighttoolbar()"
>{{ t('login.enter') }}
</q-btn>
</div>
</div>
</q-page>
</template>
<script lang="ts" src="./reverif_email.ts"></script>
<style lang="scss" scoped>
@import './reverif_email.scss';
</style>

View File

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

View File

@@ -0,0 +1,25 @@
$heightBtn: 100%;
.card .product-image {
height: 300px;
}
.container{
margin-top: 4px;
margin-bottom: 4px;
}
.prod_trov{
font-style: italic;
color: blue;
}
.fixed-group {
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: #ffffff; /* Customize the background color as needed */
z-index: 1000; /* Adjust the z-index to ensure it's above other elements */
transition: all 1s ease;
}

View File

@@ -0,0 +1,162 @@
import { defineComponent, onMounted, ref, watch, computed, onBeforeUnmount } from 'vue'
import { tools } from '@tools'
import { useUserStore } from '@store/UserStore'
import { useRouter } from 'vue-router'
import { useGlobalStore } from '@store/globalStore'
import { useProducts } from '@store/Products'
import { useI18n } from 'vue-i18n'
import { toolsext } from '@store/Modules/toolsext'
import { useQuasar } from 'quasar'
import { costanti } from '@costanti'
import { shared_consts } from '@/common/shared_vuejs'
import OllamaChat from '@/components/OllamaChat/OllamaChat.vue'
import type { IProduct } from '@/model'
export default defineComponent({
name: 'ollama',
components: { OllamaChat },
props: {},
setup() {
const userStore = useUserStore()
const globalStore = useGlobalStore()
const productStore = useProducts()
const router = useRouter()
const $q = useQuasar()
const { t } = useI18n()
const search = ref('')
const cosa = ref(0)
const cat = ref('')
const loadpage = ref(false)
const refreshpage = ref(false)
const arrProducts = ref<any>([])
// Create a ref for the component to fix
const componentToFixRef = ref(<any>null);
const isFixed = ref(false);
// Register the scroll event on component mount
const handleScroll = () => {
const scrollTop = window.scrollY || document.documentElement.scrollTop;
// Set a threshold value based on how much scroll is needed to fix the components
const threshold = 300;
// Update the isFixed ref based on the scroll position
isFixed.value = scrollTop > threshold;
};
watch(() => cat.value, (newval, oldval) => {
calcArrProducts()
})
watch(() => search.value, (newval, oldval) => {
calcArrProducts()
if (tools.scrollTop() > 300) {
tools.scrollToTopValue(300)
}
})
watch(() => cosa.value, (newval, oldval) => {
tools.setCookie(tools.COOK_COSA_PRODOTTI, cosa.value.toString())
if (cosa.value !== shared_consts.PROD.TUTTI)
cat.value = ''
calcArrProducts()
})
function calcArrProducts() {
refreshpage.value = true
let arrprod = productStore.getProducts(cosa.value)
const catstr = cat.value;
const lowerSearchText = search.value.toLowerCase().trim();
if ((!lowerSearchText || (lowerSearchText && lowerSearchText.length < 2)) && !catstr) {
} else {
arrprod = arrprod.filter((product: IProduct) => {
const lowerName = product.productInfo.name!.toLowerCase();
const hasCategoria = !catstr || (catstr && product.productInfo.idCatProds?.includes(catstr));
// Use a regular expression to match whole words
const codeMatch = new RegExp(`\\b${lowerSearchText}\\b`, 'i');
const nameMatch = new RegExp(`\\b${lowerSearchText}`, 'i');
// Check if any word in lowerName starts with lowerSearchText
const anyWordStartsWithSearch = lowerName.split(/\s+/).some(word => nameMatch.test(word));
return (codeMatch.test(product.productInfo.code) || anyWordStartsWithSearch) && hasCategoria;
});
}
arrProducts.value = arrprod
refreshpage.value = false
}
/*function getProducts() {
let arrprod = productStore.getProducts(cosa.value)
if (!search.value) {
return arrprod
}
let lowerSearchText = search.value.toLowerCase();
let catstr = cat.value;
return arrprod.filter((product: IProduct) => {
let lowerName = product.productInfo.name!.toLowerCase();
const hasCategoria = !catstr || (catstr && product.productInfo.idCatProds?.includes(catstr));
return (product.productInfo.code!.includes(search.value) || lowerName.includes(lowerSearchText)) && hasCategoria
});
}*/
async function mounted() {
loadpage.value = false
await productStore.loadProducts(true)
cosa.value = tools.getCookie(tools.COOK_COSA_PRODOTTI, shared_consts.PROD.TUTTI, true)
// Inizializza
loadpage.value = true
window.addEventListener('scroll', handleScroll);
calcArrProducts()
}
// Remove the event listener on component destroy
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll);
});
function getCatProds() {
const arrcat = productStore.getCatProds(cosa.value)
const riscat: any = [{ label: 'Tutti', value: '', icon: undefined, color: undefined }]
for (const rec of arrcat) {
riscat.push({ label: rec.name, value: rec._id, icon: rec.icon, color: rec.color })
}
return riscat
}
onMounted(mounted)
return {
userStore,
costanti,
tools,
toolsext,
search,
cosa,
shared_consts,
getCatProds,
cat,
productStore,
t,
loadpage,
refreshpage,
componentToFixRef,
isFixed,
arrProducts,
}
}
})

View File

@@ -0,0 +1,18 @@
<template>
<q-page class="column">
<div class="text-center">
<q-spinner v-if="!loadpage" color="primary" size="3em" :thickness="2" />
</div>
<div v-if="loadpage && (tools.isUserOk() && tools.isLogged())">
<OllamaChat :baseUrl="tools.getServerHost()" height="100%"
></OllamaChat>
</div>
</q-page>
</template>
<script lang="ts" src="./ollama.ts">
</script>
<style lang="scss" scoped>
@import './ollama.scss';
</style>

View File

@@ -1,249 +1,295 @@
import { CMyFieldDb } from '@/components/CMyFieldDb' import { CMyFieldDb } from '@/components/CMyFieldDb';
import { CMyFieldRec } from '@/components/CMyFieldRec' import { CMyFieldRec } from '@/components/CMyFieldRec';
import { CTitleBanner } from '@/components/CTitleBanner' import { CTitleBanner } from '@/components/CTitleBanner';
import { CProfile } from '@/components/CProfile' import { CProfile } from '@/components/CProfile';
import { CLabel } from '@/components/CLabel' import { CLabel } from '@/components/CLabel';
import { CCopyBtn } from '@/components/CCopyBtn' import { CCopyBtn } from '@/components/CCopyBtn';
import { CSkill } from '@/components/CSkill' import { CSkill } from '@/components/CSkill';
import { CDateTime } from '@/components/CDateTime' import { CDateTime } from '@/components/CDateTime';
import { CMyGroup } from '@/components/CMyGroup' import { CMyGroup } from '@/components/CMyGroup';
import { CUserNote } from '@/components/CUserNote' import { CUserNote } from '@/components/CUserNote';
import { CMyCircuit } from '@/components/CMyCircuit' import { CMyCircuit } from '@/components/CMyCircuit';
import { CNotifAtTop } from '@/components/CNotifAtTop' import { CNotifAtTop } from '@/components/CNotifAtTop';
import { CMyActivities } from '@/components/CMyActivities' import { CMyActivities } from '@/components/CMyActivities';
import { CSendCoins } from '@/components/CSendCoins' import { CSendCoins } from '@/components/CSendCoins';
import { CContactUser } from '@/components/CContactUser' import { CContactUser } from '@/components/CContactUser';
import { CTimeAgo } from '@/components/CTimeAgo' import { CTimeAgo } from '@/components/CTimeAgo';
import { CMyUser } from '@/components/CMyUser' import { CMyUser } from '@/components/CMyUser';
import { CUserNonVerif } from '@/components/CUserNonVerif' import { CUserNonVerif } from '@/components/CUserNonVerif';
import { CCheckIfIsLogged } from '@/components/CCheckIfIsLogged' import { CCheckIfIsLogged } from '@/components/CCheckIfIsLogged';
import CPageUserNotFound from '@/components/CPageUserNotFound/CPageUserNotFound.vue' import CPageUserNotFound from '@/components/CPageUserNotFound/CPageUserNotFound.vue';
import { tools } from '@tools' import { tools } from '@tools';
import { computed, defineComponent, onMounted, ref, watch } from 'vue' import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { useUserStore } from '@store/UserStore' import { useUserStore } from '@store/UserStore';
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router';
import { useGlobalStore } from '@store/globalStore' import { useGlobalStore } from '@store/globalStore';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n';
import { toolsext } from '@store/Modules/toolsext' import { toolsext } from '@store/Modules/toolsext';
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar';
import { costanti } from '@costanti' import { costanti } from '@costanti';
import type { IMyCircuit, IMyGroup, IUserFields } from 'model'; import type { IMyCircuit, IMyGroup, IUserFields } from 'model';
import { ICircuit, IFriends } from 'model' import { ICircuit, IFriends } from 'model';
import { shared_consts } from '@/common/shared_vuejs' import { shared_consts } from '@/common/shared_vuejs';
import { static_data } from '@/db/static_data' import { static_data } from '@/db/static_data';
import { fieldsTable } from '@store/Modules/fieldsTable' import { fieldsTable } from '@store/Modules/fieldsTable';
import { useNotifStore } from '@store/NotifStore' import { useNotifStore } from '@store/NotifStore';
import MixinUsers from '@/mixins/mixin-users' import MixinUsers from '@/mixins/mixin-users';
export default defineComponent({ export default defineComponent({
name: 'myprofile', name: 'myprofile',
components: { components: {
CProfile, CTitleBanner, CMyFieldDb, CSkill, CDateTime, CCopyBtn, CUserNonVerif, CMyFieldRec, CMyUser, CProfile,
CMyGroup, CLabel, CMyCircuit, CSendCoins, CNotifAtTop, CTitleBanner,
CCheckIfIsLogged, CTimeAgo, CContactUser, CMyActivities, CUserNote, CPageUserNotFound, CMyFieldDb,
CSkill,
CDateTime,
CCopyBtn,
CUserNonVerif,
CMyFieldRec,
CMyUser,
CMyGroup,
CLabel,
CMyCircuit,
CSendCoins,
CNotifAtTop,
CCheckIfIsLogged,
CTimeAgo,
CContactUser,
CMyActivities,
CUserNote,
CPageUserNotFound,
}, },
props: {}, props: {},
setup() { setup() {
const userStore = useUserStore() const userStore = useUserStore();
const globalStore = useGlobalStore() const globalStore = useGlobalStore();
const $route = useRoute() const $route = useRoute();
const $q = useQuasar() const $q = useQuasar();
const { t } = useI18n() const { t } = useI18n();
const site = ref(globalStore.site) const site = ref(globalStore.site);
const { getRefLink } = MixinUsers() const { getRefLink } = MixinUsers();
const animation = ref('fade') const animation = ref('fade');
const spinner_visible = ref(false) const shownote = ref(false);
const shownote = ref(false) const usersList = ref({ show: false, title: '', list: [] });
const usersList = ref({ show: false, title: '', list: [] })
const username = computed(() => $route.params.username ? $route.params.username.toString() : userStore.my.username) const username = computed(() =>
const idnotif = computed(() => $route.query.idnotif ? $route.query.idnotif.toString() : '') $route.params.username ? $route.params.username.toString() : userStore.my.username
const isDebugOn = computed(() => tools.isDebugOn()) );
const idnotif = computed(() =>
$route.query.idnotif ? $route.query.idnotif.toString() : ''
);
const isDebugOn = computed(() => tools.isDebugOn());
const sendRIS = computed(() => $route.query.sr ? $route.query.sr : '') const sendRIS = computed(() => ($route.query.sr ? $route.query.sr : ''));
const causalDest = computed(() => $route.query.cd ? $route.query.cd : '') const causalDest = computed(() => ($route.query.cd ? $route.query.cd : ''));
const $router = useRouter() const $router = useRouter();
const filtroutente = ref(<any[]>[]) const filtroutente = ref(<any[]>[]);
const showPic = ref(false) const showPic = ref(false);
const caricato = ref(false) const caricato = ref(false);
const showsendCoinTo = ref(false) const showsendCoinTo = ref(false);
const showinghand = ref(false) const showinghand = ref(false);
const actualcard = ref('mygoods') const actualcard = ref('mygoods');
const mostranota = ref(false) const mostranota = ref(false);
const notifStore = useNotifStore() const notifStore = useNotifStore();
const quantiHandShake = computed(() => (userStore.userprofile.profile.handshake ? userStore.userprofile.profile.handshake.length : 0) + ' ' + t('handshake.strettedimano')) const quantiHandShake = computed(
const handshake_inCommon = computed(() => userStore.getMyHandshakeInCommon(userStore.userprofile)) () =>
const quanteHandShakeInCommon = computed(() => (handshake_inCommon.value ? handshake_inCommon.value.length : 0) + ' ' + t('handshake.incommon')) (userStore.userprofile.profile.handshake
? userStore.userprofile.profile.handshake.length
: 0) +
' ' +
t('handshake.strettedimano')
);
const handshake_inCommon = computed(() =>
userStore.getMyHandshakeInCommon(userStore.userprofile)
);
const quanteHandShakeInCommon = computed(
() =>
(handshake_inCommon.value ? handshake_inCommon.value.length : 0) +
' ' +
t('handshake.incommon')
);
const mycards = computed(() => { const mycards = computed(() => {
return costanti.MAINCARDS.filter((rec: any) => rec.table) return costanti.MAINCARDS.filter((rec: any) => rec.table);
}) });
const listgroupsfiltered = ref(<IMyGroup[]>[]) const listgroupsfiltered = ref(<IMyGroup[]>[]);
const listcircuitsfiltered = ref(<IMyCircuit[]>[]) const listcircuitsfiltered = ref(<IMyCircuit[]>[]);
const tab = ref('') const tab = ref('');
function profile() { function profile() {
return userStore.my.profile return userStore.my.profile;
} }
function myusername() { function myusername() {
return userStore.my.username return userStore.my.username;
} }
async function loadProfile() { async function loadProfile() {
console.log('loadProfile...', username.value) console.log('loadProfile...', username.value);
try { try {
caricato.value = false;
caricato.value = false if (sendRIS.value) {
$q.loading.show({
if (sendRIS.value) message: 'Caricamento in corso...',
spinner_visible.value = true spinnerColor: 'white',
backgroundColor: 'black',
messageColor: 'white',
});
}
// Carica il profilo di quest'utente // Carica il profilo di quest'utente
if (username.value) { if (username.value) {
await userStore.loadUserProfile({ username: username.value, idnotif: idnotif.value }).then((ris) => { await userStore
console.log('loadUserProfile = ', ris) .loadUserProfile({ username: username.value, idnotif: idnotif.value })
userStore.userprofile = ris .then((ris) => {
console.log('loadUserProfile = ', ris);
userStore.userprofile = ris;
if (userStore.userprofile) { if (userStore.userprofile) {
filtroutente.value = [{ userId: userStore.userprofile._id }] filtroutente.value = [{ userId: userStore.userprofile._id }];
notifStore.setAsRead(idnotif.value) notifStore.setAsRead(idnotif.value);
try { try {
if (userStore.userprofile) if (userStore.userprofile)
listgroupsfiltered.value = globalStore.mygroups.filter((grp: IMyGroup) => userStore.userprofile.profile.mygroups.findIndex((rec: IMyGroup) => rec.groupname === grp.groupname) >= 0) listgroupsfiltered.value = globalStore.mygroups.filter(
(grp: IMyGroup) =>
userStore.userprofile.profile.mygroups.findIndex(
(rec: IMyGroup) => rec.groupname === grp.groupname
) >= 0
);
} catch (e) { } catch (e) {
listgroupsfiltered.value = [] listgroupsfiltered.value = [];
} }
try { try {
listcircuitsfiltered.value = userStore.userprofile.profile.mycircuits listcircuitsfiltered.value = userStore.userprofile.profile.mycircuits;
} catch (e) { } catch (e) {
listcircuitsfiltered.value = [] listcircuitsfiltered.value = [];
} }
} }
}) });
caricato.value = true caricato.value = true;
} }
} catch (e) { } catch (e) {
console.error('ERR loadProfile', e) console.error('ERR loadProfile', e);
} }
} }
watch(() => username.value, (to: any, from: any) => { watch(
loadProfile() () => username.value,
}) (to: any, from: any) => {
loadProfile();
}
);
watch(() => actualcard.value, (to: any, from: any) => { watch(
loadProfile() () => actualcard.value,
}) (to: any, from: any) => {
loadProfile();
}
);
function mounted() { function mounted() {
tab.value = tools.isRisoApp() ? 'attivita' : 'info' tab.value = tools.isRisoApp() ? 'attivita' : 'info';
loadProfile() loadProfile();
} }
function getImgUser() { function getImgUser() {
if (userStore.userprofile) if (userStore.userprofile) return userStore.getImgByProfile(userStore.userprofile);
return userStore.getImgByProfile(userStore.userprofile) else return '';
else
return ''
} }
function checkifShow(col: string) { function checkifShow(col: string) {
//++Todo: checkifShow Permessi ! //++Todo: checkifShow Permessi !
return true return true;
} }
function isMyRecord(username: string) { function isMyRecord(username: string) {
return username === userStore.my.username return username === userStore.my.username;
} }
function getLinkWebSite() { function getLinkWebSite() {
if (userStore.userprofile) { if (userStore.userprofile) {
let mysite = userStore.userprofile.profile.website let mysite = userStore.userprofile.profile.website;
if (mysite) { if (mysite) {
if (!mysite.startsWith('http')) { if (!mysite.startsWith('http')) {
mysite = 'https://' + mysite mysite = 'https://' + mysite;
} }
} }
return mysite return mysite;
} else { } else {
return '' return '';
} }
} }
onMounted(mounted) onMounted(mounted);
function gotoPage(link: string) { function gotoPage(link: string) {
$router.push(link) $router.push(link);
} }
function getlinkpage() { function getlinkpage() {
if (userStore.userprofile) if (userStore.userprofile)
return self.location.host + '/my/' + userStore.userprofile.username return self.location.host + '/my/' + userStore.userprofile.username;
else else return '';
return ''
} }
function showed() { function showed() {
spinner_visible.value = false $q.loading.hide()
} }
function salvaUserProv(userprofile: IUserFields) { function salvaUserProv(userprofile: IUserFields) {
if (userprofile) if (userprofile) userStore.userprofile = userprofile;
userStore.userprofile = userprofile
} }
function saveDaContattare() { function saveDaContattare() {
const globalStore = useGlobalStore() const globalStore = useGlobalStore();
const { t } = useI18n() const { t } = useI18n();
const mydatatosave = { const mydatatosave = {
id: userStore.userprofile._id, id: userStore.userprofile._id,
table: 'users', table: 'users',
fieldsvalue: { 'profile.da_contattare': userStore.userprofile.profile.da_contattare } fieldsvalue: {
} 'profile.da_contattare': userStore.userprofile.profile.da_contattare,
},
};
globalStore.saveFieldValue(mydatatosave).then((esito) => { globalStore.saveFieldValue(mydatatosave).then((esito) => {
if (esito) { if (esito) {
tools.showPositiveNotif($q, t('db.recupdated')) tools.showPositiveNotif($q, t('db.recupdated'));
} else { } else {
tools.showNegativeNotif($q, t('db.recfailed')) tools.showNegativeNotif($q, t('db.recfailed'));
} }
}) });
} }
function savePerm() { function savePerm() {
const globalStore = useGlobalStore() const globalStore = useGlobalStore();
const { t } = useI18n() const { t } = useI18n();
const mydatatosave = { const mydatatosave = {
id: userStore.userprofile._id, id: userStore.userprofile._id,
table: 'users', table: 'users',
fieldsvalue: { 'perm': userStore.userprofile.perm } fieldsvalue: { perm: userStore.userprofile.perm },
} };
globalStore.saveFieldValue(mydatatosave).then((esito) => { globalStore.saveFieldValue(mydatatosave).then((esito) => {
if (esito) { if (esito) {
tools.showPositiveNotif($q, t('db.recupdated')) tools.showPositiveNotif($q, t('db.recupdated'));
} else { } else {
tools.showNegativeNotif($q, t('db.recfailed')) tools.showNegativeNotif($q, t('db.recfailed'));
} }
}) });
} }
return { return {
@@ -285,7 +331,6 @@ export default defineComponent({
gotoPage, gotoPage,
sendRIS, sendRIS,
causalDest, causalDest,
spinner_visible,
showed, showed,
tab, tab,
shownote, shownote,
@@ -293,6 +338,6 @@ export default defineComponent({
salvaUserProv, salvaUserProv,
saveDaContattare, saveDaContattare,
savePerm, savePerm,
} };
} },
}) });

View File

@@ -1,10 +1,15 @@
<template> <template>
<div v-if="isDebugOn && false" class="bg-red text-white"> <div
v-if="isDebugOn && false"
class="bg-red text-white"
>
getImgUser(): {{ getImgUser() }} getImgUser(): {{ getImgUser() }}
<span v-if="!!tools.isLogged()">Logged: {{ tools.isLogged() }}</span> - <span v-if="!!tools.isLogged()">Logged: {{ tools.isLogged() }}</span> -
<span v-if="!!tools.isUserOk()">UserOk: {{ tools.isUserOk() }}</span> - <span v-if="!!tools.isUserOk()">UserOk: {{ tools.isUserOk() }}</span> -
<span v-if="!!userStore.userprofile.date_reg">Date_Reg: {{ tools.getstrDate(userStore.userprofile.date_reg) }}</span> - <span v-if="!!userStore.userprofile.date_reg"
<span>tools.isEmailVerified(): {{ tools.isEmailVerified() }}</span> - >Date_Reg: {{ tools.getstrDate(userStore.userprofile.date_reg) }}</span
>
- <span>tools.isEmailVerified(): {{ tools.isEmailVerified() }}</span> -
<span <span
>tools.getLinkUserTelegramByUser(): >tools.getLinkUserTelegramByUser():
{{ tools.getLinkUserTelegramByUser(userStore.userprofile) }}</span {{ tools.getLinkUserTelegramByUser(userStore.userprofile) }}</span
@@ -18,8 +23,17 @@
v-if="!caricato" v-if="!caricato"
class="fit column no-wrap justify-evenly items-center content-start" class="fit column no-wrap justify-evenly items-center content-start"
> >
<q-skeleton type="QAvatar" size="140px" height="140px" animation="fade" /> <q-skeleton
<q-card flat bordered style="width: 250px"> type="QAvatar"
size="140px"
height="140px"
animation="fade"
/>
<q-card
flat
bordered
style="width: 250px"
>
<div class="text-h6"> <div class="text-h6">
<q-skeleton :animation="animation" /> <q-skeleton :animation="animation" />
</div> </div>
@@ -62,9 +76,7 @@
>online</q-badge >online</q-badge
> >
<q-badge <q-badge
v-if=" v-if="userStore.IsHandShakeByUsername(userStore.userprofile.username)"
userStore.IsHandShakeByUsername(userStore.userprofile.username)
"
align="bottom" align="bottom"
floating floating
color="red" color="red"
@@ -101,9 +113,7 @@
{{ userStore.userprofile.username }} {{ userStore.userprofile.username }}
</div> </div>
<div <div
v-if=" v-if="userStore.userprofile && userStore.userprofile.profile.qualifica"
userStore.userprofile && userStore.userprofile.profile.qualifica
"
class="col-12 text-h8 q-mt-sm" class="col-12 text-h8 q-mt-sm"
> >
<span v-if="userStore.userprofile.profile.qualifica"> <span v-if="userStore.userprofile.profile.qualifica">
@@ -115,9 +125,7 @@
</span> </span>
</div> </div>
<div <div
v-if=" v-if="userStore.userprofile && userStore.userprofile.profile.biografia"
userStore.userprofile && userStore.userprofile.profile.biografia
"
class="col-12 text-h8 q-mt-sm" class="col-12 text-h8 q-mt-sm"
> >
{{ userStore.userprofile.profile.biografia }} {{ userStore.userprofile.profile.biografia }}
@@ -185,19 +193,12 @@
</CTitleBanner> </CTitleBanner>
</div> </div>
<div>
<q-inner-loading :showing="spinner_visible">
<q-spinner-tail size="3em" color="primary" />
</q-inner-loading>
</div>
<div v-if="site && site.confpages && site.confpages.showNameSurname"> <div v-if="site && site.confpages && site.confpages.showNameSurname">
<div class="text-h6"> <div class="text-h6">
<span v-if="checkifShow('name') && userStore.userprofile.name"> <span v-if="checkifShow('name') && userStore.userprofile.name">
{{ userStore.userprofile.name }}</span {{ userStore.userprofile.name }}</span
> >
<span <span v-if="checkifShow('surname') && userStore.userprofile.surname"
v-if="checkifShow('surname') && userStore.userprofile.surname"
>&nbsp;{{ userStore.userprofile.surname }}</span >&nbsp;{{ userStore.userprofile.surname }}</span
> >
</div> </div>
@@ -206,10 +207,7 @@
{{ userStore.userprofile.username }} {{ userStore.userprofile.username }}
</div> </div>
<div <div
v-if=" v-if="userStore.userprofile.profile.qualifica && userStore.userprofile._id"
userStore.userprofile.profile.qualifica &&
userStore.userprofile._id
"
class="col-12 text-h8 q-mt-sm" class="col-12 text-h8 q-mt-sm"
> >
<span v-if="userStore.userprofile.profile.qualifica"> <span v-if="userStore.userprofile.profile.qualifica">
@@ -258,14 +256,28 @@
" "
class="col-12 text-h8 q-mt-sm" class="col-12 text-h8 q-mt-sm"
> >
<div v-if="!mostranota" class="text-center"> <div
<q-btn label="Note" @click="mostranota = true" color="green"> v-if="!mostranota"
<q-badge color="red" floating>1</q-badge> class="text-center"
>
<q-btn
label="Note"
@click="mostranota = true"
color="green"
>
<q-badge
color="red"
floating
>1</q-badge
>
</q-btn> </q-btn>
</div> </div>
<div v-else> <div v-else>
<strong>Note del Facilitatore</strong>:<br /> <strong>Note del Facilitatore</strong>:<br />
<q-banner rounded class="bg-green-8 text-white"> <q-banner
rounded
class="bg-green-8 text-white"
>
<div class="text-h7 text-center"> <div class="text-h7 text-center">
{{ userStore.userprofile.profile.note }} {{ userStore.userprofile.profile.note }}
</div> </div>
@@ -329,9 +341,7 @@
<q-chip <q-chip
v-if=" v-if="
userStore.IsHandShakeByMe(userStore.userprofile) && userStore.IsHandShakeByMe(userStore.userprofile) &&
userStore.IsHandShakeByUsername( userStore.IsHandShakeByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
color="green" color="green"
dense dense
@@ -341,11 +351,9 @@
icon="fas fa-handshake" icon="fas fa-handshake"
> >
<span <span
>&nbsp;<em >&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
class="q-pa-xxs text-white rounded-borders shadow-2"
>
{{ {{
$t("db.both_fiducia", { $t('db.both_fiducia', {
username: userStore.userprofile.username, username: userStore.userprofile.username,
}) })
}} }}
@@ -355,9 +363,7 @@
<q-chip <q-chip
v-else-if=" v-else-if="
!userStore.IsHandShakeByMe(userStore.userprofile) && !userStore.IsHandShakeByMe(userStore.userprofile) &&
userStore.IsHandShakeByUsername( userStore.IsHandShakeByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
color="blue" color="blue"
dense dense
@@ -367,11 +373,9 @@
icon="fas fa-handshake" icon="fas fa-handshake"
> >
<span <span
>&nbsp;<em >&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
class="q-pa-xxs text-white rounded-borders shadow-2"
>
{{ {{
$t("db.handshake_him", { $t('db.handshake_him', {
username: userStore.userprofile.username, username: userStore.userprofile.username,
}) })
}} }}
@@ -381,9 +385,7 @@
<q-chip <q-chip
v-else-if=" v-else-if="
userStore.IsHandShakeByMe(userStore.userprofile) && userStore.IsHandShakeByMe(userStore.userprofile) &&
!userStore.IsHandShakeByUsername( !userStore.IsHandShakeByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
color="blue" color="blue"
dense dense
@@ -393,11 +395,9 @@
icon="fas fa-handshake" icon="fas fa-handshake"
> >
<span <span
>&nbsp;<em >&nbsp;<em class="q-pa-xxs text-white rounded-borders shadow-2">
class="q-pa-xxs text-white rounded-borders shadow-2"
>
{{ {{
$t("db.handshake_you", { $t('db.handshake_you', {
username: userStore.userprofile.username, username: userStore.userprofile.username,
}) })
}} }}
@@ -407,21 +407,13 @@
</div> </div>
<!--HANDSHAKE--> <!--HANDSHAKE-->
<div <div v-if="!isMyRecord(userStore.userprofile.username) && tools.isUserOk()">
v-if="
!isMyRecord(userStore.userprofile.username) && tools.isUserOk()
"
>
<div <div
class="row centeritems q-pa-sm" class="row centeritems q-pa-sm"
v-if="!userStore.IsHandShakeByMe(userStore.userprofile)" v-if="!userStore.IsHandShakeByMe(userStore.userprofile)"
> >
<q-btn <q-btn
v-if=" v-if="userStore.IsHandShakeByUsername(userStore.userprofile.username)"
userStore.IsHandShakeByUsername(
userStore.userprofile.username
)
"
icon="fas fa-handshake" icon="fas fa-handshake"
color="positive" color="positive"
dense dense
@@ -461,9 +453,7 @@
class="row centeritems q-ma-sm q-pa-sm" class="row centeritems q-ma-sm q-pa-sm"
v-if=" v-if="
costanti.ENABLE_FRIENDS && costanti.ENABLE_FRIENDS &&
userStore.IsReqFriendByUsername( userStore.IsReqFriendByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
> >
<q-btn <q-btn
@@ -497,12 +487,8 @@
<q-btn <q-btn
v-if=" v-if="
costanti.ENABLE_FRIENDS && costanti.ENABLE_FRIENDS &&
!userStore.IsMyFriendByUsername( !userStore.IsMyFriendByUsername(userStore.userprofile.username) &&
userStore.userprofile.username !userStore.IsAskedFriendByUsername(userStore.userprofile.username)
) &&
!userStore.IsAskedFriendByUsername(
userStore.userprofile.username
)
" "
icon="fas fa-user-plus" icon="fas fa-user-plus"
color="primary" color="primary"
@@ -523,9 +509,8 @@
<div class="row justify-center"> <div class="row justify-center">
<q-btn <q-btn
v-if=" v-if="
userStore.IsMyFriendByUsername( userStore.IsMyFriendByUsername(userStore.userprofile.username) ||
userStore.userprofile.username userStore.IsHandShakeByMe(userStore.userprofile)
) || userStore.IsHandShakeByMe(userStore.userprofile)
" "
class="text-center" class="text-center"
rounded rounded
@@ -553,15 +538,13 @@
/> />
</q-item-section> </q-item-section>
<q-item-section>{{ <q-item-section>{{
$t("handshake.remove_from_myhandshake") $t('handshake.remove_from_myhandshake')
}}</q-item-section> }}</q-item-section>
</q-item> </q-item>
<q-item <q-item
v-if=" v-if="
costanti.ENABLE_FRIENDS && costanti.ENABLE_FRIENDS &&
userStore.IsMyFriendByUsername( userStore.IsMyFriendByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
clickable clickable
icon="fas fa-user-minus" icon="fas fa-user-minus"
@@ -576,17 +559,18 @@
" "
> >
<q-item-section avatar> <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>
<q-item-section>{{ <q-item-section>{{
$t("friends.remove_from_myfriends") $t('friends.remove_from_myfriends')
}}</q-item-section> }}</q-item-section>
</q-item> </q-item>
<q-item <q-item
v-if=" v-if="
userStore.IsMyFriendByUsername( userStore.IsMyFriendByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
clickable clickable
icon="fas fa-ban" icon="fas fa-ban"
@@ -600,17 +584,16 @@
" "
> >
<q-item-section avatar> <q-item-section avatar>
<q-icon color="negative" name="fas fa-ban" /> <q-icon
color="negative"
name="fas fa-ban"
/>
</q-item-section> </q-item-section>
<q-item-section>{{ <q-item-section>{{ $t('friends.block_user') }}</q-item-section>
$t("friends.block_user")
}}</q-item-section>
</q-item> </q-item>
<q-item <q-item
v-if=" v-if="
userStore.IsMyFriendByUsername( userStore.IsMyFriendByUsername(userStore.userprofile.username)
userStore.userprofile.username
)
" "
clickable clickable
v-close-popup v-close-popup
@@ -623,11 +606,12 @@
" "
> >
<q-item-section avatar> <q-item-section avatar>
<q-icon color="negative" name="fas fa-flag" /> <q-icon
color="negative"
name="fas fa-flag"
/>
</q-item-section> </q-item-section>
<q-item-section>{{ <q-item-section>{{ $t('friends.report_user') }}</q-item-section>
$t("friends.report_user")
}}</q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-menu> </q-menu>
@@ -636,12 +620,8 @@
<q-btn <q-btn
v-if=" v-if="
costanti.ENABLE_FRIENDS && costanti.ENABLE_FRIENDS &&
userStore.IsAskedFriendByUsername( userStore.IsAskedFriendByUsername(userStore.userprofile.username) &&
userStore.userprofile.username !userStore.IsMyFriendByUsername(userStore.userprofile.username)
) &&
!userStore.IsMyFriendByUsername(
userStore.userprofile.username
)
" "
icon="fas fa-user-minus" icon="fas fa-user-minus"
outline outline
@@ -677,7 +657,11 @@
/> />
</q-tabs> </q-tabs>
<q-tab-panels v-model="tab" animated keep-alive> <q-tab-panels
v-model="tab"
animated
keep-alive
>
<q-tab-panel name="attivita"> <q-tab-panel name="attivita">
<CMyActivities <CMyActivities
:username_prop="userStore.userprofile.username" :username_prop="userStore.userprofile.username"
@@ -687,8 +671,7 @@
<q-tab-panel name="info"> <q-tab-panel name="info">
<div <div
v-if=" v-if="
userStore.userprofile._id && userStore.userprofile._id && userStore.userprofile.profile.biografia
userStore.userprofile.profile.biografia
" "
class="col-12 text-h8 q-mt-sm" class="col-12 text-h8 q-mt-sm"
> >
@@ -698,9 +681,7 @@
<div <div
v-if=" v-if="
userStore.userprofile && userStore.userprofile && userStore.userprofile._id && tools.isUserOk()
userStore.userprofile._id &&
tools.isUserOk()
" "
> >
<div <div
@@ -713,12 +694,18 @@
v-bind="$attrs" v-bind="$attrs"
:copy="false" :copy="false"
:value=" :value="
userStore.userprofile.profile.resid_str_comune + (userStore.userprofile.profile.resid_province ? ' (' + userStore.userprofile.profile.resid_province + ')' : '') userStore.userprofile.profile.resid_str_comune +
(userStore.userprofile.profile.resid_province
? ' (' + userStore.userprofile.profile.resid_province + ')'
: '')
" "
label="Comune" label="Comune"
/> />
<CLabel <CLabel
v-if="!!userStore.userprofile.profile.resid_province && !userStore.userprofile.profile.resid_str_comune" v-if="
!!userStore.userprofile.profile.resid_province &&
!userStore.userprofile.profile.resid_str_comune
"
v-bind="$attrs" v-bind="$attrs"
:copy="false" :copy="false"
:value=" :value="
@@ -752,9 +739,7 @@
label="Sito Web" label="Sito Web"
> >
<span <span
v-html=" v-html="tools.getlinkhref(getLinkWebSite(), getLinkWebSite())"
tools.getlinkhref(getLinkWebSite(), getLinkWebSite())
"
/> />
</CLabel> </CLabel>
@@ -801,7 +786,10 @@
</q-tab-panels> </q-tab-panels>
</div> </div>
</div> </div>
<q-page-sticky position="top-right" :offset="[18, 18]" class="z-top"> <q-page-sticky
position="top-right"
:offset="[18, 18]"
>
<q-fab <q-fab
icon="fas fa-ellipsis-v" icon="fas fa-ellipsis-v"
color="accent" color="accent"
@@ -837,14 +825,12 @@
v-if="userStore.isFacilitatore || userStore.isAdmin" v-if="userStore.isFacilitatore || userStore.isAdmin"
color="green" color="green"
:icon=" :icon="
userStore.userprofile.profile && userStore.userprofile.profile && userStore.userprofile.profile.da_contattare
userStore.userprofile.profile.da_contattare
? 'fas fa-user-slash' ? 'fas fa-user-slash'
: 'fas fa-comment' : 'fas fa-comment'
" "
:label=" :label="
userStore.userprofile.profile && userStore.userprofile.profile && userStore.userprofile.profile.da_contattare
userStore.userprofile.profile.da_contattare
? t('profile.togli_da_contattare') ? t('profile.togli_da_contattare')
: t('profile.da_contattare') : t('profile.da_contattare')
" "
@@ -892,8 +878,16 @@
/> />
</q-fab> </q-fab>
</q-page-sticky> </q-page-sticky>
<q-dialog v-model="showPic" full-height full-width> <q-dialog
<img :src="getImgUser()" :alt="username" class="full-width" /> v-model="showPic"
full-height
full-width
>
<img
:src="getImgUser()"
:alt="username"
class="full-width"
/>
</q-dialog> </q-dialog>
<div <div
@@ -909,17 +903,23 @@
style="z-index: 1" style="z-index: 1"
> >
<q-menu> <q-menu>
<q-list v-if="true" style="min-width: 200px"> <q-list
v-if="true"
style="min-width: 200px"
>
<q-item <q-item
clickable clickable
v-close-popup v-close-popup
@click.stop="gotoPage('/editprofile')" @click.stop="gotoPage('/editprofile')"
> >
<q-item-section avatar> <q-item-section avatar>
<q-icon color="blue" name="fas fa-pencil-alt" /> <q-icon
color="blue"
name="fas fa-pencil-alt"
/>
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
{{ $t("shared.edit_profile") }} {{ $t('shared.edit_profile') }}
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@@ -950,11 +950,20 @@
<q-toolbar-title> <q-toolbar-title>
{{ usersList.title }} {{ usersList.title }}
</q-toolbar-title> </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-toolbar>
<q-card-section class="inset-shadow"> <q-card-section class="inset-shadow">
<div v-for="(rec, i) in usersList.list" :key="i"> <div
v-for="(rec, i) in usersList.list"
:key="i"
>
<CMyUser <CMyUser
:mycontact="rec" :mycontact="rec"
:visu="costanti.FIND_PEOPLE" :visu="costanti.FIND_PEOPLE"
@@ -975,10 +984,8 @@
</div> </div>
</template> </template>
<script lang="ts" src="./myprofile.ts"> <script lang="ts" src="./myprofile.ts"></script>
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./myprofile.scss"; @import './myprofile.scss';
</style> </style>