9 Commits

46 changed files with 808 additions and 477 deletions

View File

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

View File

@@ -4,29 +4,19 @@
"id": 0, "id": 0,
"name": "Unregistered", "name": "Unregistered",
"privilegeBuy": false, "privilegeBuy": false,
"privilegeAdminPanel": false, "privilegeAdminPanel": false
"privilegeFileAccess": false
}, },
{ {
"id": 1, "id": 1,
"name": "User", "name": "User",
"privilegeBuy": true, "privilegeBuy": true,
"privilegeAdminPanel": false, "privilegeAdminPanel": false
"privilegeFileAccess": false
}, },
{ {
"id": 2, "id": 2,
"name": "Admin", "name": "Admin",
"privilegeBuy": true, "privilegeBuy": true,
"privilegeAdminPanel": true, "privilegeAdminPanel": true
"privilegeFileAccess": false
},
{
"id": 3,
"name": "Super-Admin",
"privilegeBuy": true,
"privilegeAdminPanel": true,
"privilegeFileAccess": true
} }
] ]
} }

View File

@@ -19,7 +19,7 @@
"iban": "DE92500105175721645777" "iban": "DE92500105175721645777"
} }
], ],
"accountRoleId": 2 "accountRoleId": 1
}, },
{ {
"username": "katjaStoiber", "username": "katjaStoiber",
@@ -94,7 +94,7 @@
"iban": "DE41500105172184936679" "iban": "DE41500105172184936679"
} }
], ],
"accountRoleId": 3 "accountRoleId": 2
}, },
{ {
"username": "guitarhero", "username": "guitarhero",

View File

@@ -18,35 +18,42 @@ account.get("/", verifyToken, (req: Request, res: Response) => {
.then(accounts => { .then(accounts => {
res.status(200).json(accounts) res.status(200).json(accounts)
}) })
.catch(error => {
res.status(500).send()
})
}) })
// Login user // Login user
account.get("/account/login", async (req: Request, res: Response) => { account.get("/account/login", async (req: Request, res: Response) => {
const encryptedPassword = encryptString(String(req.query.password)) const encryptedPassword = encryptString(String(req.query.password))
// Using raw SQL code for SQL injections! try {
const [results, metadata] = // Using raw SQL code for SQL injections!
await sequelize.query( const [results, metadata] =
"SELECT * FROM Accounts " + await sequelize.query(
"WHERE (username='" + req.query.username + "SELECT * FROM Accounts " +
"' AND password='" + encryptedPassword + "')" "WHERE (username='" + req.query.username +
) "' AND password='" + encryptedPassword + "')"
)
if (results.length != 0) { if (results.length != 0) {
// Creating session token // Creating session token
const token = jwt.sign({ userId: results[0]["id"] }, 'sjcucjdkdf') const token = jwt.sign({ userId: results[0]["id"] }, 'sjcucjdkdf')
// Status: 200 OK // Status: 200 OK
res.status(200).json({ res.status(200).json({
success: true, success: true,
token: token token: token
}) })
} else { } else {
// Status: 401 Unauthorized // Status: 401 Unauthorized
res.status(401).json({ res.status(401).json({
code: 401, code: 401,
message: "Unauthorized" message: "Unauthorized"
}) })
}
} catch (e) {
res.status(500).send()
} }
}) })
@@ -61,6 +68,9 @@ account.get("/account/data", verifyToken, async(req: Request, res: Response) =>
.then(account => { .then(account => {
res.status(200).json(account) res.status(200).json(account)
}) })
.catch(error => {
res.status(500).send()
})
}) })
@@ -102,7 +112,7 @@ account.post("/account", async (req: Request, res: Response) => {
.then(account => { .then(account => {
// Status: 201 Created // Status: 201 Created
res.status(201).json(account) res.status(201).json(account)
}).catch(reason => { }).catch(error => {
// Status: 409 Conflict // Status: 409 Conflict
res.status(409).json({ res.status(409).json({
code: 409, code: 409,
@@ -117,32 +127,28 @@ account.patch("/account", verifyToken, (req: Request, res: Response) => {
where: { id: req.body.id } where: { id: req.body.id }
}) })
.then(async result => { .then(async result => {
for (let payment of req.body.payments) { Payment.destroy({
if (payment.id == undefined) { where: {
payment["accountId"] = req.body.id accountId: req.body.id
await Payment.create(payment)
} else {
await Payment.update(payment,
{
where: { id: payment.id }
}
)
} }
})
Address.destroy({
where: {
accountId: req.body.id
}
})
for (let payment of req.body.payments) {
payment["accountId"] = req.body.id
await Payment.create(payment)
} }
for (let address of req.body.addresses) { for (let address of req.body.addresses) {
if (address.id == undefined) { address["accountId"] = req.body.id
address["accountId"] = req.body.id
await Address.create(address) await Address.create(address)
} else {
await Address.update(address,
{
where: { id: address.id }
}
)
}
} }
// Status: 200 OK // Status: 200 OK
@@ -167,4 +173,7 @@ account.delete("/account/:id", (req: Request, res: Response) => {
.then(account => { .then(account => {
res.status(200).send() res.status(200).send()
}) })
.catch(error => {
res.status(500).send()
})
}) })

View File

@@ -21,16 +21,14 @@ band.get("/", (req: Request, res: Response) => {
Band.findAll({ Band.findAll({
include: [ include: [
{
model: Rating,
},
{ {
model: Genre, model: Genre,
attributes: { attributes: {
exclude: [ "id" ] exclude: [ "id" ]
} }
}, },
Concert Concert,
Rating
] ]
}) })
.then(bands => { .then(bands => {
@@ -65,6 +63,9 @@ band.get("/", (req: Request, res: Response) => {
res.status(200).json(bands) res.status(200).json(bands)
}) })
.catch(error => {
res.status(500).send()
})
}) })
/** /**
@@ -122,7 +123,7 @@ band.get("/band/:name", (req: Request, res: Response) => {
res.status(200).json(band) res.status(200).json(band)
}) })
.catch(e => { .catch(error => {
res.status(404).send() res.status(404).send()
}) })
}) })
@@ -137,10 +138,13 @@ band.get("/search", async (req: Request, res: Response) => {
// On stacked prompts, execute last prompt // On stacked prompts, execute last prompt
if (prompts.length > 1) { if (prompts.length > 1) {
const [results, metadata] = try {
await sequelize.query(prompts[prompts.length - 2]) const [results, metadata] =
await sequelize.query(prompts[prompts.length - 2])
res.status(200).json(results) res.status(200).json(results)
} catch (e) {
res.status(400).send()
}
} else { } else {
Band.findAll({ Band.findAll({
where: { where: {
@@ -153,7 +157,7 @@ band.get("/search", async (req: Request, res: Response) => {
.then(bands => { .then(bands => {
res.status(200).json(bands) res.status(200).json(bands)
}) })
.catch(e => { .catch(error => {
res.status(200).send() res.status(200).send()
}) })
} }
@@ -172,6 +176,9 @@ band.patch("/", (req: Request, res: Response) => {
.then(result => { .then(result => {
res.status(200).json(result) res.status(200).json(result)
}) })
.catch(error => {
res.status(500).send()
})
}) })
@@ -183,6 +190,9 @@ band.post("/", (req: Request, res: Response) => {
.then(result => { .then(result => {
res.status(200).json(result) res.status(200).json(result)
}) })
.catch(error => {
res.status(500).send()
})
}) })
/** /**

View File

@@ -8,4 +8,7 @@ city.get("/", (req: Request, res: Response) => {
.then(cities => { .then(cities => {
res.status(200).json(cities) res.status(200).json(cities)
}) })
.catch(error => {
res.status(500).send()
})
}) })

View File

@@ -35,6 +35,9 @@ concert.get("/", (req: Request, res: Response) => {
res.status(200).json(concerts) res.status(200).json(concerts)
}) })
.catch(error => {
res.status(500).send()
})
}) })
@@ -154,4 +157,7 @@ concert.get("/search", (req: Request, res: Response) => {
.then(concerts => { .then(concerts => {
res.status(200).json(concerts) res.status(200).json(concerts)
}) })
.catch(error => {
res.status(500).send()
})
}) })

View File

@@ -11,13 +11,17 @@ export const exercises = Router()
exercises.get("/", (req: Request, res: Response) => { exercises.get("/", (req: Request, res: Response) => {
Exercise.findAll({ Exercise.findAll({
include: [ ExerciseGroup ] include: [ ExerciseGroup ]
}).then(result => {
result.sort((a, b) => {
return (a.dataValues.exerciseGroup.dataValues.groupNr * 10 + a.dataValues.exerciseNr) > (b.dataValues.exerciseGroup.dataValues.groupNr * 10 + b.dataValues.exerciseNr) ? 1 : -1
})
res.status(200).json(result)
}) })
.then(result => {
result.sort((a, b) => {
return (a.dataValues.exerciseGroup.dataValues.groupNr * 10 + a.dataValues.exerciseNr) > (b.dataValues.exerciseGroup.dataValues.groupNr * 10 + b.dataValues.exerciseNr) ? 1 : -1
})
res.status(200).json(result)
})
.catch(error => {
res.status(500).send()
})
}) })
/** /**
@@ -54,21 +58,7 @@ exercises.post("/:groupNr/:exerciseNr/:state", (req: Request, res: Response) =>
changed: changed changed: changed
}) })
}) })
.catch(error => {
res.status(500).send()
// ExerciseGroup.findOne({ })
// where: { groupNr: req.params.groupNr }
// })
// .then(group => {
// Exercise.findOne({
// where: {
// exerciseNr: req.params.exerciseNr,
// exerciseGroupId: group.id
// }
// })
// .then(exercise => {
// exercise.update({ solved: req.params.state == "1"})
// res.status(200).send()
// })
// })
}) })

View File

@@ -3,6 +3,7 @@ import fs, { createReadStream } from "fs"
import multer from "multer" import multer from "multer"
const upload = multer({ dest: './backend/images/' }) const upload = multer({ dest: './backend/images/' })
import licenses from "../data/licenses.json" import licenses from "../data/licenses.json"
import path from 'path'
export const files = Router() export const files = Router()
@@ -10,13 +11,13 @@ export const files = Router()
* Get all folders * Get all folders
*/ */
files.get("/folders", async (req: Request, res: Response) => { files.get("/folders", async (req: Request, res: Response) => {
let dirNames = fs.readdirSync("./backend/images") let dirNames = fs.readdirSync(path.resolve(__dirname, "../images"))
let result = [] let result = []
dirNames.forEach(dir => { dirNames.forEach(dir => {
result.push({ result.push({
name: dir, name: dir,
nrOfItems: fs.readdirSync("./backend/images/" + dir).length nrOfItems: fs.readdirSync(path.resolve(__dirname, "../images/" + dir)).length
}) })
}) })
@@ -31,26 +32,30 @@ files.get("/folders", async (req: Request, res: Response) => {
*/ */
files.get("/:folder", async (req: Request, res: Response) => { files.get("/:folder", async (req: Request, res: Response) => {
let result = [] let result = []
let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/") let fileNames = fs.readdirSync(path.resolve(__dirname, "../images/" + req.params.folder))
fileNames.forEach(file => { try {
let resData = "" fileNames.forEach(file => {
let url = "http://localhost:3000/static/" + req.params.folder + "/" + file let resData = ""
let url = "http://localhost:3000/static/" + req.params.folder + "/" + file
if (file.endsWith("html") || file.endsWith("js")) { if (file.endsWith("html") || file.endsWith("js")) {
resData = fs.readFileSync("./backend/images/" + req.params.folder + "/" + file, "utf8") resData = fs.readFileSync(path.resolve(__dirname, "../images/" + req.params.folder + "/" + file), "utf8")
} }
result.push({ result.push({
name: file, name: file,
size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size, size: fs.statSync(path.resolve(__dirname, "../images/" + req.params.folder + "/" + file)).size,
content: resData, content: resData,
url: url, url: url,
copyright: licenses.find(data => data.image == file) copyright: licenses.find(data => data.image == file)
})
}) })
})
res.status(200).json(result) res.status(200).json(result)
} catch (error) {
res.status(400).json(error)
}
}) })
@@ -58,7 +63,5 @@ files.get("/:folder", async (req: Request, res: Response) => {
* Upload a file * Upload a file
*/ */
files.post("/", upload.single("file"), function (req: Request, res: Response, next: NextFunction) { files.post("/", upload.single("file"), function (req: Request, res: Response, next: NextFunction) {
console.log(req.file)
res.status(200).send() res.status(200).send()
}) })

View File

@@ -98,7 +98,7 @@ location.get("/location/:urlName", (req: Request, res: Response) => {
res.status(200).json(location) res.status(200).json(location)
}) })
.catch(e => { .catch(error => {
res.status(404).send() res.status(404).send()
}) })
}) })
@@ -133,4 +133,7 @@ location.get("/search", (req: Request, res: Response) => {
.then(locations => { .then(locations => {
res.status(200).json(locations) res.status(200).json(locations)
}) })
.catch(error => {
res.status(500).send()
})
}) })

View File

@@ -10,17 +10,15 @@ import { City } from "../models/locations/city.model";
import { Seat } from "../models/locations/seat.model"; import { Seat } from "../models/locations/seat.model";
import { SeatRow } from "../models/locations/seatRow.model"; import { SeatRow } from "../models/locations/seatRow.model";
import { SeatGroup } from "../models/locations/seatGroup.model"; import { SeatGroup } from "../models/locations/seatGroup.model";
import { verifyToken } from "../middlewares/auth.middleware";
import { Account } from "../models/user/account.model"; import { Account } from "../models/user/account.model";
import { Exercise } from "backend/models/exercises/exercise.model";
export const order = Router() export const order = Router()
// Get all orders // Get all orders
order.get("/", (req: Request, res: Response) => { order.get("/", verifyToken, (req: Request, res: Response) => {
Order.findAll({ Order.findAll({
include: [ include: [
Account,
Address,
{ {
model: Ticket, model: Ticket,
include: [ include: [
@@ -35,14 +33,29 @@ order.get("/", (req: Request, res: Response) => {
include: [ City ] include: [ City ]
} }
] ]
},
{
model: Seat,
include: [
{
model: SeatRow,
include: [ SeatGroup ]
}
]
} }
] ]
} },
Address,
Payment,
Account
] ]
}) })
.then(orders => { .then(orders => {
res.status(200).json(orders) res.status(200).json(orders)
}) })
.catch(error => {
res.status(500).send()
})
}) })
@@ -90,6 +103,9 @@ order.get("/:id", (req: Request, res: Response) => {
.then(orders => { .then(orders => {
res.status(200).json(orders) res.status(200).json(orders)
}) })
.catch(error => {
res.status(500).send()
})
}) })
// Place a new order // Place a new order
@@ -116,4 +132,21 @@ order.post("/", (req: Request, res: Response) => {
// Created // Created
res.status(201).json(order) res.status(201).json(order)
}) })
.catch(error => {
res.status(500).send()
})
})
order.patch("/", (req: Request, res: Response) => {
Order.update(req.body, {
where: {
id: req.body.id
}
})
.then(affectedCount => {
res.status(200).send()
})
.catch(error => {
res.status(500).send()
})
}) })

