From ed6842bc390ae52336beb380c78ebb3dec709f10 Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Tue, 11 Aug 2026 14:47:06 +0200 Subject: [PATCH] feat(admin): redesign Posts panel as a 3-column media grid (Gitea #7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list view made post content hard to scan — thumbnails rendered at inconsistent sizes and captions were easy to miss. Switches to a responsive grid (3 cols on desktop) with a fixed-size thumbnail per card, a same-size placeholder for posts without media, a caption below each card, and a play-icon overlay + Mux thumbnail for video posts (listPosts now also selects videoPost.muxPlaybackId). --- src/app/p/[secret]/posts/page.tsx | 122 +++++++++++++++++------------- src/trpc/routers/admin.ts | 1 + 2 files changed, 72 insertions(+), 51 deletions(-) diff --git a/src/app/p/[secret]/posts/page.tsx b/src/app/p/[secret]/posts/page.tsx index e6ad5ca..13e28dc 100644 --- a/src/app/p/[secret]/posts/page.tsx +++ b/src/app/p/[secret]/posts/page.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useTRPC } from "@/trpc/client"; -import { Trash2, MessageSquare, Loader2, ChevronDown } from "lucide-react"; +import { Trash2, MessageSquare, Loader2, ChevronDown, Play, ImageOff } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; @@ -82,58 +82,78 @@ export default function AdminPostsPage() { ) : ( -
- {posts.map((post) => { - const thumb = post.images[0]; - return ( -
- {thumb ? ( - - ) : ( -
- )} -
-

- {post.caption ?? No caption} -

-

- {post.pet.name} · {post.reactionCount} 🐾 · {post.commentCount} 💬 - {post.reportCount > 0 && ( - - ⚑ {post.reportCount} report{post.reportCount !== 1 ? "s" : ""} - +

+
+ {posts.map((post) => { + const thumb = post.images[0]; + const isVideo = post.type === "VIDEO"; + const muxPlaybackId = post.videoPost?.muxPlaybackId; + const mediaUrl = thumb + ? getMediaUrl(thumb.storageKey) + : isVideo && muxPlaybackId + ? `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=480&time=0` + : null; + + return ( +
+
+ {mediaUrl ? ( + + ) : ( +
+
)} -

+ {isVideo && ( +
+
+ )} +
+ +
+

+ {post.caption ?? No caption} +

+

+ {post.pet.name} · {post.reactionCount} 🐾 · {post.commentCount} 💬 + {post.reportCount > 0 && ( + + ⚑ {post.reportCount} report{post.reportCount !== 1 ? "s" : ""} + + )} +

+
+ + +
+
-
- - -
-
- ); - })} + ); + })} +
{hasNextPage && (
diff --git a/src/trpc/routers/admin.ts b/src/trpc/routers/admin.ts index 5c83551..ac7b5ee 100644 --- a/src/trpc/routers/admin.ts +++ b/src/trpc/routers/admin.ts @@ -166,6 +166,7 @@ export const adminRouter = router({ include: { pet: { select: { id: true, name: true, avatarKey: true, ownerId: true } }, images: { orderBy: { position: "asc" }, take: 1 }, + videoPost: { select: { muxPlaybackId: true } }, }, orderBy: { createdAt: "desc" }, take: input.limit + 1,