Add more user feedback (loading buttons, empty states)
This commit is contained in:
@@ -20,9 +20,12 @@ app.use(bodyParser.json())
|
|||||||
// Create database and tables
|
// Create database and tables
|
||||||
startDatabase()
|
startDatabase()
|
||||||
|
|
||||||
|
// Static files
|
||||||
|
const path = require('path')
|
||||||
|
app.use('/static', express.static(path.join(__dirname, 'images')))
|
||||||
|
|
||||||
// Add delay for more realistic response times
|
// Add delay for more realistic response times
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
console.log(123)
|
|
||||||
setTimeout(next, Math.floor((Math.random () * 4000) + 100))
|
setTimeout(next, Math.floor((Math.random () * 4000) + 100))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -33,9 +36,6 @@ app.use("/products", product)
|
|||||||
app.use("/orders", order)
|
app.use("/orders", order)
|
||||||
app.use("/accounts", account)
|
app.use("/accounts", account)
|
||||||
|
|
||||||
// Static files
|
|
||||||
const path = require('path')
|
|
||||||
app.use('/static', express.static(path.join(__dirname, 'images')))
|
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
|
|||||||
5
software/src/data/enums/serverStateEnum.ts
Normal file
5
software/src/data/enums/serverStateEnum.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum ServerStateEnum {
|
||||||
|
ONLINE,
|
||||||
|
OFFLINE,
|
||||||
|
PENDING
|
||||||
|
}
|
||||||
@@ -115,5 +115,12 @@
|
|||||||
"exerciseGroup3": "Aufgabengruppe 3: Cross-Site Scripting (XSS)",
|
"exerciseGroup3": "Aufgabengruppe 3: Cross-Site Scripting (XSS)",
|
||||||
"exercise": "Aufgabe {0}"
|
"exercise": "Aufgabe {0}"
|
||||||
},
|
},
|
||||||
"serverState": "Server Status"
|
"serverState": "Server Status",
|
||||||
|
"required": "Darf nicht leer bleiben",
|
||||||
|
"noDigitsAllowed": "Zahlen sind nicht erlaubt",
|
||||||
|
"notEnoughChars": "Nicht lang genug",
|
||||||
|
"passwordToShort": "Passwort zu kurz",
|
||||||
|
"onlyDigitsAllowed": "Nur Zahlen erlaubt",
|
||||||
|
"noOrders": "Keine Bestellungen gefunden",
|
||||||
|
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,5 +115,12 @@
|
|||||||
"exerciseGroup3": "Exercise Group 3: Cross-Site Scripting (XSS)",
|
"exerciseGroup3": "Exercise Group 3: Cross-Site Scripting (XSS)",
|
||||||
"exercise": "Exercise {0}"
|
"exercise": "Exercise {0}"
|
||||||
},
|
},
|
||||||
"serverState": "Server State:"
|
"serverState": "Server State:",
|
||||||
|
"required": "Is required",
|
||||||
|
"noDigitsAllowed": "Digits are not allowed",
|
||||||
|
"notEnoughChars": "Too short",
|
||||||
|
"passwordToShort": "Password too short",
|
||||||
|
"onlyDigitsAllowed": "Only digits are allowed",
|
||||||
|
"noOrders": "No orders found",
|
||||||
|
"noOrdersText": "There are no orders with this account until now. Go to the basket page and order something!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import { useAccountStore } from '@/data/stores/accountStore';
|
|||||||
|
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
||||||
|
const loginInProgress = ref(false)
|
||||||
const username = ref("duranduran")
|
const username = ref("duranduran")
|
||||||
const password = ref("H4nn0ver")
|
const password = ref("H4nn0ver")
|
||||||
|
|
||||||
function startLogin() {
|
async function startLogin() {
|
||||||
accountStore.login(username.value, password.value)
|
loginInProgress.value = true
|
||||||
|
await accountStore.login(username.value, password.value)
|
||||||
|
loginInProgress.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -33,6 +36,7 @@ function startLogin() {
|
|||||||
<outlined-button
|
<outlined-button
|
||||||
@click="showRegisterCard = true"
|
@click="showRegisterCard = true"
|
||||||
prepend-icon="mdi-plus"
|
prepend-icon="mdi-plus"
|
||||||
|
:disabled="loginInProgress"
|
||||||
>
|
>
|
||||||
{{ $t('account.noAccountRegister') }}
|
{{ $t('account.noAccountRegister') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
@@ -40,6 +44,7 @@ function startLogin() {
|
|||||||
<outlined-button
|
<outlined-button
|
||||||
append-icon="mdi-arrow-right"
|
append-icon="mdi-arrow-right"
|
||||||
@click="startLogin"
|
@click="startLogin"
|
||||||
|
:loading="loginInProgress"
|
||||||
>
|
>
|
||||||
{{ $t('menu.login') }}
|
{{ $t('menu.login') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
|
|||||||
@@ -4,10 +4,96 @@ import { ref } from 'vue';
|
|||||||
import cardView from '@/components/cardView.vue';
|
import cardView from '@/components/cardView.vue';
|
||||||
import outlinedButton from '@/components/outlinedButton.vue';
|
import outlinedButton from '@/components/outlinedButton.vue';
|
||||||
import { useAccountStore } from '@/data/stores/accountStore';
|
import { useAccountStore } from '@/data/stores/accountStore';
|
||||||
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
import { useFeedbackStore } from '@/data/stores/feedbackStore';
|
||||||
|
|
||||||
const newUser = ref(new AccountModel())
|
const newUser = ref(new AccountModel())
|
||||||
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
|
||||||
const accountStore = useAccountStore()
|
const accountStore = useAccountStore()
|
||||||
|
const feedbackStore = useFeedbackStore()
|
||||||
|
const registerInProgress = ref(false)
|
||||||
|
|
||||||
|
const stringRules = [
|
||||||
|
value => {
|
||||||
|
if (value) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('required')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (/[^0-9]/.test(value)) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('noDigitsAllowed')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (value?.length >= 4) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('notEnoughChars')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const passwordRules = [
|
||||||
|
value => {
|
||||||
|
if (value) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('required')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (value?.length >= 8) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('passwordToShort')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const postalRules = [
|
||||||
|
value => {
|
||||||
|
if (/[0-9]/.test(value)) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('onlyDigitsAllowed')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (value?.length == 5) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('notEnoughChars')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const numberRules = [
|
||||||
|
value => {
|
||||||
|
if (value) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('required')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value => {
|
||||||
|
if (/[0-9]/.test(value)) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return feedbackStore.i18n.t('onlyDigitsAllowed')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async function registerAccount() {
|
||||||
|
registerInProgress.value = true
|
||||||
|
|
||||||
|
await accountStore.registerAccount(newUser.value)
|
||||||
|
registerInProgress.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -19,6 +105,7 @@ const accountStore = useAccountStore()
|
|||||||
prepend-icon="mdi-account"
|
prepend-icon="mdi-account"
|
||||||
v-model="newUser.username"
|
v-model="newUser.username"
|
||||||
clearable
|
clearable
|
||||||
|
:rules="stringRules"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
@@ -29,6 +116,7 @@ const accountStore = useAccountStore()
|
|||||||
type="password"
|
type="password"
|
||||||
v-model="newUser.password"
|
v-model="newUser.password"
|
||||||
clearable
|
clearable
|
||||||
|
:rules="passwordRules"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -40,11 +128,17 @@ const accountStore = useAccountStore()
|
|||||||
prepend-icon="mdi-card-account-details"
|
prepend-icon="mdi-card-account-details"
|
||||||
v-model="newUser.firstName"
|
v-model="newUser.firstName"
|
||||||
clearable
|
clearable
|
||||||
|
:rules="stringRules"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-text-field :label="$t('userInfo.lastName')" v-model="newUser.lastName" clearable />
|
<v-text-field
|
||||||
|
:label="$t('userInfo.lastName')"
|
||||||
|
v-model="newUser.lastName"
|
||||||
|
clearable
|
||||||
|
:rules="stringRules"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@@ -55,10 +149,16 @@ const accountStore = useAccountStore()
|
|||||||
prepend-icon="mdi-numeric"
|
prepend-icon="mdi-numeric"
|
||||||
v-model="newUser.addresses[0].street"
|
v-model="newUser.addresses[0].street"
|
||||||
clearable
|
clearable
|
||||||
|
:rules="stringRules"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="4">
|
<v-col cols="4">
|
||||||
<v-text-field :label="$t('userInfo.houseNumber')" v-model="newUser.addresses[0].houseNumber" clearable />
|
<v-text-field
|
||||||
|
:label="$t('userInfo.houseNumber')"
|
||||||
|
v-model="newUser.addresses[0].houseNumber"
|
||||||
|
clearable
|
||||||
|
:rules="numberRules"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@@ -69,10 +169,16 @@ const accountStore = useAccountStore()
|
|||||||
prepend-icon="mdi-city"
|
prepend-icon="mdi-city"
|
||||||
v-model="newUser.addresses[0].postalCode"
|
v-model="newUser.addresses[0].postalCode"
|
||||||
clearable
|
clearable
|
||||||
|
:rules="postalRules"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-text-field :label="$t('userInfo.city')" v-model="newUser.addresses[0].city" clearable />
|
<v-text-field
|
||||||
|
:label="$t('userInfo.city')"
|
||||||
|
v-model="newUser.addresses[0].city"
|
||||||
|
clearable
|
||||||
|
:rules="stringRules"
|
||||||
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
@@ -80,13 +186,15 @@ const accountStore = useAccountStore()
|
|||||||
<outlined-button
|
<outlined-button
|
||||||
prepend-icon="mdi-arrow-left"
|
prepend-icon="mdi-arrow-left"
|
||||||
@click="showRegisterCard = false"
|
@click="showRegisterCard = false"
|
||||||
|
:disabled="registerInProgress"
|
||||||
>
|
>
|
||||||
{{ $t('account.backToLogin') }}
|
{{ $t('account.backToLogin') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
|
|
||||||
<outlined-button
|
<outlined-button
|
||||||
prepend-icon="mdi-account-plus"
|
prepend-icon="mdi-account-plus"
|
||||||
@click="accountStore.registerAccount(newUser)"
|
@click="registerAccount"
|
||||||
|
:loading="registerInProgress"
|
||||||
>
|
>
|
||||||
{{ $t('account.register') }}
|
{{ $t('account.register') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
|
|||||||
@@ -27,10 +27,23 @@ function formatDateTimeString(string: string) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-container max-width="1000">
|
<v-container max-width="1000">
|
||||||
<v-row v-for="order in accountStore.orders">
|
<v-row
|
||||||
|
v-if="accountStore.orders.length > 0"
|
||||||
|
v-for="order in accountStore.orders"
|
||||||
|
>
|
||||||
<v-col>
|
<v-col>
|
||||||
<orders-card :order="order" />
|
<orders-card :order="order" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col>
|
||||||
|
<v-empty-state
|
||||||
|
icon="mdi-basket-off"
|
||||||
|
:title="$t('noOrders')"
|
||||||
|
:text="$t('noOrdersText')"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
@@ -6,21 +6,22 @@ import outlinedButton from '@/components/outlinedButton.vue';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import confirmDialog from '@/components/confirmDialog.vue';
|
import confirmDialog from '@/components/confirmDialog.vue';
|
||||||
import { getServerState, resetDatabase } from '@/data/api/mainApi';
|
import { getServerState, resetDatabase } from '@/data/api/mainApi';
|
||||||
|
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
|
||||||
|
|
||||||
const feedbackStore = useFeedbackStore()
|
const feedbackStore = useFeedbackStore()
|
||||||
const showConfirmDialog = ref(false)
|
const showConfirmDialog = ref(false)
|
||||||
const serverOnline = ref(false)
|
const serverOnline = ref(ServerStateEnum.PENDING)
|
||||||
|
|
||||||
getServerState()
|
getServerState()
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result.status == 200) {
|
if (result.status == 200) {
|
||||||
serverOnline.value = true
|
serverOnline.value = ServerStateEnum.ONLINE
|
||||||
} else {
|
} else {
|
||||||
serverOnline.value = false
|
serverOnline.value = ServerStateEnum.OFFLINE
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
serverOnline.value = false
|
serverOnline.value = ServerStateEnum.OFFLINE
|
||||||
})
|
})
|
||||||
|
|
||||||
async function resetDb() {
|
async function resetDb() {
|
||||||
@@ -48,15 +49,20 @@ function resetSettings() {
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
{{ $t('serverState') }}:
|
{{ $t('serverState') }}:
|
||||||
<span v-if="serverOnline" class="text-green">
|
<span v-if="serverOnline == ServerStateEnum.ONLINE" class="text-green">
|
||||||
<v-icon icon="mdi-check" />
|
<v-icon icon="mdi-check" />
|
||||||
Online
|
Online
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else class="text-red">
|
<span v-else-if="serverOnline == ServerStateEnum.OFFLINE" class="text-red">
|
||||||
<v-icon icon="mdi-alert-circle" />
|
<v-icon icon="mdi-alert-circle" />
|
||||||
Offline
|
Offline
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span v-else-if="serverOnline == ServerStateEnum.PENDING" class="text-orange">
|
||||||
|
<v-icon icon="mdi-clock" />
|
||||||
|
Pending...
|
||||||
|
</span>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -65,7 +71,7 @@ function resetSettings() {
|
|||||||
@click="showConfirmDialog = true"
|
@click="showConfirmDialog = true"
|
||||||
prepend-icon="mdi-database-refresh"
|
prepend-icon="mdi-database-refresh"
|
||||||
color="red"
|
color="red"
|
||||||
:disabled="!serverOnline"
|
:disabled="serverOnline != ServerStateEnum.ONLINE"
|
||||||
>
|
>
|
||||||
{{ $t('preferences.resetDatabase') }}
|
{{ $t('preferences.resetDatabase') }}
|
||||||
</outlined-button>
|
</outlined-button>
|
||||||
|
|||||||
Reference in New Issue
Block a user