diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..f573c55 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,49 @@ +name: Build & Publish Docker Image + +on: + push: + branches: [main] + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest + type=sha,prefix=sha- + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/docker-compose.yml b/docker-compose.yml index aede5f0..2aa5f52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: mongodb: image: mongo:8 @@ -31,9 +29,7 @@ services: retries: 5 app: - build: - context: . - dockerfile: Dockerfile + image: ghcr.io/r0m1k3/podcastic:latest container_name: podcastic-app restart: unless-stopped ports: diff --git a/frontend/src/components/EpisodeCard.tsx b/frontend/src/components/EpisodeCard.tsx index 7dd3196..b805a2e 100644 --- a/frontend/src/components/EpisodeCard.tsx +++ b/frontend/src/components/EpisodeCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Episode } from '../services/episodeService'; -import { Play, Pause, Calendar, Activity, Check, CheckCircle2, Circle } from 'lucide-react'; +import { Play, Pause, Calendar, Activity, CheckCircle2, Circle } from 'lucide-react'; import { useAudio } from '../context/AudioContext'; interface EpisodeCardProps { @@ -75,7 +75,6 @@ export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead }
{isCompleted && (
- LU
)} @@ -112,7 +111,7 @@ export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead }
- {podcast?.author || 'Artiste Premium'} + {podcast?.author || ''}
@@ -140,7 +139,7 @@ export default function EpisodeCard({ episode, onPlay, onDetails, onToggleRead } ? : } - {isCompleted ? 'Lu' : 'Lu ?'} + {isCompleted ? 'Lu' : 'Marquer'} )}
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 5997c64..54e058e 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -40,7 +40,7 @@ export default function Header({ title, subtitle, user, onLogout }: HeaderProps)
{user.username} - Premium Member + Membre
diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx index aa5d385..69d3cd7 100644 --- a/frontend/src/components/Navigation.tsx +++ b/frontend/src/components/Navigation.tsx @@ -26,7 +26,7 @@ export default function Navigation() {
PODCASTIC - Premium Audio + Your audio space
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 23eb70d..e232be9 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { episodeService, Episode } from '../services/episodeService'; import { authService } from '../services/authService'; @@ -13,6 +13,7 @@ import { Sparkles, Play, Clock, TrendingUp, Loader, RefreshCcw } from 'lucide-re export default function Dashboard() { const { playEpisode, currentEpisode, closePlayer, setUserId } = useAudio(); + const queryClient = useQueryClient(); const [user, setUser] = useState(null); const [detailsEpisode, setDetailsEpisode] = useState(null); const [isDetailsOpen, setIsDetailsOpen] = useState(false); @@ -41,21 +42,29 @@ export default function Dashboard() { }; const handleToggleRead = async (episode: Episode, completed: boolean) => { + const newProgress = { position: completed ? episode.duration : 0, isCompleted: completed }; + // Optimistic update: local state + all cached query pages + setEpisodes(prev => + prev.map(ep => ep._id === episode._id ? { ...ep, progress: newProgress } : ep) + ); + queryClient.setQueriesData( + { queryKey: ['episodes', 'latest'], exact: false }, + (old: any) => { + if (!old?.episodes) return old; + return { + ...old, + episodes: old.episodes.map((ep: Episode) => + ep._id === episode._id ? { ...ep, progress: newProgress } : ep + ), + }; + } + ); try { - await episodeService.saveProgress( - episode._id, - completed ? episode.duration : 0, - completed - ); - setEpisodes(prev => - prev.map(ep => - ep._id === episode._id - ? { ...ep, progress: { position: completed ? ep.duration : 0, isCompleted: completed } } - : ep - ) - ); + await episodeService.saveProgress(episode._id, newProgress.position, completed); } catch (error) { console.error('Failed to toggle read state:', error); + // Revert on error + queryClient.invalidateQueries({ queryKey: ['episodes', 'latest'] }); } }; @@ -163,7 +172,7 @@ export default function Dashboard() {
- Podcastic Premium + Podcastic

Découvrez les nouveautés de votre univers audio. @@ -181,9 +190,9 @@ export default function Dashboard() { Reprendre : {episodesInProgress[0].title.substring(0, 20)}... ) : ( - + - Découvrir + Découvrir des podcasts )} diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index c3048c8..8b764d2 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -61,7 +61,7 @@ export default function Login() {

PODCASTIC

- Expérience Audio Premium + Votre espace audio