Skip to content

Commit 3040769

Browse files
committed
feat: Add configuration management, enhance episode item UI, and improve RSS fetching with proxy support
1 parent 7267cb1 commit 3040769

File tree

4 files changed

+267
-163
lines changed

4 files changed

+267
-163
lines changed

App.tsx

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Podcast, Episode, PlaybackState, Theme } from './types';
44
import { storageService } from './services/storageService';
55
import { rssService } from './services/rssService';
66
import { shareService, SharedData } from './services/shareService';
7+
import { APP_CONFIG } from './config';
78
import Player from './components/Player';
89
import EpisodeItem from './components/EpisodeItem';
910
import 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

Comments
 (0)