Skip to content

Commit bc8edb3

Browse files
committed
feat(#349): add changelog page to web UI
- Add ChangelogPage component that fetches and renders CHANGELOG.md from GitHub using react-markdown - Create useChangelog hook with 5-minute caching and manual refresh capability - Add changelog navigation item and /changelog route to sidebar - Add/update tests
1 parent f9fbd7a commit bc8edb3

File tree

8 files changed

+1659
-0
lines changed

8 files changed

+1659
-0
lines changed

client/package-lock.json

Lines changed: 1088 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"lodash": "^4.17.21",
2121
"react": "^18.2.0",
2222
"react-dom": "^18.2.0",
23+
"react-markdown": "^10.1.0",
2324
"react-router-dom": "^6.11.1",
2425
"react-scripts": "5.0.1",
2526
"react-swipeable": "^7.0.1",

client/src/App.test.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ jest.mock('./components/StorageStatus', () => {
8787
};
8888
});
8989

90+
jest.mock('./components/ChangelogPage', () => {
91+
return function ChangelogPage() {
92+
return <div data-testid="changelog-page">Changelog Page Component</div>;
93+
};
94+
});
95+
9096
jest.mock('./components/ErrorBoundary', () => {
9197
return function ErrorBoundary({ children }: { children: React.ReactNode; fallbackMessage?: string }) {
9298
return <>{children}</>;
@@ -244,6 +250,7 @@ describe('App Component', () => {
244250
expect(screen.getByText('Your Channels')).toBeInTheDocument();
245251
expect(screen.getByText('Manage Downloads')).toBeInTheDocument();
246252
expect(screen.getByText('Downloaded Videos')).toBeInTheDocument();
253+
expect(screen.getByText('Changelog')).toBeInTheDocument();
247254
});
248255

249256
test('shows login link when not authenticated', async () => {

client/src/App.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,15 @@ import VideoLibraryIcon from '@mui/icons-material/VideoLibrary';
4141
import LoginIcon from '@mui/icons-material/Login';
4242
import LogoutIcon from '@mui/icons-material/Logout';
4343
import ShieldIcon from '@mui/icons-material/Shield';
44+
import NewReleasesIcon from '@mui/icons-material/NewReleases';
4445
import Configuration from './components/Configuration';
4546
import ChannelManager from './components/ChannelManager';
4647
import DownloadManager from './components/DownloadManager';
4748
import VideosPage from './components/VideosPage';
4849
import LocalLogin from './components/LocalLogin';
4950
import InitialSetup from './components/InitialSetup';
5051
import ChannelPage from './components/ChannelPage';
52+
import ChangelogPage from './components/ChangelogPage';
5153
import StorageStatus from './components/StorageStatus';
5254
import { useConfig } from './hooks/useConfig';
5355
import ErrorBoundary from './components/ErrorBoundary';
@@ -603,6 +605,31 @@ function AppContent() {
603605
primary='Downloaded Videos'
604606
/>
605607
</ListItem>
608+
<ListItem
609+
button
610+
component={Link}
611+
to='/changelog'
612+
onClick={handleDrawerToggle}
613+
sx={{
614+
bgcolor: location.pathname === '/changelog' ? 'action.selected' : 'transparent',
615+
borderLeft: location.pathname === '/changelog' ? (theme) => `4px solid ${theme.palette.primary.main}` : 'none',
616+
'&:hover': {
617+
bgcolor: 'action.hover',
618+
},
619+
paddingX: isMobile ? '8px' : '16px'
620+
}}
621+
>
622+
<ListItemIcon sx={{ minWidth: isMobile ? 46 : 56 }}>
623+
<NewReleasesIcon sx={{ color: location.pathname === '/changelog' ? 'primary.main' : 'inherit' }} />
624+
</ListItemIcon>
625+
<ListItemText
626+
primaryTypographyProps={{
627+
fontSize: isMobile ? 'small' : 'medium',
628+
fontWeight: location.pathname === '/changelog' ? 'bold' : 'normal'
629+
}}
630+
primary='Changelog'
631+
/>
632+
</ListItem>
606633
{!token && !isPlatformManaged && (
607634
<ListItem
608635
button
@@ -716,6 +743,10 @@ function AppContent() {
716743
)
717744
}
718745
/>
746+
<Route
747+
path='/changelog'
748+
element={<ChangelogPage />}
749+
/>
719750
{token ? (
720751
<>
721752
<Route
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import React from 'react';
2+
import {
3+
Card,
4+
CardContent,
5+
Typography,
6+
Box,
7+
CircularProgress,
8+
Alert,
9+
Button,
10+
Link,
11+
useTheme,
12+
useMediaQuery,
13+
} from '@mui/material';
14+
import RefreshIcon from '@mui/icons-material/Refresh';
15+
import ReactMarkdown from 'react-markdown';
16+
import { useChangelog } from '../hooks/useChangelog';
17+
18+
const CHANGELOG_GITHUB_URL =
19+
'https://github.com/DialmasterOrg/Youtarr/blob/main/CHANGELOG.md';
20+
21+
function ChangelogPage() {
22+
const theme = useTheme();
23+
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
24+
const { content, loading, error, refetch } = useChangelog();
25+
26+
return (
27+
<Card elevation={8} style={{ marginBottom: '16px' }}>
28+
<CardContent>
29+
<Box
30+
display="flex"
31+
justifyContent="space-between"
32+
alignItems="center"
33+
mb={2}
34+
>
35+
<Typography
36+
variant={isMobile ? 'h6' : 'h5'}
37+
component="h2"
38+
align="center"
39+
sx={{ flexGrow: 1 }}
40+
>
41+
Changelog
42+
</Typography>
43+
<Button
44+
startIcon={<RefreshIcon />}
45+
onClick={refetch}
46+
disabled={loading}
47+
size="small"
48+
>
49+
Refresh
50+
</Button>
51+
</Box>
52+
53+
{loading && (
54+
<Box display="flex" justifyContent="center" py={4}>
55+
<CircularProgress />
56+
</Box>
57+
)}
58+
59+
{error && (
60+
<Alert severity="warning" sx={{ mb: 2 }}>
61+
<Typography variant="body2" sx={{ mb: 1 }}>
62+
Unable to load changelog: {error}
63+
</Typography>
64+
<Typography variant="body2">
65+
You can view the changelog directly on GitHub:{' '}
66+
<Link
67+
href={CHANGELOG_GITHUB_URL}
68+
target="_blank"
69+
rel="noopener noreferrer"
70+
>
71+
{CHANGELOG_GITHUB_URL}
72+
</Link>
73+
</Typography>
74+
<Button onClick={refetch} size="small" sx={{ mt: 1 }}>
75+
Retry
76+
</Button>
77+
</Alert>
78+
)}
79+
80+
{content && !loading && (
81+
<Box
82+
sx={{
83+
'& h1, & h2, & h3': {
84+
color: 'primary.main',
85+
mt: 2,
86+
mb: 1,
87+
},
88+
'& h1': { fontSize: '1.75rem' },
89+
'& h2': {
90+
fontSize: '1.5rem',
91+
borderBottom: `1px solid ${theme.palette.divider}`,
92+
pb: 1,
93+
},
94+
'& h3': { fontSize: '1.25rem' },
95+
'& a': {
96+
color: 'primary.main',
97+
textDecoration: 'none',
98+
'&:hover': { textDecoration: 'underline' },
99+
},
100+
'& ul, & ol': {
101+
pl: 3,
102+
mb: 2,
103+
},
104+
'& li': {
105+
mb: 0.5,
106+
},
107+
'& code': {
108+
backgroundColor: 'action.hover',
109+
padding: '2px 6px',
110+
borderRadius: 1,
111+
fontFamily: 'monospace',
112+
},
113+
'& pre': {
114+
backgroundColor: 'action.hover',
115+
padding: 2,
116+
borderRadius: 1,
117+
overflow: 'auto',
118+
},
119+
}}
120+
>
121+
<ReactMarkdown>{content}</ReactMarkdown>
122+
</Box>
123+
)}
124+
</CardContent>
125+
</Card>
126+
);
127+
}
128+
129+
export default ChangelogPage;

0 commit comments

Comments
 (0)