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:
1 parent
b146dfead7
commit
162fea4afb
26 files changed
+805
-45
No files matched your search
@@ -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>
|
||||
Reference in new issue
Block a user