Make the login/reset/forbidden dialogs look better.

This commit is contained in:
Leo Vasanko
2025-09-30 17:03:51 -06:00
parent ed7d3ee0fc
commit 382341e5ee
5 changed files with 171 additions and 89 deletions

View File

@@ -2,17 +2,25 @@
<div class="app-shell"> <div class="app-shell">
<StatusMessage /> <StatusMessage />
<main class="app-main"> <main class="app-main">
<!-- Only render views after authentication status is determined -->
<template v-if="initialized">
<LoginView v-if="store.currentView === 'login'" /> <LoginView v-if="store.currentView === 'login'" />
<ProfileView v-if="store.currentView === 'profile'" /> <ProfileView v-if="store.currentView === 'profile'" />
<DeviceLinkView v-if="store.currentView === 'device-link'" /> <DeviceLinkView v-if="store.currentView === 'device-link'" />
<ResetView v-if="store.currentView === 'reset'" /> <ResetView v-if="store.currentView === 'reset'" />
<PermissionDeniedView v-if="store.currentView === 'permission-denied'" /> <PermissionDeniedView v-if="store.currentView === 'permission-denied'" />
</template>
<!-- Show loading state while determining auth status -->
<div v-else class="loading-container">
<div class="loading-spinner"></div>
<p>Loading...</p>
</div>
</main> </main>
</div> </div>
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue' import { onMounted, ref } from 'vue'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import StatusMessage from '@/components/StatusMessage.vue' import StatusMessage from '@/components/StatusMessage.vue'
import LoginView from '@/components/LoginView.vue' import LoginView from '@/components/LoginView.vue'
@@ -22,6 +30,7 @@ import ResetView from '@/components/ResetView.vue'
import PermissionDeniedView from '@/components/PermissionDeniedView.vue' import PermissionDeniedView from '@/components/PermissionDeniedView.vue'
const store = useAuthStore() const store = useAuthStore()
const initialized = ref(false)
onMounted(async () => { onMounted(async () => {
// Detect restricted mode: // Detect restricted mode:
@@ -52,10 +61,43 @@ onMounted(async () => {
} }
try { try {
await store.loadUserInfo() await store.loadUserInfo()
initialized.value = true
store.selectView()
} catch (error) { } catch (error) {
console.log('Failed to load user info:', error) console.log('Failed to load user info:', error)
store.currentView = 'login' store.currentView = 'login'
} initialized.value = true
store.selectView() store.selectView()
}
}) })
</script> </script>
<style scoped>
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 1rem;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid var(--color-border);
border-top: 4px solid var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-container p {
color: var(--color-text-muted);
margin: 0;
}
</style>

View File

@@ -597,3 +597,49 @@ th {
top: 1rem; top: 1rem;
} }
} }
/* Dialog styles for auth views */
.dialog-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.dialog-container {
max-width: 90vw;
max-height: 90vh;
overflow-y: auto;
}
.dialog-content {
flex: none;
width: 100%;
max-width: 480px;
padding: 2rem;
background: var(--color-surface);
border-radius: var(--radius-lg);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
border: 1px solid var(--color-border);
}
.dialog-content--wide {
max-width: 540px;
}
.dialog-content--narrow {
max-width: 420px;
}
@media (max-width: 720px) {
.dialog-content {
padding: 1.5rem;
}
}

View File

@@ -1,9 +1,10 @@
<template> <template>
<section class="view-root view-login"> <div class="dialog-backdrop">
<div class="view-content view-content--narrow"> <div class="dialog-container">
<div class="dialog-content dialog-content--narrow">
<header class="view-header"> <header class="view-header">
<h1>🔐 {{ (authStore.settings?.rp_name || 'Passkey') + ' Login' }}</h1> <h1>🔐 {{ (authStore.settings?.rp_name || location.origin)}}</h1>
<p class="view-lede">Sign in securely with a device you trust.</p> <p class="view-lede">User authentication is required for access.</p>
</header> </header>
<section class="section-block"> <section class="section-block">
<form class="section-body" @submit.prevent="handleLogin"> <form class="section-body" @submit.prevent="handleLogin">
@@ -17,7 +18,8 @@
</form> </form>
</section> </section>
</div> </div>
</section> </div>
</div>
</template> </template>
<script setup> <script setup>
@@ -44,16 +46,12 @@ const handleLogin = async () => {
</script> </script>
<style scoped> <style scoped>
.view-content--narrow {
max-width: 420px;
}
.view-lede { .view-lede {
margin: 0; margin: 0;
color: var(--color-text-muted); color: var(--color-text-muted);
} }
.view-login .section-body { .section-body {
gap: 1.5rem; gap: 1.5rem;
} }

View File

@@ -1,6 +1,7 @@
<template> <template>
<section class="view-root view-denied"> <div class="dialog-backdrop">
<div class="view-content view-content--narrow"> <div class="dialog-container">
<div class="dialog-content dialog-content--wide">
<header class="view-header"> <header class="view-header">
<h1>🚫 Forbidden</h1> <h1>🚫 Forbidden</h1>
</header> </header>
@@ -20,7 +21,8 @@
</div> </div>
</section> </section>
</div> </div>
</section> </div>
</div>
</template> </template>
<script setup> <script setup>
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
@@ -42,10 +44,6 @@ async function logout() {
} }
</script> </script>
<style scoped> <style scoped>
.view-content--narrow {
max-width: 540px;
}
.view-lede { .view-lede {
margin: 0; margin: 0;
color: var(--color-text-muted); color: var(--color-text-muted);

View File

@@ -1,6 +1,7 @@
<template> <template>
<section class="view-root view-reset"> <div class="dialog-backdrop">
<div class="view-content view-content--narrow"> <div class="dialog-container">
<div class="dialog-content">
<header class="view-header"> <header class="view-header">
<h1>🔑 Add New Credential</h1> <h1>🔑 Add New Credential</h1>
<p class="view-lede"> <p class="view-lede">
@@ -31,7 +32,8 @@
</div> </div>
</section> </section>
</div> </div>
</section> </div>
</div>
</template> </template>
<script setup> <script setup>
@@ -63,10 +65,6 @@ async function register() {
</script> </script>
<style scoped> <style scoped>
.view-content--narrow {
max-width: 480px;
}
.view-lede { .view-lede {
margin: 0; margin: 0;
color: var(--color-text-muted); color: var(--color-text-muted);