import { useEffect, useState } from "react"; import { fetchViewer, getStoredToken, storeToken, TOKEN_EVENT, } from "@/lib/githubClient"; import { FEEDBACK_REPO } from "@/lib/config"; // Resolves to the connected GitHub user, or null. Re-checks whenever the // stored token changes (connect/disconnect in any component). export function useGitHubUser(): { login: string } | null { const [user, setUser] = useState<{ login: string } | null>(null); useEffect(() => { let cancelled = false; const sync = () => { if (!getStoredToken()) { setUser(null); return; } fetchViewer() .then((u) => !cancelled && setUser(u)) .catch(() => !cancelled && setUser(null)); }; sync(); window.addEventListener(TOKEN_EVENT, sync); return () => { cancelled = true; window.removeEventListener(TOKEN_EVENT, sync); }; }, []); return user; } export function GitHubConnect() { const user = useGitHubUser(); const [open, setOpen] = useState(false); const [value, setValue] = useState(""); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); if (user) { return ( @{user.login} ); } const save = async () => { setSaving(true); setError(null); storeToken(value.trim()); try { await fetchViewer(); setOpen(false); setValue(""); } catch (e) { storeToken(null); setError(e instanceof Error ? e.message : "Token check failed"); } finally { setSaving(false); } }; return (
{open && (

The token is stored only in this browser and sent only to api.github.com.

Generate a fine-grained PAT ↗
Repository: {FEEDBACK_REPO}
Permission: Issues — Read & write
setValue(e.target.value)} onKeyDown={(e) => e.key === "Enter" && value.trim() && save()} placeholder="github_pat_…" className="mt-3 w-full rounded-lg border border-hairline bg-page px-2 py-1.5 font-mono text-xs" /> {error && (

{error}

)}
)}
); }