Skip to content

Commit 161c7f5

Browse files
committed
Added optional GitHub login, only display name is required input to fill the placeholders
1 parent aca0d1e commit 161c7f5

12 files changed

Lines changed: 889 additions & 259 deletions

File tree

src/components/GitHubAuthModal/index.js

Lines changed: 0 additions & 90 deletions
This file was deleted.
Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState } from 'react';
22
import Link from '@docusaurus/Link';
33
import styles from './styles.module.css';
4-
import GitHubAuthModal from '../GitHubAuthModal';
4+
import LoginModal from '../LoginModal';
55
import { useTracker } from '../../context/TrackerContext';
66

77
const GitHubMark = () => (
@@ -16,40 +16,61 @@ const GitHubMark = () => (
1616
</svg>
1717
);
1818

19+
// Generic person glyph for local-mode sessions, which have no avatar of
20+
// their own.
21+
const PersonIcon = () => (
22+
<svg
23+
viewBox="0 0 16 16"
24+
width="16"
25+
height="16"
26+
className={styles.icon}
27+
aria-hidden="true"
28+
>
29+
<path
30+
fill="currentColor"
31+
d="M8 8a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Zm0 1.5c-3.03 0-6 1.5-6 3.75V15h12v-1.75c0-2.25-2.97-3.75-6-3.75Z"
32+
/>
33+
</svg>
34+
);
35+
1936
export default function GitHubConnectButton() {
2037
const [isModalOpen, setIsModalOpen] = useState(false);
2138
const { progress } = useTracker();
39+
const session = progress?.session;
2240

23-
// Determine if the user is already "logged in" by checking for a token
24-
const isConnected = !!progress?.token;
25-
26-
if (isConnected) {
41+
if (session?.authMode === 'github') {
2742
// Once connected, this doubles as the entry point to /profile —
2843
// it's the only nav element that links there.
2944
return (
3045
<Link to="/profile" className={`${styles.connectBtn} ${styles.connected}`}>
31-
{progress.user?.avatar_url ? (
32-
<img src={progress.user.avatar_url} alt="" className={styles.avatar} />
46+
{session.avatarUrl ? (
47+
<img src={session.avatarUrl} alt="" className={styles.avatar} />
3348
) : (
3449
<GitHubMark />
3550
)}
36-
<span>{progress.user?.login ?? 'GitHub Connected'}</span>
51+
<span>{session.username}</span>
52+
</Link>
53+
);
54+
}
55+
56+
if (session?.authMode === 'local') {
57+
return (
58+
<Link to="/profile" className={`${styles.connectBtn} ${styles.connected} ${styles.localConnected}`}>
59+
<PersonIcon />
60+
<span>{session.username}</span>
3761
</Link>
3862
);
3963
}
4064

4165
return (
4266
<>
4367
<button className={styles.connectBtn} onClick={() => setIsModalOpen(true)}>
44-
<GitHubMark />
45-
<span>Connect GitHub</span>
68+
<PersonIcon />
69+
<span>Log In</span>
4670
</button>
4771

4872
{/* The modal is rendered at the root level here, waiting to pop up */}
49-
<GitHubAuthModal
50-
isOpen={isModalOpen}
51-
onClose={() => setIsModalOpen(false)}
52-
/>
73+
<LoginModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
5374
</>
5475
);
5576
}

src/components/GitHubConnectButton/styles.module.css

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
background-color: #131518;
2424
}
2525

26-
/* Styled state when token exists in LocalStorage */
26+
/* Styled state when a GitHub session exists */
2727
.connectBtn.connected {
2828
background-color: #2da44e;
2929
border-color: rgba(27, 31, 36, 0.15);
@@ -33,6 +33,16 @@
3333
background-color: #2c974b;
3434
}
3535

36+
/* Local-mode sessions aren't GitHub-branded, so they get a neutral
37+
accent color instead of GitHub green. */
38+
.connectBtn.localConnected {
39+
background-color: var(--ifm-color-primary, #0969da);
40+
}
41+
42+
.connectBtn.localConnected:hover {
43+
background-color: var(--ifm-color-primary-dark, #085ec4);
44+
}
45+
3646
.icon {
3747
flex-shrink: 0;
3848
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import styles from './styles.module.css';
3+
4+
const CLASSIC_TOKEN_URL =
5+
'https://github.com/settings/tokens/new?scopes=gist&description=The%20Python%20Ledger%20Sync';
6+
7+
/**
8+
* Renders the "paste a GitHub PAT" flow: instructions, a token input,
9+
* and a status area. Doesn't know or care whether it's logging in fresh
10+
* or upgrading an existing local account — the caller supplies
11+
* `onSubmit(pat)`, which should verify the token and return the GitHub
12+
* user object (or throw).
13+
*/
14+
export default function GitHubPatForm({ onSubmit, onSuccess, submitLabel = 'Validate and Connect' }) {
15+
const [token, setToken] = useState('');
16+
const [status, setStatus] = useState({ state: 'idle', message: '' });
17+
const isMountedRef = useRef(true);
18+
19+
useEffect(
20+
() => () => {
21+
isMountedRef.current = false;
22+
},
23+
[]
24+
);
25+
26+
const handleSubmit = async () => {
27+
const trimmed = token.trim();
28+
if (!trimmed) {
29+
setStatus({ state: 'error', message: 'Paste a token first.' });
30+
return;
31+
}
32+
setStatus({ state: 'validating', message: 'Validating token with GitHub...' });
33+
try {
34+
const user = await onSubmit(trimmed);
35+
// The caller (e.g. the profile page's upgrade section) may stop
36+
// rendering this form the instant the session flips to
37+
// 'github' — in that case there's nothing left to update.
38+
if (!isMountedRef.current) return;
39+
setStatus({ state: 'success', message: `Token verified. Connected as ${user.login}!` });
40+
onSuccess?.(user);
41+
} catch (error) {
42+
if (!isMountedRef.current) return;
43+
setStatus({ state: 'error', message: error.message || 'Invalid token. Please check and try again.' });
44+
}
45+
};
46+
47+
return (
48+
<div className={styles.wrapper}>
49+
<section className={styles.securityNotice}>
50+
<h3>🔐 Security & Privacy Notice</h3>
51+
<p>
52+
This site uses <strong>client-side tracking</strong>. Your Personal Access Token (PAT) is stored{' '}
53+
<em>only</em> in your browser's local storage and is sent directly to the GitHub API. It is{' '}
54+
<strong>never</strong> transmitted to our servers.
55+
</p>
56+
</section>
57+
58+
<section className={styles.instructions}>
59+
<h3>How to Generate Your Token</h3>
60+
<ol>
61+
<li>
62+
Go to{' '}
63+
<a href={CLASSIC_TOKEN_URL} target="_blank" rel="noopener noreferrer">
64+
GitHub's classic token page
65+
</a>{' '}
66+
(pre-filled with the <code>gist</code> scope).
67+
</li>
68+
<li>
69+
Click <strong>&quot;Generate token&quot;</strong>.
70+
</li>
71+
<li>Copy the generated token and paste it below.</li>
72+
</ol>
73+
<p className={styles.scopeNote}>
74+
Cloud sync backs your progress up to a private Gist, which needs a <strong>classic</strong> token
75+
with the <code>gist</code> scope — fine-grained tokens don't support Gists yet. Any token works
76+
for just verifying your identity, but only a classic <code>gist</code>-scoped token can back up
77+
your progress.
78+
</p>
79+
</section>
80+
81+
<section className={styles.tokenInputArea}>
82+
<label htmlFor="patInput">Paste your PAT (ghp_... or github_pat_...) here:</label>
83+
<input
84+
type="password"
85+
id="patInput"
86+
value={token}
87+
onChange={(e) => setToken(e.target.value)}
88+
placeholder="ghp_aBC123..."
89+
/>
90+
</section>
91+
92+
{status.message && (
93+
<div className={`${styles.statusMessage} ${styles[status.state]}`}>{status.message}</div>
94+
)}
95+
96+
<button
97+
type="button"
98+
className={styles.primaryButton}
99+
onClick={handleSubmit}
100+
disabled={status.state === 'validating'}
101+
>
102+
{status.state === 'validating' ? 'Verifying...' : submitLabel}
103+
</button>
104+
</div>
105+
);
106+
}

0 commit comments

Comments
 (0)