Migrate the Nuxt 3 + Vue 3 SSR frontend application for basicstack.org to our self-hosted Forgejo instance. This repository will serve as the source for all future development and deployment of the basicstack.org website. The application includes: - Nuxt 3 + Vue 3 SSR setup - Directus CMS integration - Tailwind CSS styling - Kubernetes deployment manifests - Docker containerization Co-Authored-By: Paperclip <noreply@paperclip.ing>
86 lines
1.9 KiB
Vue
86 lines
1.9 KiB
Vue
<template>
|
||
<div v-if="item" class="infra-detail">
|
||
<div class="page-header">
|
||
<div class="container">
|
||
<NuxtLink to="/infrastructure" class="breadcrumb">← Infrastructure</NuxtLink>
|
||
<span class="badge badge-primary">{{ categoryLabels[item.category] ?? item.category }}</span>
|
||
<h1>{{ item.title }}</h1>
|
||
<p v-if="item.summary" class="summary">{{ item.summary }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container page-body">
|
||
<div class="prose" v-html="item.content" />
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="not-found container">
|
||
<h1>Not found</h1>
|
||
<NuxtLink to="/infrastructure" class="btn btn-primary">Back to Infrastructure</NuxtLink>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const route = useRoute()
|
||
const { getInfrastructureItem } = useDirectus()
|
||
|
||
const { data: item } = await useAsyncData(`infra-${route.params.slug}`, () =>
|
||
getInfrastructureItem(route.params.slug as string)
|
||
)
|
||
|
||
const categoryLabels: Record<string, string> = {
|
||
hosting: 'Hosting',
|
||
security: 'Security',
|
||
monitoring: 'Monitoring',
|
||
backup: 'Backup',
|
||
disaster_recovery: 'Disaster Recovery',
|
||
}
|
||
|
||
if (item.value) {
|
||
useSeoMeta({
|
||
title: `${item.value.title} – BasicStack`,
|
||
description: item.value.summary ?? undefined,
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-header {
|
||
background: var(--color-bg-secondary);
|
||
border-bottom: 1px solid var(--color-border);
|
||
padding: var(--space-2xl) 0;
|
||
}
|
||
|
||
.breadcrumb {
|
||
color: var(--color-text-muted);
|
||
font-size: 0.875rem;
|
||
display: block;
|
||
margin-bottom: var(--space-lg);
|
||
}
|
||
|
||
.page-header .badge {
|
||
display: inline-block;
|
||
margin-bottom: var(--space-md);
|
||
}
|
||
|
||
.page-header h1 {
|
||
margin-bottom: var(--space-sm);
|
||
}
|
||
|
||
.summary {
|
||
color: var(--color-text-muted);
|
||
font-size: 1.0625rem;
|
||
margin: 0;
|
||
max-width: 65ch;
|
||
}
|
||
|
||
.page-body {
|
||
padding: var(--space-2xl) var(--space-lg);
|
||
max-width: 768px;
|
||
}
|
||
|
||
.not-found {
|
||
padding: var(--space-3xl) var(--space-lg);
|
||
text-align: center;
|
||
}
|
||
</style>
|