140 lines
3.9 KiB
Vue
140 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|
import { t } from '@/i18n'
|
|
|
|
import ActivityAvatar from '@/components/ActivityAvatar.vue'
|
|
import ActivityBadge from '@/components/ActivityBadge.vue'
|
|
import ActivitySentence from '@/components/ActivitySentence.vue'
|
|
import type { ActivityItem } from '@/lib/activity'
|
|
import { fullLocalTime, recentWhenLabel } from '@/lib/activityTime'
|
|
|
|
const SKELETON_ROWS = 5
|
|
const SKELETON_SENTENCE_WIDTHS = ['w-17/20', 'w-7/10', 'w-9/10', 'w-3/5', 'w-4/5']
|
|
|
|
defineProps<{
|
|
items: ActivityItem[]
|
|
loading: boolean
|
|
showSkeleton: boolean
|
|
error: boolean
|
|
own: boolean
|
|
}>()
|
|
|
|
defineEmits<{
|
|
retry: []
|
|
}>()
|
|
|
|
function metaLine(item: ActivityItem, own: boolean) {
|
|
const when = recentWhenLabel(item.created_at)
|
|
if (own) return when
|
|
const name = item.actor.type === 'anonymous' ? t('Not signed in') : item.actor.name
|
|
return `${name} · ${when}`
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section
|
|
data-testid="recent-activity-card"
|
|
:aria-busy="loading && showSkeleton ? 'true' : undefined"
|
|
class="flex min-w-0 flex-col gap-2.5"
|
|
>
|
|
<div class="flex items-baseline justify-between">
|
|
<h2 class="text-base/6 font-semibold text-highlighted">
|
|
{{ own ? t('Your recent activity') : t('Recent activity') }}
|
|
</h2>
|
|
<RouterLink to="/activity" class="text-toned underline underline-offset-3">
|
|
{{ t('View all') }}
|
|
</RouterLink>
|
|
</div>
|
|
|
|
<template v-if="loading">
|
|
<div v-if="showSkeleton" class="border border-default bg-default">
|
|
<div
|
|
v-for="n in SKELETON_ROWS"
|
|
:key="n"
|
|
data-testid="recent-activity-skeleton"
|
|
class="flex min-h-13 items-center gap-3 px-3 py-2 lg:min-h-11"
|
|
:class="n > 1 ? 'border-t border-muted' : ''"
|
|
>
|
|
<USkeleton class="size-6 shrink-0 rounded-none bg-accented" />
|
|
<USkeleton
|
|
class="h-3 rounded-none bg-accented"
|
|
:class="SKELETON_SENTENCE_WIDTHS[n - 1]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<UAlert
|
|
v-else-if="error"
|
|
role="alert"
|
|
color="error"
|
|
variant="soft"
|
|
orientation="vertical"
|
|
:close="false"
|
|
icon="i-lucide-circle-x"
|
|
:title="t('Couldn\'t load recent activity')"
|
|
:description="t('Check your connection and try again.')"
|
|
:ui="{ actions: 'mt-2' }"
|
|
>
|
|
<template #actions>
|
|
<UButton
|
|
color="neutral"
|
|
variant="outline"
|
|
icon="i-lucide-rotate-cw"
|
|
class="h-11 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 lg:h-7"
|
|
:ui="{ leadingIcon: 'size-3.5' }"
|
|
@click="$emit('retry')"
|
|
>
|
|
{{ t('Try again') }}
|
|
</UButton>
|
|
</template>
|
|
</UAlert>
|
|
|
|
<div
|
|
v-else-if="items.length === 0"
|
|
data-testid="recent-activity-empty"
|
|
class="flex flex-col items-center gap-1.5 border border-default bg-default px-6 py-8 text-center"
|
|
>
|
|
<UIcon name="i-lucide-history" class="size-5 text-muted" />
|
|
<span class="font-semibold text-highlighted">{{ t('No activity yet') }}</span>
|
|
<span class="max-w-65 text-muted">
|
|
{{
|
|
own
|
|
? t('Your actions will appear here.')
|
|
: t('Actions on your site will appear here.')
|
|
}}
|
|
</span>
|
|
</div>
|
|
|
|
<ul v-else class="border border-default bg-default">
|
|
<li
|
|
v-for="(item, index) in items"
|
|
:key="item.id"
|
|
data-testid="recent-activity-row"
|
|
class="flex min-h-13 items-center gap-3 px-3 py-2 lg:min-h-11"
|
|
:class="index > 0 ? 'border-t border-muted' : ''"
|
|
>
|
|
<ActivityAvatar :actor="item.actor" />
|
|
<div class="flex min-w-0 flex-1 flex-col">
|
|
<span class="truncate text-default" data-testid="recent-activity-sentence">
|
|
<ActivitySentence :item="item" />
|
|
</span>
|
|
<span
|
|
data-testid="recent-activity-meta"
|
|
:title="fullLocalTime(item.created_at)"
|
|
class="truncate text-xs text-muted"
|
|
>
|
|
{{ metaLine(item, own) }}
|
|
</span>
|
|
</div>
|
|
<ActivityBadge v-if="item.outcome === 'failed'" tone="fail" :label="t('Failed')" />
|
|
<UIcon
|
|
v-else-if="item.security"
|
|
name="i-lucide-shield-alert"
|
|
data-testid="recent-activity-security"
|
|
class="size-3.5 shrink-0 text-warning"
|
|
/>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</template>
|