Build & Deploy / build-and-push (discord-gateway) (push) Failing after 2m22s
Build & Deploy / build-and-push (backend) (push) Failing after 3m22s
Build & Deploy / build-and-push (proxy) (push) Successful in 1m36s
Build & Deploy / deploy (push) Skipped
- Consolidate all DB schema definitions into packages/shared as single source of truth - Migrate backend from raw SQL to Drizzle ORM across all modules - Extract frontend inline UI into separate component files - Refactor discord-gateway circuitBreaker into conversationState + moderationState - Convert messageStore to Proxy singleton pattern - Add validateBody/validateQuery middleware + Zod schemas for API endpoints - Modernize Docker builds with multi-stage + pnpm deploy - Migrate CI/CD from deployment to image-based pipeline - Remove 60+ unused/dead files (~15K lines) - Update color scheme from sky-blue to teal-cyan - Move DB connection management to @bete/shared/database Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { ImageIcon } from "lucide-react";
|
|
import { Card } from "@/components/ui/card";
|
|
import type { MessageRecord } from "@/lib/types";
|
|
import { extractFirstImage } from "./message-card";
|
|
|
|
export function ImagesGrid({
|
|
images,
|
|
onSelect,
|
|
}: {
|
|
images: MessageRecord[];
|
|
onSelect: (id: string) => void;
|
|
}) {
|
|
if (!images || images.length === 0) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<ImageIcon
|
|
className="size-10 text-muted-foreground/40 mb-3"
|
|
aria-label="No images"
|
|
/>
|
|
<p className="text-sm text-muted-foreground">No images yet.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 animate-fade-in-up">
|
|
{images.map((msg) => {
|
|
const imgUrl = extractFirstImage(msg.metadata);
|
|
return (
|
|
<Card
|
|
key={msg.id}
|
|
className="group relative overflow-hidden cursor-pointer"
|
|
onClick={() => onSelect(msg.id)}
|
|
>
|
|
<div className="aspect-square relative bg-muted">
|
|
{imgUrl ? (
|
|
<img
|
|
src={imgUrl}
|
|
alt={msg.content || "Image"}
|
|
className="absolute inset-0 size-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
/>
|
|
) : (
|
|
<div className="flex items-center justify-center size-full text-muted-foreground text-xs">
|
|
No image
|
|
</div>
|
|
)}
|
|
{msg.content && (
|
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-end p-3">
|
|
<p className="text-xs text-white/90 line-clamp-2">
|
|
{msg.username}: {msg.content}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|