Files
2026-10-03 23:45:04 +02:00

116 lines
4.6 KiB
Vue

<script setup lang="ts">
import type { CardBorderEffectKey } from '~~/shared/utils/cardBorderEffects'
import type { CardEffectKey } from '~~/shared/utils/cardEffects'
import CardBackgroundEffect from '@/components/effects/CardBackgroundEffect.vue'
import CardBorderEffect from '@/components/effects/CardBorderEffect.vue'
import CardVignette from '@/components/effects/CardVignette.vue'
import { isCardBorderEffect } from '~~/shared/utils/cardBorderEffects'
import { isCardEffect } from '~~/shared/utils/cardEffects'
import { cn } from '@/lib/utils'
interface ListedUser {
id: number
name: string
color: string | null
avatarUrl: string | null
cardEffect: string | null
cardEffectColor: string | null
cardVignette: boolean
cardBorderEffect: string | null
cardBorderEffectColor: string | null
createdAt: string
stats: {
uploads: number
comments: number
}
}
const { locale } = useI18n()
const { data: users } = await useFetch<ListedUser[]>('/api/users', {
key: 'users-list',
default: () => [],
})
function cardEffect(user: ListedUser): CardEffectKey | null {
return user.cardEffect && isCardEffect(user.cardEffect) ? user.cardEffect : null
}
function cardBorderEffect(user: ListedUser): CardBorderEffectKey | null {
return user.cardBorderEffect && isCardBorderEffect(user.cardBorderEffect) ? user.cardBorderEffect : null
}
const GRID_COLS = 'grid-cols-[2.5rem_minmax(0,1fr)_3rem_2.5rem_2.5rem] sm:grid-cols-[2.5rem_minmax(0,1fr)_3.5rem_4.5rem_4.5rem_10rem]'
function joinDate(value: string) {
return new Intl.DateTimeFormat(locale.value, { dateStyle: 'medium' }).format(new Date(value))
}
</script>
<template>
<div class="px-4 lg:px-6">
<h1 class="mb-4 text-xl font-semibold">
{{ $t('nav.users') }}
</h1>
<p v-if="!users.length" class="text-sm text-muted-foreground">
{{ $t('misc.noUsers') }}
</p>
<div v-else class="max-w-4xl overflow-x-auto">
<div :class="cn('grid items-center gap-3 px-3 py-2 text-xs text-muted-foreground', GRID_COLS)">
<span />
<span>{{ $t('common.name') }}</span>
<span class="text-right">ID</span>
<span class="hidden text-right sm:block">{{ $t('misc.uploadsCol') }}</span>
<span class="hidden text-right sm:block">{{ $t('misc.commentsCol') }}</span>
<span class="hidden text-right sm:block">{{ $t('misc.joined') }}</span>
</div>
<div class="mt-1 flex flex-col gap-2">
<NuxtLink
v-for="user in users"
:key="user.id"
:to="`/user/${user.id}`"
:class="cn(
'relative block overflow-hidden rounded-md border transition-colors',
cardBorderEffect(user) ? 'border-transparent' : 'hover:border-primary/50',
)"
>
<div v-if="cardEffect(user)" class="pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit] [clip-path:inset(0_round_var(--radius-md))]">
<CardBackgroundEffect :effect="cardEffect(user)!" :color="user.cardEffectColor ?? user.color ?? '#6366f1'" />
<div class="absolute inset-0 bg-background/60" />
<CardVignette v-if="user.cardVignette" surface="var(--color-background)" />
</div>
<CardBorderEffect v-if="cardBorderEffect(user)" :effect="cardBorderEffect(user)!" :color="user.cardBorderEffectColor ?? user.color ?? '#6366f1'" />
<div :class="cn('relative grid items-center gap-3 px-3 py-2.5', GRID_COLS)">
<NuxtImg
v-if="user.avatarUrl"
:src="user.avatarUrl"
:alt="user.name"
width="40"
height="40"
fit="cover"
class="size-10 shrink-0 rounded-sm"
/>
<span
v-else
class="flex size-10 shrink-0 items-center justify-center rounded-sm text-xs font-medium"
:style="user.color ? { backgroundColor: user.color, color: '#fff' } : { backgroundColor: 'var(--color-muted)' }"
>{{ user.name.slice(0, 2).toUpperCase() }}</span>
<span
class="truncate font-medium"
:style="user.color ? { color: user.color } : undefined"
>{{ user.name }}</span>
<span class="text-right text-sm text-muted-foreground">{{ user.id }}</span>
<span class="hidden text-right text-sm sm:block">{{ user.stats.uploads }}</span>
<span class="hidden text-right text-sm sm:block">{{ user.stats.comments }}</span>
<span class="hidden text-right text-sm text-muted-foreground sm:block">{{ joinDate(user.createdAt) }}</span>
</div>
</NuxtLink>
</div>
</div>
</div>
</template>