From 701a44e53b820c6f30000e36e01528559bc15baf Mon Sep 17 00:00:00 2001 From: nishupr Date: Sat, 4 Jul 2026 10:47:24 +0530 Subject: [PATCH] feat: add copy-to-clipboard button to resource prompt cards (#751) --- src/components/home/ResourcesTabs.tsx | 162 +++++++++++++++++--------- 1 file changed, 104 insertions(+), 58 deletions(-) diff --git a/src/components/home/ResourcesTabs.tsx b/src/components/home/ResourcesTabs.tsx index 5797c811..1910939a 100644 --- a/src/components/home/ResourcesTabs.tsx +++ b/src/components/home/ResourcesTabs.tsx @@ -21,6 +21,8 @@ import { Layout, Rocket, Database, + Check, + Copy, } from 'lucide-react'; import { PremiumCard } from '../ui/PremiumCard'; import styles from './Resources.module.css'; @@ -354,59 +356,90 @@ const categoryConfig: Record = { }; export default function ResourcesTabs() { - // Custom Auth Fallback - const { user } = useAuth() || { user: { uid: 'test-user-id' } }; - - // 5 Main Sections - Reordered: Roadmaps First - const mainSections = [ - { id: 'roadmaps', label: 'Roadmaps', icon: }, - { id: 'ai-prompts', label: 'AI Prompts', icon: }, - { id: 'internships', label: 'Internships', icon: }, - { id: 'learning', label: 'Learning', icon: }, - { id: 'practice', label: 'Practice', icon: }, - ]; - - const router = useRouter(); - const pathname = usePathname(); - const searchParams = useSearchParams(); - - const [activeMainTab, setActiveMainTab] = useState('roadmaps'); - const ITEMS_PER_PAGE = 2; - const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); - - useEffect(() => { - setVisibleCount(ITEMS_PER_PAGE); - }, [activeMainTab]); - const [activeSubTab, setActiveSubTab] = useState(aiPromptsCategories[0]); - const [isInternshipModalOpen, setIsInternshipModalOpen] = useState(false); - const [isRoadmapModalOpen, setIsRoadmapModalOpen] = useState(false); - const [isMindsetModalOpen, setIsMindsetModalOpen] = useState(false); - const [activeRoadmap, setActiveRoadmap] = useState(null); - - // Progress State mapping roadmap IDs to completion percentages - const [progressData, setProgressData] = useState>({}); - const [isLoading, setIsLoading] = useState(false); - const observerRef = useRef(null); - - useEffect(() => { - // Sync states on mount from URL parameters - const tab = searchParams.get('tab'); - if (tab) { - setActiveMainTab(tab); - } - const subtab = searchParams.get('subtab'); - if (subtab) { - setActiveSubTab(subtab); - } - }, []); + // Custom Auth Fallback + const { user } = useAuth() || { user: { uid: 'test-user-id' } }; + + // 5 Main Sections - Reordered: Roadmaps First + const mainSections = [ + { id: 'roadmaps', label: 'Roadmaps', icon: }, + { id: 'ai-prompts', label: 'AI Prompts', icon: }, + { id: 'internships', label: 'Internships', icon: }, + { id: 'learning', label: 'Learning', icon: }, + { id: 'practice', label: 'Practice', icon: }, + ]; + + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const [activeMainTab, setActiveMainTab] = useState('roadmaps'); + const ITEMS_PER_PAGE = 2; + const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); - const updateQueryParam = (key: string, value: string) => { - const params = new URLSearchParams(searchParams.toString()); - params.set(key, value); - router.push(`${pathname}?${params.toString()}`, { scroll: false }); + useEffect(() => { + setVisibleCount(ITEMS_PER_PAGE); + }, [activeMainTab]); + const [activeSubTab, setActiveSubTab] = useState(aiPromptsCategories[0]); + const [isInternshipModalOpen, setIsInternshipModalOpen] = useState(false); + const [isRoadmapModalOpen, setIsRoadmapModalOpen] = useState(false); + const [isMindsetModalOpen, setIsMindsetModalOpen] = useState(false); + const [activeRoadmap, setActiveRoadmap] = useState(null); + + // Progress State mapping roadmap IDs to completion percentages + const [progressData, setProgressData] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + const observerRef = useRef(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( + null + ); + const copyResetTimerRef = useRef(null); + + useEffect(() => { + return () => { + if (copyResetTimerRef.current !== null) { + clearTimeout(copyResetTimerRef.current); + } }; + }, []); - useEffect(() => { + 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 + const tab = searchParams.get('tab'); + if (tab) { + setActiveMainTab(tab); + } + const subtab = searchParams.get('subtab'); + if (subtab) { + setActiveSubTab(subtab); + } + }, []); + + const updateQueryParam = (key: string, value: string) => { + const params = new URLSearchParams(searchParams.toString()); + params.set(key, value); + router.push(`${pathname}?${params.toString()}`, { scroll: false }); + }; + + useEffect(() => { if (activeMainTab !== 'roadmaps') return; const observer = new IntersectionObserver( @@ -627,16 +660,29 @@ export default function ResourcesTabs() {
-
- - Copy - -
+ {prompt.example}
-