routing and UX improvements

- Always route naked domain and unknown slugs to /home
- Redirect hidden pages to home for non-admin visitors
- Add two-step delete confirmation for pages in sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Justin Visser 2026-03-03 20:32:21 +01:00
parent a972d207be
commit 5ad08ff0e3
3 changed files with 21 additions and 6 deletions

View file

@ -61,7 +61,7 @@ watchEffect(() => {
// Redirect "/" to first page once pages are loaded // Redirect "/" to first page once pages are loaded
watch(pages, (loaded) => { watch(pages, (loaded) => {
if (loaded.length > 0 && (route.path === "/" || route.path === "")) { if (loaded.length > 0 && (route.path === "/" || route.path === "")) {
void router.replace({ name: "page", params: { slug: loaded[0].slug } }); void router.replace({ name: "page", params: { slug: "home" } });
} }
}); });
@ -76,7 +76,7 @@ function scrollToTop() {
onMounted(async () => { onMounted(async () => {
await Promise.all([loadSiteTitle(), fetchPages()]); await Promise.all([loadSiteTitle(), fetchPages()]);
if (pages.value.length > 0 && (route.path === "/" || route.path === "")) { if (pages.value.length > 0 && (route.path === "/" || route.path === "")) {
void router.replace({ name: "page", params: { slug: pages.value[0].slug } }); void router.replace({ name: "page", params: { slug: "home" } });
} }
}); });
</script> </script>

View file

@ -48,14 +48,21 @@ async function saveRename(page: Page) {
} }
// --- Delete page --- // --- Delete page ---
const confirmDeleteId = ref<string | null>(null);
async function handleDelete(page: Page) { async function handleDelete(page: Page) {
if (confirmDeleteId.value !== page.id) {
confirmDeleteId.value = page.id;
return;
}
confirmDeleteId.value = null;
const result = await deletePage(page.id); const result = await deletePage(page.id);
if (result.ok && route.params.slug === page.slug) { if (result.ok && route.params.slug === page.slug) {
const remaining = pages.value.filter((p) => p.id !== page.id); const remaining = pages.value.filter((p) => p.id !== page.id);
if (remaining.length > 0) { if (remaining.length > 0) {
await router.push({ name: "page", params: { slug: remaining[0].slug } }); await router.push({ name: "page", params: { slug: remaining[0].slug } });
} else { } else {
await router.push("/"); await router.push({ name: "page", params: { slug: "home" } });
} }
} }
} }
@ -157,6 +164,7 @@ async function handleLogin() {
v-else v-else
:to="{ name: 'page', params: { slug: page.slug } }" :to="{ name: 'page', params: { slug: page.slug } }"
class="flex items-center gap-1.5 rounded-md px-2 py-2 text-sm transition-colors" class="flex items-center gap-1.5 rounded-md px-2 py-2 text-sm transition-colors"
@blur.capture="confirmDeleteId = null"
:class="[ :class="[
$route.params.slug === page.slug $route.params.slug === page.slug
? 'border-l-2 border-primary-400 bg-primary-50 font-medium text-gray-900 dark:border-primary-400 dark:bg-gray-600 dark:text-white' ? 'border-l-2 border-primary-400 bg-primary-50 font-medium text-gray-900 dark:border-primary-400 dark:bg-gray-600 dark:text-white'
@ -200,8 +208,9 @@ async function handleLogin() {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg> </svg>
</button> </button>
<!-- Delete --> <!-- Delete (two-step confirm) -->
<button <button
v-if="confirmDeleteId !== page.id"
class="rounded p-0.5 text-gray-400 hover:text-red-500 dark:text-gray-600 dark:hover:text-red-400" class="rounded p-0.5 text-gray-400 hover:text-red-500 dark:text-gray-600 dark:hover:text-red-400"
title="Delete page" title="Delete page"
@click.prevent="handleDelete(page)" @click.prevent="handleDelete(page)"
@ -210,6 +219,12 @@ async function handleLogin() {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</button> </button>
<button
v-else
class="rounded px-1 py-0.5 text-xs font-medium text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
title="Click again to confirm deletion"
@click.prevent="handleDelete(page)"
>sure?</button>
</span> </span>
</router-link> </router-link>
</div> </div>

View file

@ -37,11 +37,11 @@ async function fetchSegments(pageId: string) {
function resolvePage(slug: string) { function resolvePage(slug: string) {
if (!pages.value.length) return; if (!pages.value.length) return;
const found = pages.value.find((p) => p.slug === slug); const found = pages.value.find((p) => p.slug === slug);
if (found) { if (found && (!found.is_hidden || isAdmin.value)) {
currentPage.value = found; currentPage.value = found;
void fetchSegments(found.id); void fetchSegments(found.id);
} else { } else {
void router.replace({ name: "page", params: { slug: pages.value[0].slug } }); void router.replace({ name: "page", params: { slug: "home" } });
} }
} }