added lots of animations, added debounce to search query, fixed faulty refetching when logging out from profile page

This commit is contained in:
Sockenklaus
2021-11-14 11:20:01 +01:00
parent a92342d445
commit 0d00d73eb4
11 changed files with 174 additions and 118 deletions

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body class="bg-light">
<body style="overflow-Y: scroll;" class="bg-light">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

View File

@@ -3,8 +3,12 @@
<v-main>
<Notification />
<div class="container shadow px-4 pt-3 pb-5 text-center">
<VNavigation />
<router-view></router-view>
<VNavigation />
<router-view v-slot="{ Component, route }">
<transition name="fade-slide-y" mode="out-in">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
</div>
</v-main>
@@ -18,11 +22,28 @@ import VNavigation from './components/VNavigation.vue';
</script>
<style>
<style lang="scss" scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
.fade-slide-y- {
&enter-active {
transition: all .2s ease
}
&leave-active {
transition: all .2s cubic-bezier(1,.5,.8,1)
// transition: all .3s ease
}
&enter-from,
&leave-to {
transform: translateY(10px);
opacity: 0;
}
}
</style>

View File

@@ -29,6 +29,7 @@
import { ref, computed} from 'vue';
import type { PropType, Ref } from 'vue';
import { union } from 'lodash';
import _debounce from 'lodash/debounce'
const props = defineProps({
columns: {
@@ -50,9 +51,9 @@ const unionColumns = computed(() => {
return union(props.columns, columnsChecked.value)
})
async function search() {
const search = _debounce(() => {
emit('search', queryString.value)
}
}, 150)
</script>

View File

@@ -1,19 +1,3 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useUser } from '@/stores/user';
const userStore = useUser()
const router = useRouter()
async function onLogout() {
if(await userStore.logout()){
router.push({name: 'Login'})
}
}
</script>
<template>
<nav class="nav justify-content-center border-bottom mb-4 pb-2 d-flex">
<router-link class="nav-link" :to="{name: 'Home'}">Home</router-link>
@@ -32,6 +16,20 @@ async function onLogout() {
</nav>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { useUser } from '@/stores/user';
const userStore = useUser()
const router = useRouter()
function onLogout() {
userStore.logout(router)
}
</script>
<style scoped>
</style>

View File

@@ -29,25 +29,50 @@ function onCancel() {
Zurück
</router-link>
<button v-if="!isActive" type="button" @click="onEdit" class="btn btn-primary ms-auto">
<i class="bi bi-pen"></i>
Mitarbeiter bearbeiten
</button>
<button v-if="isActive" type="submit" @click.prevent="onSave" class="btn btn-success ms-auto">
<i class="bi bi-save"></i>
Mitarbeiter speichern
</button>
<button v-if="isActive" type="button" @click="onCancel" class="btn btn-outline-secondary ms-3">
<i class="bi bi-x-lg"></i>
Abbrechen
</button>
<transition name="button-save" mode="out-in">
<div class="ms-auto" v-if="isActive">
<button v-if="isActive" type="submit" @click.prevent="onSave" class="btn btn-success">
<i class="bi bi-save"></i>
Mitarbeiter speichern
</button>
<button v-if="isActive" type="button" @click="onCancel" class="btn btn-outline-secondary ms-3">
<i class="bi bi-x-lg"></i>
Abbrechen
</button>
</div>
<div v-else class="ms-auto">
<button v-if="!isActive" type="button" @click="onEdit" key="button-not-active" class="btn btn-primary">
<i class="bi bi-pen"></i>
Mitarbeiter bearbeiten
</button>
</div>
</transition>
</div>
</template>
<style scoped>
<style lang="scss" scoped>
@mixin animation-target($offset) {
opacity: 0;
transform: translateX($offset);
}
.button-save-{
&enter-active,
&leave-active {
transition: all .1s ease-in-out
}
&enter-from,
&leave-to {
@include animation-target(15px)
}
&leave-to {
@include animation-target(-15px)
}
}
</style>

View File

@@ -23,7 +23,7 @@ export const useNotifications = defineStore('notifications', {
text:text,
}
)
if (timeout !== -1) setTimeout(() => this.remove(id), timeout)
if (timeout > -1) setTimeout(() => this.remove(id), timeout)
}
}
})

