@@ -729,6 +729,13 @@ <h1 class="contributors-title">Meet Our <span>Contributors</span></h1>
729729 </ div >
730730 </ div >
731731
732+ <!-- Token Settings Button -->
733+ < div style ="text-align: center; margin: 20px 0; ">
734+ < button id ="tokenSettingsBtn " style ="padding: 10px 20px; background: var(--primary); color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 0.9rem; ">
735+ < i class ="fas fa-key "> </ i > GitHub Token Settings
736+ </ button >
737+ </ div >
738+
732739 <!-- Search Section -->
733740 < div class ="search-section ">
734741 < div class ="search-container ">
@@ -798,6 +805,54 @@ <h4>Connect With Us</h4>
798805 </ div >
799806 </ footer >
800807
808+ <!-- Token Settings Modal -->
809+ < div id ="tokenModal " style ="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 10000; align-items: center; justify-content: center; ">
810+ < div style ="background: white; padding: 30px; border-radius: 12px; max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; ">
811+ < h2 style ="margin-top: 0; color: var(--primary); "> GitHub Token Settings</ h2 >
812+ < p style ="color: var(--text-light); margin-bottom: 20px; ">
813+ Add a GitHub Personal Access Token to increase API rate limits from 60/hour to 5,000/hour.
814+ </ p >
815+
816+ < div style ="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; ">
817+ < h3 style ="font-size: 1rem; margin-top: 0; "> How to get a GitHub Token:</ h3 >
818+ < ol style ="margin: 10px 0; padding-left: 20px; color: var(--text-light); ">
819+ < li > Go to < a href ="https://github.com/settings/tokens " target ="_blank " style ="color: var(--primary); "> GitHub Settings → Developer settings → Personal access tokens</ a > </ li >
820+ < li > Click "Generate new token (classic)"</ li >
821+ < li > Give it a name (e.g., "AnimateItNow")</ li >
822+ < li > Select expiration (or "No expiration")</ li >
823+ < li > Check < strong > public_repo</ strong > scope (read-only access)</ li >
824+ < li > Click "Generate token" and copy it</ li >
825+ < li > Paste it below</ li >
826+ </ ol >
827+ </ div >
828+
829+ < div style ="margin-bottom: 20px; ">
830+ < label style ="display: block; margin-bottom: 8px; font-weight: 600; "> GitHub Personal Access Token:</ label >
831+ < input
832+ type ="password "
833+ id ="githubTokenInput "
834+ placeholder ="ghp_xxxxxxxxxxxxxxxxxxxx "
835+ style ="width: 100%; padding: 12px; border: 2px solid var(--border); border-radius: 8px; font-size: 0.9rem; box-sizing: border-box; "
836+ />
837+ < small style ="color: var(--text-light); display: block; margin-top: 5px; ">
838+ Token is stored locally in your browser and never sent to our servers.
839+ </ small >
840+ </ div >
841+
842+ < div style ="display: flex; gap: 10px; justify-content: flex-end; ">
843+ < button id ="tokenCancelBtn " style ="padding: 10px 20px; background: #e9ecef; color: var(--text); border: none; border-radius: 8px; cursor: pointer; ">
844+ Cancel
845+ </ button >
846+ < button id ="tokenSaveBtn " style ="padding: 10px 20px; background: var(--primary); color: white; border: none; border-radius: 8px; cursor: pointer; ">
847+ Save Token
848+ </ button >
849+ < button id ="tokenRemoveBtn " style ="padding: 10px 20px; background: #dc3545; color: white; border: none; border-radius: 8px; cursor: pointer; ">
850+ Remove Token
851+ </ button >
852+ </ div >
853+ </ div >
854+ </ div >
855+
801856 <!-- Scroll to top button -->
802857 < button onclick ="scrollToTop() " id ="scrollBtn " title ="Go to top "> ↑</ button >
803858
@@ -809,24 +864,83 @@ <h4>Connect With Us</h4>
809864 let contributorsData = [ ] ;
810865 let filteredContributors = [ ] ;
811866
812- // GitHub API headers with hardcoded token
813- // IMPORTANT: Make sure this token has 'public_repo' scope and hasn't been revoked
814- const GITHUB_TOKEN = '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' . trim ( ) ;
815-
816- function getApiHeaders ( ) {
817- // Validate token format
818- if ( ! GITHUB_TOKEN || GITHUB_TOKEN . length < 10 ) {
819- console . error ( '❌ Invalid token format' ) ;
820- throw new Error ( 'GitHub token is missing or invalid' ) ;
867+ // GitHub Token Management
868+ function getGitHubToken ( ) {
869+ return localStorage . getItem ( 'github_token' ) || null ;
870+ }
871+
872+ function setGitHubToken ( token ) {
873+ if ( token && token . trim ( ) ) {
874+ localStorage . setItem ( 'github_token' , token . trim ( ) ) ;
875+ } else {
876+ localStorage . removeItem ( 'github_token' ) ;
821877 }
822-
823- return {
878+ }
879+
880+ function getApiHeaders ( ) {
881+ const headers = {
824882 'Accept' : 'application/vnd.github.v3+json' ,
825- 'User-Agent' : 'AnimateItNow-Contributors' ,
826- // Classic tokens use 'token' prefix (not 'Bearer')
827- 'Authorization' : `token ${ GITHUB_TOKEN } `
883+ 'User-Agent' : 'AnimateItNow-Contributors'
828884 } ;
829- }
885+ const token = getGitHubToken ( ) ;
886+ if ( token ) {
887+ headers [ 'Authorization' ] = `token ${ token } ` ;
888+ }
889+ return headers ;
890+ }
891+
892+ // Token Modal Management
893+ const tokenModal = document . getElementById ( 'tokenModal' ) ;
894+ const tokenSettingsBtn = document . getElementById ( 'tokenSettingsBtn' ) ;
895+ const tokenSaveBtn = document . getElementById ( 'tokenSaveBtn' ) ;
896+ const tokenCancelBtn = document . getElementById ( 'tokenCancelBtn' ) ;
897+ const tokenRemoveBtn = document . getElementById ( 'tokenRemoveBtn' ) ;
898+ const tokenInput = document . getElementById ( 'githubTokenInput' ) ;
899+
900+ // Load existing token
901+ const existingToken = getGitHubToken ( ) ;
902+ if ( existingToken ) {
903+ tokenInput . value = existingToken ;
904+ }
905+
906+ tokenSettingsBtn . addEventListener ( 'click' , ( ) => {
907+ tokenModal . style . display = 'flex' ;
908+ } ) ;
909+
910+ tokenCancelBtn . addEventListener ( 'click' , ( ) => {
911+ tokenModal . style . display = 'none' ;
912+ tokenInput . value = existingToken || '' ;
913+ } ) ;
914+
915+ tokenSaveBtn . addEventListener ( 'click' , ( ) => {
916+ const token = tokenInput . value . trim ( ) ;
917+ if ( token ) {
918+ setGitHubToken ( token ) ;
919+ alert ( 'Token saved! Refreshing page...' ) ;
920+ location . reload ( ) ;
921+ } else {
922+ alert ( 'Please enter a valid token' ) ;
923+ }
924+ } ) ;
925+
926+ tokenRemoveBtn . addEventListener ( 'click' , ( ) => {
927+ if ( confirm ( 'Are you sure you want to remove the token?' ) ) {
928+ setGitHubToken ( '' ) ;
929+ tokenInput . value = '' ;
930+ alert ( 'Token removed! Refreshing page...' ) ;
931+ location . reload ( ) ;
932+ }
933+ } ) ;
934+
935+ // Close modal on outside click
936+ tokenModal . addEventListener ( 'click' , ( e ) => {
937+ if ( e . target === tokenModal ) {
938+ tokenModal . style . display = 'none' ;
939+ tokenInput . value = existingToken || '' ;
940+ }
941+ } ) ;
942+
943+ // Note: Use getApiHeaders() function instead of apiHeaders constant
830944
831945 // Fetch all pages of contributors
832946 async function fetchAllContributors ( ) {
@@ -837,34 +951,12 @@ <h4>Connect With Us</h4>
837951 while ( true ) {
838952 try {
839953 const url = `https://api.github.com/repos/${ owner } /${ repo } /contributors?per_page=${ perPage } &page=${ page } ` ;
840- const headers = getApiHeaders ( ) ;
841-
842- // Debug: Log request details (remove token from log)
843- console . log ( `Fetching contributors page ${ page } ` ) ;
844- console . log ( 'Headers:' , { ...headers , Authorization : 'token ***' } ) ;
845-
846- const response = await fetch ( url , {
847- method : 'GET' ,
848- headers : headers ,
849- mode : 'cors' ,
850- cache : 'no-cache'
851- } ) ;
954+ const response = await fetch ( url , { headers : getApiHeaders ( ) } ) ;
852955
853956 // Check rate limit
854957 const remaining = response . headers . get ( 'X-RateLimit-Remaining' ) ;
855958 const resetTime = response . headers . get ( 'X-RateLimit-Reset' ) ;
856959
857- if ( response . status === 401 ) {
858- const errorData = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
859- console . error ( '401 Error Details:' , errorData ) ;
860- console . error ( 'Response Headers:' , {
861- 'X-RateLimit-Remaining' : remaining ,
862- 'X-RateLimit-Reset' : resetTime ,
863- 'X-GitHub-Media-Type' : response . headers . get ( 'X-GitHub-Media-Type' )
864- } ) ;
865- throw new Error ( `GitHub API authentication failed (401). ${ errorData . message || 'Bad credentials. Token may be invalid, expired, or missing required scopes.' } ` ) ;
866- }
867-
868960 if ( response . status === 403 ) {
869961 if ( resetTime ) {
870962 const resetDate = new Date ( parseInt ( resetTime ) * 1000 ) ;
@@ -874,8 +966,7 @@ <h4>Connect With Us</h4>
874966 }
875967
876968 if ( ! response . ok ) {
877- const errorData = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
878- throw new Error ( `GitHub API error! Status: ${ response . status } . ${ errorData . message || '' } ` ) ;
969+ throw new Error ( `GitHub API error! Status: ${ response . status } ` ) ;
879970 }
880971
881972 const contributors = await response . json ( ) ;
@@ -920,7 +1011,8 @@ <h4>Connect With Us</h4>
9201011 }
9211012
9221013 try {
923- console . log ( "🔄 Fetching contributors with token..." ) ;
1014+ const token = getGitHubToken ( ) ;
1015+ console . log ( token ? "🔄 Fetching contributors with token..." : "🔄 Fetching contributors (unauthenticated - limited to 60/hour)..." ) ;
9241016 const contributors = await fetchAllContributors ( ) ;
9251017
9261018 if ( ! Array . isArray ( contributors ) || contributors . length === 0 ) {
0 commit comments