Move banner system to store, migrate login/register API handling to own file, display Account details on accountPage

This commit is contained in:
2024-09-19 16:20:12 +02:00
parent 5b3a753233
commit ed264ff026
19 changed files with 250 additions and 141 deletions

View File

@@ -41,7 +41,7 @@ TODO
## Structure ## Structure
### Database ### Database
![database-erm](misc/images//database.png) ![database-erm](misc/images/database.png)
### Backend API endpoints ### Backend API endpoints

View File

@@ -5,7 +5,7 @@ import { validateString } from "../scripts/validateHelper";
export const account = Router() export const account = Router()
// Login user // Login user
account.get("/", (req: Request, res: Response) => { account.post("/login", (req: Request, res: Response) => {
Account.findOne({ Account.findOne({
raw: true, raw: true,
where: { username: req.body.username } where: { username: req.body.username }
@@ -13,17 +13,17 @@ account.get("/", (req: Request, res: Response) => {
.then(account => { .then(account => {
if (account != null) { if (account != null) {
if (account.password == req.body.password) { if (account.password == req.body.password) {
// Status: 200 Created // Status: 200 OK
res.status(201).json({ res.status(200).json({
loginSuccessful: true, loginSuccessful: true,
userId: account.id, account: account,
message: "" message: ""
}).send() }).send()
} else { } else {
// Status: 401 Unauthorized // Status: 401 Unauthorized
res.status(401).json({ res.status(401).json({
loginSuccessful: false, loginSuccessful: false,
userId: -1, account: null,
message: "Wrong password" message: "Wrong password"
}).send() }).send()
} }
@@ -40,7 +40,7 @@ account.get("/", (req: Request, res: Response) => {
}) })
// Creating a new user // Creating a new user
account.post("/", (req: Request, res: Response) => { account.post("/register", (req: Request, res: Response) => {
if (!validateString(req.body.username, 4)) if (!validateString(req.body.username, 4))
{ {
// Status: 400 Bad request // Status: 400 Bad request

View File

@@ -10,7 +10,7 @@ category.get("/", (req: Request, res: Response, next: NextFunction) => {
res.status(200).json(categories).send() res.status(200).json(categories).send()
}) })
.catch(error => { .catch(error => {
res.status(400).json({ message: error }).send() res.status(400)//.json({ message: error }).send()
}) })
}) })

View File

@@ -1,61 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { BannerStateEnum } from '@/data/enums/bannerStateEnum'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import BannerModel from '@/data/models/bannerModel';
import { getBannerMessage } from '@/scripts/contentScripts';
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const i18n = useI18n() const feedbackStore = useFeedbackStore()
const alertBanner = defineModel("alertBanner", { required: true, type: BannerModel })
const title = ref("")
const color = ref("")
const icon = ref("")
function refreshBanner() {
switch (alertBanner.value.bannerState) {
case BannerStateEnum.ERROR: {
title.value = i18n.t('bannerMessages.error'); break;
}
case BannerStateEnum.DATABASERESETSUCCESSFUL: {
title.value = i18n.t('bannerMessages.databaseResetSuccessful'); break;
}
case BannerStateEnum.LOGINSUCCESSFUL: {
title.value = i18n.t('bannerMessages.loginSuccessful'); break;
}
case BannerStateEnum.WRONGLOGIN: {
title.value = i18n.t('bannerMessages.wrongLogin'); break;
}
}
switch (alertBanner.value.bannerState) {
case BannerStateEnum.ERROR:
case BannerStateEnum.WRONGLOGIN:
color.value = "red"
icon.value = "mdi-alert-circle"
break;
case BannerStateEnum.DATABASERESETSUCCESSFUL:
case BannerStateEnum.LOGINSUCCESSFUL:
color.value = "green"
icon.value = "mdi-check-circle"
break
}
}
watch(() => alertBanner.value.bannerState, () => {
refreshBanner()
})
refreshBanner()
</script> </script>
<template> <template>
<v-expand-transition> <v-expand-transition>
<v-row v-if="alertBanner.show"> <v-row v-if="feedbackStore.showBanner">
<v-col> <v-col>
<v-alert v-model="alertBanner.show" :color="color" :icon="icon" closable> <v-alert
{{ title }} v-model="feedbackStore.showBanner"
:color="feedbackStore.color"
:icon="feedbackStore.icon"
closable
>
{{ feedbackStore.title }}
</v-alert> </v-alert>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -35,16 +35,16 @@ const navRail = defineModel("navRail", { type: Boolean })
</v-list-subheader> </v-list-subheader>
<v-expand-transition> <v-expand-transition>
<div v-if="userStore.userAccountId == -1"> <div v-if="!userStore.loggedIn">
<v-list-item v-if="userStore.userAccountId == -1" :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link /> <v-list-item v-if="!userStore.loggedIn" :title="$t('menu.login')" prepend-icon="mdi-login" to="/login" link />
</div> </div>
</v-expand-transition> </v-expand-transition>
<v-expand-transition> <v-expand-transition>
<div v-if="userStore.userAccountId != -1"> <div v-if="userStore.loggedIn">
<v-list-item :title="$t('menu.logout')" prepend-icon="mdi-logout" @click="userStore.userAccountId = -1" link />
<v-list-item :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link /> <v-list-item :title="$t('menu.account')" prepend-icon="mdi-account" to="/account" link />
<v-list-item :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link /> <v-list-item :title="$t('menu.orders')" prepend-icon="mdi-cart-check" to="/orders" link />
<v-list-item :title="$t('menu.logout')" prepend-icon="mdi-logout" @click="userStore.logout" link />
</div> </div>
</v-expand-transition> </v-expand-transition>

View File

@@ -0,0 +1,15 @@
import axios from "axios"
import { AccountModel } from "../models/accountModel"
const BASE_URL = "http://localhost:3000/accounts"
export async function login(username: string, password: string) {
return await axios.post(BASE_URL + "/login", {
username: username,
password: password
})
}
export async function register(account: AccountModel) {
return await axios.post(BASE_URL + "/register", account)
}

View File

@@ -2,5 +2,7 @@ export enum BannerStateEnum {
DATABASERESETSUCCESSFUL, DATABASERESETSUCCESSFUL,
ERROR, ERROR,
WRONGLOGIN, WRONGLOGIN,
LOGINSUCCESSFUL LOGINSUCCESSFUL,
REGISTERSUCCESSFUL,
USERNAMEINUSE
} }

View File

@@ -1,6 +0,0 @@
import { BannerStateEnum } from "../enums/bannerStateEnum"
export default class BannerModel {
show: boolean = false
bannerState: BannerStateEnum = BannerStateEnum.ERROR
}

View File

@@ -0,0 +1,63 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { BannerStateEnum } from "../enums/bannerStateEnum";
import { Composer } from 'vue-i18n';
export const useFeedbackStore = defineStore("feedbackStore", {
state: () => ({
showBanner: ref(false),
title: ref(""),
color: ref(""),
icon: ref(""),
$i18n: {}
}),
getters: {
i18n(): Composer {
return this.$i18n.global as Composer
}
},
actions: {
changeBanner(bannerState: BannerStateEnum) {
switch (bannerState) {
case BannerStateEnum.ERROR: {
this.title = this.i18n.t('bannerMessages.error'); break;
}
case BannerStateEnum.DATABASERESETSUCCESSFUL: {
this.title = this.i18n.t('bannerMessages.databaseResetSuccessful'); break;
}
case BannerStateEnum.LOGINSUCCESSFUL: {
this.title = this.i18n.t('bannerMessages.loginSuccessful'); break;
}
case BannerStateEnum.WRONGLOGIN: {
this.title = this.i18n.t('bannerMessages.wrongLogin'); break;
}
case BannerStateEnum.REGISTERSUCCESSFUL: {
this.title = this.i18n.t("bannerMessages.registerSuccessful"); break;
}
case BannerStateEnum.USERNAMEINUSE: {
this.title = this.i18n.t("bannerMessages.usernameInUse"); break;
}
}
switch (bannerState) {
case BannerStateEnum.ERROR:
case BannerStateEnum.WRONGLOGIN:
case BannerStateEnum.USERNAMEINUSE:
this.color = "red"
this.icon = "mdi-alert-circle"
break;
case BannerStateEnum.DATABASERESETSUCCESSFUL:
case BannerStateEnum.LOGINSUCCESSFUL:
case BannerStateEnum.REGISTERSUCCESSFUL:
this.color = "green"
this.icon = "mdi-check-circle"
break
}
this.showBanner = true
}
}
})

View File

@@ -2,11 +2,61 @@ import { defineStore } from "pinia";
import { useLocalStorage } from "@vueuse/core"; import { useLocalStorage } from "@vueuse/core";
import { ThemeEnum } from "../enums/themeEnums"; import { ThemeEnum } from "../enums/themeEnums";
import { LanguageEnum } from "../enums/languageEnum"; import { LanguageEnum } from "../enums/languageEnum";
import { AccountModel } from "../models/accountModel";
import { login, register } from "../api/accountApi";
import { useFeedbackStore } from "./feedbackStore";
import { BannerStateEnum } from "../enums/bannerStateEnum";
export const useUserStore = defineStore('user', { export const useUserStore = defineStore('userStore', {
state: () => ({ state: () => ({
theme: useLocalStorage<ThemeEnum>("hackmycart/userStore/theme", ThemeEnum.DARKRED), theme: useLocalStorage<ThemeEnum>("hackmycart/userStore/theme", ThemeEnum.DARKRED),
language: useLocalStorage<LanguageEnum>("hackmycart/userStore/language", LanguageEnum.GERMAN), language: useLocalStorage<LanguageEnum>("hackmycart/userStore/language", LanguageEnum.GERMAN),
userAccountId: useLocalStorage<number>("hackmycart/userStore/userAccountId", -1) userAccount: useLocalStorage<AccountModel>("hackmycart/userStore/userAccount", new AccountModel()),
loggedIn: useLocalStorage<Boolean>("hackmycart/userStore/loggedIn", false)
}),
actions: {
async login(username: string, password: string) {
const feedbackStore = useFeedbackStore()
await login(username, password)
.then(result => {
if (result.data.loginSuccessful) {
this.userAccount = result.data.account
this.loggedIn = true
feedbackStore.changeBanner(BannerStateEnum.LOGINSUCCESSFUL)
}
}) })
.catch(error => {
this.userAccount = new AccountModel()
this.loggedIn = false
feedbackStore.changeBanner(BannerStateEnum.WRONGLOGIN)
})
},
async registerAccount(account: AccountModel) {
const feedbackStore = useFeedbackStore()
await register(account)
.then(res => {
console.log(res)
if (res.status == 200) {
feedbackStore.changeBanner(BannerStateEnum.REGISTERSUCCESSFUL)
}
})
.catch((error) => {
console.log(error)
if (error.status == 400) {
feedbackStore.changeBanner(BannerStateEnum.USERNAMEINUSE)
}
})
},
logout() {
this.userAccount = new AccountModel()
this.loggedIn = false
}
}
}) })

View File

@@ -62,7 +62,9 @@
"loginSuccessful": "Login erfolgreich!", "loginSuccessful": "Login erfolgreich!",
"wrongLogin": "Falscher Username oder falsches Passwort!", "wrongLogin": "Falscher Username oder falsches Passwort!",
"error": "Es ist ein Fehler aufgetreten...", "error": "Es ist ein Fehler aufgetreten...",
"databaseResetSuccessful": "Datenbank erfolgreich zurück gesetzt!" "databaseResetSuccessful": "Datenbank erfolgreich zurück gesetzt!",
"registerSuccessful": "Account erfolgreich erstellt!",
"usernameInUse": "Der Accountname ist bereits vergeben!"
}, },
"oclock": "Uhr" "oclock": "Uhr"
} }

View File

@@ -62,7 +62,9 @@
"loginSuccessful": "Login erfolgreich!", "loginSuccessful": "Login erfolgreich!",
"wrongLogin": "Falscher Username oder falsches Passwort!", "wrongLogin": "Falscher Username oder falsches Passwort!",
"error": "Some error occurred...", "error": "Some error occurred...",
"databaseResetSuccessful": "Database reset successful!" "databaseResetSuccessful": "Database reset successful!",
"usernameInUse": "Username always in use!",
"registerSuccessful": "Account successfully created!"
}, },
"oclock": "o'clock" "oclock": "o'clock"
} }

View File

@@ -5,6 +5,7 @@ import vuetify from './plugins/vuetify'
import router from './plugins/router' import router from './plugins/router'
import pinia from './plugins/pinia' import pinia from './plugins/pinia'
import { i18n } from './plugins/i18n' import { i18n } from './plugins/i18n'
import { useFeedbackStore } from './data/stores/feedbackStore'
createApp(App) createApp(App)
.use(vuetify) .use(vuetify)
@@ -12,3 +13,6 @@ createApp(App)
.use(pinia) .use(pinia)
.use(i18n) .use(i18n)
.mount('#app') .mount('#app')
const feedbackStore = useFeedbackStore()
feedbackStore.$i18n = i18n

View File

@@ -1,23 +1,83 @@
<script setup lang="ts"> <script setup lang="ts">
import axios from 'axios'; import { useUserStore } from '@/data/stores/userStore';
axios.get("http://127.0.0.1:3000/") const userStore = useUserStore()
</script> </script>
<template> <template>
<v-container max-width="1000">
<v-row>
<v-col>
<v-card title="Account">
<v-container> <v-container>
<v-row> <v-row>
<v-col> <v-col>
<v-card> <v-text-field
<v-row> :label="$t('account.username')"
<v-col> v-model="userStore.userAccount.username"
<v-text-field :label="$t('account.username')" disabled /> disabled
/>
</v-col> </v-col>
<v-col> <v-col>
<v-text-field /> <v-text-field
:label="$t('account.password')"
v-model="userStore.userAccount.password"
type="password"
/>
</v-col> </v-col>
</v-row> </v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('userInfo.firstName')"
v-model="userStore.userAccount.firstName"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.lastName')"
v-model="userStore.userAccount.lastName"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('userInfo.street')"
v-model="userStore.userAccount.street"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.houseNumber')"
v-model="userStore.userAccount.houseNumber"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:label="$t('userInfo.postalCode')"
v-model="userStore.userAccount.postalCode"
/>
</v-col>
<v-col>
<v-text-field
:label="$t('userInfo.city')"
v-model="userStore.userAccount.city"
/>
</v-col>
</v-row>
</v-container>
<v-card-actions>
<!-- todo -->
<v-btn >Save</v-btn>
</v-card-actions>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </v-row>

View File

@@ -2,25 +2,23 @@
import { ref } from 'vue'; import { ref } from 'vue';
import loginForm from './loginForm.vue'; import loginForm from './loginForm.vue';
import registerForm from './registerForm.vue'; import registerForm from './registerForm.vue';
import BannerModel from '@/data/models/bannerModel';
import alertBanner from '@/components/alertBanner.vue'; import alertBanner from '@/components/alertBanner.vue';
const showRegisterCard = ref(false) const showRegisterCard = ref(false)
const banner = ref(new BannerModel())
</script> </script>
<template> <template>
<v-container max-width="800"> <v-container max-width="800">
<v-row> <v-row>
<v-col> <v-col>
<alert-banner v-model:alert-banner="banner" /> <alert-banner />
</v-col> </v-col>
</v-row> </v-row>
<v-expand-transition> <v-expand-transition>
<v-row v-if="!showRegisterCard"> <v-row v-if="!showRegisterCard">
<v-col> <v-col>
<login-form v-model:show-register-card="showRegisterCard" v-model:banner="banner" /> <login-form v-model:show-register-card="showRegisterCard" />
</v-col> </v-col>
</v-row> </v-row>
</v-expand-transition> </v-expand-transition>
@@ -28,7 +26,7 @@ const banner = ref(new BannerModel())
<v-expand-transition> <v-expand-transition>
<v-row v-if="showRegisterCard"> <v-row v-if="showRegisterCard">
<v-col> <v-col>
<register-form v-model:banner="banner" v-model:show-register-card="showRegisterCard" /> <register-form v-model:show-register-card="showRegisterCard" />
</v-col> </v-col>
</v-row> </v-row>
</v-expand-transition> </v-expand-transition>

View File

@@ -1,33 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { BannerStateEnum } from '@/data/enums/bannerStateEnum'; import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
import BannerModel from '@/data/models/bannerModel';
import { useUserStore } from '@/data/stores/userStore'; import { useUserStore } from '@/data/stores/userStore';
import axios from 'axios';
import { ref } from 'vue'; import { ref } from 'vue';
const userStore = useUserStore() const userStore = useUserStore()
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false }) const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
const banner = defineModel("banner", { type: BannerModel })
const username = ref("") const username = ref("")
const password = ref("") const password = ref("")
function startLogin() { function startLogin() {
axios.post('http://127.0.0.1:3000/accounts/login', { userStore.login(username.value, password.value)
username: username.value,
password: password.value
})
.then(res => {
if (res.status == 200) {
banner.value.bannerState = BannerStateEnum.LOGINSUCCESSFUL
banner.value.show = true
userStore.userAccountId = res.data.userAccountId
}
})
.catch(res => {
banner.value.bannerState = BannerStateEnum.WRONGLOGIN
banner.value.show = true
})
} }
</script> </script>

View File

@@ -1,31 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { BannerStateEnum } from '@/data/enums/bannerStateEnum'; import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
import { AccountModel } from '@/data/models/accountModel'; import { AccountModel } from '@/data/models/accountModel';
import BannerModel from '@/data/models/bannerModel'; import { useUserStore } from '@/data/stores/userStore';
import axios from 'axios'; import axios from 'axios';
import { ref } from 'vue'; import { ref } from 'vue';
const newUser = ref(new AccountModel()) const newUser = ref(new AccountModel())
const banner = defineModel("banner", { required: true, type: BannerModel })
const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false }) const showRegisterCard = defineModel("showRegisterCard", { type: Boolean, default: false })
const userStore = useUserStore()
function registerUser() {
axios.post('http://127.0.0.1:3000/accounts/register', newUser.value)
.then(res => {
console.log(res)
if (res.status == 200) {
banner.value.bannerState = BannerStateEnum.LOGINSUCCESSFUL
banner.value.show = true
}
})
.catch((error) => {
console.log(error)
if (error.status == 400) {
banner.value.bannerState = BannerStateEnum.WRONGLOGIN
banner.value.show = true
}
})
}
</script> </script>
<template> <template>
@@ -103,7 +85,7 @@ function registerUser() {
</v-btn> </v-btn>
<v-spacer /> <v-spacer />
<v-btn prepend-icon="mdi-account-plus" color="primary" variant="outlined" <v-btn prepend-icon="mdi-account-plus" color="primary" variant="outlined"
@click="registerUser"> @click="userStore.registerAccount(newUser)">
{{ $t('account.register') }} {{ $t('account.register') }}
</v-btn> </v-btn>
</template> </template>

View File

@@ -2,15 +2,12 @@
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
import pageSetup from './pageSetup.vue'; import pageSetup from './pageSetup.vue';
import systemSetup from './systemSetup.vue'; import systemSetup from './systemSetup.vue';
import BannerModel from '@/data/models/bannerModel';
import alertBanner from '@/components/alertBanner.vue'; import alertBanner from '@/components/alertBanner.vue';
const banner: Ref<BannerModel> = ref(new BannerModel())
</script> </script>
<template> <template>
<v-container max-width="800"> <v-container max-width="800">
<alert-banner v-model:alert-banner="banner" /> <alert-banner />
<v-row> <v-row>
<v-col> <v-col>
@@ -20,7 +17,7 @@ const banner: Ref<BannerModel> = ref(new BannerModel())
<v-row> <v-row>
<v-col> <v-col>
<system-setup v-model:alert-banner="banner" /> <system-setup />
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>

View File

@@ -1,16 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { BannerStateEnum } from '@/data/enums/bannerStateEnum'; import { BannerStateEnum } from '@/data/enums/bannerStateEnum';
import BannerModel from '@/data/models/bannerModel'; import { useFeedbackStore } from '@/data/stores/feedbackStore';
import axios from 'axios'; import axios from 'axios';
const alertBanner = defineModel("alertBanner", { required: true, type: BannerModel }) const feedbackStore = useFeedbackStore()
function resetDb() { function resetDb() {
axios.get("http://127.0.0.1:3000/api/resetdatabase") axios.get("http://127.0.0.1:3000/api/resetdatabase")
.then(res => { .then(res => {
if (res.status == 200) { if (res.status == 200) {
alertBanner.value.bannerState = BannerStateEnum.DATABASERESETSUCCESSFUL feedbackStore.changeBanner(BannerStateEnum.DATABASERESETSUCCESSFUL)
alertBanner.value.show = true
} }
}) })
// todo: Request all data // todo: Request all data