feat: add hub

This commit is contained in:
2026-10-03 23:57:41 +02:00
parent 5452f72087
commit fb7b560696
52 changed files with 290 additions and 162 deletions
+3 -1
View File
@@ -4,5 +4,7 @@ export default defineAppConfig({
primary: 'amber',
neutral: 'stone'
}
}
},
/** Tiles of the hub on `/`; each app adds its own in its layer's app.config.ts, keyed by the app name */
hub: {} as Record<string, HubApp>
})
+5 -1
View File
@@ -1,5 +1,9 @@
<script setup lang="ts">
import { de } from '@nuxt/ui/locale'
</script>
<template>
<UApp>
<UApp :locale="de">
<NuxtPage />
</UApp>
</template>
+11
View File
@@ -0,0 +1,11 @@
<script setup lang="ts">
// The buttons in every app's header: back to the hub and light / dark. One size and style everywhere; don't override it.
withDefaults(defineProps<{ hubLink?: boolean }>(), { hubLink: true })
</script>
<template>
<div class="flex shrink-0 items-center gap-1 print:hidden">
<UButton v-if="hubLink" to="/" icon="i-lucide-house" color="neutral" variant="ghost" aria-label="Zur Übersicht" title="huaqo.de" />
<UColorModeButton color="neutral" variant="ghost" />
</div>
</template>
+4 -4
View File
@@ -14,7 +14,7 @@ async function unlock() {
await $fetch('/api/auth/login', { method: 'POST', body: { password: password.value } })
emit('unlocked')
} catch (e) {
error.value = errorMessage(e) ?? 'Couldn\'t unlock'
error.value = errorMessage(e) ?? 'Entsperren fehlgeschlagen'
password.value = ''
} finally {
unlocking.value = false
@@ -23,13 +23,13 @@ async function unlock() {
</script>
<template>
<UModal :open="true" :dismissible="false" :close="false" :title="title" description="Enter your password to continue.">
<UModal :open="true" :dismissible="false" :close="false" :title="title" description="Passwort eingeben, um fortzufahren.">
<template #body>
<form class="space-y-4" @submit.prevent="unlock">
<UFormField label="Password" :error="error">
<UFormField label="Passwort" :error="error">
<UInput v-model="password" type="password" autocomplete="current-password" class="w-full" autofocus />
</UFormField>
<UButton type="submit" label="Unlock" icon="i-lucide-lock-open" :loading="unlocking" :disabled="!password" />
<UButton type="submit" label="Entsperren" icon="i-lucide-lock-open" :loading="unlocking" :disabled="!password" />
</form>
</template>
</UModal>
+35 -1
View File
@@ -1,3 +1,37 @@
<script setup lang="ts">
useHead({ title: 'huaqo.de' })
const { hub } = useAppConfig()
const apps = computed(() => Object.values(hub as Record<string, HubApp>).sort((a, b) => a.title.localeCompare(b.title, 'de')))
</script>
<template>
<div />
<UMain>
<UContainer class="max-w-3xl py-8">
<div class="mb-6 flex items-center justify-between gap-3">
<h1 class="text-2xl font-semibold text-highlighted">
huaqo.de
</h1>
<AppHeaderActions :hub-link="false" />
</div>
<ul class="grid gap-3 sm:grid-cols-2">
<li v-for="app in apps" :key="app.to">
<NuxtLink
:to="app.to"
class="group flex h-full items-center gap-4 rounded-lg border border-default p-4 transition-colors hover:border-primary hover:bg-elevated/50 focus-visible:outline-2 focus-visible:outline-primary"
>
<span class="flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
<UIcon :name="app.icon" class="size-5" />
</span>
<span class="flex min-w-0 items-center gap-1.5 font-medium text-highlighted">
{{ app.title }}
<UIcon v-if="app.locked" name="i-lucide-lock" class="size-3.5 text-muted" aria-label="Passwortgeschützt" />
</span>
</NuxtLink>
</li>
</ul>
</UContainer>
</UMain>
</template>