Create OrdersPage, load orders from backend, move NavDrawer items to Component
This commit is contained in:
@@ -4,7 +4,7 @@ import { useUserStore } from './data/stores/userStore';
|
||||
import { i18n } from './plugins/i18n';
|
||||
import { ref } from 'vue';
|
||||
import vuetify from './plugins/vuetify';
|
||||
import navigationDrawer from './components/navigationDrawer.vue';
|
||||
import navigationItems from './components/navigationItems.vue';
|
||||
|
||||
const userStore = useUserStore()
|
||||
const theme = useTheme()
|
||||
@@ -23,7 +23,7 @@ i18n.global.locale = userStore.language
|
||||
</v-app-bar>
|
||||
|
||||
<v-navigation-drawer :rail="navRail" permanent>
|
||||
<navigation-drawer v-model:nav-rail="navRail" />
|
||||
<navigation-items v-model:nav-rail="navRail" />
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main>
|
||||
|
||||
@@ -1,15 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
|
||||
import BannerModel from '@/data/models/bannerModel';
|
||||
import { getBannerMessage } from '@/scripts/contentScripts';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const i18n = useI18n()
|
||||
const alertBanner = defineModel("alertBanner", { required: true, type: BannerModel })
|
||||
|
||||
const title = ref("")
|
||||
const color = ref("")
|
||||
const icon = ref("")
|
||||
|
||||
function refreshBanner() {
|
||||
switch (alertBanner.value.bannerState) {
|
||||
case BannerStateEnum.ERROR: {
|
||||
title.value = i18n.t('bannerMessages.error'); break;
|
||||
}
|
||||
case BannerStateEnum.DATABASERESETSUCCESSFUL: {
|
||||
title.value = i18n.t('bannerMessages.databaseResetSuccessful'); break;
|
||||
}
|
||||
case BannerStateEnum.LOGINSUCCESSFUL: {
|
||||
title.value = i18n.t('bannerMessages.loginSuccessful'); break;
|
||||
}
|
||||
case BannerStateEnum.WRONGLOGIN: {
|
||||
title.value = i18n.t('bannerMessages.wrongLogin'); break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (alertBanner.value.bannerState) {
|
||||
case BannerStateEnum.ERROR:
|
||||
case BannerStateEnum.WRONGLOGIN:
|
||||
color.value = "red"
|
||||
icon.value = "mdi-alert-circle"
|
||||
break;
|
||||
|
||||
case BannerStateEnum.DATABASERESETSUCCESSFUL:
|
||||
case BannerStateEnum.LOGINSUCCESSFUL:
|
||||
color.value = "green"
|
||||
icon.value = "mdi-check-circle"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => alertBanner.value.bannerState, () => {
|
||||
refreshBanner()
|
||||
})
|
||||
|
||||
refreshBanner()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-expand-transition>
|
||||
<v-row v-if="alertBanner.show">
|
||||
<v-col>
|
||||
<v-alert v-model="alertBanner.show" :color="alertBanner.color" :icon="alertBanner.icon" closable>
|
||||
{{ alertBanner.message }}
|
||||
<v-alert v-model="alertBanner.show" :color="color" :icon="icon" closable>
|
||||
{{ title }}
|
||||
</v-alert>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '@/data/stores/userStore';
|
||||
import { useBasketStore } from '@/data/stores/basketStore';
|
||||
import vuetify from '@/plugins/vuetify';
|
||||
|
||||
const userStore = useUserStore()
|
||||
const basketStore = useBasketStore()
|
||||
@@ -10,6 +9,8 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
|
||||
<template>
|
||||
<v-list>
|
||||
<!-- Shopping Section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.shopping') }}</div>
|
||||
<div v-else></div>
|
||||
@@ -25,17 +26,32 @@ const navRail = defineModel("navRail", { type: Boolean })
|
||||
|
||||
<v-divider />
|
||||
|
||||
|
||||
<!-- Account Section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.account') }}</div>
|
||||
<div v-else></div>
|
||||
</v-list-subheader>
|
||||
<v-list-item v-if="userStore.userAccountId == -1" :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link />
|
||||
<v-list-item v-else :title="$t('logout')" prepend-icon="mdi-logout" @click="userStore.userAccountId = -1" link />
|
||||
|
||||
<v-list-item v-if="userStore.userAccountId != -1" :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link />
|
||||
<v-list-item v-if="userStore.userAccountId != -1" :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link />
|
||||
<v-expand-transition>
|
||||
<div v-if="userStore.userAccountId == -1">
|
||||
<v-list-item v-if="userStore.userAccountId == -1" :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link />
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-expand-transition>
|
||||
<div v-if="userStore.userAccountId != -1">
|
||||
<v-list-item :title="$t('menu.logout')" prepend-icon="mdi-logout" @click="userStore.userAccountId = -1" link />
|
||||
<v-list-item :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link />
|
||||
<v-list-item :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link />
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
|
||||
<v-divider />
|
||||
|
||||
|
||||
<!-- System and help section -->
|
||||
|
||||
<v-list-subheader>
|
||||
<div v-if="!navRail">{{ $t('menu.systemAndHelp') }}</div>
|
||||
6
software/src/data/enums/bannerStateEnum.ts
Normal file
6
software/src/data/enums/bannerStateEnum.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export enum BannerStateEnum {
|
||||
DATABASERESETSUCCESSFUL,
|
||||
ERROR,
|
||||
WRONGLOGIN,
|
||||
LOGINSUCCESSFUL
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BannerStateEnum } from "../enums/bannerStateEnum"
|
||||
|
||||
export default class BannerModel {
|
||||
message: string = "Success!"
|
||||
show: boolean = false
|
||||
color: string = "green"
|
||||
icon: string = "mdi-check"
|
||||
bannerState: BannerStateEnum = BannerStateEnum.ERROR
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"menu":
|
||||
{
|
||||
"menu": {
|
||||
"shopping": "Shopping",
|
||||
"products": "Products",
|
||||
"basket": "Basket",
|
||||
@@ -9,7 +8,8 @@
|
||||
"orders": "Orders",
|
||||
"systemAndHelp": "System & Help",
|
||||
"helpInstructions": "Help instructions",
|
||||
"preferences": "Preferences"
|
||||
"preferences": "Preferences",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"preferences": {
|
||||
"pageSetup": "Page setup",
|
||||
@@ -19,8 +19,14 @@
|
||||
"resetDatabase": "Reset database",
|
||||
"resetPreferences": "Reset preferences"
|
||||
},
|
||||
"products": "Products",
|
||||
"product": "Product",
|
||||
"product": {
|
||||
"product": "Product",
|
||||
"products": "Products",
|
||||
"productName": "Product name",
|
||||
"brand": "Brand",
|
||||
"productPrice": "Unit price",
|
||||
"category": "Category"
|
||||
},
|
||||
"offers": "Offers",
|
||||
"categories": "Categories",
|
||||
"sortBy": "Sort by",
|
||||
@@ -30,14 +36,7 @@
|
||||
"emptyBasketText": "Go to our products and add some of them to the basket",
|
||||
"totalPrice": "Total price",
|
||||
"orderNow": "Order now",
|
||||
"category": "Category",
|
||||
"brand": "Brand",
|
||||
"productPrice": "Unit price",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"login": "Login",
|
||||
"noAccountRegister": "Create new Account!",
|
||||
"register": "Create Account",
|
||||
|
||||
"userInfo": {
|
||||
"firstName": "First Name",
|
||||
"lastName": "Family Name",
|
||||
@@ -46,6 +45,19 @@
|
||||
"postalCode": "Postal Code",
|
||||
"city": "City"
|
||||
},
|
||||
"backToLogin": "Back to Login",
|
||||
"logout": "Logout"
|
||||
|
||||
"account": {
|
||||
"backToLogin": "Back to Login",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"noAccountRegister": "Create new Account!",
|
||||
"register": "Create Account"
|
||||
},
|
||||
|
||||
"bannerMessages": {
|
||||
"loginSuccessful": "Login erfolgreich!",
|
||||
"wrongLogin": "Falscher Username oder falsches Passwort!",
|
||||
"error": "Some error occurred...",
|
||||
"databaseResetSuccessful": "Database reset successful!"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
"orders": "Bestellungen",
|
||||
"systemAndHelp": "System & Hilfe",
|
||||
"helpInstructions": "Hilfestellung",
|
||||
"preferences": "Einstellungen"
|
||||
"preferences": "Einstellungen",
|
||||
"logout": "Ausloggen"
|
||||
},
|
||||
"preferences": {
|
||||
"pageSetup": "Seiteneinstellungen",
|
||||
@@ -19,8 +20,14 @@
|
||||
"resetDatabase": "Datenbank zurücksetzen",
|
||||
"resetPreferences": "Einstellungen zurücksetzen"
|
||||
},
|
||||
"products": "Produkte",
|
||||
"product": "Produkt",
|
||||
"product": {
|
||||
"product": "Produkt",
|
||||
"products": "Produkte",
|
||||
"productName": "Product Name",
|
||||
"brand": "Marke",
|
||||
"productPrice": "Einzelpreis",
|
||||
"category": "Kategorie"
|
||||
},
|
||||
"offers": "Angebote",
|
||||
"categories": "Kategorien",
|
||||
"sortBy": "Sortieren nach",
|
||||
@@ -29,15 +36,8 @@
|
||||
"emptyBasketTitle": "Keine Artikel im Warenkorb",
|
||||
"emptyBasketText": "Gehe zu unseren Produkten und lege Artikel in den Warenkorb",
|
||||
"totalPrice": "Gesamtpreis",
|
||||
"orderNow": "Jetzt bestellen",
|
||||
"category": "Kategorie",
|
||||
"brand": "Marke",
|
||||
"productPrice": "Einzelpreis",
|
||||
"username": "Username",
|
||||
"password": "Passwort",
|
||||
"login": "Login",
|
||||
"noAccountRegister": "Neuen Account erstellen!",
|
||||
"register": "Account erstellen",
|
||||
"orderNow": "Jetzt bestellen",
|
||||
|
||||
"userInfo": {
|
||||
"firstName": "Vorname",
|
||||
"lastName": "Nachname",
|
||||
@@ -46,6 +46,19 @@
|
||||
"postalCode": "Postleitzahl",
|
||||
"city": "Stadt"
|
||||
},
|
||||
"backToLogin": "Zurück zum Login",
|
||||
"logout": "Ausloggen"
|
||||
|
||||
"account": {
|
||||
"username": "Username",
|
||||
"password": "Passwort",
|
||||
"noAccountRegister": "Neuen Account erstellen!",
|
||||
"register": "Account erstellen",
|
||||
"backToLogin": "Zurück zum Login"
|
||||
},
|
||||
|
||||
"bannerMessages": {
|
||||
"loginSuccessful": "Login erfolgreich!",
|
||||
"wrongLogin": "Falscher Username oder falsches Passwort!",
|
||||
"error": "Es ist ein Fehler aufgetreten...",
|
||||
"databaseResetSuccessful": "Datenbank erfolgreich zurück gesetzt!"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
Orders
|
||||
</template>
|
||||
@@ -12,10 +12,10 @@ const basketStore = useBasketStore()
|
||||
<v-card :title="$t('menu.basket')" prepend-icon="mdi-cart">
|
||||
<v-card-subtitle v-if="basketStore.itemsInBasket.length > 0">
|
||||
<div v-if="basketStore.itemsInBasket.length == 1">
|
||||
{{ basketStore.itemsInBasket.length }} {{ $t('product') }}
|
||||
{{ basketStore.itemsInBasket.length }} {{ $t('product.product') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ basketStore.itemsInBasket.length }} {{ $t('products') }}
|
||||
{{ basketStore.itemsInBasket.length }} {{ $t('product.products') }}
|
||||
</div>
|
||||
</v-card-subtitle>
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ function removeFromBasket(basketItem: BasketItemModel) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ $t('category') }}</th>
|
||||
<th>{{ $t('brand') }}</th>
|
||||
<th>{{ $t('products') }}</th>
|
||||
<th>{{ $t('product.category') }}</th>
|
||||
<th>{{ $t('product.brand') }}</th>
|
||||
<th>{{ $t('product.products') }}</th>
|
||||
<th class="text-center">{{ $t('quantity') }}</th>
|
||||
<th class="text-right">{{ $t('productPrice') }}</th>
|
||||
<th class="text-right">{{ $t('product.productPrice') }}</th>
|
||||
<th class="text-right">{{ $t('totalPrice') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
|
||||
import BannerModel from '@/data/models/bannerModel';
|
||||
import { useUserStore } from '@/data/stores/userStore';
|
||||
import axios from 'axios';
|
||||
@@ -17,18 +18,14 @@ function startLogin() {
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
banner.value.message = "Logged in!"
|
||||
banner.value.color = "green"
|
||||
banner.value.icon = "mdi-check"
|
||||
banner.value.bannerState = BannerStateEnum.LOGINSUCCESSFUL
|
||||
banner.value.show = true
|
||||
|
||||
userStore.userAccountId = res.data.userAccountId
|
||||
}
|
||||
})
|
||||
.catch(res => {
|
||||
banner.value.message = "Wrong Username or Password!"
|
||||
banner.value.color = "red"
|
||||
banner.value.icon = "mdi-alert-circle"
|
||||
banner.value.bannerState = BannerStateEnum.WRONGLOGIN
|
||||
banner.value.show = true
|
||||
})
|
||||
}
|
||||
@@ -39,23 +36,25 @@ function startLogin() {
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field :label="$t('username')" prepend-icon="mdi-account" clearable v-model="username"/>
|
||||
<v-text-field :label="$t('account.username')" prepend-icon="mdi-account" clearable v-model="username"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field :label="$t('password')" prepend-icon="mdi-key" type="password" clearable v-model="password" />
|
||||
<v-text-field :label="$t('account.password')" prepend-icon="mdi-key" type="password"
|
||||
clearable v-model="password" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn variant="outlined" @click="showRegisterCard = true" color="primary" prepend-icon="mdi-plus">
|
||||
{{ $t('noAccountRegister') }}
|
||||
{{ $t('account.noAccountRegister') }}
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn variant="outlined" append-icon="mdi-arrow-right" color="primary" @click="startLogin">{{ $t('login') }}</v-btn>
|
||||
<v-btn variant="outlined" append-icon="mdi-arrow-right" color="primary"
|
||||
@click="startLogin">{{ $t('menu.login') }}</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
</v-card>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
|
||||
import { AccountModel } from '@/data/models/accountModel';
|
||||
import BannerModel from '@/data/models/bannerModel';
|
||||
import axios from 'axios';
|
||||
@@ -13,18 +14,14 @@ function registerUser() {
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
if (res.status == 200) {
|
||||
banner.value.message = "Created!"
|
||||
banner.value.color = "green"
|
||||
banner.value.icon = "mdi-check"
|
||||
banner.value.bannerState = BannerStateEnum.LOGINSUCCESSFUL
|
||||
banner.value.show = true
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
if (error.status == 400) {
|
||||
banner.value.color = "red"
|
||||
banner.value.icon = "mdi-alert-circle"
|
||||
banner.value.message = error.response.data.error
|
||||
banner.value.bannerState = BannerStateEnum.WRONGLOGIN
|
||||
banner.value.show = true
|
||||
}
|
||||
})
|
||||
@@ -32,12 +29,12 @@ function registerUser() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card :title="$t('register')">
|
||||
<v-card :title="$t('account.register')">
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('username')"
|
||||
:label="$t('account.username')"
|
||||
prepend-icon="mdi-account"
|
||||
v-model="newUser.username"
|
||||
clearable
|
||||
@@ -46,7 +43,7 @@ function registerUser() {
|
||||
|
||||
<v-col>
|
||||
<v-text-field
|
||||
:label="$t('password')"
|
||||
:label="$t('account.password')"
|
||||
prepend-icon="mdi-key"
|
||||
type="password"
|
||||
v-model="newUser.password"
|
||||
@@ -100,9 +97,15 @@ function registerUser() {
|
||||
</v-container>
|
||||
|
||||
<template #actions>
|
||||
<v-btn prepend-icon="mdi-arrow-left" color="primary" variant="outlined" @click="showRegisterCard = false">{{ $t('backToLogin') }}</v-btn>
|
||||
<v-btn prepend-icon="mdi-arrow-left" color="primary" variant="outlined"
|
||||
@click="showRegisterCard = false">
|
||||
{{ $t('backToLogin') }}
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn prepend-icon="mdi-account-plus" color="primary" variant="outlined" @click="registerUser">{{ $t('register') }}</v-btn>
|
||||
<v-btn prepend-icon="mdi-account-plus" color="primary" variant="outlined"
|
||||
@click="registerUser">
|
||||
{{ $t('register') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
49
software/src/pages/ordersPage/index.vue
Normal file
49
software/src/pages/ordersPage/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '@/data/stores/userStore';
|
||||
import axios from 'axios';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const userStore = useUserStore()
|
||||
const orders = ref([])
|
||||
|
||||
axios.get("http://127.0.0.1:3000/orders", {
|
||||
params: {
|
||||
accountId: userStore.userAccountId
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
orders.value = res.data
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container>
|
||||
<v-row v-for="order in orders">
|
||||
<v-col>
|
||||
<v-card
|
||||
:title="'Bestellung vom ' + order.createdAt"
|
||||
:subtitle="$t('totalPrice') + ': ' + order.totalPrice + ' €'"
|
||||
>
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('quantity') }}</th>
|
||||
<th>{{ $t('product.brand') }}</th>
|
||||
<th>{{ $t('product.productName') }}</th>
|
||||
<th>{{ $t('product.productPrice') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="orderItem in order.orderItem">
|
||||
<td>{{ orderItem.quantity }}x</td>
|
||||
<td>{{ orderItem.product.brand }}</td>
|
||||
<td>{{ orderItem.product.name }}</td>
|
||||
<td>{{ orderItem.product.price }} €</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
|
||||
import BannerModel from '@/data/models/bannerModel';
|
||||
import axios from 'axios';
|
||||
|
||||
@@ -8,6 +9,7 @@ function resetDb() {
|
||||
axios.get("http://127.0.0.1:3000/api/resetdatabase")
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
alertBanner.value.bannerState = BannerStateEnum.DATABASERESETSUCCESSFUL
|
||||
alertBanner.value.show = true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -28,10 +28,10 @@ const onlyDiscounts = defineModel("onlyDiscounts", { required: true, type: Boole
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<div v-if="numberOfItems == 1">
|
||||
{{ numberOfItems }} {{ $t('product') }}
|
||||
{{ numberOfItems }} {{ $t('product.product') }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ numberOfItems }} {{ $t('products') }}
|
||||
{{ numberOfItems }} {{ $t('product.products') }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-container class="pb-0">
|
||||
|
||||
@@ -5,6 +5,7 @@ import english from './../locales/english.json'
|
||||
type MessageSchema = typeof german
|
||||
|
||||
export const i18n = createI18n<[MessageSchema], 'de' | 'en'>({
|
||||
legacy: false,
|
||||
locale: 'de',
|
||||
messages: {
|
||||
'de': german,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import AccountPage from "@/pages/AccountPage.vue";
|
||||
import OrdersPage from "@/pages/OrdersPage.vue";
|
||||
import OrdersPage from "@/pages/ordersPage/index.vue";
|
||||
import PreferencesPage from "@/pages/preferencesPage/index.vue";
|
||||
import ProductsPage from "@/pages/productsPage/index.vue";
|
||||
import LoginPage from "@/pages/loginPage/index.vue"
|
||||
|
||||
Reference in New Issue
Block a user