11import './App.css' ;
22import packageJson from '../package.json' ;
3- import React , { useState , useEffect , useRef , useMemo } from 'react' ;
3+ import React , { useState , useEffect , useRef , useMemo , useCallback } from 'react' ;
44import toplogo from './Youtarr_text.png' ;
55import axios from 'axios' ;
66import {
@@ -34,6 +34,7 @@ import { ThemeProvider } from '@mui/material/styles';
3434import CloseIcon from '@mui/icons-material/Close' ;
3535import MenuIcon from '@mui/icons-material/Menu' ;
3636import WarningAmberIcon from '@mui/icons-material/WarningAmber' ;
37+ import Tooltip from '@mui/material/Tooltip' ;
3738import SettingsIcon from '@mui/icons-material/Settings' ;
3839import SubscriptionsIcon from '@mui/icons-material/Subscriptions' ;
3940import DownloadIcon from '@mui/icons-material/Download' ;
@@ -55,6 +56,7 @@ import { useConfig } from './hooks/useConfig';
5556import ErrorBoundary from './components/ErrorBoundary' ;
5657import DatabaseErrorOverlay from './components/DatabaseErrorOverlay' ;
5758import { lightTheme , darkTheme } from './theme' ;
59+ import { YTDLP_UPDATED_EVENT } from './components/Configuration/hooks/useYtDlpUpdate' ;
5860
5961// Event name for database error detection
6062const DB_ERROR_EVENT = 'db-error-detected' ;
@@ -76,6 +78,8 @@ function AppContent() {
7678 const [ dbErrors , setDbErrors ] = useState < string [ ] > ( [ ] ) ;
7779 const [ dbRecovered , setDbRecovered ] = useState ( false ) ;
7880 const [ countdown , setCountdown ] = useState ( 15 ) ;
81+ const [ ytDlpUpdateAvailable , setYtDlpUpdateAvailable ] = useState ( false ) ;
82+ const [ ytDlpLatestVersion , setYtDlpLatestVersion ] = useState ( '' ) ;
7983 const location = useLocation ( ) ;
8084 const theme = useTheme ( ) ;
8185 const isMobile = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
@@ -359,6 +363,53 @@ function AppContent() {
359363 } ) ;
360364 } , [ ] ) ;
361365
366+ const fetchYtDlpVersionInfo = useCallback ( ( ) => {
367+ if ( ! token ) {
368+ setYtDlpUpdateAvailable ( false ) ;
369+ setYtDlpLatestVersion ( '' ) ;
370+ return ;
371+ }
372+
373+ fetch ( '/api/ytdlp/latest-version' , {
374+ headers : { 'x-access-token' : token } ,
375+ } )
376+ . then ( ( response ) => {
377+ if ( response . ok ) {
378+ return response . json ( ) ;
379+ }
380+ throw new Error ( 'Failed to fetch yt-dlp version' ) ;
381+ } )
382+ . then ( ( data ) => {
383+ setYtDlpUpdateAvailable ( data . updateAvailable || false ) ;
384+ setYtDlpLatestVersion ( data . latestVersion || '' ) ;
385+ if ( data . currentVersion ) {
386+ setYtDlpVersion ( data . currentVersion ) ;
387+ }
388+ } )
389+ . catch ( ( err ) => {
390+ console . error ( 'Failed to fetch yt-dlp version info:' , err ) ;
391+ setYtDlpUpdateAvailable ( false ) ;
392+ setYtDlpLatestVersion ( '' ) ;
393+ } ) ;
394+ } , [ token ] ) ;
395+
396+ useEffect ( ( ) => {
397+ fetchYtDlpVersionInfo ( ) ;
398+ } , [ fetchYtDlpVersionInfo ] ) ;
399+
400+ // Listen for yt-dlp update events to refresh version display
401+ useEffect ( ( ) => {
402+ const handleYtDlpUpdated = ( ) => {
403+ fetchYtDlpVersionInfo ( ) ;
404+ } ;
405+
406+ window . addEventListener ( YTDLP_UPDATED_EVENT , handleYtDlpUpdated ) ;
407+
408+ return ( ) => {
409+ window . removeEventListener ( YTDLP_UPDATED_EVENT , handleYtDlpUpdated ) ;
410+ } ;
411+ } , [ fetchYtDlpVersionInfo ] ) ;
412+
362413 return (
363414 < ThemeProvider theme = { selectedTheme } >
364415 < CssBaseline />
@@ -450,13 +501,28 @@ function AppContent() {
450501 { clientVersion }
451502 </ Typography >
452503 { ytDlpVersion && (
453- < Typography
454- fontSize = 'x-small'
455- color = { 'textSecondary' }
456- style = { { opacity : 0.7 } }
457- >
458- yt-dlp: { ytDlpVersion }
459- </ Typography >
504+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
505+ { ytDlpUpdateAvailable && (
506+ < Tooltip title = { `yt-dlp update available (${ ytDlpLatestVersion } ). Go to Configuration to update.` } >
507+ < IconButton
508+ component = { Link }
509+ to = "/configuration"
510+ size = "small"
511+ aria-label = { `yt-dlp update available (${ ytDlpLatestVersion } ). Click to go to Configuration.` }
512+ sx = { { p : 0.25 } }
513+ >
514+ < WarningAmberIcon sx = { { fontSize : 14 , color : 'warning.main' } } />
515+ </ IconButton >
516+ </ Tooltip >
517+ ) }
518+ < Typography
519+ fontSize = 'x-small'
520+ color = { 'textSecondary' }
521+ style = { { opacity : 0.7 } }
522+ >
523+ yt-dlp: { ytDlpVersion }
524+ </ Typography >
525+ </ Box >
460526 ) }
461527 </ Box >
462528 { /* This is the matching invisible IconButton */ }
0 commit comments