From a69c0aeed4ab70b0119b1764cfafc77754ec6684 Mon Sep 17 00:00:00 2001 From: pluja Date: Thu, 22 May 2025 11:10:18 +0000 Subject: [PATCH] Release 2025-05-22-GmO6 --- web/src/components/ChatMessages.astro | 16 +--- web/src/components/CommentItem.astro | 50 +++++------ web/src/components/CommentReply.astro | 3 +- web/src/components/Header.astro | 24 +++-- web/src/components/UserBadge.astro | 87 +++++++++++++++++++ .../VerificationWarningBanner.astro | 20 +++++ web/src/constants/commentStatus.ts | 15 +++- web/src/constants/commentStatusFilters.ts | 64 ++++++++------ web/src/constants/karmaTransactionActions.ts | 4 +- web/src/lib/tailwind.ts | 8 ++ web/src/pages/account/edit.astro | 2 +- web/src/pages/account/index.astro | 34 +++++--- web/src/pages/admin/comments.astro | 73 +++++++++++----- .../admin/service-suggestions/[id].astro | 1 + .../admin/service-suggestions/index.astro | 8 +- .../pages/admin/services/[slug]/edit.astro | 7 +- web/src/pages/admin/users/[username].astro | 2 +- web/src/pages/admin/users/index.astro | 9 +- web/src/pages/service/[slug].astro | 11 --- web/src/pages/u/[username].astro | 25 +++--- 20 files changed, 316 insertions(+), 147 deletions(-) create mode 100644 web/src/components/UserBadge.astro create mode 100644 web/src/lib/tailwind.ts diff --git a/web/src/components/ChatMessages.astro b/web/src/components/ChatMessages.astro index 2897a23..4f73898 100644 --- a/web/src/components/ChatMessages.astro +++ b/web/src/components/ChatMessages.astro @@ -2,7 +2,7 @@ import { cn } from '../lib/cn' import { formatDateShort } from '../lib/timeAgo' -import MyPicture from './MyPicture.astro' +import UserBadge from './UserBadge.astro' import type { Prisma } from '@prisma/client' import type { HTMLAttributes } from 'astro/types' @@ -15,6 +15,7 @@ export type ChatMessage = { select: { id: true name: true + displayName: true picture: true } }> @@ -71,18 +72,7 @@ const { messages, userId, class: className, ...htmlProps } = Astro.props )} > {!isCurrentUser && !isNextFromSameUser && ( -

- {!!message.user.picture && ( - - )} - {message.user.name} -

+ )}

