feat: enhance public chat snapshot row with copy functionality and URL display, improving user interaction

This commit is contained in:
Anish Sarkar 2026-02-10 02:15:13 +05:30
parent 302ee5ad2f
commit efe8755132
3 changed files with 69 additions and 37 deletions

View file

@ -1,7 +1,8 @@
"use client"; "use client";
import { Copy, ExternalLink, MessageSquare, Trash2 } from "lucide-react"; import { Check, Copy, ExternalLink, MessageSquare, Trash2 } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { useCallback, useRef, useState } from "react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
@ -33,6 +34,16 @@ export function PublicChatSnapshotRow({
isDeleting = false, isDeleting = false,
memberMap, memberMap,
}: PublicChatSnapshotRowProps) { }: PublicChatSnapshotRowProps) {
const [copied, setCopied] = useState(false);
const copyTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const handleCopyClick = useCallback(() => {
onCopy(snapshot);
setCopied(true);
clearTimeout(copyTimeoutRef.current);
copyTimeoutRef.current = setTimeout(() => setCopied(false), 2000);
}, [onCopy, snapshot]);
const formattedDate = new Date(snapshot.created_at).toLocaleDateString(undefined, { const formattedDate = new Date(snapshot.created_at).toLocaleDateString(undefined, {
year: "numeric", year: "numeric",
month: "short", month: "short",
@ -63,26 +74,17 @@ export function PublicChatSnapshotRow({
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => onCopy(snapshot)} asChild
className="h-7 w-7 text-muted-foreground hover:text-foreground" className="h-7 w-7 text-muted-foreground hover:text-foreground"
> >
<Copy className="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>Copy link</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<a <a
href={snapshot.public_url} href={snapshot.public_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
> >
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</a> </a>
</Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent>Open link</TooltipContent> <TooltipContent>Open link</TooltipContent>
</Tooltip> </Tooltip>
@ -119,6 +121,35 @@ export function PublicChatSnapshotRow({
</Badge> </Badge>
</div> </div>
{/* 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">
<p
className="min-w-0 flex-1 text-[10px] font-mono text-muted-foreground break-all select-all cursor-text"
title={snapshot.public_url}
>
{snapshot.public_url}
</p>
<TooltipProvider>
<Tooltip>
<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>
</div>
{/* Footer: Date + Creator */} {/* Footer: Date + Creator */}
<div className="flex items-center gap-2 pt-2 border-t border-border/40 mt-auto"> <div className="flex items-center gap-2 pt-2 border-t border-border/40 mt-auto">
<span className="text-[11px] text-muted-foreground/60"> <span className="text-[11px] text-muted-foreground/60">

View file

@ -26,7 +26,7 @@ export function PublicChatSnapshotsList({
} }
return ( return (
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3"> <div className="grid gap-3 grid-cols-1 sm:grid-cols-2">
{snapshots.map((snapshot) => ( {snapshots.map((snapshot) => (
<PublicChatSnapshotRow <PublicChatSnapshotRow
key={snapshot.id} key={snapshot.id}

View file

@ -62,7 +62,6 @@ export function PublicChatSnapshotsManager({
const handleCopy = useCallback((snapshot: PublicChatSnapshotDetail) => { const handleCopy = useCallback((snapshot: PublicChatSnapshotDetail) => {
const publicUrl = `${window.location.origin}/public/${snapshot.share_token}`; const publicUrl = `${window.location.origin}/public/${snapshot.share_token}`;
navigator.clipboard.writeText(publicUrl); navigator.clipboard.writeText(publicUrl);
toast.success("Link copied to clipboard");
}, []); }, []);
const handleDelete = useCallback( const handleDelete = useCallback(
@ -90,7 +89,7 @@ export function PublicChatSnapshotsManager({
<Skeleton className="h-12 w-full rounded-lg" /> <Skeleton className="h-12 w-full rounded-lg" />
{/* Cards grid skeleton */} {/* Cards grid skeleton */}
<div className="grid gap-3 grid-cols-1 sm:grid-cols-2 xl:grid-cols-3"> <div className="grid gap-3 grid-cols-1 sm:grid-cols-2">
{["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( {["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => (
<Card key={key} className="border-border/60"> <Card key={key} className="border-border/60">
<CardContent className="p-4 flex flex-col gap-3"> <CardContent className="p-4 flex flex-col gap-3">
@ -102,6 +101,8 @@ export function PublicChatSnapshotsManager({
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<Skeleton className="h-5 w-24 rounded-full" /> <Skeleton className="h-5 w-24 rounded-full" />
</div> </div>
{/* URL skeleton */}
<Skeleton className="h-3 w-full rounded" />
{/* Footer: Date + Creator */} {/* Footer: Date + Creator */}
<div className="flex items-center gap-2 pt-2 border-t border-border/40"> <div className="flex items-center gap-2 pt-2 border-t border-border/40">
<Skeleton className="h-3 w-20" /> <Skeleton className="h-3 w-20" />