feat: real user avatars in reviews, conditional product link

This commit is contained in:
Kirill
2026-05-21 21:10:49 +05:00
parent 7e7bade80c
commit 57da755ea1
4 changed files with 57 additions and 25 deletions
+12 -2
View File
@@ -26,8 +26,15 @@ export type PublicReviewFeedItem = {
imageUrl: string | null imageUrl: string | null
createdAt: string createdAt: string
authorDisplay: string authorDisplay: string
productId: string authorAvatar?: string | null
productTitle: string authorAvatarType?: string | null
authorAvatarStyle?: string | null
product: {
id: string
title: string
published: boolean
slug: string
}
} }
export type PublicReviewsLatestResponse = { export type PublicReviewsLatestResponse = {
@@ -48,6 +55,9 @@ export type PublicProductReviewItem = {
imageUrl: string | null imageUrl: string | null
createdAt: string createdAt: string
authorDisplay: string authorDisplay: string
authorAvatar?: string | null
authorAvatarType?: string | null
authorAvatarStyle?: string | null
} }
export type PublicProductReviewsResponse = { export type PublicProductReviewsResponse = {
@@ -19,7 +19,13 @@ function ReviewItem({ rv }: { rv: PublicProductReviewItem }) {
<Paper variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}> <Paper variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
<Stack spacing={0.75}> <Stack spacing={0.75}>
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}> <Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
<UserAvatar userId={rv.authorDisplay} avatarUrl={null} avatarType={null} avatarStyle={null} size={32} /> <UserAvatar
userId={rv.authorDisplay}
avatarUrl={rv.authorAvatar}
avatarType={rv.authorAvatarType}
avatarStyle={rv.authorAvatarStyle}
size={32}
/>
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
<Typography sx={{ fontWeight: 700 }}>{rv.authorDisplay}</Typography> <Typography sx={{ fontWeight: 700 }}>{rv.authorDisplay}</Typography>
</Box> </Box>
@@ -103,9 +103,9 @@ export function ReviewsBlock() {
<Stack direction="row" spacing={1.5} sx={{ minWidth: { sm: 200 }, alignItems: 'center' }}> <Stack direction="row" spacing={1.5} sx={{ minWidth: { sm: 200 }, alignItems: 'center' }}>
<UserAvatar <UserAvatar
userId={r.authorDisplay} userId={r.authorDisplay}
avatarUrl={null} avatarUrl={r.authorAvatar}
avatarType={null} avatarType={r.authorAvatarType}
avatarStyle={null} avatarStyle={r.authorAvatarStyle}
size={40} size={40}
/> />
<Box> <Box>
@@ -122,20 +122,26 @@ export function ReviewsBlock() {
{formatReviewDate(r.createdAt)} {formatReviewDate(r.createdAt)}
</Typography> </Typography>
</Stack> </Stack>
<Typography {r.product.published ? (
variant="caption" <Typography
component={RouterLink} variant="caption"
to={`/products/${r.productId}`} component={RouterLink}
sx={{ to={`/products/${r.product.slug || r.product.id}`}
display: 'block', sx={{
mt: 0.25, display: 'block',
color: 'primary.main', mt: 0.25,
textDecoration: 'none', color: 'primary.main',
'&:hover': { textDecoration: 'underline' }, textDecoration: 'none',
}} '&:hover': { textDecoration: 'underline' },
> }}
{r.productTitle} >
</Typography> {r.product.title}
</Typography>
) : (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25 }}>
{r.product.title}
</Typography>
)}
</Box> </Box>
</Stack> </Stack>
+15 -5
View File
@@ -40,8 +40,8 @@ export async function registerPublicReviewRoutes(fastify) {
const rows = await prisma.review.findMany({ const rows = await prisma.review.findMany({
where: { status: 'approved', product: { published: true } }, where: { status: 'approved', product: { published: true } },
include: { include: {
user: { select: { email: true, displayName: true } }, user: { select: { email: true, displayName: true, avatar: true, avatarType: true, avatarStyle: true } },
product: { select: { id: true, title: true } }, product: { select: { id: true, title: true, published: true, slug: true } },
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
take, take,
@@ -54,8 +54,15 @@ export async function registerPublicReviewRoutes(fastify) {
imageUrl: r.imageUrl, imageUrl: r.imageUrl,
createdAt: r.createdAt, createdAt: r.createdAt,
authorDisplay: publicReviewAuthorDisplay(r.user), authorDisplay: publicReviewAuthorDisplay(r.user),
productId: r.productId, authorAvatar: r.user?.avatar ?? null,
productTitle: r.product?.title ?? '', authorAvatarType: r.user?.avatarType ?? null,
authorAvatarStyle: r.user?.avatarStyle ?? null,
product: {
id: r.product?.id ?? r.productId,
title: r.product?.title ?? '',
published: r.product?.published ?? false,
slug: r.product?.slug ?? '',
},
})) }))
return { items } return { items }
@@ -80,7 +87,7 @@ export async function registerPublicReviewRoutes(fastify) {
const total = await prisma.review.count({ where }) const total = await prisma.review.count({ where })
const rawItems = await prisma.review.findMany({ const rawItems = await prisma.review.findMany({
where, where,
include: { user: { select: { email: true, displayName: true } } }, include: { user: { select: { email: true, displayName: true, avatar: true, avatarType: true, avatarStyle: true } } },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize, skip: (page - 1) * pageSize,
take: pageSize, take: pageSize,
@@ -93,6 +100,9 @@ export async function registerPublicReviewRoutes(fastify) {
imageUrl: r.imageUrl, imageUrl: r.imageUrl,
createdAt: r.createdAt, createdAt: r.createdAt,
authorDisplay: publicReviewAuthorDisplay(r.user), authorDisplay: publicReviewAuthorDisplay(r.user),
authorAvatar: r.user?.avatar ?? null,
authorAvatarType: r.user?.avatarType ?? null,
authorAvatarStyle: r.user?.avatarStyle ?? null,
})) }))
return { items, total, page, pageSize } return { items, total, page, pageSize }