Redesign account pages, split payments and addresses to single pages, new dashboard

This commit is contained in:
2024-11-29 13:38:20 +01:00
parent c867d9d51f
commit 8a18b95031
32 changed files with 524 additions and 362 deletions

View File

@@ -1,4 +1,4 @@
# HackMyCart
# EventMaster
The most hackable Web Shop!

View File

@@ -27,6 +27,7 @@ account.get("/", verifyToken, (req: Request, res: Response) => {
account.get("/account/login", async (req: Request, res: Response) => {
const encryptedPassword = encryptString(String(req.query.password))
try {
// Using raw SQL code for SQL injections!
const [results, metadata] =
await sequelize.query(
@@ -51,6 +52,9 @@ account.get("/account/login", async (req: Request, res: Response) => {
message: "Unauthorized"
})
}
} catch (e) {
res.status(500).send()
}
})
@@ -123,32 +127,28 @@ account.patch("/account", verifyToken, (req: Request, res: Response) => {
where: { id: req.body.id }
})
.then(async result => {
Payment.destroy({
where: {
accountId: req.body.id
}
})
Address.destroy({
where: {
accountId: req.body.id
}
})
for (let payment of req.body.payments) {
if (payment.id == undefined) {
payment["accountId"] = req.body.id
await Payment.create(payment)
} else {
await Payment.update(payment,
{
where: { id: payment.id }
}
)
}
}
for (let address of req.body.addresses) {
if (address.id == undefined) {
address["accountId"] = req.body.id
await Address.create(address)
} else {
await Address.update(address,
{
where: { id: address.id }
}
)
}
}
// Status: 200 OK

View File

@@ -21,16 +21,14 @@ band.get("/", (req: Request, res: Response) => {
Band.findAll({
include: [
{
model: Rating,
},
{
model: Genre,
attributes: {
exclude: [ "id" ]
}
},
Concert
Concert,
Rating
]
})
.then(bands => {

View File

@@ -1,6 +1,6 @@
{
"name": "eventmaster",
"version": "0.1.0",
"version": "0.2.0",
"author": "Tobias Zoghaib",
"description": "Hackable ticket store for educational purposes",
"license": "MIT",

View File

@@ -12,7 +12,7 @@ defineProps({
</script>
<template>
<v-card variant="outlined" class="my-1 mx-2 px-2">
<v-card variant="outlined" class="my-1 px-2">
<v-row class="d-flex justify-center align-center">
<v-col class="text-caption text-left" v-if="descriptionText.length > 0">
{{ descriptionText }}

View File

@@ -37,7 +37,7 @@ defineProps({
{{ secondLine }}
</v-skeleton-loader>
<template #actions>
<template #actions v-if="!$slots.actions">
<outlined-button
@click="router.push(buttonRoute)"
:loading="loading"
@@ -45,6 +45,10 @@ defineProps({
{{ $t('misc.actions.more') }}
</outlined-button>
</template>
<template #actions v-else>
<slot name="actions"></slot>
</template>
</card-view>
</v-col>
</template>

View File

@@ -74,7 +74,7 @@
"emailIsNotValid": "Ungültige E-Mail Addresse",
"emailRequired": "E-Mail-Adresse benötigt",
"accountManagement": "Account verwalten",
"accountManagementDescription": "Persönliche Daten, Adressen, Bezahlmethoden",
"accountManagementDescription": "Persönliche Daten, Konto löschen",
"login": {
"pleaseLoginToOrder": "Bitte anmelden zum bestellen",
"backToLogin": "Zurück zum Login",
@@ -109,7 +109,16 @@
"addNewAccount": "Neuen Account hinzufügen",
"accountRole": "Account Rolle",
"noRealPaymentsNeeded": "Keine echten Kontodaten nötig!",
"administrator": "Administrator | Administratoren"
"administrator": "Administrator | Administratoren",
"managePaymentsDescription": "Bezahlarten hinzufügen, ändern, löschen",
"paymentsManagement": "Bezahlarten verwalten",
"payments": {
"editPayment": "Bezahlart bearbeiten",
"editAddress": "Adresse bearbeiten"
},
"addressManagementDetails": "Adressen hinzufügen, ändern, löschen",
"addressManagement": "Adressen verwalten",
"sessionTime": "Session time"
},
"order": {
"oclock": "Uhr",

View File

@@ -74,7 +74,7 @@
"emailIsNotValid": "E-Mail not valid",
"emailRequired": "E-Mail required",
"accountManagement": "Manage Account",
"accountManagementDescription": "Personal data, addresses, payments",
"accountManagementDescription": "Personal data, delete account",
"login": {
"pleaseLoginToOrder": "Please login to order",
"backToLogin": "Back to Login",
@@ -109,7 +109,16 @@
"addNewAccount": "Add new account",
"accountRole": "Account Role",
"noRealPaymentsNeeded": "No real payment data required!",
"administrator": "Administrator"
"administrator": "Administrator",
"managePaymentsDescription": "Add, change, remove payments",
"paymentsManagement": "Manage payments",
"payments": {
"editPayment": "Edit Payment",
"editAddress": "Edit address"
},
"addressManagementDetails": "Add, change, remove addresses",
"addressManagement": "Manage addresses",
"sessionTime": "Session time"
},
"order": {
"oclock": "o'clock",

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import OutlinedButton from '@/components/basics/outlinedButton.vue';
import { getIbanRules, getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
import { useAccountStore } from '@/stores/account.store';
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
import { ref } from 'vue';
const valid = ref(false)
const accountStore = useAccountStore()
</script>
<template>
<action-dialog
v-model="accountStore.showEditDialog"
max-width="800"
:title="$t('account.payments.editAddress')"
>
<v-container>
<v-form v-model="valid">
<v-row class="pt-5">
<v-col>
<v-text-field
:label="$t('account.userData.street')"
v-model="accountStore.address.street"
:rules="getStringRules()"
variant="outlined"
clearable
/>
</v-col>
<v-col>
<v-text-field
:label="$t('account.userData.houseNumber')"
v-model="accountStore.address.houseNumber"
:rules="getNumberStartRules()"
variant="outlined"
clearable
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('account.userData.postalCode')"
v-model="accountStore.address.postalCode"
:rules="getPostalRules()"
variant="outlined"
clearable
/>
</v-col>
<v-col>
<v-text-field
:label="$t('account.userData.placeOfResidence')"
v-model="accountStore.address.city"
:rules="getStringRules()"
variant="outlined"
clearable
/>
</v-col>
</v-row>
</v-form>
</v-container>
<template #actions>
<outlined-button
color="success"
prepend-icon="mdi-content-save"
:disabled="!valid"
:loading="accountStore.fetchInProgress"
@click="accountStore.saveAddress"
>
{{ $t('misc.actions.save') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import dataLayout from '@/layouts/dataLayout.vue';
import { useAccountStore } from '@/stores/account.store';
import { useFeedbackStore } from '@/stores/feedback.store';
import addressEditDialog from './addressEditDialog.vue';
const accountStore = useAccountStore()
const feedbackStore = useFeedbackStore()
const headers = [
{ title: feedbackStore.i18n.t('account.userData.street'), value: "street" },
{ title: feedbackStore.i18n.t('account.userData.houseNumber'), value: "houseNumber" },
{ title: feedbackStore.i18n.t('account.userData.postalCode'), value: "postalCode" },
{ title: feedbackStore.i18n.t('account.userData.placeOfResidence'), value: "city" },
{ title: "Aktionen", value: "actions", width: 130 }
]
accountStore.refreshAccount()
</script>
<template>
<data-layout
:add-button-string="$t('misc.actions.add')"
:fetch-in-progress="accountStore.fetchInProgress"
:on-add-click="() => { accountStore.newAddress() }"
>
<v-data-table
:headers="headers"
:items="accountStore.userAccount.addresses"
:loading="accountStore.fetchInProgress"
>
<template #item.actions="{ item }">
<v-btn
icon="mdi-pencil"
variant="plain"
color="orange"
@click="accountStore.editAddress(item)"
/>
<v-btn
icon="mdi-delete"
variant="plain"
color="red"
@click="accountStore.removeAddress(item)"
/>
</template>
</v-data-table>
</data-layout>
<address-edit-dialog />
</template>

View File

@@ -81,7 +81,6 @@ const stringRules = [
v-model="accountStore.userAccount.firstName"
variant="outlined"
:rules="stringRules"
hide-details
/>
</v-col>
<v-col>
@@ -90,7 +89,6 @@ const stringRules = [
v-model="accountStore.userAccount.lastName"
variant="outlined"
:rules="stringRules"
hide-details
/>
</v-col>
</v-row>

View File

@@ -1,111 +0,0 @@
<script setup lang="ts">
import cardView from '@/components/basics/cardView.vue';
import { useAccountStore } from '@/stores/account.store';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { AddressModel } from '@/data/models/user/addressModel';
import { getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
const accountStore = useAccountStore()
</script>
<template>
<card-view
icon="mdi-home"
:title="$t('account.userData.address', 2)"
>
<v-expansion-panels v-if="accountStore.userAccount.addresses != undefined && accountStore.userAccount.addresses.length > 0">
<v-expansion-panel
v-for="address in accountStore.userAccount.addresses"
color="primary"
>
<template #title>
<div v-if="address.street != undefined">
{{ address.street }}
</div>
&nbsp;
<div v-if="address.houseNumber != undefined">
{{ address.houseNumber }}
</div>
</template>
<template #text>
<v-row class="pt-5">
<v-col>
<v-text-field
:label="$t('account.userData.street')"
v-model="address.street"
:rules="getStringRules()"
variant="outlined"
clearable
hide-details
/>
</v-col>
<v-col>
<v-text-field
:label="$t('account.userData.houseNumber')"
v-model="address.houseNumber"
:rules="getNumberStartRules()"
variant="outlined"
clearable
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('account.userData.postalCode')"
v-model="address.postalCode"
:rules="getPostalRules()"
variant="outlined"
clearable
hide-details
/>
</v-col>
<v-col>
<v-text-field
:label="$t('account.userData.placeOfResidence')"
v-model="address.city"
:rules="getStringRules()"
variant="outlined"
clearable
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@click="accountStore.removeAddress(address)"
color="error"
prepend-icon="mdi-delete"
>
{{ $t('misc.actions.remove') }}
</outlined-button>
</v-col>
</v-row>
</template>
</v-expansion-panel>
</v-expansion-panels>
<v-empty-state
v-else
:title="$t('account.noAddresses')"
icon="mdi-home-off"
/>
<template #actions>
<outlined-button
@click="accountStore.userAccount.addresses.push(new AddressModel())"
prepend-icon="mdi-plus"
color="success"
>
{{ $t('misc.actions.add') }}
</outlined-button>
</template>
</card-view>
</template>

View File

@@ -1,8 +1,6 @@
<script setup lang="ts">
import accountDataCard from './accountDataCard.vue';
import accountManagingCard from './accountManagingCard.vue';
import addressesCard from './addressesCard.vue';
import paymentsCard from './paymentsCard.vue';
import { useRouter } from 'vue-router';
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
@@ -17,18 +15,6 @@ const router = useRouter()
</v-col>
</v-row>
<v-row>
<v-col>
<addresses-card />
</v-col>
</v-row>
<v-row>
<v-col>
<payments-card />
</v-col>
</v-row>
<v-row>
<v-col>
<account-managing-card />

View File

@@ -1,97 +0,0 @@
<script setup lang="ts">
import cardView from '@/components/basics/cardView.vue';
import { useAccountStore } from '@/stores/account.store';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { PaymentModel } from '@/data/models/user/paymentModel';
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
const accountStore = useAccountStore()
</script>
<template>
<card-view
icon="mdi-currency-usd"
:title="$t('account.userData.payment', 2)"
>
<v-row>
<v-col>
<card-view-one-line
color="warning"
prepend-icon="mdi-alert"
:title="$t('account.noRealPaymentsNeeded')"
/>
</v-col>
</v-row>
<v-row v-if="accountStore.userAccount.payments.length > 0">
<v-col>
<v-expansion-panels>
<v-expansion-panel
v-for="payment in accountStore.userAccount.payments"
color="primary"
>
<template #title>
{{ payment.bankName }}
</template>
<template #text>
<v-row class="pt-5">
<v-col>
<v-text-field
:label="$t('account.userData.bankName')"
v-model="payment.bankName"
:rules="getStringRules()"
variant="outlined"
hide-details
/>
</v-col>
<v-col>
<v-text-field
:label="$t('account.userData.iban')"
v-model="payment.iban"
:rules="getIbanRules()"
variant="outlined"
hide-details
/>
</v-col>
</v-row>
<v-row>
<v-col class="d-flex justify-center align-center">
<outlined-button
@click="accountStore.removePayment(payment)"
color="error"
prepend-icon="mdi-delete"
>
{{ $t('misc.actions.remove') }}
</outlined-button>
</v-col>
</v-row>
</template>
</v-expansion-panel>
</v-expansion-panels>
</v-col>
</v-row>
<v-row v-else>
<v-col>
<v-empty-state
:title="$t('account.noPayments')"
icon="mdi-currency-usd-off"
/>
</v-col>
</v-row>
<template #actions>
<outlined-button
@click="accountStore.userAccount.payments.push(new PaymentModel())"
prepend-icon="mdi-plus"
color="success"
>
{{ $t('misc.actions.add') }}
</outlined-button>
</template>
</card-view>
</template>

View File

@@ -1,60 +1,76 @@
<script setup lang="ts">
import { useAccountStore } from '@/stores/account.store';
import cardView from '@/components/basics/cardView.vue';
import dashboardCard from '@/components/pageParts/dashboardCard.vue';
import { useOrderStore } from '@/stores/order.store';
import OutlinedButton from '@/components/basics/outlinedButton.vue';
import { useRouter } from 'vue-router';
import moment from 'moment';
import { millisecondsToHumanReadableString } from '@/scripts/dateTimeScripts';
const accountStore = useAccountStore()
const orderStore = useOrderStore()
const router = useRouter()
orderStore.getOrdersOfAccount(accountStore.userAccount)
accountStore.refreshAccount()
</script>
<template>
<v-container max-width="1000">
<v-row>
<v-col>
<card-view
:title="$t('misc.greeting', { msg: accountStore.userAccount.username })"
icon="mdi-hand-wave"
>
<v-container>
<v-row>
<v-col>
<card-view
<dashboard-card
:title="$t('order.order', 2)"
icon="mdi-basket-check"
@click="router.push('/account/orders')"
>
{{ $t('order.ordersDescription') }}
</card-view>
</v-col>
</v-row>
:first-line="orderStore.orders.length + ' ' + $t('order.order', 2)"
:second-line="$t('order.ordersDescription')"
button-route="/account/orders"
:loading="orderStore.fetchInProgress"
/>
<v-row>
<v-col>
<card-view
<dashboard-card
:title="$t('account.accountManagement')"
icon="mdi-account"
@click="router.push('/account/data')"
>
{{ $t('account.accountManagementDescription') }}
</card-view>
</v-col>
</v-row>
:first-line="accountStore.userAccount.username"
:second-line="$t('account.accountManagementDescription')"
:loading="accountStore.fetchInProgress"
button-route="/account/data"
/>
<v-row>
<v-col>
<card-view
<dashboard-card
:title="$t('account.addressManagement')"
icon="mdi-city"
:first-line="accountStore.userAccount.addresses?.length + ' ' +
$t('account.userData.address', accountStore.userAccount.addresses?.length)"
:second-line="$t('account.addressManagementDetails')"
:loading="accountStore.fetchInProgress"
button-route="/account/addresses"
/>
<dashboard-card
:title="$t('account.paymentsManagement', 2)"
icon="mdi-currency-eur"
:first-line="accountStore.userAccount.payments?.length + ' ' +
$t('account.userData.payment', accountStore.userAccount.payments?.length)"
:second-line="$t('account.managePaymentsDescription')"
:loading="accountStore.fetchInProgress"
button-route="/account/payments"
/>
<dashboard-card
:title="$t('account.logout.logout')"
:first-line="millisecondsToHumanReadableString(moment().diff(moment(accountStore.loggedInTimeStamp))) + ' h ' + $t('account.sessionTime')"
:second-line="$t('account.logout.logoutDescription')"
icon="mdi-logout"
>
<template #actions>
<outlined-button
color="error"
@click="accountStore.logout(); router.push('/account/login')"
>
{{ $t('account.logout.logoutDescription') }}
</card-view>
</v-col>
</v-row>
</v-container>
</card-view>
</v-col>
{{ $t('account.logout.logout') }}
</outlined-button>
</template>
</dashboard-card>
</v-row>
</v-container>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import dataLayout from '@/layouts/dataLayout.vue';
import { useAccountStore } from '@/stores/account.store';
import { useFeedbackStore } from '@/stores/feedback.store';
import PaymentEditDialog from './paymentEditDialog.vue';
const accountStore = useAccountStore()
const feedbackStore = useFeedbackStore()
const headers = [
{ title: feedbackStore.i18n.t('account.userData.bankName'), value: "bankName" },
{ title: feedbackStore.i18n.t('account.userData.iban'), value: "iban" },
{ title: "Aktionen", value: "actions", width: 130 }
]
accountStore.refreshAccount()
</script>
<template>
<data-layout
:add-button-string="$t('misc.actions.add')"
:fetch-in-progress="accountStore.fetchInProgress"
:on-add-click="() => { accountStore.newPayment() }"
>
<v-data-table
:headers="headers"
:items="accountStore.userAccount.payments"
:loading="accountStore.fetchInProgress"
>
<template #item.actions="{ item }">
<v-btn
icon="mdi-pencil"
variant="plain"
color="orange"
@click="accountStore.editPayment(item)"
/>
<v-btn
icon="mdi-delete"
variant="plain"
color="red"
@click="accountStore.removePayment(item)"
/>
</template>
</v-data-table>
</data-layout>
<payment-edit-dialog />
</template>

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import OutlinedButton from '@/components/basics/outlinedButton.vue';
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
import { useAccountStore } from '@/stores/account.store';
import cardViewOneLine from '@/components/basics/cardViewOneLine.vue';
import { ref } from 'vue';
const valid = ref(false)
const accountStore = useAccountStore()
</script>
<template>
<action-dialog
v-model="accountStore.showEditDialog"
max-width="800"
:title="$t('account.payments.editPayment')"
>
<v-container>
<v-row>
<v-col>
<card-view-one-line
color="warning"
prepend-icon="mdi-alert"
:title="$t('account.noRealPaymentsNeeded')"
/>
</v-col>
</v-row>
<v-form v-model="valid">
<v-row>
<v-col>
<v-text-field
:label="$t('account.userData.bankName')"
v-model="accountStore.payment.bankName"
:rules="getStringRules(8)"
variant="outlined"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('account.userData.iban')"
v-model="accountStore.payment.iban"
:rules="getIbanRules()"
variant="outlined"
/>
</v-col>
</v-row>
</v-form>
</v-container>
<template #actions>
<outlined-button
color="success"
prepend-icon="mdi-content-save"
:disabled="!valid"
:loading="accountStore.fetchInProgress"
@click="accountStore.savePayment"
>
{{ $t('misc.actions.save') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -10,6 +10,7 @@ const accountStore = useAccountStore()
async function registerAccount() {
accountStore.registerAccount()
.then(result => {
console.log(result)
if (result) {
showRegisterCard.value = false
}

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useAccountStore } from '@/stores/account.store';
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import { useFeedbackStore } from '@/stores/feedback.store';
const accountStore = useAccountStore()
@@ -19,7 +19,7 @@ accountStore.getAllAccounts()
</script>
<template>
<admin-data-layout
<data-layout
:add-button-string="$t('account.addNewAccount')"
:fetch-in-progress="accountStore.fetchInProgress"
>
@@ -44,5 +44,5 @@ accountStore.getAllAccounts()
/> -->
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useBandStore } from '@/stores/band.store';
import bandEditDialog from './bandEditDialog.vue';
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import { useFeedbackStore } from '@/stores/feedback.store';
const bandStore = useBandStore()
@@ -22,7 +22,7 @@ bandStore.getBands()
</script>
<template>
<admin-data-layout
<data-layout
:add-button-string="$t('band.addNewBand')"
:fetch-in-progress="bandStore.fetchInProgress"
:on-add-click="() => bandStore.newBand()"
@@ -72,7 +72,7 @@ bandStore.getBands()
/> -->
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
<band-edit-dialog />
</template>

View File

@@ -2,7 +2,7 @@
import { useBandStore } from '@/stores/band.store';
import { useConcertStore } from '@/stores/concert.store';
import { useFeedbackStore } from '@/stores/feedback.store';
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import moment from 'moment';
const concertStore = useConcertStore()
@@ -25,7 +25,7 @@ concertStore.getConcerts()
</script>
<template>
<admin-data-layout
<data-layout
:add-button-string="$t('concert.addNewConcert')"
:fetch-in-progress="concertStore.fetchInProgress"
:on-add-click="() => concertStore.newConcert()"
@@ -73,5 +73,5 @@ concertStore.getConcerts()
/> -->
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
</template>

View File

@@ -5,7 +5,7 @@ import { useAccountStore } from '@/stores/account.store';
import { useLocationStore } from '@/stores/location.store';
import { useGenreStore } from '@/stores/genre.store';
import { usePreferencesStore } from '@/stores/preferences.store';
import dashboardCard from './dashboardCard.vue';
import dashboardCard from '../../../components/pageParts/dashboardCard.vue';
import { useOrderStore } from '@/stores/order.store';
import { useFilesStore } from '@/stores/files.store';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import { ref } from 'vue';
import FileUploadDialog from './fileUploadDialog.vue';
import { useFilesStore } from '@/stores/files.store';
@@ -12,7 +12,7 @@ filesStore.getStaticFolders()
</script>
<template>
<admin-data-layout
<data-layout
:add-button-string="$t('misc.uploadFile')"
:fetch-in-progress="filesStore.fetchInProgress"
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
@@ -112,7 +112,7 @@ filesStore.getStaticFolders()
</v-row>
</v-col>
</v-row>
</admin-data-layout>
</data-layout>
<file-preview-dialog
v-model:show-dialog="showPreviewDialog"

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import genreEditDialog from './genreEditDialog.vue';
import { useGenreStore } from '@/stores/genre.store';
@@ -15,7 +15,7 @@ genreStore.getGenres()
</script>
<template>
<admin-data-layout
<data-layout
:add-button-string="$t('band.addNewGenre')"
:fetch-in-progress="genreStore.fetchInProgress"
:on-add-click="() => { genreStore.newGenre() }"
@@ -48,7 +48,7 @@ genreStore.getGenres()
/> -->
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
<genre-edit-dialog />
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import { useFeedbackStore } from '@/stores/feedback.store';
import { useLocationStore } from '@/stores/location.store';
@@ -22,7 +22,7 @@ locationStore.getLocations()
</script>
<template>
<admin-data-layout
<data-layout
:fetch-in-progress="locationStore.fetchInProgress"
:add-button-string="$t('location.addLocation')"
:on-add-click="() => { locationStore.newLocation() }"
@@ -66,5 +66,5 @@ locationStore.getLocations()
/> -->
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
</template>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import dataLayout from '@/layouts/dataLayout.vue';
import { useOrderStore } from '@/stores/order.store';
import moment from 'moment';
import OrderDetailDialog from './orderDetailDialog.vue';
@@ -20,7 +20,7 @@ orderStore.getAllOrders()
</script>
<template>
<admin-data-layout
<data-layout
:hide-add-button="true"
>
<v-data-table
@@ -68,7 +68,7 @@ orderStore.getAllOrders()
</template>
</v-data-table>
</admin-data-layout>
</data-layout>
<order-detail-dialog />
</template>

View File

@@ -16,6 +16,8 @@ import PreferencesPage from "@/pages/misc/preferencesPage/index.vue";
import HelpPage from "@/pages/misc/helpPage/index.vue"
import ErrorPage from "@/pages/misc/errorPage/index.vue"
import ImageLicensePage from "@/pages/misc/imageLicensePage/index.vue"
import AccountPaymentsPage from "@/pages/account/accountPaymentsPage/index.vue"
import AccountAddressesPage from "@/pages/account/accountAddressesPage/index.vue"
const routes = [
// Main page
@@ -32,6 +34,8 @@ const routes = [
{ path: '/account/orders', component: OrdersPage },
{ path: '/account/data', component: AccountDataPage },
{ path: '/account/login', component: LoginPage },
{ path: '/account/payments', component: AccountPaymentsPage },
{ path: '/account/addresses', component: AccountAddressesPage },
// Admin
...adminRoutes,

View File

@@ -21,3 +21,15 @@ export function dateToHumanReadableString(date: Date) {
export function dateStringToHumanReadableString(string: string) {
return dateToHumanReadableString(new Date(string))
}
/**
* Format milliseconds to a readable format
*
* @param milliseconds Milliseconds to format
*
* @returns h:mm format
*/
export function millisecondsToHumanReadableString(milliseconds: number): string {
return Math.floor(milliseconds / 1000 / 60 / 60) + ':' +
String(Math.floor(milliseconds / 60000)).padStart(2, "0") + ''
}

View File

@@ -10,6 +10,7 @@ import { AccountApiModel } from "../data/models/user/accountApiModel";
import { ref } from "vue";
import { defineStore } from "pinia";
import { useExerciseStore } from "./exercise.store";
import moment, { Moment } from "moment";
export const useAccountStore = defineStore("accountStore", {
state: () => ({
@@ -17,10 +18,10 @@ export const useAccountStore = defineStore("accountStore", {
accounts: ref<Array<AccountApiModel>>([]),
/** Server token of currently logged in account */
userAccountToken: useLocalStorage("hackmycart/accountStore/userAccountToken", ""),
userAccountToken: useLocalStorage("eventMaster/accountStore/userAccountToken", ""),
/** Useraccount which is currently logged in */
userAccount: useLocalStorage("hackmycart/accountStore/userAccount", new AccountApiModel()),
userAccount: useLocalStorage("eventMaster/accountStore/userAccount", new AccountApiModel()),
/** User input on login screen */
loginData: ref<{ username: String, password: String}>(
@@ -37,7 +38,15 @@ export const useAccountStore = defineStore("accountStore", {
adminPanelVisible: ref(false),
/** Flag to activate buy option on basket page */
privilegeBuy: ref(false)
privilegeBuy: ref(false),
payment: ref(),
address: ref(),
showEditDialog: ref(false),
loggedInTimeStamp: useLocalStorage<string>("eventMaster/accountStore/loggedInTimeStamp", "")
}),
actions: {
@@ -80,6 +89,7 @@ export const useAccountStore = defineStore("accountStore", {
await getLogin(this.loginData.username, this.loginData.password)
.then(async result => {
this.userAccountToken = result.data.token
this.loggedInTimeStamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS")
getAccount(this.userAccountToken)
.then(response => {
@@ -113,14 +123,16 @@ export const useAccountStore = defineStore("accountStore", {
* Reload account information about current logged in user
*/
async refreshAccount() {
this.fetchInProgress = true
getAccount(this.userAccountToken)
.then(response => {
this.userAccount = response.data
this.fetchInProgress = false
this.privilegeBuy = true
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
this.fetchInProgress = false
})
},
@@ -133,6 +145,7 @@ export const useAccountStore = defineStore("accountStore", {
async registerAccount(): Promise<boolean> {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
let success = false
this.fetchInProgress = true
if (this.registerData.username == null || this.registerData.username.length < 4) {
@@ -142,7 +155,8 @@ export const useAccountStore = defineStore("accountStore", {
} else if (!this.registerData.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)) {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTMAILADDRESSUNVALID)
}
else {
else
{
await registerAccount(this.registerData)
.then(async res => {
if (res.status == 201) {
@@ -156,6 +170,7 @@ export const useAccountStore = defineStore("accountStore", {
}
this.fetchInProgress = false
success = true
})
.catch((error) => {
if (error.status == 400) {
@@ -165,12 +180,11 @@ export const useAccountStore = defineStore("accountStore", {
}
this.fetchInProgress = false
return false
})
}
this.fetchInProgress = false
return false
return success
},
/**
@@ -179,6 +193,7 @@ export const useAccountStore = defineStore("accountStore", {
async updateAccount() {
const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore()
this.fetchInProgress = true
// Check for exercise 0.2 completion
let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" &&
@@ -196,6 +211,7 @@ export const useAccountStore = defineStore("accountStore", {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL)
this.userAccount = res.data
this.fetchInProgress = false
}
})
},
@@ -229,6 +245,33 @@ export const useAccountStore = defineStore("accountStore", {
})
},
newAddress() {
this.address = new AddressModel()
this.showEditDialog = true
},
editAddress(address: AddressModel) {
this.address = address
this.showEditDialog = true
},
async saveAddress() {
this.fetchInProgress = true
if (this.address.id == undefined) {
this.userAccount.addresses.push(this.address)
} else {
this.userAccount.addresses = this.userAccount.addresses.filter(address => {
return address.id != this.address.id
})
this.userAccount.addresses.push(this.address)
}
await this.updateAccount()
this.showEditDialog = false
},
/**
* Remove an address from the user model
*
@@ -238,17 +281,59 @@ export const useAccountStore = defineStore("accountStore", {
this.userAccount.addresses = this.userAccount.addresses.filter((addr: AddressModel) =>
addr != address
)
this.updateAccount()
},
/**
* Remove an payment from the user model
* Add a new payment, opens dialog
*/
newPayment() {
this.payment = new PaymentModel()
this.showEditDialog = true
},
/**
* Edit existing payment, opens dialog
*
* @param payment Payment dataset to edit
*/
editPayment(payment: PaymentModel) {
this.payment = payment
this.showEditDialog = true
},
/**
* Save current edited payment
*/
async savePayment() {
this.fetchInProgress = true
if (this.payment.id == undefined) {
this.userAccount.payments.push(this.payment)
} else {
this.userAccount.payments = this.userAccount.payments.filter(payment => {
return payment.id != this.payment.id
})
this.userAccount.payments.push(this.payment)
}
await this.updateAccount()
this.showEditDialog = false
},
/**
* Remove a payment from the user model
*
* @param address Payment dataset to remove
*/
removePayment(payment: PaymentModel) {
this.userAccount.payments = this.userAccount.payments.filter((paym: PaymentModel) =>
async removePayment(payment: PaymentModel) {
this.userAccount.payments = await this.userAccount.payments.filter((paym: PaymentModel) =>
paym != payment
)
this.updateAccount()
},
/**

View File

@@ -16,7 +16,7 @@ import { useExerciseStore } from "./exercise.store";
export const useBasketStore = defineStore('basketStore', {
state: () => ({
/** Items in customers basket */
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/itemsInBasket", []),
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("eventMaster/basketStore/itemsInBasket", []),
/** Address used in the order dialog */
usedAddress: ref<AddressModel>(null),

View File

@@ -15,10 +15,10 @@ import { AccountApiModel } from "@/data/models/user/accountApiModel";
export const usePreferencesStore = defineStore('preferencesStore', {
state: () => ({
/** Selected theme by user */
theme: useLocalStorage<ThemeEnum>("hackmycart/preferencesStore/theme", ThemeEnum.DARK),
theme: useLocalStorage<ThemeEnum>("eventMaster/preferencesStore/theme", ThemeEnum.DARK),
/** Selected language by user */
language: useLocalStorage<LanguageEnum>("hackmycart/preferencesStore/language", LanguageEnum.GERMAN),
language: useLocalStorage<LanguageEnum>("eventMaster/preferencesStore/language", LanguageEnum.GERMAN),
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
@@ -36,13 +36,13 @@ export const usePreferencesStore = defineStore('preferencesStore', {
showFactoryResetDialog: ref(false),
/** Marks the first run of the app */
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
firstStartup: useLocalStorage<Boolean>("eventMaster/preferencesStore/firstStartup", true),
/** Full name of student */
studentName: useLocalStorage<string>("hackmycart/preferencesStore/studentName", ""),
studentName: useLocalStorage<string>("eventMaster/preferencesStore/studentName", ""),
/** Matrikel number */
registrationNumber: useLocalStorage<string>("hackmycart/preferencesStore/registrationNumber", "")
registrationNumber: useLocalStorage<string>("eventMaster/preferencesStore/registrationNumber", "")
}),
actions: {