Skip to content

Commit e9271ef

Browse files
amide-initclaude
andcommitted
fix: fork button no longer closes preview modal; adds repo-name copy step
Two bugs fixed: 1. Clicking fork (or anything) inside the preview modal was closing it. NavSearch's mousedown listener fired because the portal is outside containerRef. Fix: skip the close when previewOpen is true. Escape now dismisses the preview first, then closes the search panel on a second press. 2. GitHub's web fork form cannot be pre-filled via URL parameters. Replace all raw fork links with a ForkButton component that shows a two-step flow: first reveals the suggested repo name ({login}.github.io) with a one-click copy button, then a 'Go fork →' button opens the GitHub fork page in a new tab. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 35a3d5e commit e9271ef

File tree

2 files changed

+101
-37
lines changed

2 files changed

+101
-37
lines changed

src/components/NavSearch.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,27 @@ export function NavSearch({ variant = 'default' }: NavSearchProps) {
5252
const inputRef = useRef<HTMLInputElement>(null)
5353
const containerRef = useRef<HTMLDivElement>(null)
5454

55-
// Close when clicking outside
55+
// Close dropdown when clicking outside — but NOT when the preview modal is open
56+
// (the modal is a portal outside containerRef; clicking inside it must not close the search)
5657
useEffect(() => {
5758
if (!open) return
5859
const onMouseDown = (e: MouseEvent) => {
60+
if (previewOpen) return
5961
if (!containerRef.current?.contains(e.target as Node)) close()
6062
}
6163
document.addEventListener('mousedown', onMouseDown)
6264
return () => document.removeEventListener('mousedown', onMouseDown)
63-
}, [open])
65+
}, [open, previewOpen])
6466

65-
// Close on Escape
67+
// Escape: dismiss preview first, then the search panel on a second press
6668
useEffect(() => {
67-
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close() }
69+
const onKey = (e: KeyboardEvent) => {
70+
if (e.key !== 'Escape') return
71+
if (previewOpen) { setPreviewOpen(false) } else { close() }
72+
}
6873
window.addEventListener('keydown', onKey)
6974
return () => window.removeEventListener('keydown', onKey)
70-
}, [])
75+
}, [previewOpen])
7176

7277
// Auto-focus input when expanded
7378
useEffect(() => {

src/components/PortfolioPreviewModal.tsx

Lines changed: 91 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useEffect } from 'react'
1+
import { useEffect, useState } from 'react'
22
import { createPortal } from 'react-dom'
3-
import { X, GitFork } from 'lucide-react'
3+
import { X, GitFork, Copy, Check, ExternalLink } from 'lucide-react'
44
import HeroSection from '../templates/threejs/HeroSection'
55
import GitHubSection from '../templates/threejs/GitHubSection'
66
import StatsSection from '../templates/threejs/StatsSection'
@@ -166,9 +166,84 @@ function buildStats(user: PreviewUser, allRepos: PreviewRepo[]) {
166166
}
167167
}
168168

169-
// ─────────────────────────────────────────────────────────────────────────────
169+
// ── Fork button — copies repo name then opens GitHub fork page ────────────────
170+
171+
const FORK_BASE = 'https://github.com/amide-init/gitfolio/fork'
172+
173+
/**
174+
* Two-step fork flow:
175+
* 1. Show a small panel with the suggested repo name + copy button.
176+
* 2. "Go fork" button opens the GitHub fork page in a new tab.
177+
*
178+
* GitHub's web fork form cannot be pre-filled via URL params, so we give
179+
* the user the name ready to paste.
180+
*/
181+
function ForkButton({ login, className }: { login: string; className?: string }) {
182+
const [step, setStep] = useState<'idle' | 'confirm'>('idle')
183+
const [copied, setCopied] = useState(false)
184+
const repoName = `${login}.github.io`
185+
186+
const copy = async () => {
187+
await navigator.clipboard.writeText(repoName).catch(() => {})
188+
setCopied(true)
189+
setTimeout(() => setCopied(false), 2000)
190+
}
170191

171-
const FORK_URL = 'https://github.com/amide-init/gitfolio/fork'
192+
const goFork = () => {
193+
window.open(FORK_BASE, '_blank', 'noreferrer')
194+
}
195+
196+
if (step === 'confirm') {
197+
return (
198+
<div className="inline-flex flex-col items-stretch gap-2 rounded-xl border border-indigo-500/30 bg-[#0d1527] p-3 text-left shadow-[0_0_20px_rgba(99,102,241,0.15)]">
199+
<p className="text-xs text-zinc-400">
200+
After forking, set the repository name to:
201+
</p>
202+
{/* Repo name + copy */}
203+
<div className="flex items-center gap-2 rounded-md border border-zinc-700 bg-zinc-900 px-3 py-2">
204+
<code className="flex-1 text-sm font-mono font-semibold text-cyan-300">{repoName}</code>
205+
<button
206+
type="button"
207+
onClick={copy}
208+
className="flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-zinc-400 hover:text-zinc-100 transition"
209+
>
210+
{copied ? <Check className="h-3.5 w-3.5 text-green-400" /> : <Copy className="h-3.5 w-3.5" />}
211+
{copied ? 'Copied!' : 'Copy'}
212+
</button>
213+
</div>
214+
{/* Actions */}
215+
<div className="flex gap-2">
216+
<button
217+
type="button"
218+
onClick={() => setStep('idle')}
219+
className="flex-1 rounded-md border border-zinc-700 py-1.5 text-xs text-zinc-400 hover:bg-zinc-800 transition"
220+
>
221+
Back
222+
</button>
223+
<button
224+
type="button"
225+
onClick={goFork}
226+
className="group relative flex-1 overflow-hidden rounded-md bg-indigo-600 py-1.5 text-xs font-semibold text-white hover:bg-indigo-500 transition flex items-center justify-center gap-1.5"
227+
>
228+
<ExternalLink className="h-3.5 w-3.5" /> Go fork →
229+
</button>
230+
</div>
231+
</div>
232+
)
233+
}
234+
235+
return (
236+
<button
237+
type="button"
238+
onClick={() => setStep('confirm')}
239+
className={className}
240+
>
241+
<GitFork className="h-3.5 w-3.5" /> Fork &amp; make yours
242+
</button>
243+
)
244+
}
245+
246+
// ─────────────────────────────────────────────────────────────────────────────
172247

