Add more user feedback (loading buttons, empty states)

This commit is contained in:
2024-09-24 16:36:30 +02:00
parent 8329a6ae09
commit fd4c1d5a65
8 changed files with 171 additions and 20 deletions

View File

@@ -20,9 +20,12 @@ app.use(bodyParser.json())
// Create database and tables
startDatabase()
// Static files
const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'images')))
// Add delay for more realistic response times
app.use((req, res, next) => {
console.log(123)
setTimeout(next, Math.floor((Math.random () * 4000) + 100))
})
@@ -33,9 +36,6 @@ app.use("/products", product)
app.use("/orders", order)
app.use("/accounts", account)
// Static files
const path = require('path')
app.use('/static', express.static(path.join(__dirname, 'images')))
// Start server
app.listen(port, () => {

View File

@@ -0,0 +1,5 @@
export enum ServerStateEnum {
ONLINE,
OFFLINE,
PENDING
}

View File

@@ -115,5 +115,12 @@
"exerciseGroup3": "Aufgabengruppe 3: Cross-Site Scripting (XSS)",
"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!"
}

View File

@@ -115,5 +115,12 @@
"exerciseGroup3": "Exercise Group 3: Cross-Site Scripting (XSS)",
"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!"
}

View File

@@ -6,11 +6,14 @@ import { useAccountStore } from '@/data/stores/accountStore';
const accountStore = useAccountStore()
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
const loginInProgress = ref(false)
const username = ref("duranduran")
const password = ref("H4nn0ver")
function startLogin() {
accountStore.login(username.value, password.value)
async function startLogin() {
loginInProgress.value = true
await accountStore.login(username.value, password.value)
loginInProgress.value = false
}
</script>
@@ -33,6 +36,7 @@ function startLogin() {
<outlined-button
@click="showRegisterCard = true"
prepend-icon="mdi-plus"
:disabled="loginInProgress"
>
{{ $t('account.noAccountRegister') }}
</outlined-button>
@@ -40,6 +44,7 @@ function startLogin() {
<outlined-button
append-icon="mdi-arrow-right"
@click="startLogin"
:loading="loginInProgress"
>
{{ $t('menu.login') }}
</outlined-button>

View File

@@ -4,10 +4,96 @@ import { ref } from 'vue';
import cardView from '@/components/cardView.vue';
import outlinedButton from '@/components/outlinedButton.vue';
import { useAccountStore } from '@/data/stores/accountStore';
import { i18n } from '@/plugins/i18n';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
const newUser = ref(new AccountModel())
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
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>
<template>
@@ -19,6 +105,7 @@ const accountStore = useAccountStore()
prepend-icon="mdi-account"
v-model="newUser.username"
clearable
:rules="stringRules"
/>
</v-col>
@@ -29,6 +116,7 @@ const accountStore = useAccountStore()
type="password"
v-model="newUser.password"
clearable
:rules="passwordRules"
/>
</v-col>
</v-row>
@@ -40,11 +128,17 @@ const accountStore = useAccountStore()
prepend-icon="mdi-card-account-details"
v-model="newUser.firstName"
clearable
:rules="stringRules"
/>
</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-row>
@@ -55,10 +149,16 @@ const accountStore = useAccountStore()
prepend-icon="mdi-numeric"
v-model="newUser.addresses[0].street"
clearable
:rules="stringRules"
/>
</v-col>
<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-row>
@@ -69,10 +169,16 @@ const accountStore = useAccountStore()
prepend-icon="mdi-city"
v-model="newUser.addresses[0].postalCode"
clearable
:rules="postalRules"
/>
</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-row>
@@ -80,13 +186,15 @@ const accountStore = useAccountStore()
<outlined-button
prepend-icon="mdi-arrow-left"
@click="showRegisterCard = false"
:disabled="registerInProgress"
>
{{ $t('account.backToLogin') }}
</outlined-button>
<outlined-button
prepend-icon="mdi-account-plus"
@click="accountStore.registerAccount(newUser)"
@click="registerAccount"
:loading="registerInProgress"
>
{{ $t('account.register') }}
</outlined-button>

View File

@@ -27,10 +27,23 @@ function formatDateTimeString(string: string) {
<template>
<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>
<orders-card :order="order" />
</v-col>
</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>
</template>

View File

@@ -6,21 +6,22 @@ import outlinedButton from '@/components/outlinedButton.vue';
import { ref } from 'vue';
import confirmDialog from '@/components/confirmDialog.vue';
import { getServerState, resetDatabase } from '@/data/api/mainApi';
import { ServerStateEnum } from '@/data/enums/serverStateEnum';
const feedbackStore = useFeedbackStore()
const showConfirmDialog = ref(false)
const serverOnline = ref(false)
const serverOnline = ref(ServerStateEnum.PENDING)
getServerState()
.then(result => {
if (result.status == 200) {
serverOnline.value = true
serverOnline.value = ServerStateEnum.ONLINE
} else {
serverOnline.value = false
serverOnline.value = ServerStateEnum.OFFLINE
}
})
.catch(error => {
serverOnline.value = false
serverOnline.value = ServerStateEnum.OFFLINE
})
async function resetDb() {
@@ -48,15 +49,20 @@ function resetSettings() {
<v-row>
<v-col>
{{ $t('serverState') }}:
<span v-if="serverOnline" class="text-green">
<span v-if="serverOnline == ServerStateEnum.ONLINE" class="text-green">
<v-icon icon="mdi-check" />
Online
</span>
<span v-else class="text-red">
<span v-else-if="serverOnline == ServerStateEnum.OFFLINE" class="text-red">
<v-icon icon="mdi-alert-circle" />
Offline
</span>
<span v-else-if="serverOnline == ServerStateEnum.PENDING" class="text-orange">
<v-icon icon="mdi-clock" />
Pending...
</span>
</v-col>
</v-row>
<v-row>
@@ -65,7 +71,7 @@ function resetSettings() {
@click="showConfirmDialog = true"
prepend-icon="mdi-database-refresh"
color="red"
:disabled="!serverOnline"
:disabled="serverOnline != ServerStateEnum.ONLINE"
>
{{ $t('preferences.resetDatabase') }}
</outlined-button>