Better validation on text fields, change AlertBanner to Snackbar

This commit is contained in:
2024-09-24 22:18:27 +02:00
parent 3dc4c7af1e
commit 531f964841
19 changed files with 357 additions and 190 deletions

View File

@@ -7,10 +7,12 @@ import navigationItems from './components/navigationItems.vue';
import { useProductStore } from './data/stores/productStore';
import { useCategoryStore } from './data/stores/categoryStore';
import { usePreferencesStore } from './data/stores/preferencesStore';
import { useFeedbackStore } from './data/stores/feedbackStore';
const preferencesStore = usePreferencesStore()
const productStore = useProductStore()
const categoryStore = useCategoryStore()
const feedbackStore = useFeedbackStore()
const theme = useTheme()
const navRail = ref(vuetify.display.mobile)
@@ -38,6 +40,26 @@ watch(() => preferencesStore.language, () => {
</v-navigation-drawer>
<v-main>
<!-- Snackbar in the top right corner for user feedback -->
<v-snackbar
v-model="feedbackStore.showBanner"
timeout="3000"
location="top right"
:color="feedbackStore.color"
close
>
<v-icon :icon="feedbackStore.icon" />
{{ feedbackStore.title }}
<template v-slot:actions>
<v-btn
variant="text"
@click="feedbackStore.showBanner = false"
icon="mdi-close"
/>
</template>
</v-snackbar>
<!-- Here changes the router the content -->
<router-view></router-view>
</v-main>

View File

@@ -1,22 +0,0 @@
<script setup lang="ts">
import { useFeedbackStore } from '@/data/stores/feedbackStore';
const feedbackStore = useFeedbackStore()
</script>
<template>
<v-expand-transition>
<v-row v-if="feedbackStore.showBanner">
<v-col>
<v-alert
v-model="feedbackStore.showBanner"
:color="feedbackStore.color"
:icon="feedbackStore.icon"
closable
>
{{ feedbackStore.title }}
</v-alert>
</v-col>
</v-row>
</v-expand-transition>
</template>

View File

@@ -6,6 +6,7 @@ export class AccountModel {
id: number
username: string = ""
password: string = ""
email: string = ""
firstName: string = ""
lastName: string = ""
addresses: Array<AddressModel> = [ new AddressModel() ]

View File

@@ -71,7 +71,8 @@
"masterData": "Stammdaten",
"noAddresses": "Keine Adressen gefunden",
"noPayments": "Keine Bezahlarten gefunden",
"newPayment": "New Payment"
"newPayment": "New Payment",
"email": "E-Mail-Adresse"
},
"bannerMessages": {
"loginSuccessful": "Login erfolgreich!",
@@ -127,5 +128,10 @@
"noOrders": "Keine Bestellungen gefunden",
"noOrdersText": "Bisher wurden keine Bestellungen von diesem Account getätigt. Gehe zum Warenkorb und bestelle!",
"add": "Hinzufügen",
"remove": "Entfernen"
"remove": "Entfernen",
"emailIsNotValid": "Ungültige E-Mail Addresse",
"emailRequired": "E-Mail-Adresse benötigt",
"tooMuchChars": "Zu lang",
"digitsAtStartNeeded": "Muss mit einer Zahl beginnen",
"wrongIban": "Falsches IBAN Format, nur deutsche IBAN-Nummern erlaubt!"
}

View File

@@ -71,7 +71,8 @@
"masterData": "Master data",
"noAddresses": "No Addresses found",
"noPayments": "No payments found",
"newPayment": "New Payment"
"newPayment": "New Payment",
"email": "E-Mail address"
},
"bannerMessages": {
"loginSuccessful": "Login erfolgreich!",
@@ -127,5 +128,10 @@
"noOrders": "No orders found",
"noOrdersText": "There are no orders with this account until now. Go to the basket page and order something!",
"add": "Add",
"remove": "Remove"
"remove": "Remove",
"emailIsNotValid": "E-mail must be valid",
"emailRequired": "E-Mail is required",
"tooMuchChars": "Too long",
"digitsAtStartNeeded": "Has to beginn with a number",
"wrongIban": "Wrong IBAN format, only German IBANs are allowed!"
}

View File

@@ -1,10 +1,51 @@
<script setup lang="ts">
import cardView from '@/components/cardView.vue';
import outlinedButton from '@/components/outlinedButton.vue';
import { useAccountStore } from '@/data/stores/accountStore';
import { ref } from 'vue';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
const accountStore = useAccountStore()
const feedbackStore = useFeedbackStore()
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 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 >= 3) {
return true
} else {
return feedbackStore.i18n.t('notEnoughChars')
}
}
]
</script>
<template>
@@ -12,6 +53,15 @@ const accountStore = useAccountStore()
:title="$t('account.masterData')"
icon="mdi-account"
>
<v-row>
<v-col>
<v-text-field
:label="$t('account.email')"
v-model="accountStore.userAccount.email"
disabled
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
@@ -25,6 +75,7 @@ const accountStore = useAccountStore()
:label="$t('account.password')"
v-model="accountStore.userAccount.password"
type="password"
:rules="passwordRules"
/>
</v-col>
</v-row>
@@ -34,12 +85,14 @@ const accountStore = useAccountStore()
<v-text-field
:label="$t('userInfo.firstName')"
v-model="accountStore.userAccount.firstName"
:rules="stringRules"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.lastName')"
v-model="accountStore.userAccount.lastName"
:rules="stringRules"
/>
</v-col>
</v-row>

