@@ -4,6 +4,7 @@ import { Podcast, Episode, PlaybackState, Theme } from './types';
44import { storageService } from './services/storageService' ;
55import { rssService } from './services/rssService' ;
66import { shareService , SharedData } from './services/shareService' ;
7+ import { APP_CONFIG } from './config' ;
78import Player from './components/Player' ;
89import EpisodeItem from './components/EpisodeItem' ;
910import confetti from 'canvas-confetti' ;
@@ -25,8 +26,8 @@ const App: React.FC = () => {
2526 const [ searchResults , setSearchResults ] = useState < Podcast [ ] > ( [ ] ) ;
2627 const [ suggestedPodcasts , setSuggestedPodcasts ] = useState < Podcast [ ] > ( [ ] ) ;
2728 const [ loadingSuggestions , setLoadingSuggestions ] = useState ( true ) ;
28- const [ view , setView ] = useState < 'home' | 'podcast' | 'history' | 'new' > ( 'home' ) ;
29- const [ theme , setTheme ] = useState < Theme > ( storageService . getTheme ( ) ) ;
29+ const [ view , setView ] = useState < 'home' | 'podcast' | 'history' | 'new' | 'queue' > ( 'home' ) ;
30+ const [ theme , setTheme ] = useState < Theme > ( storageService . getTheme ( ) || APP_CONFIG . defaultTheme ) ;
3031
3132 // PWA Installation State
3233 const [ deferredPrompt , setDeferredPrompt ] = useState < any > ( null ) ;
@@ -114,7 +115,6 @@ const App: React.FC = () => {
114115 } , [ ] ) ;
115116
116117 useEffect ( ( ) => {
117- // PWA Installation Listener
118118 const handleBeforeInstall = ( e : Event ) => {
119119 e . preventDefault ( ) ;
120120 setDeferredPrompt ( e ) ;
@@ -123,10 +123,9 @@ const App: React.FC = () => {
123123
124124 window . addEventListener ( 'beforeinstallprompt' , handleBeforeInstall ) ;
125125
126- // Request persistent storage to protect user's library
127126 if ( navigator . storage && navigator . storage . persist ) {
128127 navigator . storage . persist ( ) . then ( persistent => {
129- if ( persistent ) console . log ( "AuraPod storage is persistent." ) ;
128+ if ( persistent ) console . log ( "Storage is persistent." ) ;
130129 } ) ;
131130 }
132131
@@ -147,6 +146,7 @@ const App: React.FC = () => {
147146 const viewParam = params . get ( 'view' ) ;
148147 if ( viewParam === 'history' ) setView ( 'history' ) ;
149148 if ( viewParam === 'new' ) setView ( 'new' ) ;
149+ if ( viewParam === 'queue' ) setView ( 'queue' ) ;
150150
151151 const shareCode = params . get ( 's' ) ;
152152 if ( shareCode ) {
@@ -434,16 +434,16 @@ const App: React.FC = () => {
434434 } ;
435435
436436 return (
437- < div className = "flex h-screen overflow-hidden text-zinc-600 dark:text-zinc-300 bg-white dark:bg-zinc-950" >
437+ < div className = "flex h-screen overflow-hidden text-zinc-600 dark:text-zinc-300 bg-white dark:bg-zinc-950 font-sans " >
438438 { /* Sidebar */ }
439- < aside className = "w-64 bg-zinc-50 dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 flex flex-col hidden md:flex" >
439+ < aside className = "w-64 bg-zinc-50 dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 flex flex-col hidden md:flex shrink-0 " >
440440 < div className = "p-6" >
441441 < div className = "flex items-center gap-3 mb-8 text-zinc-900 dark:text-white group cursor-pointer" onClick = { ( ) => setView ( 'home' ) } >
442442 < div className = "w-9 h-9 aura-logo rounded-xl flex items-center justify-center text-white animate-pulse-slow" >
443443 < i className = "fa-solid fa-microphone-lines text-sm relative z-10" > </ i >
444444 </ div >
445445 < div className = "flex flex-col" >
446- < h1 className = "text-xl font-bold tracking-tight leading-none" > AuraPod </ h1 >
446+ < h1 className = "text-xl font-bold tracking-tight leading-none" > { APP_CONFIG . appName } </ h1 >
447447 < span className = "text-[8px] font-bold text-zinc-400 uppercase tracking-widest mt-0.5" > Standalone</ span >
448448 </ div >
449449 </ div >
@@ -459,7 +459,16 @@ const App: React.FC = () => {
459459 onClick = { loadNewEpisodes }
460460 className = { `w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition ${ view === 'new' ? 'bg-indigo-50 dark:bg-zinc-800 text-indigo-600 dark:text-white font-medium' : 'hover:bg-zinc-200/50 dark:hover:bg-zinc-800/50' } ` }
461461 >
462- < i className = "fa-solid fa-sparkles text-sm" > </ i > New Releases
462+ < i className = "fa-solid fa-bolt-lightning text-sm" > </ i > New Releases
463+ </ button >
464+ < button
465+ onClick = { ( ) => { setView ( 'queue' ) ; } }
466+ className = { `w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition ${ view === 'queue' ? 'bg-indigo-50 dark:bg-zinc-800 text-indigo-600 dark:text-white font-medium' : 'hover:bg-zinc-200/50 dark:hover:bg-zinc-800/50' } ` }
467+ >
468+ < i className = "fa-solid fa-list-ul text-sm" > </ i > Play Queue
469+ { queue . length > 0 && (
470+ < span className = "ml-auto text-[10px] bg-indigo-500 text-white px-1.5 py-0.5 rounded-full" > { queue . length } </ span >
471+ ) }
463472 </ button >
464473 < button
465474 onClick = { ( ) => { setView ( 'history' ) ; syncHistory ( ) ; } }
@@ -502,12 +511,11 @@ const App: React.FC = () => {
502511 ) ) }
503512 </ div >
504513
505- { /* PWA Install Promo */ }
506514 { showInstallBanner && (
507515 < div className = "mt-8 p-4 bg-indigo-500/10 dark:bg-indigo-900/20 border border-indigo-500/20 rounded-2xl animate-fade-in relative overflow-hidden group" >
508516 < div className = "absolute inset-0 aura-logo opacity-5 group-hover:opacity-10 transition" > </ div >
509517 < p className = "text-[10px] font-bold text-indigo-600 dark:text-indigo-400 uppercase tracking-widest mb-2 relative" > Always Ready</ p >
510- < h4 className = "text-xs font-bold text-zinc-900 dark:text-white leading-tight mb-3 relative" > Use AuraPod as an App</ h4 >
518+ < h4 className = "text-xs font-bold text-zinc-900 dark:text-white leading-tight mb-3 relative" > Use { APP_CONFIG . appName } as an App</ h4 >
511519 < button
512520 onClick = { installApp }
513521 className = "w-full py-2 bg-indigo-600 text-white text-[10px] font-bold rounded-xl shadow-lg shadow-indigo-500/20 hover:bg-indigo-700 transition relative"
@@ -554,7 +562,11 @@ const App: React.FC = () => {
554562 < header className = "h-16 border-b border-zinc-100 dark:border-zinc-900 flex items-center px-8 justify-between shrink-0 bg-white/80 dark:bg-zinc-950/50 backdrop-blur-md sticky top-0 z-10" >
555563 < div className = "flex items-center gap-4" >
556564 < h2 className = "font-bold text-zinc-900 dark:text-white text-lg" >
557- { view === 'home' ? 'Discover' : view === 'history' ? 'History' : view === 'new' ? 'New Releases' : activePodcast ?. title }
565+ { view === 'home' ? 'Discover' :
566+ view === 'history' ? 'History' :
567+ view === 'new' ? 'New Releases' :
568+ view === 'queue' ? 'Play Queue' :
569+ activePodcast ?. title }
558570 </ h2 >
559571 </ div >
560572 < div className = "flex items-center gap-4" >
@@ -590,7 +602,7 @@ const App: React.FC = () => {
590602 { searchResults . length > 0 && (
591603 < div className = "mt-12" >
592604 < h3 className = "text-xl font-bold text-zinc-900 dark:text-white mb-8" > Search Results</ h3 >
593- < div className = "grid grid-cols-2 md :grid-cols-3 lg:grid-cols-5 gap-8" >
605+ < div className = "grid grid-cols-2 sm :grid-cols-3 lg:grid-cols-4 xl :grid-cols-5 gap-8" >
594606 { searchResults . map ( result => (
595607 < div
596608 key = { result . id }
@@ -622,23 +634,23 @@ const App: React.FC = () => {
622634 < div className = "mt-8" >
623635 < h3 className = "text-xl font-bold text-zinc-900 dark:text-white mb-8" > Trending Worldwide</ h3 >
624636 { loadingSuggestions ? (
625- < div className = "grid grid-cols-1 md :grid-cols-2 lg :grid-cols-4 gap-6 animate-pulse" >
626- { [ ...Array ( 8 ) ] . map ( ( _ , i ) => (
637+ < div className = "grid grid-cols-1 lg :grid-cols-2 xl :grid-cols-3 gap-6 animate-pulse" >
638+ { [ ...Array ( 6 ) ] . map ( ( _ , i ) => (
627639 < div key = { i } className = "h-24 bg-zinc-100 dark:bg-zinc-900/40 rounded-2xl" > </ div >
628640 ) ) }
629641 </ div >
630642 ) : (
631- < div className = "grid grid-cols-1 md :grid-cols-2 lg :grid-cols-4 gap-6" >
643+ < div className = "grid grid-cols-1 lg :grid-cols-2 xl :grid-cols-3 gap-6" >
632644 { suggestedPodcasts . map ( podcast => (
633645 < button
634646 key = { podcast . feedUrl || podcast . id }
635647 onClick = { ( ) => handleSelectPodcast ( podcast ) }
636- className = "bg-zinc-50 dark:bg-zinc-900/40 hover:bg-white dark:hover:bg-zinc-900 p-4 rounded-2xl border border-zinc-200 dark:border-zinc-800 transition flex items-center gap-4 group shadow-sm text-left"
648+ className = "bg-zinc-50 dark:bg-zinc-900/40 hover:bg-white dark:hover:bg-zinc-900 p-4 rounded-2xl border border-zinc-200 dark:border-zinc-800 transition flex items-center gap-4 group shadow-sm text-left overflow-hidden "
637649 >
638- < img src = { podcast . image } className = "w-12 h-12 rounded-xl object-cover shrink-0 shadow-sm" alt = "" />
639- < div className = "overflow-hidden flex-1" >
650+ < img src = { podcast . image } className = "w-16 h-16 rounded-xl object-cover shrink-0 shadow-sm" alt = "" />
651+ < div className = "overflow-hidden flex-1 min-w-0 " >
640652 < p className = "text-sm font-bold text-zinc-900 dark:text-white truncate" > { podcast . title } </ p >
641- < p className = "text-[10px] text-zinc-500 truncate" > { podcast . author } </ p >
653+ < p className = "text-[10px] text-zinc-500 truncate font-medium " > { podcast . author } </ p >
642654 </ div >
643655 { isSubscribed ( podcast . feedUrl ) ? (
644656 < i className = "fa-solid fa-check text-green-500 shrink-0 ml-auto" > </ i >
@@ -664,15 +676,15 @@ const App: React.FC = () => {
664676 < h3 className = "text-3xl font-extrabold text-zinc-900 dark:text-white tracking-tight" > Waves of the Past</ h3 >
665677 < button
666678 onClick = { clearHistory }
667- className = "text-[10px] font-bold text-red-500 hover:text-red-600 transition tracking-widest uppercase"
679+ className = "text-[10px] font-bold text-red-500 hover:text-red-600 transition tracking-widest uppercase px-4 py-2 hover:bg-red-50 dark:hover:bg-red-950/20 rounded-xl "
668680 >
669681 < i className = "fa-solid fa-trash-can mr-2" > </ i > Clear History
670682 </ button >
671683 </ div >
672684
673685 { Object . keys ( history ) . length === 0 ? (
674686 < div className = "text-center py-40 space-y-4" >
675- < div className = "w-20 h-20 bg-zinc-50 dark:bg-zinc-900 rounded-full flex items-center justify-center mx-auto text-zinc-200" >
687+ < div className = "w-20 h-20 bg-zinc-50 dark:bg-zinc-900 rounded-full flex items-center justify-center mx-auto text-zinc-200 shadow-inner " >
676688 < i className = "fa-solid fa-clock-rotate-left text-3xl" > </ i >
677689 </ div >
678690 < p className = "text-zinc-500 italic font-medium" > Your waves haven't broken yet. Start listening to see your history.</ p >
@@ -691,11 +703,62 @@ const App: React.FC = () => {
691703 description : item . description ,
692704 pubDate : item . pubDate ,
693705 podcastId : item . podcastId ,
694- audioUrl : item . audioUrl
706+ audioUrl : item . audioUrl ,
707+ duration : item . duration ? `${ Math . floor ( item . duration / 60 ) } m` : 'Shared'
695708 } }
696709 progress = { ( item . currentTime / ( item . duration || 1 ) ) * 100 }
697- isHistory
698710 onPlay = { ( ) => handlePlayFromHistory ( item ) }
711+ onQueue = { ( ) => {
712+ const ep = {
713+ id : item . episodeId ,
714+ podcastId : item . podcastId ,
715+ title : item . title || '' ,
716+ description : item . description || '' ,
717+ pubDate : item . pubDate || '' ,
718+ audioUrl : item . audioUrl || '' ,
719+ duration : item . duration ? `${ Math . floor ( item . duration / 60 ) } m` : '0:00' ,
720+ link : '' ,
721+ image : item . image ,
722+ podcastTitle : item . podcastTitle
723+ } ;
724+ addToQueue ( ep ) ;
725+ } }
726+ />
727+ ) ) }
728+ </ div >
729+ ) }
730+ </ div >
731+ ) }
732+
733+ { view === 'queue' && (
734+ < div className = "max-w-5xl mx-auto" >
735+ < div className = "flex items-center justify-between mb-10" >
736+ < h3 className = "text-3xl font-extrabold text-zinc-900 dark:text-white tracking-tight" > Upcoming Frequencies</ h3 >
737+ < button
738+ onClick = { clearQueue }
739+ className = "text-[10px] font-bold text-red-500 hover:text-red-600 transition tracking-widest uppercase px-4 py-2 hover:bg-red-50 dark:hover:bg-red-950/20 rounded-xl"
740+ >
741+ < i className = "fa-solid fa-layer-group mr-2" > </ i > Clear Queue
742+ </ button >
743+ </ div >
744+
745+ { queue . length === 0 ? (
746+ < div className = "text-center py-40 space-y-4" >
747+ < div className = "w-20 h-20 bg-zinc-50 dark:bg-zinc-900 rounded-full flex items-center justify-center mx-auto text-zinc-200 shadow-inner" >
748+ < i className = "fa-solid fa-list-ul text-3xl" > </ i >
749+ </ div >
750+ < p className = "text-zinc-500 italic font-medium" > The queue is silent. Add episodes to keep the waves rolling.</ p >
751+ </ div >
752+ ) : (
753+ < div className = "grid grid-cols-1 gap-6" >
754+ { queue . map ( ( episode , idx ) => (
755+ < EpisodeItem
756+ key = { episode . id + idx }
757+ isActive = { currentEpisode ?. id === episode . id }
758+ episode = { episode }
759+ progress = { getProgress ( episode . id ) }
760+ onPlay = { ( ) => { setPlayerAutoplay ( true ) ; setCurrentEpisode ( episode ) ; removeFromQueue ( episode . id ) ; } }
761+ onRemove = { ( ) => removeFromQueue ( episode . id ) }
699762 />
700763 ) ) }
701764 </ div >
@@ -751,9 +814,9 @@ const App: React.FC = () => {
751814
752815 < div className = "flex flex-col md:flex-row gap-10 mb-20 items-start" >
753816 < img src = { activePodcast . image } className = "w-56 h-56 md:w-72 md:h-72 rounded-[2.5rem] shadow-2xl object-cover shrink-0" alt = "" />
754- < div className = "space-y-6 flex-1 pt-2" >
817+ < div className = "space-y-6 flex-1 pt-2 min-w-0 " >
755818 < div className = "flex items-center justify-between" >
756- < span className = "bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 px-4 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest" > Aura Verified</ span >
819+ < span className = "bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 px-4 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest shrink-0 " > Aura Verified</ span >
757820 < div className = "flex items-center gap-3" >
758821 { ! isSubscribed ( activePodcast . feedUrl ) ? (
759822 < button
@@ -775,8 +838,8 @@ const App: React.FC = () => {
775838 </ button >
776839 </ div >
777840 </ div >
778- < h3 className = "text-5xl font-extrabold text-zinc-900 dark:text-white leading-tight tracking-tight" > { activePodcast . title } </ h3 >
779- < p className = "text-xl text-indigo-600 dark:text-indigo-400 font-bold" > { activePodcast . author } </ p >
841+ < h3 className = "text-5xl font-extrabold text-zinc-900 dark:text-white leading-tight tracking-tight break-words " > { activePodcast . title } </ h3 >
842+ < p className = "text-xl text-indigo-600 dark:text-indigo-400 font-bold truncate " > { activePodcast . author } </ p >
780843 < p className = "text-zinc-500 dark:text-zinc-400 text-sm max-w-3xl leading-relaxed font-medium" dangerouslySetInnerHTML = { { __html : activePodcast . description } } > </ p >
781844 </ div >
782845 </ div >
0 commit comments