Add pinia dependency for state management
This commit is contained in:
51
package-lock.json
generated
51
package-lock.json
generated
@@ -23,6 +23,7 @@
|
|||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"json-editor-vue": "^0.15.1",
|
"json-editor-vue": "^0.15.1",
|
||||||
"md-editor-v3": "^4.18.0",
|
"md-editor-v3": "^4.18.0",
|
||||||
|
"pinia": "^2.2.4",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^4.0.0",
|
"primevue": "^4.0.0",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
@@ -5023,6 +5024,56 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.6.3",
|
||||||
|
"vue-demi": "^0.14.10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.4.0",
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.6.14 || ^3.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||||
|
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
"json-editor-vue": "^0.15.1",
|
"json-editor-vue": "^0.15.1",
|
||||||
"md-editor-v3": "^4.18.0",
|
"md-editor-v3": "^4.18.0",
|
||||||
|
"pinia": "^2.2.4",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primevue": "^4.0.0",
|
"primevue": "^4.0.0",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useLayout } from '@/layout/composables/layout';
|
import { useLayout } from '@/layout/composables/layout';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
import { UserPrefStore } from '../stores/UserPrefStore.js';
|
||||||
import AppFooter from './AppFooter.vue';
|
import AppFooter from './AppFooter.vue';
|
||||||
import AppSidebar from './AppSidebar.vue';
|
import AppSidebar from './AppSidebar.vue';
|
||||||
import AppTopbar from './AppTopbar.vue';
|
import AppTopbar from './AppTopbar.vue';
|
||||||
@@ -9,6 +10,11 @@ const { layoutConfig, layoutState, isSidebarActive, resetMenu } = useLayout();
|
|||||||
|
|
||||||
const outsideClickListener = ref(null);
|
const outsideClickListener = ref(null);
|
||||||
const page = ref("progetti");
|
const page = ref("progetti");
|
||||||
|
const userPrefStore = UserPrefStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
userPrefStore.fetchUserData();
|
||||||
|
});
|
||||||
|
|
||||||
watch(isSidebarActive, (newVal) => {
|
watch(isSidebarActive, (newVal) => {
|
||||||
//console.log("routerLink", routerLink);
|
//console.log("routerLink", routerLink);
|
||||||
@@ -55,13 +61,13 @@ const isOutsideClicked = (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="layout-wrapper" :class="containerClass">
|
<div v-if="userPrefStore.userLoaded" class="layout-wrapper" :class="containerClass">
|
||||||
<app-topbar :page="page"></app-topbar>
|
<app-topbar :page="page"></app-topbar>
|
||||||
<div class="layout-sidebar">
|
<div class="layout-sidebar">
|
||||||
<app-sidebar></app-sidebar>
|
<app-sidebar></app-sidebar>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-main-container">
|
<div class="layout-main-container">
|
||||||
<div class="layout-main">
|
<div class="layout-main">
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</div>
|
</div>
|
||||||
<app-footer></app-footer>
|
<app-footer></app-footer>
|
||||||
|
|||||||
@@ -4,29 +4,21 @@ import { useAuth } from '@websanova/vue-auth/src/v3.js';
|
|||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
|
import { ScenarioStore } from '../stores/ScenarioStore.js';
|
||||||
|
import { UserPrefStore } from '../stores/UserPrefStore.js';
|
||||||
|
|
||||||
import AppConfigurator from './AppConfigurator.vue';
|
import AppConfigurator from './AppConfigurator.vue';
|
||||||
import AppProfileMenu from './AppProfileMenu.vue';
|
import AppProfileMenu from './AppProfileMenu.vue';
|
||||||
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const props = defineProps(['page']);
|
const props = defineProps(['page']);
|
||||||
|
const userPrefStore = UserPrefStore();
|
||||||
|
const scenario_store = ScenarioStore();
|
||||||
|
|
||||||
const { onMenuToggle, toggleDarkMode, isDarkTheme } = useLayout();
|
const { onMenuToggle, toggleDarkMode, isDarkTheme } = useLayout();
|
||||||
|
|
||||||
/*const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get('/userApplications');
|
|
||||||
items.value = response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Errore durante il recupero dei dati:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Richiama il metodo all'inizializzazione del componente
|
|
||||||
onMounted(() => {
|
|
||||||
fetchData();
|
|
||||||
});*/
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -83,7 +75,14 @@ onMounted(() => {
|
|||||||
<AppConfigurator />
|
<AppConfigurator />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dropdown
|
||||||
|
v-model="userPrefStore.selectedApp"
|
||||||
|
:options="userPrefStore.availableApp"
|
||||||
|
optionLabel="fe_name"
|
||||||
|
placeholder="Select an Option"
|
||||||
|
class="dropdown-list"
|
||||||
|
@change="scenario_store.fetchApplicationScenarios()"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="layout-topbar-menu-button layout-topbar-action"
|
class="layout-topbar-menu-button layout-topbar-action"
|
||||||
v-styleclass="{ selector: '@next', enterFromClass: 'hidden', enterActiveClass: 'animate-scalein', leaveToClass: 'hidden', leaveActiveClass: 'animate-fadeout', hideOnOutsideClick: true }"
|
v-styleclass="{ selector: '@next', enterFromClass: 'hidden', enterActiveClass: 'animate-scalein', leaveToClass: 'hidden', leaveActiveClass: 'animate-fadeout', hideOnOutsideClick: true }"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import driverAuthBearer from '@websanova/vue-auth/dist/drivers/auth/bearer.esm.j
|
|||||||
import driverHttpAxios from '@websanova/vue-auth/dist/drivers/http/axios.1.x.esm.js';
|
import driverHttpAxios from '@websanova/vue-auth/dist/drivers/http/axios.1.x.esm.js';
|
||||||
import driverRouterVueRouter from '@websanova/vue-auth/dist/drivers/router/vue-router.2.x.esm.js';
|
import driverRouterVueRouter from '@websanova/vue-auth/dist/drivers/router/vue-router.2.x.esm.js';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
@@ -51,8 +52,11 @@ axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;//'http://localhost:80
|
|||||||
console.log(import.meta.env.VITE_BACKEND_URL);
|
console.log(import.meta.env.VITE_BACKEND_URL);
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
app.use(PrimeVue, {
|
app.use(PrimeVue, {
|
||||||
theme: {
|
theme: {
|
||||||
preset: Aura,
|
preset: Aura,
|
||||||
|
|||||||
51
src/stores/ScenarioStore.js
Normal file
51
src/stores/ScenarioStore.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { ScenarioService } from '../service/ScenarioService'
|
||||||
|
import { UserPrefStore } from './UserPrefStore'
|
||||||
|
|
||||||
|
export const ScenarioStore = defineStore('scenario_store', () => {
|
||||||
|
|
||||||
|
|
||||||
|
const projectScenarios = ref([])
|
||||||
|
const globalScenarios = ref([])
|
||||||
|
const applicationScenarios = ref([])
|
||||||
|
const filterString = ref('')
|
||||||
|
const allScenarios = ref([])
|
||||||
|
const userPrefStore = UserPrefStore()
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchScenarios() {
|
||||||
|
|
||||||
|
await ScenarioService.getScenariosProject(userPrefStore.selectedProject).then(resp => {
|
||||||
|
projectScenarios.value = resp.data;
|
||||||
|
allScenarios.value = [...projectScenarios.value]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function fetchApplicationScenarios() {
|
||||||
|
|
||||||
|
await ScenarioService.getScenariosApplication(userPrefStore.selectedApp).then(resp=>{
|
||||||
|
console.log("response scenari", resp);
|
||||||
|
applicationScenarios.value = resp.data
|
||||||
|
allScenarios.value = [...projectScenarios.value, ...applicationScenarios.value]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const scenarios = computed(() => {
|
||||||
|
return allScenarios.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredScenarios = computed(() => {
|
||||||
|
console.log("scenarios", allScenarios.value);
|
||||||
|
return allScenarios.value.filter((item) => {
|
||||||
|
return filterString.value
|
||||||
|
.toLowerCase()
|
||||||
|
.split(" ")
|
||||||
|
.every((v) => item.name.toLowerCase().includes(v));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return {filteredScenarios, projectScenarios, applicationScenarios, fetchScenarios,fetchApplicationScenarios,scenarios,filterString }
|
||||||
|
})
|
||||||
32
src/stores/UserPrefStore.js
Normal file
32
src/stores/UserPrefStore.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useAuth } from '@websanova/vue-auth/src/v3.js';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
export const UserPrefStore = defineStore('userpref_store', () => {
|
||||||
|
|
||||||
|
const user = ref(null)
|
||||||
|
const userLoaded = ref(false)
|
||||||
|
const selectedApp = ref(null)
|
||||||
|
|
||||||
|
async function fetchUserData(){
|
||||||
|
|
||||||
|
const auth = useAuth();
|
||||||
|
|
||||||
|
await auth.fetch().then((fetchedUser) => {
|
||||||
|
user.value = fetchedUser.data.data;
|
||||||
|
userLoaded.value = true;
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function setSelectedApp(app){
|
||||||
|
selectedApp.value = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedProject = computed(() => user.value.selectedProject)
|
||||||
|
const availableApp = computed(() => user.value.selectedProject.lstApplications)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { user,fetchUserData,userLoaded,selectedProject,availableApp,setSelectedApp,selectedApp }
|
||||||
|
})
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent"/>
|
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<DataView :value="filter" :layout="layout" paginator :rows="8">
|
<DataView :value="scenario_store.filteredScenarios" :layout="layout" paginator :rows="8">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
@@ -15,20 +15,11 @@
|
|||||||
class="search-input"
|
class="search-input"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
v-model="data.search"
|
v-model="scenario_store.filterString"
|
||||||
size="medium"
|
size="medium"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- Aggiungi il Dropdown qui -->
|
|
||||||
<Dropdown
|
|
||||||
v-model="selectedApp"
|
|
||||||
:options="dataApp.apps"
|
|
||||||
optionLabel="fe_name"
|
|
||||||
placeholder="Select an Option"
|
|
||||||
class="dropdown-list"
|
|
||||||
@change="reloadScenarios"
|
|
||||||
/>
|
|
||||||
<SelectButton v-model="layout" :options="options" :allowEmpty="false" class="layout-switch">
|
<SelectButton v-model="layout" :options="options" :allowEmpty="false" class="layout-switch">
|
||||||
<template #option="{ option }">
|
<template #option="{ option }">
|
||||||
<i :class="[option === 'list' ? 'pi pi-bars' : 'pi pi-table']" />
|
<i :class="[option === 'list' ? 'pi pi-bars' : 'pi pi-table']" />
|
||||||
@@ -81,132 +72,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ChevronRightIcon } from '@heroicons/vue/24/solid';
|
import { ChevronRightIcon } from '@heroicons/vue/24/solid';
|
||||||
import { useAuth } from '@websanova/vue-auth/src/v3.js';
|
|
||||||
import DataView from 'primevue/dataview';
|
import DataView from 'primevue/dataview';
|
||||||
import ProgressSpinner from 'primevue/progressspinner';
|
import ProgressSpinner from 'primevue/progressspinner';
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ScenarioService } from '../../service/ScenarioService.js';
|
import { ScenarioStore } from '../../stores/ScenarioStore.js';
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const layout = ref('grid');
|
const layout = ref('grid');
|
||||||
const options = ref(['list', 'grid']);
|
const options = ref(['list', 'grid']);
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const route = useRoute();
|
|
||||||
const auth = useAuth();
|
|
||||||
const user = computed(() => auth.user());
|
|
||||||
|
|
||||||
|
const scenario_store = ScenarioStore();
|
||||||
const fetchUserData = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
auth.fetch().then((fetchedUser) => {
|
|
||||||
user.value = fetchedUser;
|
|
||||||
resolve(user.value);
|
|
||||||
|
|
||||||
}).catch((error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchScenarios = (selectedProject) => {
|
|
||||||
return ScenarioService.getScenariosProject(selectedProject).then(resp => {
|
|
||||||
data.scenarios = resp.data;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*onMounted(() => {
|
|
||||||
loading.value = true
|
|
||||||
//const id = route.params.selProject;
|
|
||||||
//fetchUserData();
|
|
||||||
console.log("user after 2:", user.value);
|
|
||||||
|
|
||||||
console.log("user scenario", user.value.selectedProject);
|
|
||||||
|
|
||||||
ScenarioService.getScenariosProject(user.value.selectedProject).then(resp=>{
|
|
||||||
console.log("response scenari", resp);
|
|
||||||
data.scenarios = resp.data
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
dataApp.apps = user.value.selectedProject.lstApplications;
|
|
||||||
console.log("list apps:", dataApp.apps);
|
|
||||||
/*ProjectService.getUserApplications().then(resp=>{
|
|
||||||
dataApp.apps = resp.data
|
|
||||||
console.info("data apps", resp);
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
});*/
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
scenario_store.fetchScenarios();
|
||||||
fetchUserData()
|
loading.value = false;
|
||||||
.then(() => {
|
|
||||||
console.log("user after fetch:", user.value);
|
|
||||||
console.log("user scenario", user.value.selectedProject);
|
|
||||||
|
|
||||||
// Assicurati che selectedProject sia definito
|
|
||||||
if (user.value.selectedProject) {
|
|
||||||
return fetchScenarios(user.value.selectedProject);
|
|
||||||
} else {
|
|
||||||
throw new Error('Nessun progetto selezionato.');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
loading.value = false;
|
|
||||||
dataApp.apps = user.value.selectedProject.lstApplications;
|
|
||||||
console.log("list apps:", dataApp.apps);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Errore:", error);
|
|
||||||
loading.value = false; // Assicurati di fermare il caricamento anche in caso di errore
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const reloadScenarios = () => {
|
|
||||||
console.log("selectedApp", selectedApp.value)
|
|
||||||
|
|
||||||
ScenarioService.getScenariosApplication(selectedApp.value).then(resp=>{
|
|
||||||
console.log("response scenari", resp);
|
|
||||||
data.scenarios = resp.data
|
|
||||||
//loading.value = false
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const data = reactive({
|
|
||||||
search: null,
|
|
||||||
scenarios: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const dataApp = reactive({
|
|
||||||
search: null,
|
|
||||||
apps: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedApp = ref(null);
|
|
||||||
|
|
||||||
|
|
||||||
const filter = computed(() => {
|
|
||||||
|
|
||||||
if (data.search) {
|
|
||||||
|
|
||||||
return data.scenarios.filter((item) => {
|
|
||||||
return data.search
|
|
||||||
.toLowerCase()
|
|
||||||
.split(" ")
|
|
||||||
.every((v) => item.name.toLowerCase().includes(v));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return data.scenarios;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const executeScenario = (id) => {
|
const executeScenario = (id) => {
|
||||||
router.push({ name: 'scenario-exec', params: { id: id } });
|
router.push({ name: 'scenario-exec', params: { id: id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user