Tests, UX improvements.

This commit is contained in:
Jennie Robinson Faber 2026-04-05 14:25:29 +01:00
parent 4e6f5d36b8
commit 0ae18f495e
63 changed files with 1384 additions and 2330 deletions

View file

@ -115,11 +115,83 @@
</div>
</dl>
</section>
<!-- Notification preferences -->
<section class="detail-section">
<div class="section-label">Notification preferences</div>
<dl class="meta-list">
<div class="meta-row">
<dt>Event reminders</dt>
<dd
:class="
member.notifications?.events !== false
? 'status-ok'
: 'status-dim'
"
>
{{ member.notifications?.events !== false ? "On" : "Off" }}
</dd>
</div>
<div class="meta-row">
<dt>Community updates</dt>
<dd
:class="
member.notifications?.updates !== false
? 'status-ok'
: 'status-dim'
"
>
{{ member.notifications?.updates !== false ? "On" : "Off" }}
</dd>
</div>
<div class="meta-row">
<dt>Peer support requests</dt>
<dd
:class="
member.notifications?.peerRequests !== false
? 'status-ok'
: 'status-dim'
"
>
{{ member.notifications?.peerRequests !== false ? "On" : "Off" }}
</dd>
</div>
</dl>
</section>
<!-- Full Activity Log -->
<section class="detail-section">
<div class="section-label">Full Activity Log</div>
<ClientOnly>
<div v-if="activityLoading && !activityEntries.length" class="loading-state">
<div class="spinner" />
Loading activity...
</div>
<div v-else-if="activityEntries.length" class="activity-log">
<div v-for="entry in activityEntries" :key="entry._id" class="al-item" :class="{ 'al-admin': entry.visibility === 'admin' }">
<UIcon :name="getActivity(entry).icon" class="al-icon" />
<span class="al-text">{{ getActivity(entry).text }}</span>
<span class="al-time">{{ formatDate(entry.timestamp) }}</span>
<span v-if="entry.visibility === 'admin'" class="al-vis-badge">admin-only</span>
</div>
<div v-if="activityHasMore" class="al-load-more">
<button class="btn" :disabled="activityLoadingMore" @click="loadMoreActivity">
{{ activityLoadingMore ? 'Loading...' : 'Load More' }}
</button>
</div>
</div>
<div v-else class="loading-state">
No activity recorded.
</div>
</ClientOnly>
</section>
</template>
</div>
</template>
<script setup>
import { formatActivity } from '~/utils/activityText'
definePageMeta({
layout: "admin",
middleware: "admin",
@ -225,6 +297,50 @@ function statusClass(status) {
if (status === "cancelled" || status === "suspended") return "status-error";
return "status-dim";
}
// Activity log
const activityEntries = ref([])
const activityLoading = ref(false)
const activityLoadingMore = ref(false)
const activityHasMore = ref(false)
const activityNextCursor = ref(null)
const getActivity = (entry) => formatActivity(entry)
async function loadActivity() {
activityLoading.value = true
try {
const data = await $fetch(`/api/admin/members/${route.params.id}/activity`, {
params: { limit: 20 }
})
activityEntries.value = data.entries
activityHasMore.value = data.hasMore
activityNextCursor.value = data.nextCursor
} catch (err) {
console.error('Failed to load activity:', err)
} finally {
activityLoading.value = false
}
}
async function loadMoreActivity() {
if (!activityNextCursor.value) return
activityLoadingMore.value = true
try {
const data = await $fetch(`/api/admin/members/${route.params.id}/activity`, {
params: { limit: 20, before: activityNextCursor.value }
})
activityEntries.value.push(...data.entries)
activityHasMore.value = data.hasMore
activityNextCursor.value = data.nextCursor
} catch (err) {
console.error('Failed to load more activity:', err)
} finally {
activityLoadingMore.value = false
}
}
onMounted(loadActivity)
</script>
<style scoped>
@ -381,6 +497,62 @@ function statusClass(status) {
}
}
/* ---- ACTIVITY LOG ---- */
.activity-log {
margin-top: 12px;
border: 1px dashed var(--border);
}
.al-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-bottom: 1px dashed var(--border);
font-size: 12px;
}
.al-item:last-child {
border-bottom: none;
}
.al-admin {
opacity: 0.7;
}
.al-icon {
width: 14px;
height: 14px;
color: var(--text-faint);
flex-shrink: 0;
}
.al-text {
flex: 1;
min-width: 0;
color: var(--text-dim);
}
.al-time {
color: var(--text-faint);
font-size: 11px;
flex-shrink: 0;
}
.al-vis-badge {
font-size: 9px;
color: var(--text-faint);
border: 1px dashed var(--border);
padding: 1px 4px;
flex-shrink: 0;
}
.al-load-more {
display: flex;
justify-content: center;
padding: 12px;
}
@media (max-width: 768px) {
.admin-member-detail {
padding: 20px 16px 40px;