@@ -21,6 +21,7 @@ import {
2121} from "@/components/ui/alert-dialog"
2222import { toast } from "@/components/ui/custom-toast"
2323import { Toaster } from "@/components/ui/toaster"
24+ import { Button } from "@/components/ui/button"
2425
2526// Cache expiration time (24 hours in milliseconds)
2627const CACHE_EXPIRATION = 24 * 60 * 60 * 1000
@@ -29,6 +30,7 @@ const REPOS_PER_PAGE = 10
2930
3031export default function Home ( ) {
3132 const [ apiKey , setApiKey ] = useState < string > ( "" )
33+ const [ noApiKey , setNoApiKey ] = useState ( false )
3234 const [ collections , setCollections ] = useState < Collection [ ] > ( [ ] )
3335 const [ selectedCollection , setSelectedCollection ] = useState < Collection | null > ( null )
3436 const [ repositories , setRepositories ] = useState < Repository [ ] > ( [ ] )
@@ -181,6 +183,12 @@ export default function Home() {
181183
182184 // Load repositories for the imported collection
183185 loadRepositoriesForCollection ( collection )
186+ toast ( {
187+ id : `import-${ Date . now ( ) } ` ,
188+ title : "Collection Imported" ,
189+ description : "The collection has been successfully imported." ,
190+ variant : "default"
191+ } )
184192 }
185193
186194 const isCacheValid = ( collectionId : number ) => {
@@ -192,8 +200,6 @@ export default function Home() {
192200 }
193201
194202 const loadRepositoriesForCollection = async ( collection : Collection ) => {
195- if ( ! apiKey ) return
196-
197203 // Reset pagination
198204 setPage ( 1 )
199205 setHasMore ( true )
@@ -228,7 +234,7 @@ export default function Home() {
228234 setProgressiveLoadingStatus ( `Loading ${ topic . name } (${ i + 1 } /${ collection . topics . length } )...` )
229235
230236 try {
231- const repos = await fetchRepositoriesByTopic ( apiKey , topic . name , minStars )
237+ const repos = await fetchRepositoriesByTopic ( apiKey || undefined , topic . name , minStars )
232238
233239 // Process repositories as they come in
234240 for ( const repo of repos ) {
@@ -244,12 +250,31 @@ export default function Home() {
244250 }
245251 } catch ( error ) {
246252 console . error ( `Error fetching repositories for topic ${ topic . name } :` , error )
247- toast ( {
248- title : "Error" ,
249- description : `Failed to load repositories for topic: ${ topic . name } ` ,
250- variant : "destructive" ,
251- className : "bg-red-50 border-red-200 dark:bg-red-900/50 dark:border-red-800" ,
252- } )
253+ if ( error instanceof Error ) {
254+ if ( error . message . includes ( 'rate limit exceeded' ) ) {
255+ toast ( {
256+ id : `rate-limit-${ Date . now ( ) } ` ,
257+ title : "Rate Limit Exceeded" ,
258+ description : error . message ,
259+ variant : "destructive"
260+ } )
261+ break
262+ } else if ( error . message . includes ( 'Consider adding a GitHub API key' ) ) {
263+ toast ( {
264+ id : `api-key-recommended-${ Date . now ( ) } ` ,
265+ title : "API Key Recommended" ,
266+ description : "Adding a GitHub API key will provide better rate limits and reliability." ,
267+ variant : "default"
268+ } )
269+ } else {
270+ toast ( {
271+ id : `error-${ Date . now ( ) } ` ,
272+ title : "Error" ,
273+ description : `Failed to load repositories for topic: ${ topic . name } ` ,
274+ variant : "destructive"
275+ } )
276+ }
277+ }
253278 }
254279 }
255280
@@ -273,12 +298,14 @@ export default function Home() {
273298 setProgressiveLoadingStatus ( "" )
274299 } catch ( error ) {
275300 console . error ( "Error fetching repositories:" , error )
276- toast ( {
277- title : "Error" ,
278- description : "Failed to load repositories" ,
279- variant : "destructive" ,
280- className : "bg-red-50 border-red-200 dark:bg-red-900/50 dark:border-red-800" ,
281- } )
301+ if ( error instanceof Error ) {
302+ toast ( {
303+ id : `error-${ Date . now ( ) } ` ,
304+ title : "Error" ,
305+ description : error . message ,
306+ variant : "destructive"
307+ } )
308+ }
282309 } finally {
283310 setIsLoading ( false )
284311 }
@@ -342,26 +369,22 @@ export default function Home() {
342369 const handleToggleFavorite = ( repoId : number ) => {
343370 if ( ! selectedCollection ) return
344371
345- const collectionId = selectedCollection . id . toString ( )
346- const currentFavorites = collectionFavorites [ collectionId ] || [ ]
372+ const currentFavorites = collectionFavorites [ selectedCollection . id ] || [ ]
347373
348- // Check if the repository is already in favorites
349- const existingIndex = currentFavorites . findIndex ( ( fav ) => fav . id === repoId )
350-
351- if ( existingIndex >= 0 ) {
374+ if ( currentFavorites . some ( ( repo ) => repo . id === repoId ) ) {
352375 // Remove from favorites
353- const updatedFavorites = [ ...currentFavorites ]
354- updatedFavorites . splice ( existingIndex , 1 )
376+ const updatedFavorites = currentFavorites . filter ( ( repo ) => repo . id !== repoId )
355377
356378 setCollectionFavorites ( {
357379 ...collectionFavorites ,
358- [ collectionId ] : updatedFavorites ,
380+ [ selectedCollection . id ] : updatedFavorites ,
359381 } )
360382
361383 toast ( {
384+ id : `remove-favorite-${ Date . now ( ) } ` ,
362385 title : "Removed from favorites" ,
363386 description : "Repository removed from favorites" ,
364- className : "bg-purple-50 border-purple-200 dark:bg-purple-900/50 dark:border-purple-800" ,
387+ variant : "default"
365388 } )
366389 } else {
367390 // Find the repository in either current or seen repositories
@@ -371,13 +394,14 @@ export default function Home() {
371394 // Add to favorites
372395 setCollectionFavorites ( {
373396 ...collectionFavorites ,
374- [ collectionId ] : [ ...currentFavorites , repo ] ,
397+ [ selectedCollection . id ] : [ ...currentFavorites , repo ] ,
375398 } )
376399
377400 toast ( {
401+ id : `add-favorite-${ Date . now ( ) } ` ,
378402 title : "Added to favorites" ,
379403 description : "Repository added to favorites" ,
380- className : "bg-purple-50 border-purple-200 dark:bg-purple-900/50 dark:border-purple-800" ,
404+ variant : "default"
381405 } )
382406 }
383407 }
@@ -389,9 +413,10 @@ export default function Home() {
389413 // Check if topic already exists in collection
390414 if ( selectedCollection . topics . some ( ( t ) => t . name === topic ) ) {
391415 toast ( {
416+ id : `topic-exists-${ Date . now ( ) } ` ,
392417 title : "Topic already exists" ,
393418 description : `The topic "${ topic } " is already in this collection.` ,
394- className : "bg-purple-50 border-purple-200 dark:bg-purple-900/50 dark:border-purple-800" ,
419+ variant : "default"
395420 } )
396421 return
397422 }
@@ -414,44 +439,42 @@ export default function Home() {
414439 setSelectedCollection ( updatedCollection )
415440
416441 toast ( {
442+ id : `topic-added-${ Date . now ( ) } ` ,
417443 title : "Topic added" ,
418444 description : `Added "${ topic } " to collection. Click Refresh to load repositories.` ,
419- className : "bg-purple-50 border-purple-200 dark:bg-purple-900/50 dark:border-purple-800" ,
445+ variant : "default"
420446 } )
421447 }
422448
423- const handleDeleteCollection = ( id : number ) => {
424- setCollectionToDelete ( id )
449+ const handleDeleteCollection = ( collectionId : number ) => {
450+ setCollectionToDelete ( collectionId )
425451 }
426452
427- const confirmDeleteCollection = ( ) => {
453+ const handleConfirmDelete = ( ) => {
428454 if ( collectionToDelete === null ) return
429455
430456 const updatedCollections = collections . filter ( ( c ) => c . id !== collectionToDelete )
431457 setCollections ( updatedCollections )
432458
433- // If we deleted the selected collection, clear selection
459+ // If the deleted collection was selected, select the first available collection or null
434460 if ( selectedCollection ?. id === collectionToDelete ) {
435- setSelectedCollection ( null )
436- setRepositories ( [ ] )
437- setSeenRepositories ( [ ] )
438- }
439-
440- // Remove from stored repositories
441- if ( storedRepos [ collectionToDelete ] ) {
442- const updatedStoredRepos = { ...storedRepos }
443- delete updatedStoredRepos [ collectionToDelete ]
444- setStoredRepos ( updatedStoredRepos )
461+ setSelectedCollection ( updatedCollections [ 0 ] || null )
445462 }
446463
447- // Remove from collection favorites
448- if ( collectionFavorites [ collectionToDelete ] ) {
449- const updatedFavorites = { ...collectionFavorites }
450- delete updatedFavorites [ collectionToDelete ]
451- setCollectionFavorites ( updatedFavorites )
452- }
464+ // Remove from cache
465+ setStoredRepos ( ( prev ) => {
466+ const newStoredRepos = { ...prev }
467+ delete newStoredRepos [ collectionToDelete ]
468+ return newStoredRepos
469+ } )
453470
454471 setCollectionToDelete ( null )
472+ toast ( {
473+ id : `delete-${ Date . now ( ) } ` ,
474+ title : "Collection Deleted" ,
475+ description : "The collection has been successfully deleted." ,
476+ variant : "default"
477+ } )
455478 }
456479
457480 const openCreateModal = ( ) => {
@@ -464,10 +487,24 @@ export default function Home() {
464487 setIsModalOpen ( true )
465488 }
466489
467- const handleRefresh = ( ) => {
468- if ( selectedCollection ) {
469- loadRepositoriesForCollection ( selectedCollection )
470- }
490+ const handleRefresh = async ( ) => {
491+ if ( ! selectedCollection ) return
492+
493+ // Clear the cache for this collection
494+ setStoredRepos ( ( prev ) => {
495+ const newStoredRepos = { ...prev }
496+ delete newStoredRepos [ selectedCollection . id ]
497+ return newStoredRepos
498+ } )
499+
500+ // Reload repositories
501+ await loadRepositoriesForCollection ( selectedCollection )
502+ toast ( {
503+ id : `refresh-${ Date . now ( ) } ` ,
504+ title : "Collection Refreshed" ,
505+ description : "The collection has been refreshed with the latest data." ,
506+ variant : "default"
507+ } )
471508 }
472509
473510 // Get current collection favorites
@@ -478,26 +515,38 @@ export default function Home() {
478515
479516 return (
480517 < main className = "min-h-screen bg-gray-50 dark:bg-gray-900 flex flex-col" >
481- { apiKey ? (
482- < Navbar
483- collections = { collections }
484- selectedCollection = { selectedCollection }
485- onSelectCollection = { handleSelectCollection }
486- onCreateCollection = { openCreateModal }
487- onEditCollection = { openEditModal }
488- onDeleteCollection = { handleDeleteCollection }
489- onRefresh = { handleRefresh }
490- onExportCollection = { ( ) => setIsExportModalOpen ( true ) }
491- onImportCollection = { ( ) => setIsImportModalOpen ( true ) }
492- isLoading = { isLoading }
493- />
494- ) : null }
518+ < Navbar
519+ collections = { collections }
520+ selectedCollection = { selectedCollection }
521+ onSelectCollection = { handleSelectCollection }
522+ onCreateCollection = { openCreateModal }
523+ onEditCollection = { openEditModal }
524+ onDeleteCollection = { handleDeleteCollection }
525+ onRefresh = { handleRefresh }
526+ onExportCollection = { ( ) => setIsExportModalOpen ( true ) }
527+ onImportCollection = { ( ) => setIsImportModalOpen ( true ) }
528+ isLoading = { isLoading }
529+ />
495530
496531 < div className = "container mx-auto px-4 py-8 flex-grow" >
497- { ! apiKey ? (
532+ { ! apiKey && ! noApiKey ? (
498533 < div className = "max-w-md mx-auto" >
499534 < h1 className = "text-3xl font-bold mb-8 text-center text-purple-700 dark:text-purple-400" > GitHubie</ h1 >
500- < GitHubApiKeyForm onSubmit = { handleApiKeySubmit } />
535+ < div className = "space-y-4" >
536+ < p className = "text-gray-600 dark:text-gray-300 text-center" >
537+ Add a GitHub API key for better rate limits and reliability, or continue without one.
538+ </ p >
539+ < GitHubApiKeyForm onSubmit = { handleApiKeySubmit } />
540+ < div className = "flex justify-center" >
541+ < Button
542+ onClick = { ( ) => setNoApiKey ( true ) }
543+ variant = "outline"
544+ className = "w-full"
545+ >
546+ Continue without API key
547+ </ Button >
548+ </ div >
549+ </ div >
501550 </ div >
502551 ) : (
503552 < div className = "space-y-6" >
@@ -568,7 +617,7 @@ export default function Home() {
568617 Cancel
569618 </ AlertDialogCancel >
570619 < AlertDialogAction
571- onClick = { confirmDeleteCollection }
620+ onClick = { handleConfirmDelete }
572621 className = "bg-red-500 hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700"
573622 >
574623 Delete
0 commit comments