From 2b971f97b33877473aae49c6f2f8028c5a994850 Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Thu, 16 Apr 2026 12:57:37 +0200 Subject: [PATCH] feat: implement main navigation component and integrate protected routing in App structure --- frontend/src/App.tsx | 9 ++ frontend/src/components/Navigation.tsx | 14 +- frontend/src/pages/Library.tsx | 198 +++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/Library.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1786c6a..39e0a84 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import Login from './pages/Login'; import Dashboard from './pages/Dashboard'; import Trending from './pages/Trending'; import AddPodcast from './pages/AddPodcast'; +import Library from './pages/Library'; import Navigation from './components/Navigation'; const queryClient = new QueryClient({ @@ -63,6 +64,14 @@ export default function App() { } /> + + + + } + /> } /> } /> diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index c58f81c..1c6547f 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -1,5 +1,5 @@ import { Link, useLocation } from 'react-router-dom'; -import { Home, Flame, PlusCircle } from 'lucide-react'; +import { Home, Flame, PlusCircle, BookOpen } from 'lucide-react'; export default function Navigation() { const location = useLocation(); @@ -43,6 +43,18 @@ export default function Navigation() { Tendance + + + Bibliothèque + + (null); + const [confirmDeleteId, setConfirmDeleteId] = useState(null); + + useEffect(() => { + const loadUser = async () => { + try { + const response = await authService.getMe(); + setUser(response.user); + } catch (error) { + console.error('Failed to load user:', error); + } + }; + loadUser(); + }, []); + + const { data: subscriptionsData, isLoading } = useQuery({ + queryKey: ['podcasts', 'subscriptions'], + queryFn: () => podcastService.getUserSubscriptions(), + staleTime: 2 * 60 * 1000, + }); + + const unsubscribeMutation = useMutation({ + mutationFn: (podcastId: string) => podcastService.unsubscribe(podcastId), + onSuccess: () => { + // Invalidate subscriptions and latest episodes so dashboard refreshes too + queryClient.invalidateQueries({ queryKey: ['podcasts', 'subscriptions'] }); + queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] }); + setConfirmDeleteId(null); + }, + onError: (error: any) => { + alert(`Erreur : ${error.response?.data?.message || 'Impossible de se désabonner'}`); + setConfirmDeleteId(null); + }, + }); + + const handleLogout = async () => { + try { + await authService.logout(); + authService.clearTokens(); + window.location.href = '/login'; + } catch (error) { + console.error('Logout failed:', error); + } + }; + + const handleUnsubscribeRequest = (podcastId: string) => { + setConfirmDeleteId(podcastId); + }; + + const handleConfirmUnsubscribe = () => { + if (confirmDeleteId) { + unsubscribeMutation.mutate(confirmDeleteId); + } + }; + + const podcasts: Podcast[] = subscriptionsData?.podcasts ?? []; + + return ( +
+
+ +
+ {/* Title row */} +
+
+ +

+ {isLoading ? 'Chargement...' : `${podcasts.length} abonnement${podcasts.length !== 1 ? 's' : ''}`} +

+
+ + + Ajouter un podcast + +
+ + {/* Content */} + {isLoading ? ( +
+
+ +

Chargement de vos abonnements...

+
+
+ ) : podcasts.length === 0 ? ( +
+ +

Votre bibliothèque est vide

+

Abonnez-vous à des podcasts pour les retrouver ici

+ + + Ajouter mon premier podcast + +
+ ) : ( +
+ {podcasts.map((podcast) => ( +
+ {/* Podcast cover */} + {podcast.imageUrl ? ( +
+ {podcast.title} +
+ ) : ( +
+ +
+ )} + + {/* Info */} +
+

+ {podcast.title} +

+ {podcast.author && ( +

{podcast.author}

+ )} +
+ {podcast.episodeCount > 0 && ( + {podcast.episodeCount} épisodes + )} + {podcast.language && {podcast.language}} +
+
+ + {/* Unsubscribe button */} + {confirmDeleteId === podcast._id ? ( +
+ Confirmer le désabonnement ? + + +
+ ) : ( + + )} +
+ ))} +
+ )} +
+ + {/* Confirm modal overlay (backdrop click to cancel) */} + {confirmDeleteId && ( +
setConfirmDeleteId(null)} + /> + )} +
+ ); +}