Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions src/components/home/ResourcesTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
Layout,
Rocket,
Database,
Check,
Copy,
} from 'lucide-react';
import { PremiumCard } from '../ui/PremiumCard';
import styles from './Resources.module.css';
Expand Down Expand Up @@ -387,6 +389,37 @@ export default function ResourcesTabs() {
const [progressData, setProgressData] = useState<Record<string, number>>({});
const [isLoading, setIsLoading] = useState(false);
const observerRef = useRef<HTMLDivElement>(null);
// Tracks which prompt's example text was just copied, so we can show a
// temporary "Copied!" confirmation on that specific card.
const [copiedPromptTitle, setCopiedPromptTitle] = useState<string | null>(
null
);
const copyResetTimerRef = useRef<number | null>(null);

useEffect(() => {
return () => {
if (copyResetTimerRef.current !== null) {
clearTimeout(copyResetTimerRef.current);
}
};
}, []);

const handleCopyPrompt = async (title: string, example: string) => {
try {
await navigator.clipboard.writeText(example);
setCopiedPromptTitle(title);

if (copyResetTimerRef.current !== null) {
clearTimeout(copyResetTimerRef.current);
}

copyResetTimerRef.current = window.setTimeout(() => {
setCopiedPromptTitle(null);
}, 2000);
} catch {
// Clipboard access can fail in unsupported or insecure contexts.
}
};

useEffect(() => {
// Sync states on mount from URL parameters
Expand Down Expand Up @@ -627,11 +660,26 @@ export default function ResourcesTabs() {

<div className="mt-auto pt-4 border-t border-white/5">
<div className="bg-black/40 rounded-lg p-3 text-xs text-slate-300 font-mono border border-white/5 relative group/code">
<div className="absolute right-2 top-2 opacity-0 group-hover/code:opacity-100 transition-opacity">
<span className="text-[10px] bg-primary/20 text-primary px-2 py-1 rounded">
Copy
</span>
</div>
<button
type="button"
aria-label="Copy prompt to clipboard"
onClick={() =>
handleCopyPrompt(prompt.title, prompt.example)
}
className="absolute right-2 top-2 z-10 flex items-center gap-1 text-[10px] bg-primary/20 text-primary px-2 py-1 rounded opacity-0 group-hover/code:opacity-100 group-focus-within/code:opacity-100 focus-visible:opacity-100 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/70"
>
{copiedPromptTitle === prompt.title ? (
<>
<Check size={12} />
Copied!
</>
) : (
<>
<Copy size={12} />
Copy
</>
)}
</button>
{prompt.example}
</div>
<button className="w-full mt-4 py-2 bg-white/5 hover:bg-white/10 text-white text-sm font-medium rounded-lg transition-colors border border-white/10 hover:border-white/20 flex items-center justify-center gap-2 group/btn">
Expand Down
Loading