2026-02-02 16:24:13 +02:00
|
|
|
|
"use client";
|
|
|
|
|
|
|
2026-04-03 17:28:12 +05:30
|
|
|
|
import { Check, Copy, Dot, ExternalLink, MessageSquare, Trash2 } from "lucide-react";
|
2026-02-10 02:15:13 +05:30
|
|
|
|
import { useCallback, useRef, useState } from "react";
|
2026-03-17 18:31:58 +05:30
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
2026-02-09 19:00:57 +05:30
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
2026-02-02 16:24:13 +02:00
|
|
|
|
import { Button } from "@/components/ui/button";
|
2026-02-09 19:00:57 +05:30
|
|
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
|
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
2026-02-02 16:24:13 +02:00
|
|
|
|
import type { PublicChatSnapshotDetail } from "@/contracts/types/chat-threads.types";
|
2026-03-16 21:10:46 +05:30
|
|
|
|
import { useMediaQuery } from "@/hooks/use-media-query";
|
2026-02-02 16:24:13 +02:00
|
|
|
|
|
2026-02-09 19:00:57 +05:30
|
|
|
|
function getInitials(name: string): string {
|
|
|
|
|
|
const parts = name.trim().split(/\s+/);
|
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
|
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
return name.slice(0, 2).toUpperCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 16:24:13 +02:00
|
|
|
|
interface PublicChatSnapshotRowProps {
|
|
|
|
|
|
snapshot: PublicChatSnapshotDetail;
|
|
|
|
|
|
canDelete: boolean;
|
|
|
|
|
|
onCopy: (snapshot: PublicChatSnapshotDetail) => void;
|
|
|
|
|
|
onDelete: (snapshot: PublicChatSnapshotDetail) => void;
|
|
|
|
|
|
isDeleting?: boolean;
|
2026-02-09 19:00:57 +05:30
|
|
|
|
memberMap: Map<string, { name: string; email?: string; avatarUrl?: string }>;
|
2026-02-02 16:24:13 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function PublicChatSnapshotRow({
|
|
|
|
|
|
snapshot,
|
|
|
|
|
|
canDelete,
|
|
|
|
|
|
onCopy,
|
|
|
|
|
|
onDelete,
|
|
|
|
|
|
isDeleting = false,
|
2026-02-09 19:00:57 +05:30
|
|
|
|
memberMap,
|
2026-02-02 16:24:13 +02:00
|
|
|
|
}: PublicChatSnapshotRowProps) {
|
2026-02-10 02:15:13 +05:30
|
|
|
|
const [copied, setCopied] = useState(false);
|
2026-02-21 22:55:54 +05:30
|
|
|
|
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
|
2026-03-16 21:10:46 +05:30
|
|
|
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
2026-02-10 02:15:13 +05:30
|
|
|
|
|
|
|
|
|
|
const handleCopyClick = useCallback(() => {
|
|
|
|
|
|
onCopy(snapshot);
|
|
|
|
|
|
setCopied(true);
|
2026-02-21 22:55:54 +05:30
|
|
|
|
if (copyTimeoutRef.current) clearTimeout(copyTimeoutRef.current);
|
2026-02-10 02:15:13 +05:30
|
|
|
|
copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
|
|
|
|
|
}, [onCopy, snapshot]);
|
|
|
|
|
|
|
2026-02-02 16:24:13 +02:00
|
|
|
|
const formattedDate = new Date(snapshot.created_at).toLocaleDateString(undefined, {
|
|
|
|
|
|
year: "numeric",
|
|
|
|
|
|
month: "short",
|
|
|
|
|
|
day: "numeric",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-02-10 19:06:21 +05:30
|
|
|
|
const member = snapshot.created_by_user_id ? memberMap.get(snapshot.created_by_user_id) : null;
|
2026-02-09 19:00:57 +05:30
|
|
|
|
|
2026-02-02 16:24:13 +02:00
|
|
|
|
return (
|
2026-02-09 19:00:57 +05:30
|
|
|
|
<Card className="group relative overflow-hidden transition-all duration-200 border-border/60 hover:shadow-md h-full">
|
|
|
|
|
|
<CardContent className="p-4 flex flex-col gap-3 h-full">
|
|
|
|
|
|
{/* Header: Title + Actions */}
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<div className="min-w-0 pr-16 sm:pr-0 sm:group-hover:pr-16">
|
|
|
|
|
|
<h4
|
|
|
|
|
|
className="text-sm font-semibold tracking-tight truncate"
|
|
|
|
|
|
title={snapshot.thread_title}
|
|
|
|
|
|
>
|
|
|
|
|
|
{snapshot.thread_title}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-0.5 shrink-0 sm:hidden sm:group-hover:flex absolute right-0 top-0">
|
2026-02-09 19:00:57 +05:30
|
|
|
|
<TooltipProvider>
|
2026-03-16 21:10:46 +05:30
|
|
|
|
<Tooltip open={isDesktop ? undefined : false}>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
2026-03-17 04:40:46 +05:30
|
|
|
|
asChild
|
|
|
|
|
|
className="h-7 w-7 text-muted-foreground hover:text-foreground"
|
2026-02-09 19:00:57 +05:30
|
|
|
|
>
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<a href={snapshot.public_url} target="_blank" rel="noopener noreferrer">
|
|
|
|
|
|
<ExternalLink className="h-3 w-3" />
|
|
|
|
|
|
</a>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
</Button>
|
|
|
|
|
|
</TooltipTrigger>
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<TooltipContent>Open link</TooltipContent>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</TooltipProvider>
|
2026-03-17 04:40:46 +05:30
|
|
|
|
{canDelete && (
|
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
|
<Tooltip open={isDesktop ? undefined : false}>
|
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={() => onDelete(snapshot)}
|
|
|
|
|
|
disabled={isDeleting}
|
|
|
|
|
|
className="h-7 w-7 text-muted-foreground hover:text-destructive"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trash2 className="h-3 w-3" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
|
<TooltipContent>Delete</TooltipContent>
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-02-02 16:24:13 +02:00
|
|
|
|
</div>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
|
2026-02-10 19:06:21 +05:30
|
|
|
|
{/* Message count badge */}
|
|
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
|
|
|
|
<Badge
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
className="text-[10px] px-1.5 py-0.5 border-muted-foreground/20 text-muted-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
<MessageSquare className="h-2.5 w-2.5 mr-1" />
|
|
|
|
|
|
{snapshot.message_count} messages
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
</div>
|
2026-02-10 02:15:13 +05:30
|
|
|
|
|
2026-02-10 19:06:21 +05:30
|
|
|
|
{/* Public URL – selectable fallback for manual copy */}
|
|
|
|
|
|
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 px-2.5 py-1.5">
|
2026-02-21 22:55:54 +05:30
|
|
|
|
<div className="min-w-0 flex-1 overflow-x-auto scrollbar-hide">
|
|
|
|
|
|
<p
|
|
|
|
|
|
className="text-[10px] font-mono text-muted-foreground whitespace-nowrap select-all cursor-text"
|
|
|
|
|
|
title={snapshot.public_url}
|
|
|
|
|
|
>
|
|
|
|
|
|
{snapshot.public_url}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<TooltipProvider>
|
|
|
|
|
|
<Tooltip open={isDesktop ? undefined : false}>
|
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
onClick={handleCopyClick}
|
|
|
|
|
|
className="h-6 w-6 shrink-0 text-muted-foreground hover:text-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
{copied ? (
|
|
|
|
|
|
<Check className="h-3 w-3 text-green-500" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Copy className="h-3 w-3" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
|
<TooltipContent>{copied ? "Copied!" : "Copy link"}</TooltipContent>
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</TooltipProvider>
|
2026-02-10 19:06:21 +05:30
|
|
|
|
</div>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
|
2026-02-10 19:06:21 +05:30
|
|
|
|
{/* Footer: Date + Creator */}
|
2026-02-09 19:00:57 +05:30
|
|
|
|
<div className="flex items-center gap-2 pt-2 border-t border-border/40 mt-auto">
|
2026-02-10 19:06:21 +05:30
|
|
|
|
<span className="text-[11px] text-muted-foreground/60">{formattedDate}</span>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
{member && (
|
|
|
|
|
|
<>
|
2026-04-03 17:28:12 +05:30
|
|
|
|
<Dot className="h-4 w-4 text-muted-foreground/30" />
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<TooltipProvider>
|
|
|
|
|
|
<Tooltip open={isDesktop ? undefined : false}>
|
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
|
<div className="flex items-center gap-1.5 cursor-default">
|
2026-03-17 18:31:58 +05:30
|
|
|
|
<Avatar className="size-4.5 shrink-0">
|
|
|
|
|
|
{member.avatarUrl && (
|
|
|
|
|
|
<AvatarImage src={member.avatarUrl} alt={member.name} />
|
|
|
|
|
|
)}
|
|
|
|
|
|
<AvatarFallback className="text-[9px]">
|
|
|
|
|
|
{getInitials(member.name)}
|
|
|
|
|
|
</AvatarFallback>
|
|
|
|
|
|
</Avatar>
|
2026-03-17 04:40:46 +05:30
|
|
|
|
<span className="text-[11px] text-muted-foreground/60 truncate max-w-[120px]">
|
|
|
|
|
|
{member.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
|
<TooltipContent side="bottom">{member.email || member.name}</TooltipContent>
|
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
</TooltipProvider>
|
2026-02-09 19:00:57 +05:30
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
2026-02-02 16:24:13 +02:00
|
|
|
|
);
|
|
|
|
|
|
}
|