View File

@@ -3,6 +3,8 @@ import cardView from '@/components/cardView.vue';
import { useAccountStore } from '@/data/stores/accountStore';
import outlinedButton from '@/components/outlinedButton.vue';
import { AddressModel } from '@/data/models/addressModel';
import { useFeedbackStore } from '@/data/stores/feedbackStore';
import { getNumberStartRules, getPostalRules, getStringRules } from '@/scripts/validationRules';
const accountStore = useAccountStore()
</script>
@@ -27,12 +29,16 @@ const accountStore = useAccountStore()
<v-text-field
:label="$t('userInfo.street')"
v-model="address.street"
:rules="getStringRules()"
clearable
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.houseNumber')"
v-model="address.houseNumber"
:rules="getNumberStartRules()"
clearable
/>
</v-col>
</v-row>
@@ -42,12 +48,16 @@ const accountStore = useAccountStore()
<v-text-field
:label="$t('userInfo.postalCode')"
v-model="address.postalCode"
:rules="getPostalRules()"
clearable
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.city')"
v-model="address.city"
:rules="getStringRules()"
clearable
/>
</v-col>
</v-row>

View File

@@ -3,6 +3,7 @@ import cardView from '@/components/cardView.vue';
import { useAccountStore } from '@/data/stores/accountStore';
import outlinedButton from '@/components/outlinedButton.vue';
import { PaymentModel } from '@/data/models/paymentModel';
import { getIbanRules, getStringRules } from '@/scripts/validationRules';
const accountStore = useAccountStore()
@@ -29,12 +30,14 @@ const accountStore = useAccountStore()
<v-text-field
:label="$t('userInfo.bankName')"
v-model="payment.bankName"
:rules="getStringRules()"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.iban')"
v-model="payment.iban"
:rules="getIbanRules()"
/>
</v-col>
</v-row>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import { useBasketStore } from '@/data/stores/basketStore';
import productsTable from './productsTable.vue';
import alertBanner from '@/components/alertBanner.vue';
import cardView from '@/components/cardView.vue';
import orderingDialog from './orderingDialog.vue';
import outlinedButton from '@/components/outlinedButton.vue';
@@ -15,11 +14,6 @@ const showOrderingDialog = ref()
<template>
<v-container max-width="1000">
<v-row>
<v-col>
<alert-banner />
</v-col>
</v-row>
<v-row>
<v-col>
<card-view

View File

@@ -2,19 +2,12 @@
import { ref } from 'vue';
import loginForm from './loginForm.vue';
import registerForm from './registerForm.vue';
import alertBanner from '@/components/alertBanner.vue';
const showRegisterCard = ref(false)
</script>
<template>
<v-container max-width="800">
<v-row>
<v-col>
<alert-banner />
</v-col>
</v-row>
<v-container max-width="500">
<v-expand-transition>
<v-row v-if="!showRegisterCard">
<v-col>

View File

