Docs
Moderation Console
Moderation Console This example creates an admin page for pending comments. Menu Create a menu item: Field Value label Moderation path /moderation permission {"or":[{"route":"/blog_comment","methods":["GET"]}]} Page Extension Create a page extension attached to that menu. <templa
Moderation Console
This example creates an admin page for pending comments.
Menu
Create a menu item:
| Field | Value |
|---|---|
label |
Moderation |
path |
/moderation |
permission |
{"or":[{"route":"/blog_comment","methods":["GET"]}]} |
Page Extension
Create a page extension attached to that menu.
<template>
<div class="space-y-4">
<ErrorState v-if="error" :error="error" />
<LoadingState v-else-if="pending" />
<div v-else-if="rows.length" class="space-y-3">
<div v-for="comment in rows" :key="comment.id" class="rounded-md border border-gray-200 p-4 dark:border-gray-800">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-sm text-gray-500">{{ comment.author?.email }}</p>
<p class="mt-2">{{ comment.body }}</p>
</div>
<div class="flex gap-2">
<PermissionGate :condition="{ and: [{ route: '/blog_comment', methods: ['PATCH'] }] }">
<UButton size="sm" color="primary" @click="updateStatus(comment.id, 'approved')">Approve</UButton>
<UButton size="sm" color="neutral" variant="outline" @click="updateStatus(comment.id, 'rejected')">Reject</UButton>
</PermissionGate>
</div>
</div>
</div>
</div>
<EmptyState v-else title="No pending comments" />
</div>
</template>
<script setup>
const { registerPageHeader } = usePageHeaderRegistry();
registerPageHeader({
title: 'Moderation',
description: 'Review pending comments',
leadingIcon: '',
variant: 'minimal'
});
const rows = ref([]);
const pending = ref(false);
const error = ref(null);
const load = async () => {
pending.value = true;
error.value = null;
try {
const response = await $fetch('/api/blog_comment', {
query: {
filter: JSON.stringify({ status: { _eq: 'pending' } }),
fields: 'id,body,status,author.email,createdAt',
sort: '-createdAt',
limit: 25
}
});
rows.value = response.data || [];
} catch (err) {
error.value = err;
} finally {
pending.value = false;
}
};
const updateStatus = async (id, status) => {
await $fetch(`/api/blog_comment/${id}`, {
method: 'PATCH',
body: { status }
});
await load();
};
onMounted(load);
</script>
Use the raw data route /data/blog_comment as a secondary inspection link when operators need full metadata.