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, 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(null); const activeStep = ref('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(null); const circuititalia = ref(null); const mylistcircuits = ref([]); const usersList = ref({ show: false, title: '', list: [] }); // Stato apertura/chiusura expansion items - controlla quale step è aperto const openedStep = ref(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: '

RISO utilizza Telegram per connettere la sua community in tutta Italia!

' + '

' + '✅ Chat provinciali e nazionali RISO attive
' + '✅ Migliaia di utenti con cui interagire
' + '✅ Eventi, iniziative e aggiornamenti in tempo reale
' + '✅ Gruppi ampi senza limiti WhatsApp
' + '✅ Gratuito, sicuro e senza pubblicità' + '

' + '

Unisciti alla community su Telegram e scopri tutto quello che RISO ha da offrire!

', 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, }; }, });