diff --git a/app/api/auth/github/route.js b/app/api/auth/github/route.js index 3fc0106..7030bf3 100644 --- a/app/api/auth/github/route.js +++ b/app/api/auth/github/route.js @@ -3,7 +3,7 @@ import { buildGitHubAuthorizationUrl } from '../../../../lib/github-oauth.js'; const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID; -export async function GET() { +export async function GET(request) { if (!GITHUB_CLIENT_ID) { return NextResponse.json( { error: 'GitHub OAuth is not configured' }, @@ -11,5 +11,6 @@ export async function GET() { ); } - return NextResponse.redirect(buildGitHubAuthorizationUrl(GITHUB_CLIENT_ID)); + const login = new URL(request.url).searchParams.get('login') || ''; + return NextResponse.redirect(buildGitHubAuthorizationUrl(GITHUB_CLIENT_ID, login)); } diff --git a/app/page.jsx b/app/page.jsx index c9e6310..2a3075b 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -19,6 +19,7 @@ import { enrichWithCollaborators } from '../lib/collaboration.js'; import dynamic from 'next/dynamic'; const Globe = dynamic(() => import('../components/Globe.jsx'), { ssr: false }); +const PENDING_CLAIM_KEY = 'devglobe-pending-claim'; export default function Home() { const [developers, setDevelopers] = useState([]); @@ -39,6 +40,7 @@ export default function Home() { const [cardRequest, setCardRequest] = useState(0); const [cardContext, setCardContext] = useState(null); const [showAddMe, setShowAddMe] = useState(false); + const [verificationUsername, setVerificationUsername] = useState(''); const [showClaimPending, setShowClaimPending] = useState(false); const [showAiProfile, setShowAiProfile] = useState(false); const [showIntroductions, setShowIntroductions] = useState(false); @@ -118,7 +120,7 @@ export default function Home() { setCardRequest(0); setShowClaimPending(true); setSidebarOpen(false); - return; + return { ok: false, ...result }; } setClaimStatus('claimed'); setClaimedLogins(prev => new Set(prev).add(user.login)); @@ -152,15 +154,34 @@ export default function Home() { setCardContext('claim'); setCardRequest(request => request + 1); setSidebarOpen(false); + return { ok: true, ...result }; } else { const data = await res.json(); console.error('Claim failed:', data.error); + return { ok: false, ...data }; } } catch (err) { console.error('Claim error:', err); + return { ok: false, error: err.message }; } }, [user, developers]); + useEffect(() => { + if (!user) return; + let pendingUsername = ''; + try { pendingUsername = localStorage.getItem(PENDING_CLAIM_KEY) || ''; } catch { return; } + if (!pendingUsername) return; + + if (pendingUsername.toLowerCase() !== user.login.toLowerCase()) { + setVerificationUsername(pendingUsername); + setShowAddMe(true); + return; + } + + localStorage.removeItem(PENDING_CLAIM_KEY); + void handleClaim(); + }, [user, handleClaim]); + const handleToggleTheme = useCallback(() => { setTheme(prev => { const next = prev === 'dark' ? 'light' : 'dark'; @@ -369,6 +390,7 @@ export default function Home() { }, []); const handleAddMe = useCallback(() => { + setVerificationUsername(''); setShowAddMe(true); }, []); @@ -490,7 +512,14 @@ export default function Home() { {compareDevs.length === 2 && ( )} - {showAddMe && } + {showAddMe && ( + + )} {showClaimPending && setShowClaimPending(false)} />} {showAiProfile && ( { inputRef.current?.focus(); @@ -56,6 +58,7 @@ export default function AddMeModal({ onClose }) { setError(data.error || 'Something went wrong. Please try again.'); return; } + setUsername(data.username || clean); setStatus('success'); } catch (err) { setStatus('error'); @@ -63,6 +66,30 @@ export default function AddMeModal({ onClose }) { } }; + const handleVerify = async () => { + try { + localStorage.setItem(PENDING_CLAIM_KEY, normalizedUsername); + } catch { /* Continue with the current session when storage is unavailable. */ } + + if (!user) { + window.location.assign(`/api/auth/github?login=${encodeURIComponent(normalizedUsername)}`); + return; + } + + if (!identityMatches) return; + + setStatus('verifying'); + setError(''); + const result = await onVerify(); + if (!result?.ok) { + setStatus('success'); + setError('We could not verify your profile. Please try again.'); + return; + } + try { localStorage.removeItem(PENDING_CLAIM_KEY); } catch { /* Ignore storage cleanup failures. */ } + onClose(); + }; + return (
e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Add me to DevGlobe"> @@ -70,16 +97,46 @@ export default function AddMeModal({ onClose }) { {status === 'success' ? (
-
🎉
+

Nomination received

-

{SUCCESS_MESSAGE}

- + {user && !identityMatches ? ( +
+ Sign in as @{normalizedUsername} + You are currently signed in as @{user.login}. We will keep this nomination pending rather than verify the wrong account. +
+ ) : ( +

+ Verify ownership of @{normalizedUsername} with GitHub to publish your profile now. Otherwise, it will remain in the review queue. +

+ )} + {error &&
{error}
} + {(!user || identityMatches) && ( + + )} + +
+ ) : status === 'verifying' ? ( +
+ ) : ( <>

Add me to DevGlobe

- Submit your GitHub username to be featured on the globe. We'll review and add you within a week. + Submit your profile, then verify with GitHub to publish instantly. Unverified nominations stay in the review queue.