View File

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

View File

@@ -12,7 +12,7 @@ defineProps({
</script> </script>
<template> <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-row class="d-flex justify-center align-center">
<v-col class="text-caption text-left" v-if="descriptionText.length > 0"> <v-col class="text-caption text-left" v-if="descriptionText.length > 0">
{{ descriptionText }} {{ descriptionText }}

View File

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

View File

@@ -1,5 +1,6 @@
import axios from "axios" import axios from "axios"
import { BasketItemModel } from "../models/ordering/basketItemModel" import { BasketItemModel } from "../models/ordering/basketItemModel"
import { OrderApiModel } from "../models/apiEndpoints/orderApiModel"
const BASE_URL = "http://localhost:3000/orders" const BASE_URL = "http://localhost:3000/orders"
@@ -25,13 +26,6 @@ export async function createOrder(
} }
} }
console.log({
accountId: accountId,
tickets: tickets,
paymentId: paymentId,
addressId: addressId
})
return axios.post(BASE_URL, { return axios.post(BASE_URL, {
accountId: accountId, accountId: accountId,
tickets: tickets, tickets: tickets,
@@ -40,6 +34,14 @@ export async function createOrder(
}) })
} }
export async function fetchAllOrders() { export async function fetchAllOrders(token: string) {
return axios.get(BASE_URL) return axios.get(BASE_URL, {
headers: {
"Authorization": token
}
})
}
export async function patchOrder(order: OrderApiModel) {
return axios.patch(BASE_URL, order)
} }

