feat(frontend): upgrade react-router-dom 5→6 (Phase 3)

- Update package.json: react-router-dom ^5.2.0→^6.28.0
- Migrate App.js: Switch→Routes, component→element props, path="*" for 404
- Migrate 5 pages: useHistory→useNavigate, history.push()→navigate()
  - GroupsOverviewPage.js (4x navigate)
  - ModerationGroupsPage.js (1x navigate)
  - ModerationGroupImagesPage.js (2x navigate)
  - PublicGroupImagesPage.js (import updated)
  - SlideshowPage.js (4x navigate + keyboard handler)
- Regenerate package-lock.json with react-router v6

 Tested: Production build 254.46 KB gzip (+1.17 KB)
 Manual test: Navigation, moderation routing, slideshow ESC - all working

Phase 3 complete: Modern react-router v6 with improved routing API.
This commit is contained in:
Matthias Lotz 2025-10-28 22:59:59 +01:00
parent 93534587d2
commit 5ba463427b
8 changed files with 61 additions and 137 deletions

View File

@ -20,7 +20,7 @@
"react-dropzone": "^11.3.1",
"react-helmet": "^6.1.0",
"react-lottie": "^1.2.3",
"react-router-dom": "^5.2.0",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"sass": "^1.32.8",
"sweetalert2": "^10.15.6",
@ -3840,6 +3840,14 @@
"node": ">=8.9.0"
}
},
"node_modules/@remix-run/router": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -9657,18 +9665,6 @@
"node": "*"
}
},
"node_modules/history": {
"version": "4.10.1",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"license": "BSD-3-Clause",
@ -17020,69 +17016,35 @@
}
},
"node_modules/react-router": {
"version": "5.2.0",
"license": "MIT",
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
"dependencies": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.23.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "5.2.0",
"license": "MIT",
"version": "6.30.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
"dependencies": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
"@remix-run/router": "1.23.0",
"react-router": "6.30.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=15"
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-router/node_modules/isarray": {
"version": "0.0.1",
"license": "MIT"
},
"node_modules/react-router/node_modules/mini-create-react-context": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz",
"integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dependencies": {
"@babel/runtime": "^7.12.1",
"tiny-warning": "^1.0.3"
},
"peerDependencies": {
"prop-types": "^15.0.0",
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/react-router/node_modules/path-to-regexp": {
"version": "1.8.0",
"license": "MIT",
"dependencies": {
"isarray": "0.0.1"
}
},
"node_modules/react-router/node_modules/react-is": {
"version": "16.13.1",
"license": "MIT"
},
"node_modules/react-scripts": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@ -17518,10 +17480,6 @@
"node": ">=8"
}
},
"node_modules/resolve-pathname": {
"version": "3.0.0",
"license": "MIT"
},
"node_modules/resolve-url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz",
@ -18966,19 +18924,6 @@
}
}
},
"node_modules/tailwindcss/node_modules/yaml": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/tapable": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
@ -19149,10 +19094,6 @@
"license": "MIT",
"optional": true
},
"node_modules/tiny-invariant": {
"version": "1.1.0",
"license": "MIT"
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"license": "MIT"
@ -19396,19 +19337,6 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "3.9.10",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
"integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/typescript-plugin-styled-components": {
"version": "1.4.4",
"license": "MIT",
@ -19607,10 +19535,6 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/value-equal": {
"version": "1.0.1",
"license": "MIT"
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@ -15,7 +15,7 @@
"react-dropzone": "^11.3.1",
"react-helmet": "^6.1.0",
"react-lottie": "^1.2.3",
"react-router-dom": "^5.2.0",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"sass": "^1.32.8",
"sweetalert2": "^10.15.6",

View File

@ -1,5 +1,5 @@
import './App.css';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
// Pages
import MultiUploadPage from './Components/Pages/MultiUploadPage';
@ -13,15 +13,15 @@ import FZF from './Components/Pages/404Page.js'
function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={MultiUploadPage} />
<Route path="/slideshow" component={SlideshowPage} />
<Route path="/groups/:groupId" component={PublicGroupImagesPage} />
<Route path="/groups" component={GroupsOverviewPage} />
<Route path="/moderation" exact component={ModerationGroupsPage} />
<Route path="/moderation/groups/:groupId" component={ModerationGroupImagesPage} />
<Route component={FZF} />
</Switch>
<Routes>
<Route path="/" exact element={<MultiUploadPage />} />
<Route path="/slideshow" element={<SlideshowPage />} />
<Route path="/groups/:groupId" element={<PublicGroupImagesPage />} />
<Route path="/groups" element={<GroupsOverviewPage />} />
<Route path="/moderation" exact element={<ModerationGroupsPage />} />
<Route path="/moderation/groups/:groupId" element={<ModerationGroupImagesPage />} />
<Route path="*" element={<FZF />} />
</Routes>
</Router>
);
}

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import {
Container,
@ -25,7 +25,7 @@ import '../../App.css';
function GroupsOverviewPage() {
// use CSS classes from GroupsOverviewPage.css
const history = useHistory();
const navigate = useNavigate();
const [groups, setGroups] = useState([]);
const [loading, setLoading] = useState(true);
@ -50,19 +50,19 @@ function GroupsOverviewPage() {
};
const handleViewSlideshow = (groupId) => {
history.push(`/slideshow/${groupId}`);
navigate(`/slideshow/${groupId}`);
};
const handleViewGroup = (groupId) => {
history.push(`/groups/${groupId}`);
navigate(`/groups/${groupId}`);
};
const handleCreateNew = () => {
history.push('/multi-upload');
navigate('/multi-upload');
};
const handleGoHome = () => {
history.push('/');
navigate('/');
};
const formatDate = (dateString) => {

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Container } from '@material-ui/core';
import Swal from 'sweetalert2/dist/sweetalert2.js';
import 'sweetalert2/src/sweetalert2.scss';
@ -15,7 +15,7 @@ import DescriptionInput from '../ComponentUtils/MultiUpload/DescriptionInput';
const ModerationGroupImagesPage = () => {
const { groupId } = useParams();
const history = useHistory();
const navigate = useNavigate();
const [group, setGroup] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@ -86,7 +86,7 @@ const ModerationGroupImagesPage = () => {
}
Swal.fire({ icon: 'success', title: 'Gruppe erfolgreich aktualisiert', timer: 1500, showConfirmButton: false });
history.push('/moderation');
navigate('/moderation');
} catch (e) {
console.error(e);
Swal.fire({ icon: 'error', title: 'Fehler beim Speichern', text: e.message });
@ -144,7 +144,7 @@ const ModerationGroupImagesPage = () => {
<DescriptionInput metadata={metadata} onMetadataChange={setMetadata} />
<div className="action-buttons">
<Button className="btn btn-secondary" onClick={() => history.push('/moderation')}> Zurück</Button>
<Button className="btn btn-secondary" onClick={() => navigate('/moderation')}> Zurück</Button>
<Button className="primary-button" onClick={handleSave} disabled={saving}>{saving ? 'Speichern...' : 'Speichern'}</Button>
</div>
</>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Helmet } from 'react-helmet';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Container } from '@material-ui/core';
import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer';
@ -12,7 +12,7 @@ const ModerationGroupsPage = () => {
const [error, setError] = useState(null);
const [selectedGroup, setSelectedGroup] = useState(null);
const [showImages, setShowImages] = useState(false);
const history = useHistory();
const navigate = useNavigate();
useEffect(() => {
loadModerationGroups();
@ -134,7 +134,7 @@ const ModerationGroupsPage = () => {
// Navigate to the dedicated group images page
const viewGroupImages = (group) => {
history.push(`/moderation/groups/${group.groupId}`);
navigate(`/moderation/groups/${group.groupId}`);
};
if (loading) {

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Container } from '@material-ui/core';
import Navbar from '../ComponentUtils/Headers/Navbar';
import Footer from '../ComponentUtils/Footer';
@ -9,7 +9,7 @@ import ImageGallery from '../ComponentUtils/ImageGallery';
const PublicGroupImagesPage = () => {
const { groupId } = useParams();
const history = useHistory();
const navigate = useNavigate();
const [group, setGroup] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { makeStyles } from '@material-ui/core/styles';
import {
Typography,
@ -108,7 +108,7 @@ const useStyles = makeStyles({
function SlideshowPage() {
const classes = useStyles();
const history = useHistory();
const navigate = useNavigate();
const [allGroups, setAllGroups] = useState([]);
const [currentGroupIndex, setCurrentGroupIndex] = useState(0);
@ -184,7 +184,7 @@ function SlideshowPage() {
const handleKeyPress = (event) => {
switch (event.key) {
case 'Escape':
history.push('/');
navigate('/');
break;
case ' ':
case 'ArrowRight':
@ -197,7 +197,7 @@ function SlideshowPage() {
document.addEventListener('keydown', handleKeyPress);
return () => document.removeEventListener('keydown', handleKeyPress);
}, [nextImage, history]);
}, [nextImage, navigate]);
// Aktuelle Gruppe und Bild
const currentGroup = allGroups[currentGroupIndex];
@ -221,7 +221,7 @@ function SlideshowPage() {
<Typography style={{ color: 'white', fontSize: '24px' }}>{error}</Typography>
<IconButton
className={classes.homeButton}
onClick={() => history.push('/')}
onClick={() => navigate('/')}
title="Zur Startseite"
>
<HomeIcon />
@ -240,7 +240,7 @@ function SlideshowPage() {
</Typography>
<IconButton
className={classes.homeButton}
onClick={() => history.push('/')}
onClick={() => navigate('/')}
title="Zur Startseite"
>
<HomeIcon />
@ -255,7 +255,7 @@ function SlideshowPage() {
{/* Navigation Buttons */}
<IconButton
className={classes.homeButton}
onClick={() => history.push('/')}
onClick={() => navigate('/')}
title="Zur Startseite"
>
<HomeIcon />
@ -263,7 +263,7 @@ function SlideshowPage() {
<IconButton
className={classes.exitButton}
onClick={() => history.push('/')}
onClick={() => navigate('/')}
title="Slideshow beenden"
>
<ExitIcon />