@@ -9,10 +9,28 @@ const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, defaul
const loginInProgress = ref(false)
const username = ref("duranduran")
const password = ref("H4nn0ver")
const usernameWrong = ref(false)
const passwordWrong = ref(false)
async function startLogin() {
loginInProgress.value = true
await accountStore.login(username.value, password.value)
usernameWrong.value = false
passwordWrong.value = false
if (username.value == null || username.value.length == 0) {
usernameWrong.value = true
}
if (password.value == null || password.value.length == 0) {
passwordWrong.value = true
}
if (username.value != null && username.value.length > 0 &&
password.value != null && password.value.length > 0)
{
await accountStore.login(username.value, password.value)
}
loginInProgress.value = false
}
</script>
@@ -21,14 +39,26 @@ async function startLogin() {
<card-view :title="$t('menu.login')" prepend-icon="mdi-login" elevation="8">
<v-row>
<v-col>
<v-text-field :label="$t('account.username')" prepend-icon="mdi-account" clearable v-model="username"/>
<v-text-field
:label="$t('account.username')"
prepend-icon="mdi-account"
v-model="username"
:error="usernameWrong"
clearable
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field :label="$t('account.password')" prepend-icon="mdi-key" type="password"
clearable v-model="password" />
<v-text-field
:label="$t('account.password')"
prepend-icon="mdi-key"
type="password"
v-model="password"
:error="passwordWrong"
clearable
/>
</v-col>
</v-row>

View File

@@ -4,90 +4,14 @@ 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';
import { getEmailRules, getPasswordRules, getStringRules } from '@/scripts/validationRules';
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
@@ -97,7 +21,10 @@ async function registerAccount() {
</script>
<template>
<card-view :title="$t('account.register')">
<card-view
:title="$t('account.register')"
icon="mdi-account-plus"
>
<v-row>
<v-col>
<v-text-field
@@ -105,10 +32,12 @@ async function registerAccount() {
prepend-icon="mdi-account"
v-model="newUser.username"
clearable
:rules="stringRules"
:rules="getStringRules()"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('account.password')"
@@ -116,7 +45,7 @@ async function registerAccount() {
type="password"
v-model="newUser.password"
clearable
:rules="passwordRules"
:rules="getPasswordRules()"
/>
</v-col>
</v-row>
@@ -124,60 +53,11 @@ async function registerAccount() {
<v-row>
<v-col>
<v-text-field
:label="$t('userInfo.firstName')"
prepend-icon="mdi-card-account-details"
v-model="newUser.firstName"
:label="$t('account.email')"
prepend-icon="mdi-mail"
v-model="newUser.email"
:rules="getEmailRules()"
clearable
:rules="stringRules"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.lastName')"
v-model="newUser.lastName"
clearable
:rules="stringRules"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('userInfo.street')"
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
:rules="numberRules"
/>
</v-col>
</v-row>
<v-row>
<v-col cols="4">
<v-text-field
:label="$t('userInfo.postalCode')"
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
:rules="stringRules"
/>
</v-col>
</v-row>

View File

@@ -1,13 +1,10 @@
<script setup lang="ts">
import pageSetup from './pageSetup.vue';
import systemSetup from './systemSetup.vue';
import alertBanner from '@/components/alertBanner.vue';
</script>
<template>
<v-container max-width="800">
<alert-banner />
<v-row>
<v-col>
<page-setup />

View File

@@ -25,10 +25,13 @@ getServerState()
})
async function resetDb() {
serverOnline.value = ServerStateEnum.PENDING
await resetDatabase()
.then(result => {
if (result.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.DATABASERESETSUCCESSFUL)
serverOnline.value = ServerStateEnum.ONLINE
}
})

View File

@@ -3,7 +3,6 @@ import productCard from "./productCard.vue"
import productDetails from "./productDetailsDialog.vue"
import { ref, watch } from "vue";
import { useProductStore } from "@/data/stores/productStore";
import alertBanner from "@/components/alertBanner.vue";
import filterNavDrawer from "./filterNavDrawer.vue";
import { ProductModel } from '@/data/models/productModel';
@@ -24,12 +23,6 @@ watch(() => productStore.onlyDiscounts, async () => { productStore.filterProduct
<template>
<v-container max-width="1000">
<v-row>
<v-col>
<alert-banner />
</v-col>
</v-row>
<v-row dense>
<v-col
v-if="productStore.filteredProducts.length > 0"

View File

@@ -0,0 +1,169 @@
import { useFeedbackStore } from "@/data/stores/feedbackStore"
/**
* Check a string for no numbers and more than four digits
*
* @returns Array of rules
*/
export function getStringRules() {
const feedbackStore = useFeedbackStore()
return [
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')
}
}
]
}
/**
* Check a string for exact five digits length and only digits
*
* @returns Array of rules
*/
export function getPostalRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value?.length == 5) {
return true
} else if (value?.length < 5) {
return feedbackStore.i18n.t('notEnoughChars')
} else {
return feedbackStore.i18n.t('tooMuchChars')
}
},
value => {
if (value?.length == 5) {
return true
} else {
return feedbackStore.i18n.t('notEnoughChars')
}
},
value => {
if (/^\d+$/.test(value)) {
return true
} else {
return feedbackStore.i18n.t('onlyDigitsAllowed')
}
}
]
}
/**
* Check a string for not empty and first char is a digit
*
* @returns Array of rules
*/
export function getNumberStartRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value) {
return true
} else {
return feedbackStore.i18n.t('required')
}
},
value => {
if (/^\d/.test(value)) {
return true
} else {
return feedbackStore.i18n.t('digitsAtStartNeeded')
}
},
]
}
/**
* Check a string for email format
*
* @returns Array of rules
*/
export function getEmailRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value) return true
return feedbackStore.i18n.t('emailRequired')
},
value => {
if (/.+@.+\..+/.test(value)) return true
return feedbackStore.i18n.t('emailIsNotValid')
},
]
}
/**
* Check a string for a good password
*
* @returns Array of rules
*/
export function getPasswordRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value) {
return true
} else {
return feedbackStore.i18n.t('required')
}
},
value => {
if (value?.length >= 8) {
return true
} else {
return feedbackStore.i18n.t('passwordToShort')
}
}
]
}
/**
* Check a string for IBAN format
*
* @returns Array of rules
*/
export function getIbanRules() {
const feedbackStore = useFeedbackStore()
return [
value => {
if (value) {
return true
} else {
return feedbackStore.i18n.t('required')
}
},
value => {
if (/[A-Z]{2}[0-9]{2}(?:[ ]?[0-9]{4}){4}(?!(?:[ ]?[0-9]){3})(?:[ ]?[0-9]{1,2})?/.test(value)) {
return true
} else {
return feedbackStore.i18n.t('wrongIban')
}
}
]
}