Skip to content

Commit 77b173a

Browse files
committed
feat: GitHub PAT + account section in Settings panel — sign in, view profile, sign out
1 parent c1a53ff commit 77b173a

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

components/settings-panel.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { MobileConnect } from './mobile-connect'
77
import { SessionPresence } from './session-presence'
88
import { CaffeinateToggle } from './caffeinate-toggle'
99
import { useGateway } from '@/context/gateway-context'
10+
import { useGitHubAuth } from '@/context/github-auth-context'
11+
import { fetchAuthenticatedUser, type GitHubUser } from '@/lib/github-api'
1012
import { THEME_PRESETS, useTheme, type ThemeMode, type ThemePreset } from '@/context/theme-context'
1113

1214
type SettingsTab = 'connect' | 'general'
@@ -46,6 +48,18 @@ export function SettingsPanel({
4648
const [tab, setTab] = useState<SettingsTab>((initialTab as SettingsTab) || 'connect')
4749
const { status, gatewayUrl } = useGateway()
4850
const { themeId, setThemeId, mode, setMode } = useTheme()
51+
const { token: ghToken, authenticated: ghAuth, setManualToken, clearToken } = useGitHubAuth()
52+
const [ghUser, setGhUser] = useState<GitHubUser | null>(null)
53+
const [patInput, setPatInput] = useState('')
54+
const [showPatField, setShowPatField] = useState(false)
55+
56+
// Fetch GitHub user on token change
57+
const ghTokenRef = useRef(ghToken)
58+
if (ghTokenRef.current !== ghToken) {
59+
ghTokenRef.current = ghToken
60+
if (ghToken) fetchAuthenticatedUser().then(u => setGhUser(u))
61+
else setGhUser(null)
62+
}
4963

5064
const themeGroups = useMemo(() => groupThemes(), [])
5165
const isMobile = typeof window !== 'undefined' && window.innerWidth <= 768
@@ -327,6 +341,110 @@ export function SettingsPanel({
327341
<CaffeinateToggle />
328342
</section>
329343

344+
{/* GitHub Account */}
345+
<section className="rounded-[24px] border border-[var(--glass-border)] bg-[color-mix(in_srgb,var(--bg-elevated)_88%,transparent)] p-4 shadow-[var(--shadow-sm)]">
346+
<div className="mb-3 flex items-center gap-2">
347+
<span className="flex h-8 w-8 items-center justify-center rounded-2xl bg-[color-mix(in_srgb,var(--brand)_12%,transparent)] text-[var(--brand)]">
348+
<Icon icon="lucide:github" width={14} />
349+
</span>
350+
<div>
351+
<h3 className="text-sm font-medium text-[var(--text-primary)]">GitHub</h3>
352+
<p className="text-[11px] text-[var(--text-secondary)]">
353+
Connect your account to browse repos.
354+
</p>
355+
</div>
356+
</div>
357+
358+
{ghAuth && ghUser ? (
359+
<div className="space-y-3">
360+
<div className="flex items-center gap-2.5">
361+
{ghUser.avatar_url && (
362+
<img src={ghUser.avatar_url} alt="" className="w-8 h-8 rounded-full border border-[var(--border)]" />
363+
)}
364+
<div>
365+
<p className="text-[13px] font-medium text-[var(--text-primary)]">{ghUser.name ?? ghUser.login}</p>
366+
<p className="text-[11px] text-[var(--text-secondary)]">@{ghUser.login}</p>
367+
</div>
368+
</div>
369+
<button
370+
onClick={() => { clearToken(); setGhUser(null) }}
371+
className="text-[12px] text-[var(--color-deletions)] hover:underline cursor-pointer"
372+
>
373+
Sign out
374+
</button>
375+
</div>
376+
) : ghAuth ? (
377+
<div className="flex items-center gap-2">
378+
<span className="w-2 h-2 rounded-full bg-green-500" />
379+
<span className="text-[12px] text-[var(--text-secondary)]">Connected</span>
380+
<button
381+
onClick={() => { clearToken(); setGhUser(null) }}
382+
className="ml-auto text-[12px] text-[var(--color-deletions)] hover:underline cursor-pointer"
383+
>
384+
Sign out
385+
</button>
386+
</div>
387+
) : (
388+
<div className="space-y-3">
389+
{!showPatField ? (
390+
<button
391+
onClick={() => setShowPatField(true)}
392+
className="inline-flex items-center gap-2 rounded-full border border-[var(--border)] bg-[color-mix(in_srgb,var(--bg)_92%,transparent)] px-3 py-2 text-xs font-medium text-[var(--text-primary)] transition hover:border-[var(--border-hover)] hover:bg-[color-mix(in_srgb,var(--text-primary)_5%,transparent)] cursor-pointer"
393+
>
394+
<Icon icon="lucide:key-round" width={14} />
395+
Add Personal Access Token
396+
</button>
397+
) : (
398+
<div className="space-y-2">
399+
<div className="flex items-center gap-1.5">
400+
<div className="flex-1 relative">
401+
<Icon icon="lucide:key-round" width={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[var(--text-disabled)]" />
402+
<input
403+
type="password"
404+
value={patInput}
405+
onChange={(e) => setPatInput(e.target.value)}
406+
onKeyDown={(e) => {
407+
if (e.key === 'Enter' && patInput.trim()) {
408+
setManualToken(patInput.trim())
409+
setPatInput('')
410+
setShowPatField(false)
411+
}
412+
if (e.key === 'Escape') { setShowPatField(false); setPatInput('') }
413+
}}
414+
placeholder="ghp_xxxx..."
415+
autoCapitalize="off"
416+
autoCorrect="off"
417+
spellCheck={false}
418+
className="w-full pl-8 pr-3 py-2 rounded-lg border border-[var(--border)] bg-[color-mix(in_srgb,var(--bg)_80%,transparent)] text-[13px] text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--brand)] transition-colors"
419+
/>
420+
</div>
421+
<button
422+
onClick={() => {
423+
if (patInput.trim()) {
424+
setManualToken(patInput.trim())
425+
setPatInput('')
426+
setShowPatField(false)
427+
}
428+
}}
429+
disabled={!patInput.trim()}
430+
className="shrink-0 px-3 py-2 rounded-lg text-[12px] font-medium transition-all cursor-pointer disabled:opacity-40 disabled:cursor-default bg-[var(--brand)] text-[var(--brand-contrast,#fff)]"
431+
>
432+
Save
433+
</button>
434+
</div>
435+
<p className="text-[10px] text-[var(--text-disabled)] leading-relaxed">
436+
Create at{' '}
437+
<a href="https://github.com/settings/tokens" target="_blank" rel="noopener noreferrer" className="text-[var(--brand)] underline">
438+
github.com/settings/tokens
439+
</a>
440+
{' '}with <span className="font-mono">repo</span> scope.
441+
</p>
442+
</div>
443+
)}
444+
</div>
445+
)}
446+
</section>
447+
330448
<section className="rounded-[24px] border border-[var(--glass-border)] bg-[color-mix(in_srgb,var(--bg-elevated)_88%,transparent)] p-4 shadow-[var(--shadow-sm)]">
331449
<div className="flex items-center gap-3">
332450
<span className="flex h-10 w-10 items-center justify-center rounded-[18px] bg-[color-mix(in_srgb,var(--brand)_12%,transparent)] text-[var(--brand)]">

0 commit comments

Comments
 (0)