mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-09 15:52:40 +02:00
feat: enhance public chat snapshot row with copy functionality and URL display, improving user interaction
This commit is contained in:
parent
302ee5ad2f
commit
efe8755132
3 changed files with 69 additions and 37 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue