finnow/web-app/src/pages/UserAccountLayout.vue

122 lines
2.8 KiB
Vue

<!--
The shell layout for all user-authenticated parts of the app. It defines a top
bar with some controls and user information, and a router view for all child
pages.
-->
<script setup lang="ts">
import { AuthApiClient } from '@/api/auth';
import { secondsUntilExpired } from '@/api/token-util';
import { useAuthStore } from '@/stores/auth-store';
import { onMounted, ref, type Ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter()
const authStore = useAuthStore()
const authCheckTimer: Ref<number | undefined> = ref(undefined)
onMounted(async () => {
authStore.$subscribe(async (_, state) => {
if (state.state === null) {
await router.replace('/login')
}
})
authCheckTimer.value = setInterval(checkAuth, 1000)
})
async function checkAuth() {
if (!authStore.state) {
clearInterval(authCheckTimer.value)
authCheckTimer.value = undefined
await router.replace('/login')
return
}
const secondsUntilExpiration = secondsUntilExpired(authStore.state.token)
if (secondsUntilExpiration < 60 && secondsUntilExpiration > 5) {
const api = new AuthApiClient()
try {
const newToken = await api.getNewToken()
authStore.state.token = newToken
} catch (err) {
console.warn('Failed to refresh token.', err)
}
} else if (secondsUntilExpiration <= 0) {
authStore.onUserLoggedOut()
}
}
</script>
<template>
<div>
<header class="app-header-bar">
<div>
<h1 class="app-header-text" @click="router.push('/')">Finnow</h1>
</div>
<div>
<span class="app-user-widget">
Welcome, <em>{{ authStore.state?.username }}</em>
</span>
<span class="app-logout-button" @click="authStore.onUserLoggedOut()">
Log out
<font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket"></font-awesome-icon>
</span>
</div>
</header>
<RouterView :key="$route.fullPath"></RouterView>
</div>
</template>
<style lang="css">
.app-header-bar {
background-color: var(--theme-primary);
color: var(--bg-primary);
display: flex;
justify-content: space-between;
align-items: center;
}
.app-header-text {
display: inline;
margin: 0 1em 0 1em;
padding: 0;
font-family: 'PlaywriteNL';
font-size: 20px;
cursor: pointer;
}
.app-header-text:hover {
color: var(--theme-secondary);
}
.app-logout-button {
cursor: pointer;
font-weight: bold;
margin-right: 1em;
}
.app-logout-button:hover {
color: var(--bg-secondary);
text-decoration: underline;
}
.app-user-widget {
margin-right: 1em;
font-weight: bold;
}
.app-header-link {
font-weight: bold;
color: var(--bg-primary);
text-decoration: none;
margin: 0 0.5em 0 0.5em;
}
.app-header-link:hover {
color: var(--bg-secondary);
text-decoration: underline;
}
</style>