- { - comment.author.picture && ( - - ) - } - - - {comment.author.displayName ?? comment.author.name} - + { (comment.author.verified || comment.author.admin || comment.author.verifier) && ( @@ -307,20 +292,35 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin: { comment.status === 'VERIFIED' && ( - + ) } { (comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING') && (showPending || isHighlightParent || isAuthorOrPrivileged) && ( - + ) } { comment.status === 'REJECTED' && isAuthorOrPrivileged && ( - + ) } diff --git a/web/src/components/CommentReply.astro b/web/src/components/CommentReply.astro index dfddaa2..03e24bc 100644 --- a/web/src/components/CommentReply.astro +++ b/web/src/components/CommentReply.astro @@ -11,6 +11,7 @@ import InputHoneypotTrap from './InputHoneypotTrap.astro' import InputRating from './InputRating.astro' import InputText from './InputText.astro' import InputWrapper from './InputWrapper.astro' +import UserBadge from './UserBadge.astro' import type { Prisma } from '@prisma/client' import type { HTMLAttributes } from 'astro/types' @@ -67,7 +68,7 @@ const userCommentsDisabled = user ? user.karmaUnlocks.commentsDisabled : false

- Commenting as: {user.name} + Commenting as:
diff --git a/web/src/components/Header.astro b/web/src/components/Header.astro index e79bd14..148538a 100644 --- a/web/src/components/Header.astro +++ b/web/src/components/Header.astro @@ -12,6 +12,7 @@ import HeaderNotificationIndicator from './HeaderNotificationIndicator.astro' import HeaderSplashTextScript from './HeaderSplashTextScript.astro' import Logo from './Logo.astro' import Tooltip from './Tooltip.astro' +import UserBadge from './UserBadge.astro' const user = Astro.locals.user const actualUser = Astro.locals.actualUser @@ -131,9 +132,12 @@ const splashText = showSplashText ? sample(splashTexts) : null user ? ( <> {actualUser && ( - - ({actualUser.name}) - + )} - - {user.name} - + /> + {actualUser ? ( , 'href'> & + VariantProps & { + user: Prisma.UserGetPayload<{ + select: { + name: true + displayName: true + picture: true + } + }> + classNames?: { + image?: string + text?: string + } + children?: never + } + +const { user, href, class: className, size = 'sm', classNames, noLink = false, ...htmlProps } = Astro.props +const { base, image, text } = userBadge({ size, noLink }) + +const imageClassName = image({ class: classNames?.image }) +const imageSizePx = getSizePxFromTailwindClasses(imageClassName, 16) + +const Tag = noLink ? 'span' : 'a' +--- + + + { + !!user.picture && ( + + ) + } + + {user.displayName ?? user.name} + + diff --git a/web/src/components/VerificationWarningBanner.astro b/web/src/components/VerificationWarningBanner.astro index 98b29fb..a41ae08 100644 --- a/web/src/components/VerificationWarningBanner.astro +++ b/web/src/components/VerificationWarningBanner.astro @@ -19,6 +19,11 @@ type Props = { verificationSummary: true listedAt: true createdAt: true + verificationSteps: { + select: { + status: true + } + } } }> } @@ -67,3 +72,18 @@ const wasRecentlyAdded = isPast(listedDate) && differenceInDays(new Date(), list ) : null } + +{ + service.verificationStatus !== 'VERIFICATION_FAILED' && + service.verificationSteps.some((step) => step.status === 'FAILED') && ( +
+ + + This service has failed one or more verification steps. Review the verification details carefully. + +
+ ) +} diff --git a/web/src/constants/commentStatus.ts b/web/src/constants/commentStatus.ts index 4f8a9d9..985d601 100644 --- a/web/src/constants/commentStatus.ts +++ b/web/src/constants/commentStatus.ts @@ -1,12 +1,15 @@ import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' import { transformCase } from '../lib/strings' +import type BadgeSmall from '../components/BadgeSmall.astro' import type { CommentStatus } from '@prisma/client' +import type { ComponentProps } from 'astro/types' type CommentStatusInfo = { id: T icon: string label: string + color: ComponentProps['color'] creativeWorkStatus: string | undefined } @@ -20,37 +23,43 @@ export const { id, icon: 'ri:question-line', label: id ? transformCase(id, 'title') : String(id), + color: 'gray', creativeWorkStatus: undefined, }), [ { id: 'PENDING', icon: 'ri:question-line', - label: 'Pending', + label: 'Unmoderated', + color: 'yellow', creativeWorkStatus: 'Deleted', }, { id: 'HUMAN_PENDING', icon: 'ri:question-line', - label: 'Pending', + label: 'Unmoderated', + color: 'yellow', creativeWorkStatus: 'Deleted', }, { id: 'VERIFIED', - icon: 'ri:check-line', + icon: 'ri:verified-badge-fill', label: 'Verified', + color: 'blue', creativeWorkStatus: 'Verified', }, { id: 'REJECTED', icon: 'ri:close-line', label: 'Rejected', + color: 'red', creativeWorkStatus: 'Deleted', }, { id: 'APPROVED', icon: 'ri:check-line', label: 'Approved', + color: 'green', creativeWorkStatus: 'Active', }, ] as const satisfies CommentStatusInfo[] diff --git a/web/src/constants/commentStatusFilters.ts b/web/src/constants/commentStatusFilters.ts index 52938d3..d394852 100644 --- a/web/src/constants/commentStatusFilters.ts +++ b/web/src/constants/commentStatusFilters.ts @@ -1,15 +1,20 @@ import { makeHelpersForOptions } from '../lib/makeHelpersForOptions' import { transformCase } from '../lib/strings' +import { commentStatusById } from './commentStatus' + +import type BadgeSmall from '../components/BadgeSmall.astro' import type { Prisma } from '@prisma/client' +import type { ComponentProps } from 'astro/types' type CommentStatusFilterInfo = { value: T label: string + color: ComponentProps['color'] + icon: string whereClause: Prisma.CommentWhereInput - styles: { + classNames: { filter: string - badge: string } } @@ -24,9 +29,10 @@ export const { value, label: value ? transformCase(value, 'title') : String(value), whereClause: {}, - styles: { + color: 'gray', + icon: 'ri:question-line', + classNames: { filter: 'border-zinc-700 transition-colors hover:border-green-500/50', - badge: '', }, }), [ @@ -34,84 +40,92 @@ export const { label: 'All', value: 'all', whereClause: {}, - styles: { + color: 'gray', + icon: 'ri:question-line', + classNames: { filter: 'border-green-500 bg-green-500/20 text-green-400', - badge: '', }, }, { - label: 'Pending', value: 'pending', + label: commentStatusById.PENDING.label, + color: commentStatusById.PENDING.color, + icon: commentStatusById.PENDING.icon, whereClause: { OR: [{ status: 'PENDING' }, { status: 'HUMAN_PENDING' }], }, - styles: { + classNames: { filter: 'border-blue-500 bg-blue-500/20 text-blue-400', - badge: 'rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500', }, }, { - label: 'Human Pending', value: 'human-pending', + label: commentStatusById.HUMAN_PENDING.label, + color: commentStatusById.HUMAN_PENDING.color, + icon: commentStatusById.HUMAN_PENDING.icon, whereClause: { status: 'HUMAN_PENDING' }, - styles: { + classNames: { filter: 'border-blue-500 bg-blue-500/20 text-blue-400', - badge: 'rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500', }, }, { - label: 'Rejected', value: 'rejected', + label: commentStatusById.REJECTED.label, + color: commentStatusById.REJECTED.color, + icon: commentStatusById.REJECTED.icon, whereClause: { status: 'REJECTED', }, - styles: { + classNames: { filter: 'border-red-500 bg-red-500/20 text-red-400', - badge: 'rounded-sm bg-red-500/20 px-2 py-0.5 text-[12px] font-medium text-red-500', }, }, { label: 'Suspicious', value: 'suspicious', + color: 'red', + icon: 'ri:close-circle-fill', whereClause: { suspicious: true, }, - styles: { + classNames: { filter: 'border-red-500 bg-red-500/20 text-red-400', - badge: 'rounded-sm bg-red-500/20 px-2 py-0.5 text-[12px] font-medium text-red-500', }, }, { - label: 'Verified', value: 'verified', + label: commentStatusById.VERIFIED.label, + color: commentStatusById.VERIFIED.color, + icon: commentStatusById.VERIFIED.icon, whereClause: { status: 'VERIFIED', }, - styles: { + classNames: { filter: 'border-blue-500 bg-blue-500/20 text-blue-400', - badge: 'rounded-sm bg-blue-500/20 px-2 py-0.5 text-[12px] font-medium text-blue-500', }, }, { - label: 'Approved', value: 'approved', + label: commentStatusById.APPROVED.label, + color: commentStatusById.APPROVED.color, + icon: commentStatusById.APPROVED.icon, whereClause: { status: 'APPROVED', }, - styles: { + classNames: { filter: 'border-green-500 bg-green-500/20 text-green-400', - badge: 'rounded-sm bg-green-500/20 px-2 py-0.5 text-[12px] font-medium text-green-500', }, }, { label: 'Needs Review', value: 'needs-review', + color: 'yellow', + icon: 'ri:question-line', whereClause: { requiresAdminReview: true, }, - styles: { + classNames: { filter: 'border-yellow-500 bg-yellow-500/20 text-yellow-400', - badge: 'rounded-sm bg-yellow-500/20 px-2 py-0.5 text-[12px] font-medium text-yellow-500', }, }, ] as const satisfies CommentStatusFilterInfo[] diff --git a/web/src/constants/karmaTransactionActions.ts b/web/src/constants/karmaTransactionActions.ts index 39d9b57..37cf233 100644 --- a/web/src/constants/karmaTransactionActions.ts +++ b/web/src/constants/karmaTransactionActions.ts @@ -78,8 +78,8 @@ export const { }, { value: 'MANUAL_ADJUSTMENT', - slug: 'manual-adjustment', - label: 'Manual adjustment', + slug: 'gift', + label: 'Gift', icon: 'ri:gift-line', }, ] as const satisfies KarmaTransactionActionInfo[] diff --git a/web/src/lib/tailwind.ts b/web/src/lib/tailwind.ts new file mode 100644 index 0000000..eb175f0 --- /dev/null +++ b/web/src/lib/tailwind.ts @@ -0,0 +1,8 @@ +import { parseIntWithFallback } from './numbers' + +const TW_SIZING_TO_PX_RATIO = 4 + +export function getSizePxFromTailwindClasses(className: string, fallbackPxSize: number) { + const twSizing = /(?: |^|\n)(?:(?:size-(\d+))|(?:w-(\d+))|(?:h-(\d+)))(?: |$|\n)/.exec(className)?.[1] + return parseIntWithFallback(twSizing, fallbackPxSize / TW_SIZING_TO_PX_RATIO) * TW_SIZING_TO_PX_RATIO +} diff --git a/web/src/pages/account/edit.astro b/web/src/pages/account/edit.astro index b7fe181..9a10c26 100644 --- a/web/src/pages/account/edit.astro +++ b/web/src/pages/account/edit.astro @@ -22,7 +22,7 @@ const inputErrors = isInputError(result?.error) ? result.error.fields : {} --- { select: { name: true, displayName: true, + picture: true, }, }, comment: { @@ -158,11 +160,11 @@ if (!user) return Astro.rewrite('/404') --- -

{user.name}

- {user.displayName &&

{user.displayName}

} +

+ {user.displayName ?? user.name} +

+ {user.displayName &&

{user.name}

} { (user.admin || user.verified || user.verifier) && (
@@ -429,7 +433,7 @@ if (!user) return Astro.rewrite('/404')
  • - - {suggestion.user.name} - +
    diff --git a/web/src/pages/admin/services/[slug]/edit.astro b/web/src/pages/admin/services/[slug]/edit.astro index 29727e2..7f8ef0e 100644 --- a/web/src/pages/admin/services/[slug]/edit.astro +++ b/web/src/pages/admin/services/[slug]/edit.astro @@ -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( {service.verificationRequests.map((request) => ( - {request.user.displayName ?? request.user.name} + + + {new Date(request.createdAt).toLocaleString()} ))} diff --git a/web/src/pages/admin/users/[username].astro b/web/src/pages/admin/users/[username].astro index 76aab45..5f843a4 100644 --- a/web/src/pages/admin/users/[username].astro +++ b/web/src/pages/admin/users/[username].astro @@ -116,7 +116,7 @@ if (!user) return Astro.rewrite('/404') --- diff --git a/web/src/pages/admin/users/index.astro b/web/src/pages/admin/users/index.astro index efcd8c7..7a7d16b 100644 --- a/web/src/pages/admin/users/index.astro +++ b/web/src/pages/admin/users/index.astro @@ -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' : ''}`} > -
    {user.name}
    + {user.internalNotes.length > 0 && ( ) => { ) .join('\n\n')} > - + {user.internalNotes.length} internal {pluralize('note', user.internalNotes.length)} )} diff --git a/web/src/pages/service/[slug].astro b/web/src/pages/service/[slug].astro index 2a13542..4472d76 100644 --- a/web/src/pages/service/[slug].astro +++ b/web/src/pages/service/[slug].astro @@ -467,17 +467,6 @@ const ogImageTemplateData = { } - { - service.verificationSteps.some((step) => step.status === VerificationStepStatus.FAILED) && ( -
    - - - This service has failed one or more verification steps. Review the verification details carefully. - -
    - ) - } -
    { !!service.imageUrl && ( diff --git a/web/src/pages/u/[username].astro b/web/src/pages/u/[username].astro index 1f5dbea..aaee6f5 100644 --- a/web/src/pages/u/[username].astro +++ b/web/src/pages/u/[username].astro @@ -10,6 +10,7 @@ import InputTextArea from '../../components/InputTextArea.astro' import MyPicture from '../../components/MyPicture.astro' import TimeFormatted from '../../components/TimeFormatted.astro' import Tooltip from '../../components/Tooltip.astro' +import UserBadge from '../../components/UserBadge.astro' import { getKarmaTransactionActionInfo } from '../../constants/karmaTransactionActions' import { karmaUnlocks } from '../../constants/karmaUnlocks' import { SUPPORT_EMAIL } from '../../constants/project' @@ -64,6 +65,7 @@ const user = await Astro.locals.banners.try('user', async () => { select: { name: true, displayName: true, + picture: true, }, }, comment: { @@ -175,8 +177,8 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id ---

    - {user.name} + {user.displayName ?? user.name} {isCurrentUser && (You)}

    - {user.displayName &&

    {user.displayName}

    } + {user.displayName &&

    {user.name}

    }