feat: settings page, sidebar and drawer destination "Settings" (icon settings, after Integration), route /settings (verified), showing only the running build's Version

This commit is contained in:
fsylvain committed 2026-09-30 00:19:29 +02:00
1 parent b146dfead7
commit 162fea4afb
26 files changed
+805 -45

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import DashboardShell from '@/components/DashboardShell.vue'
import { ApiError } from '@/composables/useApi'
import { useSettings } from '@/composables/useSettings'
const SKELETON_DELAY_MS = 150
const UNREACHABLE_COPY = "Couldn't reach RenewCMS. Check your connection and try again."
type LoadState = 'loading' | 'ready' | 'error'
const { getSettings } = useSettings()
const loadState = ref<LoadState>('loading')
const version = ref('')
const showSkeleton = ref(false)
let skeletonTimer: ReturnType<typeof setTimeout> | null = null
function clearSkeletonTimer() {
if (skeletonTimer !== null) {
clearTimeout(skeletonTimer)
skeletonTimer = null
}
}
function isHandledGlobally(error: unknown) {
return error instanceof ApiError && (error.status === 401 || error.status === 403)
}
async function loadSettings() {
loadState.value = 'loading'
showSkeleton.value = false
clearSkeletonTimer()
skeletonTimer = setTimeout(() => {
showSkeleton.value = true
}, SKELETON_DELAY_MS)
try {
const settings = await getSettings()
version.value = settings.version
loadState.value = 'ready'
} catch (error) {
if (!isHandledGlobally(error)) {
loadState.value = 'error'
}
} finally {
clearSkeletonTimer()
showSkeleton.value = false
}
}
onMounted(loadSettings)
onBeforeUnmount(clearSkeletonTimer)
</script>
<template>
<DashboardShell>
<div class="flex max-w-180 flex-col gap-5">
<div class="flex flex-col gap-0.5">
<h1 class="text-xl font-semibold text-highlighted">Settings</h1>
<p class="text-muted">Workspace information.</p>
</div>
<div
data-testid="settings-card"
:aria-busy="loadState === 'loading' && showSkeleton ? 'true' : undefined"
class="border border-default bg-default p-4 lg:p-5"
>
<UAlert
v-if="loadState === 'error'"
role="alert"
color="error"
variant="soft"
orientation="vertical"
:close="false"
icon="i-lucide-circle-x"
:description="UNREACHABLE_COPY"
:ui="{ actions: 'mt-2' }"
>
<template #actions>
<UButton
color="neutral"
variant="outline"
icon="i-lucide-rotate-cw"
class="h-11 gap-1.5 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 md:h-8"
:ui="{ leadingIcon: 'size-3.5' }"
@click="loadSettings"
>
Try again
</UButton>
</template>
</UAlert>
<div v-else class="flex items-center justify-between gap-4">
<span class="text-lg font-semibold text-highlighted">Version</span>
<USkeleton
v-if="loadState === 'loading' && showSkeleton"
data-testid="settings-skeleton"
class="h-5 w-24 rounded-none bg-accented"
/>
<span
v-else-if="loadState === 'ready'"
data-testid="settings-version"
class="font-mono text-base text-toned"
>{{ version }}</span
>
</div>
</div>
</div>
</DashboardShell>
</template>