Files
myprojplanet_vite/src/components/CProfileCompletitionBanner/CProfileCompletitionBanner.ts
Surya Paolo 4985e7565d - Sistemato INVITI alla App
- Completamento Profilo
- Registrazione tramite Invito, senza richiedere conferma email.
2025-11-18 23:56:08 +01:00

833 lines
25 KiB
TypeScript
Executable File

import type { PropType } from 'vue';
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { tools } from '@tools';
import { useGlobalStore } from '@store/globalStore';
import { useUserStore } from '@store/UserStore';
import { useCircuitStore } from '@store/CircuitStore';
import { Api } from 'app/src/store/Api';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute, useRouter } from 'vue-router';
import type { ICircuit, IUserFields } from 'model';
import { costanti } from '@costanti';
import { shared_consts } from '@src/common/shared_vuejs';
import { CMySelectCity } from '@src/components/CMySelectCity';
import { CMyCircuit } from '@src/components/CMyCircuit';
import { CMyUser } from '@src/components/CMyUser';
const STEP_CITY = 100;
const STEP_CIRCUIT = 200;
const STEP_CIRCUIT_ITALIA = 210;
export default defineComponent({
name: 'CProfileCompletitionBanner',
components: {
CMySelectCity,
CMyCircuit,
CMyUser,
},
props: {
showAlsoIfSkipped: {
type: Boolean,
required: false,
default: true,
},
mycontact: {
type: Object as PropType<IUserFields | null>,
required: false,
default: null,
},
myusername: {
type: String,
required: false,
default: null,
},
},
setup(props) {
const $q = useQuasar();
const { t } = useI18n();
const userStore = useUserStore();
const circuitStore = useCircuitStore();
const globalStore = useGlobalStore();
const $router = useRouter();
const $route = useRoute();
// ========================================
// TELEGRAM VERIFICATION STATE
// ========================================
const verificationToken = ref(null);
const isGeneratingToken = ref(false);
const isVerifying = ref(false);
const pollingInterval = ref(null);
// ========================================
// TUTORIAL STATE
// ========================================
const contact = ref(<IUserFields | null>null);
const activeStep = ref<string | null>('telegram');
const currentStepIndex = ref(0); // Indice step corrente per navigazione
const nascondiavviso = ref(false);
const showBanner_utenti_verif = ref(true);
const circuitsel = ref('');
const mycircuit = ref(<ICircuit | undefined | null>null);
const circuititalia = ref(<ICircuit | undefined | null>null);
const mylistcircuits = ref(<ICircuit[] | undefined>[]);
const usersList = ref({ show: false, title: '', list: [] });
// Stato apertura/chiusura expansion items - controlla quale step è aperto
const openedStep = ref<string | null>(null);
// ========================================
// COMPUTED - TELEGRAM
// ========================================
const isTelegramSkipped = computed(() => {
return userStore.my?.profile?.telegram_verification_skipped;
});
const isTelegramVerified = computed(() => {
return (
!!(userStore.my?.profile?.username_telegram && userStore.my?.profile?.teleg_id) ||
(!props.showAlsoIfSkipped && isTelegramSkipped.value)
);
});
const telegramStatus = computed(() => {
if (isVerifying.value) {
return {
icon: 'hourglass_empty',
color: 'orange',
message: 'In attesa di verifica...',
};
}
return {
icon: 'fab fa-telegram',
color: 'grey-6',
message: 'Collega il tuo account Telegram',
};
});
// ========================================
// COMPUTED - TUTORIAL STEPS
// ========================================
const strProv = computed(() => {
if (contact.value && contact.value.profile.resid_province) {
return contact.value.profile.resid_province;
}
return '';
});
const card = computed(() => {
if (contact.value && contact.value.profile.resid_card) {
return contact.value.profile.resid_card;
}
return '';
});
const userstoverify = computed(() => {
return userStore.my.profile.userstoverify;
});
// Step definitions
const stepResidence = computed(() => ({
step: STEP_CITY,
title: t('tutorial.step_residence_title'),
extratitle: function () {
return contact.value?.profile.resid_province
? ': ' + contact.value.profile.resid_province
: '';
},
label: t('tutorial.step_residence'),
checkOk: function (): boolean {
return contact.value
? !!contact.value.profile.resid_province &&
contact.value.profile.resid_province !== '' &&
contact.value.profile.resid_province !== '0'
: false;
},
checkOkReal: function (): boolean {
return this.checkOk();
},
icon: 'house',
required: true,
}));
const stepCircuit = computed(() => ({
step: STEP_CIRCUIT,
title: t('tutorial.step_circuito_title'),
extratitle: function () {
return mycircuit.value ? ': ' + mycircuit.value.name : '';
},
label: t('tutorial.step_circuito'),
label_ok: t('tutorial.step_circuito_ok'),
checkOk: function () {
if (mycircuit.value) {
return (
userStore.IsMyCircuitByName(mycircuit.value.name) ||
userStore.IsAskedCircuitByName(mycircuit.value.name) ||
userStore.my.profile.noCircuit
);
}
return false;
},
checkOkReal: function () {
if (mycircuit.value) {
return (
userStore.IsMyCircuitByName(mycircuit.value.name) ||
userStore.IsAskedCircuitByName(mycircuit.value.name)
);
}
return false;
},
icon: 'img: /images/1ris_rosso_100.png',
required: false,
}));
const stepCircuitItalia = computed(() => ({
step: STEP_CIRCUIT_ITALIA,
title: t('tutorial.step_circuito_italia_title') || 'Circuito RIS Italia',
extratitle: function () {
return circuititalia.value ? ': ' + circuititalia.value.name : '';
},
label: t('tutorial.step_circuito_italia') || 'Unisciti al circuito nazionale',
checkOk: function (reale?: boolean) {
if (circuititalia.value) {
return (
userStore.IsMyCircuitByName(circuititalia.value.name) ||
userStore.IsAskedCircuitByName(circuititalia.value.name) ||
userStore.my.profile.noCircIta || userStore.my.profile.noCircuit
);
}
return false;
},
checkOkReal: function () {
if (circuititalia.value) {
return (
userStore.IsMyCircuitByName(circuititalia.value.name) ||
userStore.IsAskedCircuitByName(circuititalia.value.name)
);
}
return false;
},
icon: 'img: /images/1ris_rosso_100.png',
required: false,
}));
// ========================================
// COMPUTED - LISTA STEP ORDINATA E CONFIGURAZIONE UI
// ========================================
const orderedSteps = computed(() => {
const steps = [];
// Step 1: Telegram
steps.push({
key: 'telegram',
name: 'Telegram',
completed: isTelegramVerified.value,
step: 0,
});
// Step 3: Circuito Locale (solo se disponibile)
steps.push({
key: 'circuit',
name: 'Circuito RIS Locale',
completed: stepCircuit.value.checkOk(),
step: STEP_CIRCUIT,
});
// Step 4: Circuito Italia (solo se circuito locale completato)
steps.push({
key: 'circuitItalia',
name: 'Circuito RIS Italia',
completed: stepCircuitItalia.value.checkOk(),
step: STEP_CIRCUIT_ITALIA,
});
return steps;
});
// Configurazione UI per ogni step
const stepsConfig = computed(() => [
{
key: 'telegram',
visible: !isTelegramVerified.value,
disabled: false,
title: 'Verifica Telegram',
description: 'Collega il tuo account Telegram per accedere alle community RISO!',
completed: isTelegramVerified.value,
avatar: {
color: isTelegramVerified.value ? 'positive' : telegramStatus.value.color,
icon: isTelegramVerified.value ? 'check' : telegramStatus.value.icon,
isImage: false,
},
caption: isTelegramVerified.value ? 'Completato!' : telegramStatus.value.message,
badge: {
color: isTelegramVerified.value ? 'positive' : 'orange',
label: isTelegramVerified.value ? 'Fatto' : 'Da fare',
},
},
{
key: 'circuit',
visible: true,
disabled: false,
title: 'Circuito RIS Locale',
description: 'Seleziona la tua provincia di residenza per connetterti con la community locale.',
completed: stepCircuit.value.checkOk(),
avatar: {
color: stepCircuit.value.checkOk() ? 'positive' : 'orange',
icon: stepCircuit.value.checkOk() ? 'check' : '',
isImage: !stepCircuit.value.checkOk(),
imageSrc: '/images/1ris_rosso_100.png',
},
caption: stepCircuit.value.checkOk()
? 'Completato: ' + stepCircuit.value.extratitle()
: stepCircuit.value.extratitle() || 'Unisciti al circuito della tua zona',
badge: {
color: stepCircuit.value.checkOkReal() ? 'positive' : isSalta(STEP_CIRCUIT) ? 'red' : 'orange',
label: stepCircuit.value.checkOkReal() ? 'Fatto' : isSalta(STEP_CIRCUIT) ? 'Saltato' : 'Da fare',
},
},
{
key: 'circuitItalia',
visible: true,
disabled: !(circuititalia.value && stepCircuit.value.checkOkReal()),
title: 'Circuito Italia',
description: 'Entra nel circuito nazionale per accedere a opportunità in tutta Italia.',
completed: stepCircuitItalia.value.checkOk(),
avatar: {
color: stepCircuitItalia.value.checkOk() ? 'positive' : 'grey-6',
icon: stepCircuitItalia.value.checkOk() ? 'check' : '',
isImage: !stepCircuitItalia.value.checkOk(),
imageSrc: '/images/1ris_rosso_100.png',
},
caption: stepCircuitItalia.value.checkOk()
? 'Completato!'
: 'Unisciti al circuito nazionale (opzionale)',
badge: {
color: stepCircuitItalia.value.checkOkReal() ? 'positive' : (isSalta(STEP_CIRCUIT_ITALIA)) ? 'red' : 'grey',
label: stepCircuitItalia.value.checkOkReal() ? 'Fatto' : (isSalta(STEP_CIRCUIT_ITALIA)) ? 'Saltato' : 'opzionale',
},
},
]);
// ========================================
// COMPUTED - NAVIGATION
// ========================================
const canGoPrevious = computed(() => {
return currentStepIndex.value > 0;
});
const canGoNext = computed(() => {
return currentStepIndex.value < orderedSteps.value.length - 1;
});
const currentStep = computed(() => {
return orderedSteps.value[currentStepIndex.value];
});
const canAdvanceCurrentStep = computed(() => {
if (!currentStep.value) return false;
return currentStep.value.completed;
});
const showSkipButton = computed(() => {
if (!currentStep.value) return false;
return isSalta(currentStep.value.step);
});
const isCurrentStepCircuit = computed(() => {
if (!currentStep.value) return false;
// Mostra bottone Salta se lo step corrente è un circuito (locale o italia)
return (
currentStep.value.step === STEP_CIRCUIT ||
currentStep.value.step === STEP_CIRCUIT_ITALIA
);
});
// ========================================
// COMPUTED - COMPLETION
// ========================================
const totalSteps = computed(() => {
let count = 0;
count++; // Telegram
count++; // Circuito Locale
// if (mycircuit.value)
if (circuititalia.value) count++; // Circuito Italia
return count;
});
const completedSteps = computed(() => {
let count = 0;
if (isTelegramVerified.value) count++;
if (stepCircuit.value.checkOk()) count++;
if (stepCircuitItalia.value.checkOk()) count++;
return count;
});
const completionPercentage = computed(() => {
if (totalSteps.value === 0) return 100;
return Math.round((completedSteps.value / totalSteps.value) * 100);
});
const isProfileComplete = computed(() => {
return completionPercentage.value === 100;
});
// ========================================
// METHODS - NAVIGATION
// ========================================
function goToPreviousStep() {
if (canGoPrevious.value) {
currentStepIndex.value--;
updateExpandedSteps();
}
}
function goToNextStep() {
if (canGoNext.value && canAdvanceCurrentStep.value) {
currentStepIndex.value++;
updateExpandedSteps();
}
}
function skipCurrentStep() {
if (!currentStep.value) return;
askToConfirmSkip(currentStep.value.step);
}
function updateExpandedSteps() {
// Apri lo step corrente
const current = orderedSteps.value[currentStepIndex.value];
if (current) {
openedStep.value = current.key;
}
}
// ========================================
// TELEGRAM METHODS
// ========================================
const startTelegramVerification = async () => {
isGeneratingToken.value = true;
try {
const response = await Api.SendReq('/api/telegram/generate-token', 'POST', {
username: userStore.my.username,
});
verificationToken.value = response.data.token;
isVerifying.value = true;
startPolling();
$q.notify({
type: 'positive',
message:
'Token generato! Clicca su "Apri Telegram" per completare la verifica.',
timeout: 3000,
});
} catch (error) {
console.error('Errore nella generazione del token:', error);
$q.notify({
type: 'negative',
message: 'Errore nella generazione del token. Riprova.',
timeout: 2000,
});
} finally {
isGeneratingToken.value = false;
}
};
const openTelegramBot = () => {
const telegramUrl =
tools.getLinkBotTelegram('', '') + `?start=${verificationToken.value}`;
window.open(telegramUrl, '_blank');
};
const openTelegramDownload = () => {
window.open('https://telegram.org/apps', '_blank');
};
const skipTelegramVerification = () => {
$q.dialog({
title: 'Perché Telegram?',
message:
'<p><strong>RISO utilizza Telegram per connettere la sua community in tutta Italia!</strong></p>' +
'<p style="margin-top: 12px;">' +
'✅ Chat provinciali e nazionali RISO attive<br>' +
'✅ Migliaia di utenti con cui interagire<br>' +
'✅ Eventi, iniziative e aggiornamenti in tempo reale<br>' +
'✅ Gruppi ampi senza limiti WhatsApp<br>' +
'✅ Gratuito, sicuro e senza pubblicità' +
'</p>' +
'<p style="margin-top: 12px;"><em>Unisciti alla community su Telegram e scopri tutto quello che RISO ha da offrire!</em></p>',
html: true,
options: {
type: 'radio',
model: 'install',
items: [
{
label: 'Non ho Telegram, voglio installarlo ora',
value: 'install',
color: 'primary',
},
{
label: 'Salto per ora (potrò farlo in seguito)',
value: 'skip',
color: 'grey-7',
},
],
},
cancel: {
label: 'Annulla',
flat: true,
color: 'grey-7',
},
ok: {
label: 'Conferma',
color: 'primary',
},
persistent: true,
}).onOk((choice) => {
if (choice === 'install') {
openTelegramDownload();
$q.notify({
type: 'info',
message: 'Torna qui dopo aver installato Telegram!',
icon: 'fab fa-telegram',
timeout: 3000,
});
} else {
stopPolling();
isVerifying.value = false;
verificationToken.value = null;
userStore.setSkipTelegramVerif(true);
$q.notify({
type: 'info',
message: 'Potrai collegare Telegram in seguito dalle impostazioni.',
icon: 'info',
timeout: 2500,
});
}
});
};
const startPolling = () => {
pollingInterval.value = setInterval(async () => {
try {
const response = await Api.SendReq('/api/telegram/check-verification', 'GET', {
token: verificationToken.value,
});
if (response.data.verified) {
stopPolling();
isVerifying.value = false;
verificationToken.value = null;
$q.notify({
type: 'positive',
message: 'Telegram verificato con successo!',
icon: 'check_circle',
timeout: 3000,
});
await userStore.refreshUserData(response.data);
}
} catch (error) {
console.error('Errore nel controllo verifica:', error);
}
}, 3000);
};
const stopPolling = () => {
if (pollingInterval.value) {
clearInterval(pollingInterval.value);
pollingInterval.value = null;
}
};
// ========================================
// CIRCUIT SKIP METHODS
// ========================================
function isAskedToCircuit(): boolean {
return !!(
mycircuit.value &&
!userStore.IsMyCircuitByName(mycircuit.value.name) &&
!userStore.IsAskedCircuitByName(mycircuit.value.name)
);
}
function isAskedToCircuitItalia(): boolean {
return !!(
circuititalia.value &&
!userStore.IsMyCircuitByName(circuititalia.value.name) &&
!userStore.IsAskedCircuitByName(circuititalia.value.name)
);
}
function isSalta(step: number) {
return (
(step === STEP_CIRCUIT && mycircuit.value && userStore.my.profile.noCircuit) ||
(step === STEP_CIRCUIT_ITALIA && circuititalia.value && userStore.my.profile.noCircIta)
);
}
function askToConfirmSkip(mystep: number) {
if (mystep === STEP_CIRCUIT) {
return $q
.dialog({
message: t('circuit.skipentercircuit'),
html: true,
ok: {
label: t('dialog.yes'),
push: false,
},
title: '',
cancel: true,
persistent: false,
})
.onOk(() => {
userStore.savenoCircuit(isAskedToCircuit());
// Avanza allo step successivo
if (canGoNext.value) {
goToNextStep();
}
return true;
})
.onCancel(() => {
return false;
});
} else if (mystep === STEP_CIRCUIT_ITALIA) {
askToConfirmSkipItalia(mystep);
}
}
function askToConfirmSkipItalia(mystep: number) {
return $q
.dialog({
message: t('circuit.skipentercircuit_italia'),
html: true,
ok: {
label: t('dialog.yes'),
push: false,
},
title: '',
cancel: true,
persistent: false,
})
.onOk(() => {
if (mystep === STEP_CIRCUIT_ITALIA) {
userStore.savenoCircIta(isAskedToCircuitItalia());
}
// Avanza allo step successivo
if (canGoNext.value) {
goToNextStep();
}
return true;
})
.onCancel(() => {
return false;
});
}
// ========================================
// TUTORIAL METHODS
// ========================================
const updateContact = () => {
if (!props.mycontact) {
contact.value = userStore.my;
} else {
contact.value = props.mycontact;
}
if (
contact.value &&
contact.value.profile &&
contact.value.profile.resid_province === '0'
) {
contact.value.profile.resid_province = '';
}
// Trova il primo step non completato e aprilo
const firstIncompleteIndex = orderedSteps.value.findIndex(
(step) => !step.completed
);
if (firstIncompleteIndex !== -1) {
currentStepIndex.value = firstIncompleteIndex;
updateExpandedSteps();
}
};
// ========================================
// WATCHERS
// ========================================
watch(
() => strProv.value,
(newval: string) => {
mycircuit.value = circuitStore.getCircuitByProvinceAndCard(
strProv.value,
card.value
);
if (!globalStore.isPresenteCardsByProv(strProv.value)) {
if (contact.value && contact.value.profile.resid_card) {
contact.value.profile.resid_card = '';
}
}
}
);
watch(
() => card.value,
() => {
mycircuit.value = circuitStore.getCircuitByProvinceAndCard(
strProv.value,
card.value
);
}
);
watch(
() => circuitsel.value,
() => {
if (circuitsel.value) {
mycircuit.value = circuitStore.getCircuitByName(circuitsel.value);
}
}
);
// Watch per aggiornare lo step corrente quando uno viene completato
watch([isTelegramVerified, () => stepCircuit.value.checkOk()], () => {
// Trova il primo step non completato
const firstIncompleteIndex = orderedSteps.value.findIndex(
(step) => !step.completed
);
if (
firstIncompleteIndex !== -1 &&
firstIncompleteIndex !== currentStepIndex.value
) {
// Aggiorna solo se diverso dallo step corrente
currentStepIndex.value = firstIncompleteIndex;
updateExpandedSteps();
}
});
// Watch per sincronizzare openedStep con currentStepIndex
// Quando l'utente apre manualmente uno step diverso, aggiorna currentStepIndex
watch(openedStep, (newOpenedStep) => {
if (newOpenedStep) {
const stepIndex = orderedSteps.value.findIndex(
(step) => step.key === newOpenedStep
);
if (stepIndex !== -1 && stepIndex !== currentStepIndex.value) {
currentStepIndex.value = stepIndex;
}
} else {
// Se openedStep è null (tutti chiusi), apri lo step corrente
// Questo garantisce che ci sia sempre almeno uno step aperto
if (orderedSteps.value.length > 0) {
const current = orderedSteps.value[currentStepIndex.value];
if (current) {
openedStep.value = current.key;
}
}
}
});
// ========================================
// LIFECYCLE
// ========================================
onMounted(() => {
// Initialize circuits
circuititalia.value = circuitStore.getCircuitByPath('ris_italia');
// Initialize contact
if (userStore.isUserOk()) {
updateContact();
// Initialize circuits based on residence
if (contact.value?.profile.resid_province) {
mylistcircuits.value = circuitStore.getCircuitsNameByProvince(strProv.value);
mycircuit.value = circuitStore.getCircuitByProvinceAndCard(
strProv.value,
card.value
);
}
}
});
onBeforeUnmount(() => {
stopPolling();
});
return {
// State
contact,
activeStep,
currentStepIndex,
verificationToken,
isGeneratingToken,
isVerifying,
nascondiavviso,
showBanner_utenti_verif,
circuitsel,
mycircuit,
circuititalia,
mylistcircuits,
usersList,
openedStep,
// Computed
isTelegramVerified,
isTelegramSkipped,
telegramStatus,
isProfileComplete,
completionPercentage,
totalSteps,
completedSteps,
stepResidence,
stepCircuit,
stepCircuitItalia,
userstoverify,
// Navigation Computed
canGoPrevious,
canGoNext,
canAdvanceCurrentStep,
showSkipButton,
isCurrentStepCircuit,
orderedSteps,
currentStep,
stepsConfig,
// Methods - Telegram
startTelegramVerification,
openTelegramBot,
skipTelegramVerification,
openTelegramDownload,
// Methods - Navigation
goToPreviousStep,
goToNextStep,
skipCurrentStep,
// Methods - Circuit
isSalta,
askToConfirmSkip,
askToConfirmSkipItalia,
// Stores & Utils
tools,
userStore,
globalStore,
costanti,
t,
$q,
};
},
});