Add timeout modal.
Build and Deploy Web App / build-and-deploy (push) Successful in 20s Details

This commit is contained in:
andrewlalis 2025-10-23 20:32:34 -04:00
parent 7455a55766
commit 83db4baa5b
3 changed files with 75 additions and 24 deletions

View File

@ -0,0 +1,53 @@
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth-store'
import { useTemplateRef, ref, computed } from 'vue'
import ModalWrapper from './common/ModalWrapper.vue'
import AppButton from './common/AppButton.vue'
const timeoutModal = useTemplateRef("timeoutModal")
const secondsUntilLogout = ref(30)
const timeoutTimerId = ref<number | undefined>()
const timePhrase = computed(() => {
if (secondsUntilLogout.value !== 1) {
return secondsUntilLogout.value + " seconds"
}
return secondsUntilLogout.value + " second"
})
const authStore = useAuthStore()
function start() {
if (timeoutModal.value?.isOpen()) return
secondsUntilLogout.value = 30
timeoutModal.value?.show()
timeoutTimerId.value = window.setInterval(() => {
secondsUntilLogout.value = secondsUntilLogout.value - 1
if (secondsUntilLogout.value <= 0) {
authStore.onUserLoggedOut()
window.clearInterval(timeoutTimerId.value)
}
}, 1000)
}
function dismissTimeout() {
window.clearInterval(timeoutTimerId.value)
timeoutModal.value?.close()
}
defineExpose({ start })
</script>
<template>
<ModalWrapper ref="timeoutModal">
<template v-slot:default>
<p>
You've been inactive for a while, so to ensure the safety of your
personal information, you will be logged out in
<strong>{{ timePhrase }}</strong>
unless you click below to remain logged in.
</p>
</template>
<template v-slot:buttons>
<AppButton @click="dismissTimeout()">Stay Logged In</AppButton>
</template>
</ModalWrapper>
</template>

View File

@ -23,24 +23,20 @@ function close(returnValue?: string) {
dialog.value?.close(returnValue) dialog.value?.close(returnValue)
} }
defineExpose({ show, close }) function isOpen(): boolean {
return dialog.value?.open ?? false
}
defineExpose({ show, close, isOpen })
</script> </script>
<template> <template>
<Teleport to="body"> <Teleport to="body">
<dialog <dialog ref="dialog" class="app-modal-dialog" :id="id">
ref="dialog"
class="app-modal-dialog"
:id="id"
>
<slot></slot> <slot></slot>
<div class="app-modal-dialog-actions"> <div class="app-modal-dialog-actions">
<slot name="buttons"> <slot name="buttons">
<AppButton <AppButton button-style="secondary" @click="close()">Close</AppButton>
button-style="secondary"
@click="close()"
>Close</AppButton
>
</slot> </slot>
</div> </div>
</dialog> </dialog>

View File

@ -7,14 +7,23 @@ pages.
import { AuthApiClient } from '@/api/auth' import { AuthApiClient } from '@/api/auth'
import { getSelectedProfile } from '@/api/profile' import { getSelectedProfile } from '@/api/profile'
import { secondsUntilExpired } from '@/api/token-util' import { secondsUntilExpired } from '@/api/token-util'
import IdleTimeoutModal from '@/components/IdleTimeoutModal.vue'
import { useAuthStore } from '@/stores/auth-store' import { useAuthStore } from '@/stores/auth-store'
import { onMounted, ref, type Ref } from 'vue' import { useIdleObserver } from '@idle-observer/vue3'
import { onMounted, ref, useTemplateRef, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const IDLE_TIMEOUT_SECONDS = 300
const idleTimeoutModal = useTemplateRef("idleTimeoutModal")
useIdleObserver({
timeout: IDLE_TIMEOUT_SECONDS * 1000,
onIdle: () => idleTimeoutModal.value?.start()
})
const authCheckTimer: Ref<number | undefined> = ref(undefined) const authCheckTimer: Ref<number | undefined> = ref(undefined)
onMounted(async () => { onMounted(async () => {
@ -61,31 +70,24 @@ async function checkAuth() {
<div> <div>
<header class="app-header-bar"> <header class="app-header-bar">
<div> <div>
<h1 <h1 class="app-header-text" @click="onHeaderClicked()">
class="app-header-text"
@click="onHeaderClicked()"
>
Finnow Finnow
</h1> </h1>
</div> </div>
<div> <div>
<span <span class="app-user-widget" @click="router.push('/me')">
class="app-user-widget"
@click="router.push('/me')"
>
<font-awesome-icon icon="fa-user"></font-awesome-icon> <font-awesome-icon icon="fa-user"></font-awesome-icon>
</span> </span>
<span <span class="app-logout-button" @click="authStore.onUserLoggedOut()">
class="app-logout-button"
@click="authStore.onUserLoggedOut()"
>
<font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket"></font-awesome-icon> <font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket"></font-awesome-icon>
</span> </span>
</div> </div>
</header> </header>
<RouterView></RouterView> <RouterView></RouterView>
<IdleTimeoutModal ref="idleTimeoutModal" />
</div> </div>
</template> </template>