View File

@@ -74,7 +74,7 @@
"emailIsNotValid": "Ungültige E-Mail Addresse", "emailIsNotValid": "Ungültige E-Mail Addresse",
"emailRequired": "E-Mail-Adresse benötigt", "emailRequired": "E-Mail-Adresse benötigt",
"accountManagement": "Account verwalten", "accountManagement": "Account verwalten",
"accountManagementDescription": "Persönliche Daten, Adressen, Bezahlmethoden", "accountManagementDescription": "Persönliche Daten, Konto löschen",
"login": { "login": {
"pleaseLoginToOrder": "Bitte anmelden zum bestellen", "pleaseLoginToOrder": "Bitte anmelden zum bestellen",
"backToLogin": "Zurück zum Login", "backToLogin": "Zurück zum Login",
@@ -109,7 +109,16 @@
"addNewAccount": "Neuen Account hinzufügen", "addNewAccount": "Neuen Account hinzufügen",
"accountRole": "Account Rolle", "accountRole": "Account Rolle",
"noRealPaymentsNeeded": "Keine echten Kontodaten nötig!", "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": { "order": {
"oclock": "Uhr", "oclock": "Uhr",

View File

@@ -74,7 +74,7 @@
"emailIsNotValid": "E-Mail not valid", "emailIsNotValid": "E-Mail not valid",
"emailRequired": "E-Mail required", "emailRequired": "E-Mail required",
"accountManagement": "Manage Account", "accountManagement": "Manage Account",
"accountManagementDescription": "Personal data, addresses, payments", "accountManagementDescription": "Personal data, delete account",
"login": { "login": {
"pleaseLoginToOrder": "Please login to order", "pleaseLoginToOrder": "Please login to order",
"backToLogin": "Back to Login", "backToLogin": "Back to Login",
@@ -109,7 +109,16 @@
"addNewAccount": "Add new account", "addNewAccount": "Add new account",
"accountRole": "Account Role", "accountRole": "Account Role",
"noRealPaymentsNeeded": "No real payment data required!", "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": { "order": {
"oclock": "o'clock", "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" v-model="accountStore.userAccount.firstName"
variant="outlined" variant="outlined"
:rules="stringRules" :rules="stringRules"
hide-details
/> />
</v-col> </v-col>
<v-col> <v-col>
@@ -90,7 +89,6 @@ const stringRules = [
v-model="accountStore.userAccount.lastName" v-model="accountStore.userAccount.lastName"
variant="outlined" variant="outlined"
:rules="stringRules" :rules="stringRules"
hide-details
/> />
</v-col> </v-col>
</v-row> </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.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"> <script setup lang="ts">
import accountDataCard from './accountDataCard.vue'; import accountDataCard from './accountDataCard.vue';
import accountManagingCard from './accountManagingCard.vue'; import accountManagingCard from './accountManagingCard.vue';
import addressesCard from './addressesCard.vue';
import paymentsCard from './paymentsCard.vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue'; import accountSubPageLayout from '@/layouts/accountSubPageLayout.vue';
@@ -17,18 +15,6 @@ const router = useRouter()
</v-col> </v-col>
</v-row> </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-row>
<v-col> <v-col>
<account-managing-card /> <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"> <script setup lang="ts">
import { useAccountStore } from '@/stores/account.store'; 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 { useRouter } from 'vue-router';
import moment from 'moment';
import { millisecondsToHumanReadableString } from '@/scripts/dateTimeScripts';
const accountStore = useAccountStore() const accountStore = useAccountStore()
const orderStore = useOrderStore()
const router = useRouter() const router = useRouter()
orderStore.getOrdersOfAccount(accountStore.userAccount)
accountStore.refreshAccount()
</script> </script>
<template> <template>
<v-container max-width="1000"> <v-container>
<v-row> <v-row>
<v-col> <dashboard-card
<card-view :title="$t('order.order', 2)"
:title="$t('misc.greeting', { msg: accountStore.userAccount.username })" icon="mdi-basket-check"
icon="mdi-hand-wave" :first-line="orderStore.orders.length + ' ' + $t('order.order', 2)"
> :second-line="$t('order.ordersDescription')"
<v-container> button-route="/account/orders"
<v-row> :loading="orderStore.fetchInProgress"
<v-col> />
<card-view
:title="$t('order.order', 2)"
icon="mdi-basket-check"
@click="router.push('/account/orders')"
>
{{ $t('order.ordersDescription') }}
</card-view>
</v-col>
</v-row>
<v-row> <dashboard-card
<v-col> :title="$t('account.accountManagement')"
<card-view icon="mdi-account"
:title="$t('account.accountManagement')" :first-line="accountStore.userAccount.username"
icon="mdi-account" :second-line="$t('account.accountManagementDescription')"
@click="router.push('/account/data')" :loading="accountStore.fetchInProgress"
> button-route="/account/data"
{{ $t('account.accountManagementDescription') }} />
</card-view>
</v-col>
</v-row>
<v-row> <dashboard-card
<v-col> :title="$t('account.addressManagement')"
<card-view icon="mdi-city"
:title="$t('account.logout.logout')" :first-line="accountStore.userAccount.addresses?.length + ' ' +
icon="mdi-logout" $t('account.userData.address', accountStore.userAccount.addresses?.length)"
@click="accountStore.logout(); router.push('/account/login')" :second-line="$t('account.addressManagementDetails')"
> :loading="accountStore.fetchInProgress"
{{ $t('account.logout.logoutDescription') }} button-route="/account/addresses"
</card-view> />
</v-col>
</v-row>
</v-container>
</card-view>
</v-col> <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.logout') }}
</outlined-button>
</template>
</dashboard-card>
</v-row> </v-row>
</v-container> </v-container>
</template> </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() { async function registerAccount() {
accountStore.registerAccount() accountStore.registerAccount()
.then(result => { .then(result => {
console.log(result)
if (result) { if (result) {
showRegisterCard.value = false showRegisterCard.value = false
} }

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import { useAccountStore } from '@/stores/account.store';
import { useLocationStore } from '@/stores/location.store'; import { useLocationStore } from '@/stores/location.store';
import { useGenreStore } from '@/stores/genre.store'; import { useGenreStore } from '@/stores/genre.store';
import { usePreferencesStore } from '@/stores/preferences.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 { useOrderStore } from '@/stores/order.store';
import { useFilesStore } from '@/stores/files.store'; import { useFilesStore } from '@/stores/files.store';

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue'; import dataLayout from '@/layouts/dataLayout.vue';
import { useOrderStore } from '@/stores/order.store'; import { useOrderStore } from '@/stores/order.store';
import moment from 'moment'; import moment from 'moment';
import OrderDetailDialog from './orderDetailDialog.vue'; import OrderDetailDialog from './orderDetailDialog.vue';
@@ -13,19 +13,21 @@ const headers = [
{ title: "Adresse", value: "street" }, { title: "Adresse", value: "street" },
{ title: "Stadt", value: "city" }, { title: "Stadt", value: "city" },
{ title: "Versendet", value: "shipped" }, { title: "Versendet", value: "shipped" },
{ title: "", value: "edit", width: 130 } { title: "Aktionen", value: "edit", width: 130 }
] ]
orderStore.getAllOrders() orderStore.getAllOrders()
</script> </script>
<template> <template>
<admin-data-layout <data-layout
:hide-add-button="true" :hide-add-button="true"
> >
<v-data-table <v-data-table
:headers="headers" :headers="headers"
:items="orderStore.orders" :items="orderStore.orders"
:loading="orderStore.fetchInProgress"
:items-per-page="100"
> >
<template #item.account="{ item }"> <template #item.account="{ item }">
{{ item.account.firstName }} {{ item.account.lastName }} {{ item.account.firstName }} {{ item.account.lastName }}
@@ -46,27 +48,27 @@ orderStore.getAllOrders()
<template #item.shipped="{ item }"> <template #item.shipped="{ item }">
<v-icon <v-icon
:icon="item.shipped ? 'mdi-check' : 'mdi-close'" :icon="item.shipped ? 'mdi-check' : 'mdi-close'"
:color="item.shipped ? 'green' : 'red'" :color="item.shipped ? 'success' : 'error'"
/> />
</template> </template>
<template #item.edit="{ item }"> <template #item.edit="{ item }">
<!-- todo <v-btn <v-btn
icon="mdi-eye" icon="mdi-eye"
variant="plain" variant="plain"
@click="orderStore.openDetails(item)" @click="orderStore.openDetails(item)"
/> --> />
<!-- todo <v-btn <v-btn
icon="mdi-delete" :icon="item.shipped ? 'mdi-close-circle-outline' : 'mdi-check-circle-outline'"
variant="plain" variant="plain"
color="red" :color="item.shipped ? 'error' : 'success'"
@click="orderStore.deleteOrder(item)" @click="orderStore.changeOrderShippedState(item, !item.shipped)"
/> --> />
</template> </template>
</v-data-table> </v-data-table>
</admin-data-layout> </data-layout>
<order-detail-dialog /> <order-detail-dialog />
</template> </template>

View File

@@ -12,15 +12,67 @@ const orderStore = useOrderStore()
v-model="orderStore.showDetailDialog" v-model="orderStore.showDetailDialog"
:title="$t('order.order')" :title="$t('order.order')"
icon="mdi-basket" icon="mdi-basket"
max-width="800"
> >
<v-list> <v-list>
<v-list-subheader> <v-list-subheader>
{{ $t('ticket.ticket', 2) }} {{ $t('account.account') }}
</v-list-subheader> </v-list-subheader>
<v-list-item v-for="ticket of orderStore.order.tickets"> <v-list-item prepend-icon="mdi-account">
{{ moment(ticket.concert.date).format("DD.MM.YYYY") }} - {{ orderStore.order.account.username }}
{{ ticket.concert.band.name }} - {{ ticket.concert.name }} </v-list-item>
<v-list-item prepend-icon="mdi-card-account-details">
{{ orderStore.order.account.firstName }} {{ orderStore.order.account.lastName }}
</v-list-item>
<v-list-item prepend-icon="mdi-home">
{{ orderStore.order.address.street }} {{ orderStore.order.address.houseNumber }}
</v-list-item>
<v-list-item prepend-icon="mdi-city">
{{ orderStore.order.address.postalCode }} {{ orderStore.order.address.city }}
</v-list-item>
<v-list-subheader>
{{ $t('order.order') }}
</v-list-subheader>
<v-list-item prepend-icon="mdi-calendar">
{{ moment(orderStore.order.orderedAt).format("DD.MM.YYYY, HH:mm:ss") }}
</v-list-item>
<v-list-item prepend-icon="mdi-truck">
{{ orderStore.order.shipped ? 'Versendet' : 'Nicht versendet' }}
</v-list-item>
<v-list-item>
<v-table>
<thead>
<tr>
<th>{{ $t('concert.date') }}</th>
<th>{{ $t('concert.name') }}</th>
<th>{{ $t('band.name') }}</th>
<th>{{ $t('location.name') }}</th>
<th>{{ $t('location.seat.seatGroup') }}</th>
<th>{{ $t('location.seat.seatRow') }}</th>
<th>{{ $t('location.seat.seat') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="ticket of orderStore.order.tickets">
<td>{{ moment(ticket.concert.date).format("DD.MM.YYYY") }}</td>
<td>{{ ticket.concert.name }}</td>
<td>{{ ticket.concert.band.name }}</td>
<td>{{ ticket.concert.location.name }}</td>
<td>{{ ticket.seat.seatRow.seatGroup.name }}</td>
<td>{{ ticket.seat.seatRow.row }}</td>
<td>{{ ticket.seat.seatNr }}</td>
</tr>
</tbody>
</v-table>
</v-list-item> </v-list-item>
</v-list> </v-list>
</action-dialog> </action-dialog>

View File

@@ -19,6 +19,22 @@ function getDotColor(exerciseGroupNr: number) {
case 3: return "pink" case 3: return "pink"
} }
} }
function generateExerciseKey() {
try {
let code = ""
for (let i = 0; i < 13; i++) {
if (exerciseStore.exercises[i].solved) {
code += "3"
} else {
code += "0"
}
}
return (Number(code) + Number(preferencesStore.registrationNumber)) * 237
} catch(e) {}
}
</script> </script>
<template> <template>
@@ -26,20 +42,7 @@ function getDotColor(exerciseGroupNr: number) {
<v-row> <v-row>
<v-spacer /> <v-spacer />
<v-col
v-if="preferencesStore.studentName.length < 3 || preferencesStore.registrationNumber.length < 7"
cols="auto"
>
<card-view variant="outlined" >
{{ $t('misc.fulfillYourPersonalDataFirst') }}
</card-view>
</v-col>
<v-col cols="auto"> <v-col cols="auto">
<v-tooltip :text="$t('misc.fulfillYourPersonalDataFirst')">
<template #activator="{ props }"></template>
</v-tooltip>
<outlined-button <outlined-button
prepend-icon="mdi-file-pdf-box" prepend-icon="mdi-file-pdf-box"
@click="generateResultsPdf()" @click="generateResultsPdf()"
@@ -50,6 +53,17 @@ function getDotColor(exerciseGroupNr: number) {
</v-col> </v-col>
</v-row> </v-row>
<v-row>
<v-col class="text-h5 text-center">
<div>
Persönlicher Lösungsschlüssel:
</div>
<div>
{{ generateExerciseKey() }}
</div>
</v-col>
</v-row>
<v-row> <v-row>
<v-col> <v-col>
<card-view <card-view

View File

@@ -2,6 +2,7 @@
import actionDialog from '@/components/basics/actionDialog.vue'; import actionDialog from '@/components/basics/actionDialog.vue';
import outlinedButton from '@/components/basics/outlinedButton.vue'; import outlinedButton from '@/components/basics/outlinedButton.vue';
import ServerStateText from '@/components/pageParts/serverStateText.vue'; import ServerStateText from '@/components/pageParts/serverStateText.vue';
import { getRegisterNumberRules, getStringRules } from '@/scripts/validationRules';
import { useFeedbackStore } from '@/stores/feedback.store'; import { useFeedbackStore } from '@/stores/feedback.store';
import { usePreferencesStore } from '@/stores/preferences.store'; import { usePreferencesStore } from '@/stores/preferences.store';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
@@ -135,9 +136,9 @@ watch(() => currentStep.value, () => {
<v-col> <v-col>
<v-text-field <v-text-field
variant="outlined" variant="outlined"
hide-details
:label="$t('misc.yourFullName')" :label="$t('misc.yourFullName')"
v-model="preferencesStore.studentName" v-model="preferencesStore.studentName"
:rules="getStringRules(4)"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -146,9 +147,9 @@ watch(() => currentStep.value, () => {
<v-col> <v-col>
<v-text-field <v-text-field
variant="outlined" variant="outlined"
hide-details
:label="$t('misc.registrationNumber')" :label="$t('misc.registrationNumber')"
v-model="preferencesStore.registrationNumber" v-model="preferencesStore.registrationNumber"
:rules="getRegisterNumberRules()"
/> />
</v-col> </v-col>
</v-row> </v-row>
@@ -177,8 +178,8 @@ watch(() => currentStep.value, () => {
<outlined-button <outlined-button
v-else v-else
@click="showDialog = false; preferencesStore.firstStartup = false" @click="showDialog = false; preferencesStore.firstStartup = false"
:disabled="preferencesStore.studentName.length == 0 || :disabled="preferencesStore.studentName.length < 5 ||
preferencesStore.registrationNumber.length == 0" preferencesStore.registrationNumber.length < 8"
prepend-icon="mdi-check" prepend-icon="mdi-check"
color="success" color="success"
> >

View File

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

View File

@@ -21,3 +21,15 @@ export function dateToHumanReadableString(date: Date) {
export function dateStringToHumanReadableString(string: string) { export function dateStringToHumanReadableString(string: string) {
return dateToHumanReadableString(new Date(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

@@ -169,3 +169,31 @@ export function getIbanRules() {
} }
] ]
} }
export function getRegisterNumberRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value) {
return true
} else {
return feedbackStore.i18n.t('misc.validation.required')
}
},
value => {
if (value?.length >= 8) {
return true
} else {
return feedbackStore.i18n.t('misc.validation.notEnoughChars')
}
},
value => {
if(!isNaN(value) && !isNaN(parseFloat(value))) {
return true
} else {
return feedbackStore.i18n.t('misc.validation.onlyDigitsAllowed')
}
}
]
}

View File

@@ -10,6 +10,7 @@ import { AccountApiModel } from "../data/models/user/accountApiModel";
import { ref } from "vue"; import { ref } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { useExerciseStore } from "./exercise.store"; import { useExerciseStore } from "./exercise.store";
import moment, { Moment } from "moment";
export const useAccountStore = defineStore("accountStore", { export const useAccountStore = defineStore("accountStore", {
state: () => ({ state: () => ({
@@ -17,10 +18,10 @@ export const useAccountStore = defineStore("accountStore", {
accounts: ref<Array<AccountApiModel>>([]), accounts: ref<Array<AccountApiModel>>([]),
/** Server token of currently logged in account */ /** Server token of currently logged in account */
userAccountToken: useLocalStorage("hackmycart/accountStore/userAccountToken", ""), userAccountToken: useLocalStorage("eventMaster/accountStore/userAccountToken", ""),
/** Useraccount which is currently logged in */ /** 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 */ /** User input on login screen */
loginData: ref<{ username: String, password: String}>( loginData: ref<{ username: String, password: String}>(
@@ -37,7 +38,15 @@ export const useAccountStore = defineStore("accountStore", {
adminPanelVisible: ref(false), adminPanelVisible: ref(false),
/** Flag to activate buy option on basket page */ /** 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: { actions: {
@@ -80,6 +89,7 @@ export const useAccountStore = defineStore("accountStore", {
await getLogin(this.loginData.username, this.loginData.password) await getLogin(this.loginData.username, this.loginData.password)
.then(async result => { .then(async result => {
this.userAccountToken = result.data.token this.userAccountToken = result.data.token
this.loggedInTimeStamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS")
getAccount(this.userAccountToken) getAccount(this.userAccountToken)
.then(response => { .then(response => {
@@ -91,7 +101,7 @@ export const useAccountStore = defineStore("accountStore", {
this.privilegeBuy = true this.privilegeBuy = true
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
if (response.data.accountRoleId == 3) { if (response.data.accountRoleId == 2) {
exerciseStore.solveExercise(2, 5) exerciseStore.solveExercise(2, 5)
} }
}) })
@@ -113,14 +123,16 @@ export const useAccountStore = defineStore("accountStore", {
* Reload account information about current logged in user * Reload account information about current logged in user
*/ */
async refreshAccount() { async refreshAccount() {
this.fetchInProgress = true
getAccount(this.userAccountToken) getAccount(this.userAccountToken)
.then(response => { .then(response => {
this.userAccount = response.data this.userAccount = response.data
this.fetchInProgress = false
this.privilegeBuy = true this.privilegeBuy = true
this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel this.adminPanelVisible = response.data.accountRole.privilegeAdminPanel
this.fetchInProgress = false
}) })
}, },
@@ -133,6 +145,7 @@ export const useAccountStore = defineStore("accountStore", {
async registerAccount(): Promise<boolean> { async registerAccount(): Promise<boolean> {
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore() const exerciseStore = useExerciseStore()
let success = false
this.fetchInProgress = true this.fetchInProgress = true
if (this.registerData.username == null || this.registerData.username.length < 4) { 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,}))$/)) { } 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) feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTMAILADDRESSUNVALID)
} }
else { else
{
await registerAccount(this.registerData) await registerAccount(this.registerData)
.then(async res => { .then(async res => {
if (res.status == 201) { if (res.status == 201) {
@@ -156,6 +170,7 @@ export const useAccountStore = defineStore("accountStore", {
} }
this.fetchInProgress = false this.fetchInProgress = false
success = true
}) })
.catch((error) => { .catch((error) => {
if (error.status == 400) { if (error.status == 400) {
@@ -165,12 +180,11 @@ export const useAccountStore = defineStore("accountStore", {
} }
this.fetchInProgress = false this.fetchInProgress = false
return false
}) })
} }
this.fetchInProgress = false this.fetchInProgress = false
return false return success
}, },
/** /**
@@ -179,6 +193,7 @@ export const useAccountStore = defineStore("accountStore", {
async updateAccount() { async updateAccount() {
const feedbackStore = useFeedbackStore() const feedbackStore = useFeedbackStore()
const exerciseStore = useExerciseStore() const exerciseStore = useExerciseStore()
this.fetchInProgress = true
// Check for exercise 0.2 completion // Check for exercise 0.2 completion
let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" && let accountComplete = this.userAccount.firstName != "" && this.userAccount.lastName != "" &&
@@ -196,6 +211,7 @@ export const useAccountStore = defineStore("accountStore", {
feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL) feedbackStore.addSnackbar(BannerStateEnum.ACCOUNTUPDATESUCCESSFUL)
this.userAccount = res.data 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 * 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) => this.userAccount.addresses = this.userAccount.addresses.filter((addr: AddressModel) =>
addr != address 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 * @param address Payment dataset to remove
*/ */
removePayment(payment: PaymentModel) { async removePayment(payment: PaymentModel) {
this.userAccount.payments = this.userAccount.payments.filter((paym: PaymentModel) => this.userAccount.payments = await this.userAccount.payments.filter((paym: PaymentModel) =>
paym != payment paym != payment
) )
this.updateAccount()
}, },
/** /**

View File

@@ -16,7 +16,7 @@ import { useExerciseStore } from "./exercise.store";
export const useBasketStore = defineStore('basketStore', { export const useBasketStore = defineStore('basketStore', {
state: () => ({ state: () => ({
/** Items in customers basket */ /** Items in customers basket */
itemsInBasket: useLocalStorage<Array<BasketItemModel>>("hackmycart/basketStore/itemsInBasket", []), itemsInBasket: useLocalStorage<Array<BasketItemModel>>("eventMaster/basketStore/itemsInBasket", []),
/** Address used in the order dialog */ /** Address used in the order dialog */
usedAddress: ref<AddressModel>(null), usedAddress: ref<AddressModel>(null),
@@ -119,7 +119,6 @@ export const useBasketStore = defineStore('basketStore', {
for (let item of this.itemsInBasket) { for (let item of this.itemsInBasket) {
if (!item.concert.offered) { if (!item.concert.offered) {
exerciseStore.solveExercise(1, 2) exerciseStore.solveExercise(1, 2)
feedbackStore.addSnackbar(BannerStateEnum.EXERCISESOLVED12)
} }
} }

View File

@@ -1,16 +1,19 @@
import { fetchAllOrders, fetchUserOrders } from "@/data/api/orderApi"; import { fetchAllOrders, fetchUserOrders, patchOrder } from "@/data/api/orderApi";
import { OrderApiModel } from "@/data/models/apiEndpoints/orderApiModel"; import { OrderApiModel } from "@/data/models/apiEndpoints/orderApiModel";
import { AccountModel } from "@/data/models/user/accountModel"; import { AccountModel } from "@/data/models/user/accountModel";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import { useAccountStore } from "./account.store";
export const useOrderStore = defineStore("orderStore", { export const useOrderStore = defineStore("orderStore", {
state: () => ({ state: () => ({
/** All orders of one/all users */ /** All orders of one/all users */
orders: ref<Array<OrderApiModel>>([]), orders: ref<Array<OrderApiModel>>([]),
/** Current selected order */
order: ref<OrderApiModel>(new OrderApiModel), order: ref<OrderApiModel>(new OrderApiModel),
/** Show detail dialog on admin page */
showDetailDialog: ref<boolean>(false), showDetailDialog: ref<boolean>(false),
/** Request to server sent, waiting for data response */ /** Request to server sent, waiting for data response */
@@ -22,9 +25,10 @@ export const useOrderStore = defineStore("orderStore", {
* Get all orders from all accounts from server * Get all orders from all accounts from server
*/ */
async getAllOrders() { async getAllOrders() {
const accountStore = useAccountStore()
this.fetchInProgress = true this.fetchInProgress = true
fetchAllOrders() fetchAllOrders(accountStore.userAccountToken)
.then(res => { .then(res => {
this.orders = res.data this.orders = res.data
this.fetchInProgress = false this.fetchInProgress = false
@@ -46,13 +50,31 @@ export const useOrderStore = defineStore("orderStore", {
}) })
}, },
/**
* Open detail dialog
*
* @param order Order to view
*/
openDetails(order: OrderApiModel) { openDetails(order: OrderApiModel) {
this.order = order this.order = order
this.showDetailDialog = true this.showDetailDialog = true
}, },
async deleteOrder(order: OrderApiModel) {
// todo /**
*
* @param order
* @param shipped
*/
async changeOrderShippedState(order: OrderApiModel, shipped: boolean) {
this.fetchInProgress = true
order.shipped = shipped
patchOrder(order)
.then(res => {
this.getAllOrders()
})
} }
} }
}) })

View File

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