Release 2025-05-22-GmO6

This commit is contained in:
pluja
2025-05-22 11:10:18 +00:00
parent ed86f863e3
commit a69c0aeed4
20 changed files with 316 additions and 147 deletions

View File

@@ -1,7 +1,12 @@
---
import { z } from 'astro/zod'
import { Icon } from 'astro-icon/components'
import BadgeSmall from '../../components/BadgeSmall.astro'
import CommentModeration from '../../components/CommentModeration.astro'
import MyPicture from '../../components/MyPicture.astro'
import TimeFormatted from '../../components/TimeFormatted.astro'
import UserBadge from '../../components/UserBadge.astro'
import {
commentStatusFilters,
commentStatusFiltersZodEnum,
@@ -36,11 +41,18 @@ const [comments = [], totalComments = 0] = await Astro.locals.banners.try(
prisma.comment.findManyAndCount({
where: statusFilter.whereClause,
include: {
author: true,
author: {
select: {
name: true,
displayName: true,
picture: true,
},
},
service: {
select: {
name: true,
slug: true,
imageUrl: true,
},
},
parent: {
@@ -72,7 +84,7 @@ const totalPages = Math.ceil(totalComments / PAGE_SIZE)
class={cn([
'font-title rounded-md border px-3 py-1 text-sm',
params.status === filter.value
? filter.styles.filter
? filter.classNames.filter
: 'border-zinc-700 transition-colors hover:border-green-500/50',
])}
>
@@ -98,22 +110,7 @@ const totalPages = Math.ceil(totalComments / PAGE_SIZE)
>
<div class="mb-4 flex flex-wrap items-center gap-2">
{/* Author Info */}
<span class="font-title text-sm">{comment.author.name}</span>
{comment.author.admin && (
<span class="rounded-sm bg-yellow-500/20 px-2 py-0.5 text-[12px] font-medium text-yellow-500">
admin
</span>
)}
{comment.author.verified && !comment.author.admin && (
<span class="rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500">
verified
</span>
)}
{comment.author.verifier && !comment.author.admin && (
<span class="rounded-sm bg-green-500/20 px-2 py-0.5 text-[12px] font-medium text-green-500">
verifier
</span>
)}
<UserBadge user={comment.author} size="md" />
{/* Service Link */}
<span class="text-xs text-zinc-500">•</span>
@@ -121,21 +118,55 @@ const totalPages = Math.ceil(totalComments / PAGE_SIZE)
href={`/service/${comment.service.slug}#comment-${comment.id.toString()}`}
class="text-sm text-blue-400 transition-colors hover:text-blue-300"
>
{!!comment.service.imageUrl && (
<MyPicture
src={comment.service.imageUrl}
height={16}
width={16}
class="inline-block size-4 rounded-full align-[-0.2em]"
alt=""
/>
)}
{comment.service.name}
</a>
{/* Date */}
<span class="text-xs text-zinc-500">•</span>
<span class="text-sm text-zinc-400">{new Date(comment.createdAt).toLocaleString()}</span>
<TimeFormatted
date={comment.createdAt}
hourPrecision
caseType="sentence"
class="text-sm text-zinc-400"
/>
<span class="text-xs text-zinc-500">•</span>
{/* Status Badges */}
<span class={comment.statusFilterInfo.styles.badge}>{comment.statusFilterInfo.label}</span>
<BadgeSmall
color={comment.statusFilterInfo.color}
text={comment.statusFilterInfo.label}
icon={comment.statusFilterInfo.icon}
inlineIcon
/>
<span class="text-xs text-zinc-500">•</span>
{/* Link to Comment */}
<a
href={`/service/${comment.service.slug}?showPending=true#comment-${comment.id.toString()}`}
class="text-whit/50 flex items-center gap-1 text-sm transition-colors hover:underline"
>
<Icon name="ri:link" class="size-4" />
Open
</a>
</div>
{/* Parent Comment Context */}
{comment.parent && (
<div class="mb-4 border-l-2 border-zinc-700 pl-4">
<div class="mb-1 text-sm text-zinc-500">Replying to {comment.parent.author.name}:</div>
<div class="mb-1 text-sm opacity-50">
Replying to <UserBadge user={comment.parent.author} size="md" />
</div>
<div class="text-sm text-zinc-400">{comment.parent.content}</div>
</div>
)}

View File

@@ -66,6 +66,7 @@ const serviceSuggestion = await Astro.locals.banners.try('Error fetching service
user: {
select: {
id: true,
displayName: true,
name: true,
picture: true,
},

View File

@@ -6,6 +6,7 @@ import { orderBy as lodashOrderBy } from 'lodash-es'
import SortArrowIcon from '../../../components/SortArrowIcon.astro'
import TimeFormatted from '../../../components/TimeFormatted.astro'
import UserBadge from '../../../components/UserBadge.astro'
import {
getServiceSuggestionStatusInfo,
serviceSuggestionStatuses,
@@ -67,8 +68,9 @@ let suggestions = await prisma.serviceSuggestion.findMany({
createdAt: true,
user: {
select: {
id: true,
displayName: true,
name: true,
picture: true,
},
},
service: {
@@ -293,9 +295,7 @@ const makeSortUrl = (slug: string) => {
</div>
</td>
<td class="px-4 py-3">
<a href={`/admin/users/${suggestion.user.name}`} class="hover:text-green-500">
{suggestion.user.name}
</a>
<UserBadge user={suggestion.user} size="md" />
</td>
<td class="px-4 py-3">
<form method="POST" action={actions.admin.serviceSuggestions.update}>

View File

@@ -10,6 +10,7 @@ import { Icon } from 'astro-icon/components'
import { actions, isInputError } from 'astro:actions'
import MyPicture from '../../../../components/MyPicture.astro'
import UserBadge from '../../../../components/UserBadge.astro'
import { serviceVisibilities } from '../../../../constants/serviceVisibility'
import BaseLayout from '../../../../layouts/BaseLayout.astro'
import { cn } from '../../../../lib/cn'
@@ -80,9 +81,9 @@ const service = await Astro.locals.banners.try('Error fetching service', () =>
id: true,
user: {
select: {
id: true,
name: true,
displayName: true,
picture: true,
},
},
createdAt: true,
@@ -1183,7 +1184,9 @@ const buttonSmallWarningClasses = cn(
<tbody class="divide-y divide-zinc-700/80">
{service.verificationRequests.map((request) => (
<tr class="transition-colors hover:bg-zinc-700/40">
<td class="p-3 text-zinc-300">{request.user.displayName ?? request.user.name}</td>
<td class="p-3 text-zinc-300">
<UserBadge user={request.user} size="md" />
</td>
<td class="p-3 text-zinc-400">{new Date(request.createdAt).toLocaleString()}</td>
</tr>
))}

View File

@@ -116,7 +116,7 @@ if (!user) return Astro.rewrite('/404')
---
<BaseLayout
pageTitle={`User: ${user.name}`}
pageTitle={`${user.displayName ?? user.name} - User`}
widthClassName="max-w-screen-lg"
className={{ main: 'space-y-24' }}
>

View File

@@ -6,6 +6,7 @@ import { orderBy as lodashOrderBy } from 'lodash-es'
import SortArrowIcon from '../../../components/SortArrowIcon.astro'
import TimeFormatted from '../../../components/TimeFormatted.astro'
import Tooltip from '../../../components/Tooltip.astro'
import UserBadge from '../../../components/UserBadge.astro'
import BaseLayout from '../../../layouts/BaseLayout.astro'
import { zodParseQueryParamsStoringErrors } from '../../../lib/parseUrlFilters'
import { pluralize } from '../../../lib/pluralize'
@@ -74,6 +75,8 @@ const dbUsers = await prisma.user.findMany({
select: {
id: true,
name: true,
displayName: true,
picture: true,
verified: true,
admin: true,
verifier: true,
@@ -241,10 +244,10 @@ const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => {
class={`group hover:bg-zinc-700/30 ${user.spammer ? 'bg-red-900/10' : ''}`}
>
<td class="px-4 py-3 text-sm font-medium text-zinc-200">
<div>{user.name}</div>
<UserBadge user={user} size="md" class="flex text-white" />
{user.internalNotes.length > 0 && (
<Tooltip
class="text-2xs mt-1 text-yellow-400"
class="text-2xs font-light text-yellow-200/40"
position="right"
text={user.internalNotes
.map(
@@ -257,7 +260,7 @@ const makeSortUrl = (slug: NonNullable<(typeof filters)['sort-by']>) => {
)
.join('\n\n')}
>
<Icon name="ri:sticky-note-line" class="mr-1 inline-block size-3" />
<Icon name="ri:sticky-note-line" class="mr-0.5 inline-block size-3" />
{user.internalNotes.length} internal {pluralize('note', user.internalNotes.length)}
</Tooltip>
)}