Skip to content

Commit 1d2e745

Browse files
committed
Add PAT option
1 parent a3479b9 commit 1d2e745

3 files changed

Lines changed: 343 additions & 17 deletions

File tree

assets/app.js

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,64 @@ const App = (() => {
55

66
// Configuration
77
const CONFIG = {
8-
CLIENT_ID: 'YOUR_GITHUB_CLIENT_ID',
8+
CLIENT_ID: 'Iv23liYmAKkBpvhHAnQQ',
99
API_BASE: 'https://api.github.com',
1010
STORAGE_KEY: 'github_token',
11+
COOKIE_KEY: 'github_pat',
1112
SEARCH_LIMIT: 100,
13+
OAUTH_REDIRECT_URI: window.location.origin + window.location.pathname,
1214
};
1315

16+
// Cookie Functions
17+
function setCookie(name, value, days) {
18+
const expires = new Date();
19+
expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
20+
document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Strict`;
21+
}
22+
23+
function getCookie(name) {
24+
const nameEQ = name + "=";
25+
const ca = document.cookie.split(';');
26+
for (let i = 0; i < ca.length; i++) {
27+
let c = ca[i];
28+
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
29+
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
30+
}
31+
return null;
32+
}
33+
34+
function deleteCookie(name) {
35+
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
36+
}
37+
38+
function getStoredToken() {
39+
// Check cookie first (for PAT)
40+
const cookieToken = getCookie(CONFIG.COOKIE_KEY);
41+
if (cookieToken) return cookieToken;
42+
43+
// Fall back to localStorage (for OAuth)
44+
return localStorage.getItem(CONFIG.STORAGE_KEY);
45+
}
46+
47+
function storeToken(token, useCookie = false) {
48+
if (useCookie) {
49+
setCookie(CONFIG.COOKIE_KEY, token, 365); // 1 year
50+
} else {
51+
localStorage.setItem(CONFIG.STORAGE_KEY, token);
52+
}
53+
state.accessToken = token;
54+
}
55+
56+
function clearToken() {
57+
localStorage.removeItem(CONFIG.STORAGE_KEY);
58+
deleteCookie(CONFIG.COOKIE_KEY);
59+
state.accessToken = null;
60+
}
61+
1462
// State Management
1563
const state = {
1664
currentUser: null,
17-
accessToken: localStorage.getItem(CONFIG.STORAGE_KEY),
65+
accessToken: getStoredToken(),
1866
organizations: [],
1967
pullRequests: {
2068
incoming: [],
@@ -485,24 +533,76 @@ const App = (() => {
485533
};
486534

487535
// Auth Functions
488-
const initiateLogin = () => {
489-
const token = prompt('Please enter your GitHub Personal Access Token with repo scope:');
490-
if (token) {
491-
localStorage.setItem(CONFIG.STORAGE_KEY, token);
492-
state.accessToken = token;
493-
window.location.reload();
536+
const initiateOAuthLogin = () => {
537+
const authUrl = `https://github.com/login/oauth/authorize?client_id=${CONFIG.CLIENT_ID}&redirect_uri=${encodeURIComponent(CONFIG.OAUTH_REDIRECT_URI)}&scope=repo%20read:org`;
538+
window.location.href = authUrl;
539+
};
540+
541+
const initiatePATLogin = () => {
542+
show($('patModal'));
543+
$('patInput').focus();
544+
};
545+
546+
const closePATModal = () => {
547+
hide($('patModal'));
548+
$('patInput').value = '';
549+
};
550+
551+
const submitPAT = async () => {
552+
const token = $('patInput').value.trim();
553+
if (!token) {
554+
showToast('Please enter a valid token', 'error');
555+
return;
556+
}
557+
558+
// Test the token
559+
try {
560+
const testResponse = await fetch(`${CONFIG.API_BASE}/user`, {
561+
headers: {
562+
'Authorization': `token ${token}`,
563+
'Accept': 'application/vnd.github.v3+json'
564+
}
565+
});
566+
567+
if (testResponse.ok) {
568+
storeToken(token, true); // Store in cookie
569+
closePATModal();
570+
window.location.reload();
571+
} else {
572+
showToast('Invalid token. Please check and try again.', 'error');
573+
}
574+
} catch (error) {
575+
showToast('Failed to validate token. Please try again.', 'error');
494576
}
495577
};
496578

579+
const handleOAuthCallback = async () => {
580+
const urlParams = new URLSearchParams(window.location.search);
581+
const code = urlParams.get('code');
582+
583+
if (code) {
584+
// In a real implementation, you'd exchange this code for a token
585+
// via your backend server. For now, we'll show an error message.
586+
showToast('OAuth authentication requires a backend server. Please use Personal Access Token instead.', 'warning');
587+
// Clean up URL
588+
window.history.replaceState({}, document.title, window.location.pathname);
589+
showLoginPrompt();
590+
}
591+
};
592+
593+
const initiateLogin = () => {
594+
// Legacy function - redirect to PAT login
595+
initiatePATLogin();
596+
};
597+
497598
const handleAuthError = () => {
498-
localStorage.removeItem(CONFIG.STORAGE_KEY);
499-
state.accessToken = null;
599+
clearToken();
500600
showLoginPrompt();
501601
showToast('Authentication failed. Please login again.', 'error');
502602
};
503603

504604
const logout = () => {
505-
localStorage.removeItem(CONFIG.STORAGE_KEY);
605+
clearToken();
506606
window.location.href = window.location.pathname;
507607
};
508608

@@ -587,8 +687,24 @@ const App = (() => {
587687
}
588688
if (loginBtn) loginBtn.addEventListener('click', initiateLogin);
589689

690+
// Add event listener for PAT input Enter key
691+
const patInput = $('patInput');
692+
if (patInput) {
693+
patInput.addEventListener('keypress', (e) => {
694+
if (e.key === 'Enter') {
695+
submitPAT();
696+
}
697+
});
698+
}
699+
590700
document.addEventListener('keydown', handleKeyboardShortcuts);
591701

702+
// Check for OAuth callback
703+
if (urlParams.get('code')) {
704+
handleOAuthCallback();
705+
return;
706+
}
707+
592708
// Check for demo mode
593709
if (demo === 'true') {
594710
initializeDemoMode();
@@ -619,7 +735,11 @@ const App = (() => {
619735
return {
620736
init,
621737
logout,
622-
initiateLogin: () => window.initiateLogin = initiateLogin
738+
initiateLogin: () => window.initiateLogin = initiateLogin,
739+
initiateOAuthLogin,
740+
initiatePATLogin,
741+
closePATModal,
742+
submitPAT
623743
};
624744
})();
625745

assets/styles.css

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,4 +709,158 @@ a {
709709
left: var(--space-4);
710710
right: var(--space-4);
711711
}
712+
}
713+
714+
/* Login Options */
715+
.login-options {
716+
display: flex;
717+
flex-direction: column;
718+
gap: var(--space-3);
719+
margin-top: var(--space-6);
720+
}
721+
722+
.login-options .btn {
723+
display: flex;
724+
align-items: center;
725+
justify-content: center;
726+
gap: var(--space-2);
727+
width: 100%;
728+
}
729+
730+
.login-options svg {
731+
width: 1.25rem;
732+
height: 1.25rem;
733+
}
734+
735+
.btn-secondary {
736+
background: var(--color-surface-tertiary);
737+
color: var(--color-text-primary);
738+
border: 1px solid var(--color-border);
739+
}
740+
741+
.btn-secondary:hover {
742+
background: var(--color-surface-secondary);
743+
transform: translateY(-1px);
744+
box-shadow: var(--shadow-md);
745+
}
746+
747+
.btn-outline {
748+
background: transparent;
749+
color: var(--color-primary);
750+
border: 1px solid var(--color-primary);
751+
}
752+
753+
.btn-outline:hover {
754+
background: var(--color-primary);
755+
color: white;
756+
transform: translateY(-1px);
757+
box-shadow: var(--shadow-md);
758+
}
759+
760+
/* Modal */
761+
.modal {
762+
position: fixed;
763+
top: 0;
764+
left: 0;
765+
right: 0;
766+
bottom: 0;
767+
z-index: 1000;
768+
display: flex;
769+
align-items: center;
770+
justify-content: center;
771+
padding: var(--space-4);
772+
}
773+
774+
.modal-backdrop {
775+
position: absolute;
776+
top: 0;
777+
left: 0;
778+
right: 0;
779+
bottom: 0;
780+
background: rgba(0, 0, 0, 0.5);
781+
backdrop-filter: blur(2px);
782+
}
783+
784+
.modal-content {
785+
position: relative;
786+
background: var(--color-surface-primary);
787+
border-radius: var(--radius-lg);
788+
box-shadow: var(--shadow-lg);
789+
max-width: 32rem;
790+
width: 100%;
791+
max-height: 90vh;
792+
overflow: auto;
793+
}
794+
795+
.modal-header {
796+
display: flex;
797+
align-items: center;
798+
justify-content: space-between;
799+
padding: var(--space-6);
800+
border-bottom: 1px solid var(--color-border);
801+
}
802+
803+
.modal-header h3 {
804+
font-size: var(--text-lg);
805+
margin: 0;
806+
}
807+
808+
.modal-close {
809+
background: none;
810+
border: none;
811+
font-size: 1.5rem;
812+
line-height: 1;
813+
color: var(--color-text-tertiary);
814+
cursor: pointer;
815+
padding: var(--space-2);
816+
margin: calc(var(--space-2) * -1);
817+
border-radius: var(--radius-base);
818+
transition: all var(--transition-base);
819+
}
820+
821+
.modal-close:hover {
822+
background: var(--color-surface-secondary);
823+
color: var(--color-text-primary);
824+
}
825+
826+
.modal-body {
827+
padding: var(--space-6);
828+
}
829+
830+
.modal-body ol {
831+
margin: var(--space-4) 0;
832+
padding-left: var(--space-6);
833+
}
834+
835+
.modal-body li {
836+
margin-bottom: var(--space-2);
837+
}
838+
839+
.modal-body code {
840+
background: var(--color-surface-secondary);
841+
padding: 0.125rem 0.375rem;
842+
border-radius: var(--radius-sm);
843+
font-size: var(--text-sm);
844+
}
845+
846+
.pat-input-group {
847+
display: flex;
848+
gap: var(--space-3);
849+
margin-top: var(--space-6);
850+
}
851+
852+
.pat-input {
853+
flex: 1;
854+
padding: var(--space-3) var(--space-4);
855+
border: 1px solid var(--color-border);
856+
border-radius: var(--radius-base);
857+
background: var(--color-surface-primary);
858+
color: var(--color-text-primary);
859+
font-family: var(--font-mono);
860+
}
861+
862+
.pat-input:focus {
863+
outline: none;
864+
border-color: var(--color-primary);
865+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
712866
}

0 commit comments

Comments
 (0)