Massive style redesign, WIP.

This commit is contained in:
Leo Vasanko
2025-09-29 21:02:49 -06:00
parent 39beb31347
commit d46d50b91a
11 changed files with 1446 additions and 1089 deletions
+75 -34
View File
@@ -1,39 +1,48 @@
<template>
<div class="container">
<div class="view active">
<h1>📱 Add Another Device</h1>
<div class="device-link-section">
<div class="qr-container">
<a :href="url" id="deviceLinkText" @click="copyLink">
<canvas id="qrCode" class="qr-code"></canvas>
<p v-if="url">
{{ url.replace(/^[^:]+:\/\//, '') }}
</p>
<p v-else>
<em>Generating link...</em>
</p>
</a>
<p>
<strong>Scan and visit the URL on another device.</strong><br>
<small> Expires in 24 hours and can only be used once.</small>
</p>
<section class="view-root view-device-link">
<div class="view-content view-content--narrow">
<header class="view-header">
<h1>📱 Add Another Device</h1>
<p class="view-lede">Generate a one-time link to set up passkeys on a new device.</p>
</header>
<section class="section-block">
<div class="section-body">
<div class="device-link-section">
<div class="qr-container">
<a :href="url" class="qr-link" @click="copyLink">
<canvas ref="qrCanvas" class="qr-code"></canvas>
<p v-if="url">
{{ url.replace(/^[^:]+:\/\//, '') }}
</p>
<p v-else>
<em>Generating link...</em>
</p>
</a>
<p>
<strong>Scan and visit the URL on another device.</strong><br>
<small> Expires in 24 hours and can only be used once.</small>
</p>
</div>
</div>
<div class="button-row">
<button @click="authStore.currentView = 'profile'" class="btn-secondary">
Back to Profile
</button>
</div>
</div>
</div>
<button @click="authStore.currentView = 'profile'" class="btn-secondary">
Back to Profile
</button>
</section>
</div>
</div>
</section>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, nextTick } from 'vue'
import { useAuthStore } from '@/stores/auth'
import QRCode from 'qrcode/lib/browser'
const authStore = useAuthStore()
const url = ref(null)
const qrCanvas = ref(null)
const copyLink = async (event) => {
event.preventDefault()
@@ -44,24 +53,56 @@ const copyLink = async (event) => {
}
}
async function drawQr() {
if (!url.value || !qrCanvas.value) return
await nextTick()
QRCode.toCanvas(qrCanvas.value, url.value, { scale: 8 }, (error) => {
if (error) console.error('Failed to generate QR code:', error)
})
}
onMounted(async () => {
try {
const response = await fetch('/auth/api/create-link', { method: 'POST' })
const response = await fetch('/auth/api/create-link', { method: 'POST' })
const result = await response.json()
if (result.detail) throw new Error(result.detail)
url.value = result.url
// Generate QR code
const qrCodeElement = document.getElementById('qrCode')
if (qrCodeElement) {
QRCode.toCanvas(qrCodeElement, url.value, {scale: 8 }, error => {
if (error) console.error('Failed to generate QR code:', error)
})
}
await drawQr()
} catch (error) {
authStore.showMessage(`Failed to create device link: ${error.message}`, 'error')
authStore.currentView = 'profile'
}
})
</script>
<style scoped>
.view-content--narrow {
max-width: 540px;
}
.view-lede {
margin: 0;
color: var(--color-text-muted);
}
.qr-link {
text-decoration: none;
color: var(--color-text);
}
.button-row {
justify-content: flex-start;
}
@media (max-width: 720px) {
.button-row {
flex-direction: column;
}
.button-row button {
width: 100%;
}
}
</style>