copy shot names
Deploy / deploy (push) Successful in 2m28s

This commit is contained in:
twotalesanimation
2026-06-19 12:00:04 +02:00
parent 4c64842be6
commit d04e0252f1
+101 -25
View File
@@ -14,6 +14,8 @@ import {
Package, Package,
RotateCcw, RotateCcw,
ArrowUpDown, ArrowUpDown,
Copy,
Check,
} from 'lucide-react'; } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Montserrat } from 'next/font/google'; import { Montserrat } from 'next/font/google';
@@ -156,6 +158,15 @@ export default function ClientPortalPage({
setStatusFilter((prev) => (prev === filter ? null : filter)); setStatusFilter((prev) => (prev === filter ? null : filter));
}; };
const [copiedId, setCopiedId] = useState<string | null>(null);
const copyToClipboard = (text: string, id: string) => {
navigator.clipboard.writeText(text).then(() => {
setCopiedId(id);
setTimeout(() => setCopiedId(null), 1500);
});
};
useEffect(() => { useEffect(() => {
params.then(({ token: t }) => { params.then(({ token: t }) => {
setToken(t); setToken(t);
@@ -458,14 +469,27 @@ export default function ClientPortalPage({
/> />
</div> </div>
)} )}
<p className="font-mono text-xs text-zinc-500"> <div className="flex items-center gap-1.5 group/itemlabel">
{item.label} <p className="font-mono text-xs text-zinc-500">
{item.description && ( {item.label}
<span className="font-sans text-zinc-600 ml-2"> {item.description && (
{item.description} <span className="font-sans text-zinc-600 ml-2">
</span> {item.description}
)} </span>
</p> )}
</p>
<button
onClick={() => copyToClipboard(item.label, `label-${task.id}`)}
className="shrink-0 p-0.5 rounded opacity-0 group-hover/itemlabel:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all"
title="Copy name"
>
{copiedId === `label-${task.id}` ? (
<Check className="h-3 w-3 text-emerald-400" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</div>
{ver?.createdAt && ( {ver?.createdAt && (
<p className="ml-auto text-xs text-zinc-600"> <p className="ml-auto text-xs text-zinc-600">
{formatDate(ver.createdAt)} {formatDate(ver.createdAt)}
@@ -481,9 +505,22 @@ export default function ClientPortalPage({
)} )}
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white"> <div className="flex items-center gap-1.5">
{task.title} <p className="text-sm font-medium text-white">
</p> {task.title}
</p>
<button
onClick={(e) => { e.preventDefault(); e.stopPropagation(); copyToClipboard(task.title, `task-${task.id}`); }}
className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all"
title="Copy task name"
>
{copiedId === `task-${task.id}` ? (
<Check className="h-3 w-3 text-emerald-400" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</div>
<p className="text-xs text-zinc-500"> <p className="text-xs text-zinc-500">
{TASK_TYPE_LABELS[task.type] ?? task.type} {TASK_TYPE_LABELS[task.type] ?? task.type}
</p> </p>
@@ -597,14 +634,27 @@ export default function ClientPortalPage({
/> />
</div> </div>
)} )}
<p className="font-mono text-sm font-semibold text-zinc-300"> <div className="flex items-center gap-1.5 group/shotcode">
{shot.shotCode} <p className="font-mono text-sm font-semibold text-zinc-300">
{shot.description && ( {shot.shotCode}
<span className="font-sans font-normal text-zinc-500 ml-2"> {shot.description && (
{shot.description} <span className="font-sans font-normal text-zinc-500 ml-2">
</span> {shot.description}
)} </span>
</p> )}
</p>
<button
onClick={() => copyToClipboard(shot.shotCode, `shot-${shot.id}`)}
className="shrink-0 p-0.5 rounded opacity-0 group-hover/shotcode:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all"
title="Copy shot code"
>
{copiedId === `shot-${shot.id}` ? (
<Check className="h-3 w-3 text-emerald-400" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</div>
</div> </div>
<div className="space-y-1.5 pl-3 border-l border-zinc-800"> <div className="space-y-1.5 pl-3 border-l border-zinc-800">
{shot.tasks.map((task) => { {shot.tasks.map((task) => {
@@ -626,9 +676,22 @@ export default function ClientPortalPage({
)} )}
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm font-medium text-white"> <div className="flex items-center gap-1.5">
{task.title} <p className="text-sm font-medium text-white">
</p> {task.title}
</p>
<button
onClick={(e) => { e.preventDefault(); e.stopPropagation(); copyToClipboard(task.title, `task-${task.id}`); }}
className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all"
title="Copy task name"
>
{copiedId === `task-${task.id}` ? (
<Check className="h-3 w-3 text-emerald-400" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</div>
<p className="text-xs text-zinc-500"> <p className="text-xs text-zinc-500">
{TASK_TYPE_LABELS[task.type] ?? task.type} {TASK_TYPE_LABELS[task.type] ?? task.type}
</p> </p>
@@ -719,9 +782,22 @@ export default function ClientPortalPage({
</p> </p>
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-sm text-zinc-300 truncate"> <div className="flex items-center gap-1.5">
{task.title} <p className="text-sm text-zinc-300 truncate">
</p> {task.title}
</p>
<button
onClick={(e) => { e.preventDefault(); e.stopPropagation(); copyToClipboard(task.title, `task-${task.id}`); }}
className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-zinc-600 hover:text-zinc-300 transition-all"
title="Copy task name"
>
{copiedId === `task-${task.id}` ? (
<Check className="h-3 w-3 text-emerald-400" />
) : (
<Copy className="h-3 w-3" />
)}
</button>
</div>
{ver?.notes && ( {ver?.notes && (
<p className="text-xs text-zinc-500 truncate italic mt-0.5"> <p className="text-xs text-zinc-500 truncate italic mt-0.5">
&ldquo;{ver.notes}&rdquo; &ldquo;{ver.notes}&rdquo;