173248
type Props = {
174249
user: PreviewUser
@@ -185,12 +260,8 @@ export function PortfolioPreviewModal({ user, repos: _, allRepos, onClose }: Pro
185260
return () => { document.body.style.overflow = prev }
186261
}, [])
187262

188-
// Close on Escape
189-
useEffect(() => {
190-
const h = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
191-
window.addEventListener('keydown', h)
192-
return () => window.removeEventListener('keydown', h)
193-
}, [onClose])
263+
// Escape is handled by NavSearch (it dismisses preview first, then closes search)
264+
// No duplicate listener here.
194265

195266
const hero = buildHero(user, allRepos)
196267
const snapshot = buildSnapshot(user, allRepos)
@@ -216,14 +287,10 @@ export function PortfolioPreviewModal({ user, repos: _, allRepos, onClose }: Pro
216287
</span>
217288
</div>
218289
<div className="flex items-center gap-2">
219-
<a
220-
href={FORK_URL}
221-
target="_blank"
222-
rel="noreferrer"
290+
<ForkButton
291+
login={user.login}
223292
className="hidden sm:inline-flex items-center gap-1.5 rounded-md border border-indigo-500/40 bg-indigo-500/10 px-3 py-1.5 text-xs font-semibold text-indigo-300 transition hover:bg-indigo-500/20"
224-
>
225-
<GitFork className="h-3.5 w-3.5" /> Fork &amp; make yours
226-
</a>
293+
/>
227294
<button
228295
type="button"
229296
onClick={onClose}
@@ -263,22 +330,14 @@ export function PortfolioPreviewModal({ user, repos: _, allRepos, onClose }: Pro
263330
</span>
264331
</h2>
265332
<p className="mt-2 text-sm text-zinc-500">
266-
Fork the repo, rename it to{' '}
267-
<code className="rounded bg-zinc-800 px-1.5 py-0.5 text-zinc-300">
268-
{'<username>.github.io'}
269-
</code>
270-
, and GitHub Actions auto-builds it with your data.
333+
Fork the repo — GitHub Actions will auto-build your portfolio with your GitHub data.
271334
</p>
272-
<a
273-
href={FORK_URL}
274-
target="_blank"
275-
rel="noreferrer"
276-
className="group relative mt-6 inline-flex items-center gap-2 overflow-hidden rounded-xl bg-gradient-to-r from-blue-600 to-cyan-500 px-6 py-3 text-sm font-bold text-white shadow-[0_0_30px_rgba(59,130,246,0.35)] transition hover:shadow-[0_0_40px_rgba(59,130,246,0.55)]"
277-
>
278-
<span className="pointer-events-none absolute inset-0 -translate-x-full skew-x-[-20deg] bg-white/20 transition-transform duration-500 group-hover:translate-x-[150%]" />
279-
<GitFork className="h-4 w-4" />
280-
Fork this template
281-
</a>
335+
<div className="mt-6 flex justify-center">
336+
<ForkButton
337+
login={user.login}
338+
className="group relative inline-flex items-center gap-2 overflow-hidden rounded-xl bg-gradient-to-r from-blue-600 to-cyan-500 px-6 py-3 text-sm font-bold text-white shadow-[0_0_30px_rgba(59,130,246,0.35)] transition hover:shadow-[0_0_40px_rgba(59,130,246,0.55)]"
339+
/>
340+
</div>
282341
</div>
283342
</section>
284343

0 commit comments

Comments
 (0)