View File

@@ -1,20 +1,18 @@
import { defineStore, storeToRefs } from 'pinia'
import { defineStore } from 'pinia'
import { useNotifications } from './notifications'
import { getUnixTime } from 'date-fns'
import { AxiosError } from 'axios'
import axios from '@/axios'
import { apiUrl } from '@/axios'
import Axios from 'axios'
import { ref, reactive } from 'vue'
import { useRequest } from 'vue-request'
import { json } from 'stream/consumers'
import axios from '@/axios'
import Axios from 'axios'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
type AuthSuccResult = {
user: string,
role: string,
user: string,
role: string,
token: string
}
export const useUser = defineStore({
id: 'storeUser',
@@ -23,8 +21,9 @@ export const useUser = defineStore({
user: '',
role: '',
isLoggedIn: false,
rememberMe: false,
token: ''
token: '',
errorMessage: '',
loading: ref(false)
}
},
@@ -36,18 +35,27 @@ export const useUser = defineStore({
},
actions: {
async login(username: string, password: string):
Promise<boolean |
'E_INVALID_AUTH_PASSWORD: Password mis-match' |
'E_INVALID_AUTH_UID: User not found'>
{
async login(username: string, password: string, router: Router) {
this.loading = true
const notifications = useNotifications()
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/* async function axiosQuery<T>(username: string, password: string){
return await axios.post<T>('login',{
username,
password
})
}
const { loading } = useRequest(axiosQuery(username, password))
this.loading */
try {
const response = await axios.post<AuthSuccResult>('login', {
username: username,
password: password
username,
password
})
this.isLoggedIn = true
@@ -57,53 +65,43 @@ export const useUser = defineStore({
notifications.add('success', 'Login successful.')
return true
} catch(err : unknown) {
await router.push({name: 'Home'})
this.loading = false
} catch(err : unknown) {
this.loading = false
if (Axios.isAxiosError(err) && err.response && err.response.data){
const data = err.response.data
if(
data === 'E_INVALID_AUTH_PASSWORD: Password mis-match' ||
data === 'E_INVALID_AUTH_UID: User not found'
) return data
this.errorMessage = err.response.data as string
}
else if(err instanceof Error){
notifications.add('danger', err.message, -1)
return false
}
notifications.add('danger', err as string, -1)
return false
}
}
},
async logout(): Promise<boolean> {
async logout(router: Router) {
const notifications = useNotifications()
try {
await axios.post('logout', '', {
const result = await axios.post('logout', '', {
headers: {
'Authorization': 'Bearer '+useUser().token
}
})
await router.push({name: 'Login'})
notifications.add('success','Logged out successfully')
this.$reset()
return true
}
catch(error) {
if(error instanceof Error) {
notifications.add('danger', error.message, -1)
}
return false
}
}
},

View File

@@ -231,7 +231,7 @@ function onToggleEdit() {
}
watch(() => [route.params, route.name], ([newParam, newName], [oldParam, oldName]) => {
if(newName === oldName && newParam !== oldParam) {
if(newName === oldName && newParam?.toString() !== oldParam?.toString()) {
state.fetchFromApi(route.params.id)
createUser.value = false
}

View File

@@ -1,5 +1,5 @@
<template>
<div>
<IndexSettingsModal
id="indexSettingsModal"
:left-column="settings.columnsSelected"
@@ -13,8 +13,7 @@
@search="store.setSimpleSearch($event)"
/>
<table class="table table-hover table-bordered mt-3 ">
<table class="table table-hover mt-3 ">
<thead>
<tr>
<th
@@ -37,14 +36,13 @@
</th>
</tr>
</thead>
<tbody>
<tr v-for="employee in rows" @click="router.push({name: 'Employees/Details', params: {id: employee.id}})">
<transition-group name="list" tag="tbody">
<tr v-for="employee in rows" :key="employee.id" @click="router.push({name: 'Employees/Details', params: {id: employee.id}})">
<td v-for="col in settings.columnsSelected">
{{ employee[col] }}
{{ employee[col] }}
</td>
</tr>
</tbody>
</transition-group>
</table>
<h5 v-show="rows.length === 0" class="text-muted">Keine Daten anzuzeigen...</h5>
@@ -56,7 +54,7 @@
:last-page="store.meta.last_page"
@set-page="paginatorSetPage"
/>
</div>
</template>
<script setup lang="ts">
@@ -110,10 +108,26 @@ function paginatorSetPage(e : number){
</script>
<style scoped>
<style lang="scss" scoped>
.list- {
&enter-active,
&leave-active {
transition: all .2s ease
}
&enter-from,
&leave-to {
opacity: 0;
}
&move {
transition: all .2s ease
}
}
table thead tr:first-child th:first-child {
width: 51px;
min-width: 51px;
}
table th {

View File

@@ -1,12 +1,12 @@
<template lang="pug">
<div>
//- p {{ state }}
MonthPicker(:selectedMonth="selectedMonth" :selectedYear="selectedYear" @getMonth="selectedMonth = $event" @getYear="selectedYear = $event")
Schedule( :startDate="new Date(selectedYear, selectedMonth)")
</div>
</template>
<script lang="ts" setup>

View File

@@ -1,5 +1,6 @@
<template>
<img src="/src/assets/logo.png">
<div>
<img src="/src/assets/logo.png">
<form class="m-auto" novalidate>
<h1 class="h3 mb-4">Bitte einloggen</h1>
@@ -38,17 +39,25 @@
<button
type="submit"
class="btn btn-lg btn-success w-100 mb-5"
class="btn btn-success w-100 mb-5"
:class="{'opacity-75': userStore.loading || userStore.isLoggedIn}"
@click.prevent="onClick"
>
Einloggen
<span
v-if="userStore.loading"
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
></span>
{{userStore.loading ? "Lade..." : "Einloggen"}}
</button>
</form>
</div>
</template>
<script setup lang="ts">
import { useUser } from '@/stores/user'
import { reactive, computed, ref, watch } from 'vue'
import { reactive, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import useVuelidate from '@vuelidate/core'
import { helpers, required } from '@vuelidate/validators'
@@ -61,11 +70,11 @@ const input = reactive({
password: '',
})
const valPasswordCorrect = () => {
return apiResponse.value !== 'E_INVALID_AUTH_PASSWORD: Password mis-match'
function valPasswordCorrect() {
return userStore.errorMessage !== 'E_INVALID_AUTH_PASSWORD: Password mis-match'
}
const valUsernameCorrect = () => {
return apiResponse.value !== 'E_INVALID_AUTH_UID: User not found'
function valUsernameCorrect() {
return userStore.errorMessage !== 'E_INVALID_AUTH_UID: User not found'
}
const rules = computed(() => ({
@@ -79,30 +88,20 @@ const rules = computed(() => ({
}
}))
const v$ = useVuelidate(rules, input)
watch(input, () => {
apiResponse.value = ''
userStore.errorMessage = ''
v$.value.$reset()
})
const v$ = useVuelidate(rules, input)
const apiResponse = ref('')
userStore.$subscribe((mutation, state) => {
// if(state.isLoggedIn) router.push({name: 'Home'})
})
async function onClick() {
if(!(await v$.value.$validate())) return
const result = await userStore.login(input.username, input.password)
console.log("back in login form: "+result)
switch(result) {
case true:
router.push({name: 'Home'})
break;
case false:
break;
default:
apiResponse.value = result
if(await v$.value.$validate()) {
await userStore.login(input.username, input.password, router)
}
}