]> git.localhorst.tv Git - alttp.git/commitdiff
add helmet
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 10 Feb 2023 15:13:18 +0000 (16:13 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 10 Feb 2023 15:13:18 +0000 (16:13 +0100)
16 files changed:
package-lock.json
package.json
resources/js/components/App.js
resources/js/components/pages/AlttpSeed.js
resources/js/components/pages/AosFront.js
resources/js/components/pages/AosGenerate.js
resources/js/components/pages/AosSeed.js
resources/js/components/pages/AosTracker.js
resources/js/components/pages/Map.js
resources/js/components/pages/NotFound.js
resources/js/components/pages/Technique.js
resources/js/components/pages/Techniques.js
resources/js/components/pages/Tournament.js
resources/js/components/pages/User.js
resources/js/i18n/de.js
resources/js/i18n/en.js

index 6bc7914d3bc9a75b2944291bf70fbd3f2b52a20a..99cc3985917ceaac3744b869d87a1ea6b8f5735f 100644 (file)
@@ -23,6 +23,7 @@
                 "pusher-js": "^7.0.6",
                 "qs": "^6.10.3",
                 "react-bootstrap": "^2.2.0",
+                "react-helmet": "^6.1.0",
                 "react-i18next": "^11.15.6",
                 "react-router-bootstrap": "^0.26.0",
                 "react-router-dom": "^6.2.2",
             "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
             "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
         },
+        "node_modules/react-helmet": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
+            "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
+            "dependencies": {
+                "object-assign": "^4.1.1",
+                "prop-types": "^15.7.2",
+                "react-fast-compare": "^3.1.1",
+                "react-side-effect": "^2.1.0"
+            },
+            "peerDependencies": {
+                "react": ">=16.3.0"
+            }
+        },
+        "node_modules/react-helmet/node_modules/react-fast-compare": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
+            "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
+        },
         "node_modules/react-i18next": {
             "version": "11.15.6",
             "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.6.tgz",
                 "react-dom": ">=16.8"
             }
         },
+        "node_modules/react-side-effect": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
+            "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
+            "peerDependencies": {
+                "react": "^16.3.0 || ^17.0.0 || ^18.0.0"
+            }
+        },
         "node_modules/react-smooth": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.0.tgz",
             "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
             "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
         },
+        "react-helmet": {
+            "version": "6.1.0",
+            "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz",
+            "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==",
+            "requires": {
+                "object-assign": "^4.1.1",
+                "prop-types": "^15.7.2",
+                "react-fast-compare": "^3.1.1",
+                "react-side-effect": "^2.1.0"
+            },
+            "dependencies": {
+                "react-fast-compare": {
+                    "version": "3.2.0",
+                    "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
+                    "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
+                }
+            }
+        },
         "react-i18next": {
             "version": "11.15.6",
             "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.15.6.tgz",
                 "react-router": "6.2.2"
             }
         },
+        "react-side-effect": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz",
+            "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==",
+            "requires": {}
+        },
         "react-smooth": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.0.tgz",
index 4f7a3a6c70a58cc21c9be0b29d36e37fae99c986..2429eb1b931ce5454dbcfc361c51caaf6b26c23e 100644 (file)
@@ -89,6 +89,7 @@
         "pusher-js": "^7.0.6",
         "qs": "^6.10.3",
         "react-bootstrap": "^2.2.0",
+        "react-helmet": "^6.1.0",
         "react-i18next": "^11.15.6",
         "react-router-bootstrap": "^0.26.0",
         "react-router-dom": "^6.2.2",
index d84474cfc65da9c1a2e628f5513c20d457ece183..9656da2a75bd08aa74344af3508daf5e5aba8042 100644 (file)
@@ -1,5 +1,7 @@
 import axios from 'axios';
 import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
 import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
 
 import Footer from './common/Footer';
@@ -17,6 +19,8 @@ import UserContext from '../helpers/UserContext';
 const App = () => {
        const [user, setUser] = useState(null);
 
+       const { t } = useTranslation();
+
        const checkAuth = async () => {
                try {
                        const response = await axios.get('/api/user');
@@ -57,6 +61,10 @@ const App = () => {
        return <BrowserRouter>
                <AlttpBaseRomProvider>
                        <UserContext.Provider value={user}>
+                               <Helmet>
+                                       <title>{t('general.appName')}</title>
+                                       <meta name="description" content={t('general.appDescription')} />
+                               </Helmet>
                                <Header doLogout={doLogout} />
                                <Routes>
                                        <Route path="h/:hash" element={<AlttpSeed />} />
index 96589d4310a18a5f092347a380c74ccace93bc99..43bc020df86c09c3063a66d9484d3ff022b3c65f 100644 (file)
@@ -1,5 +1,6 @@
 import axios from 'axios';
 import React, { useCallback, useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
 import { useParams } from 'react-router-dom';
 
 import NotFound from './NotFound';
@@ -24,7 +25,6 @@ const AosSeed = () => {
                                setError(null);
                                setLoading(false);
                                setSeed(response.data);
-                               window.document.title = response.data.hash;
                        })
                        .catch(error => {
                                setError(error);
@@ -96,6 +96,11 @@ const AosSeed = () => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       {seed ?
+                               <title>{seed.hash}</title>
+                       : null}
+               </Helmet>
                <Seed onRetry={retry} patch={patch} seed={seed} />
        </ErrorBoundary>;
 };
index d308f7d6b2ca3b349a9e1a0c3854cd56cd10c85c..9651110d3c80e134e88cb73e4de8472ea09c0019 100644 (file)
@@ -1,9 +1,9 @@
 import React from 'react';
 import { Button, Col, Container, Row } from 'react-bootstrap';
-import { withTranslation } from 'react-i18next';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
 
 import Icon from '../common/Icon';
-import i18n from '../../i18n';
 
 const authEndpoint = 'https://discord.com/oauth2/authorize';
 const clientId = '951113702839549982';
@@ -11,10 +11,13 @@ const botUrl = `${authEndpoint}?client_id=${clientId}&scope=bot%20applications.c
 const commandUrl = `${authEndpoint}?client_id=${clientId}&scope=applications.commands`;
 
 const AosFront = () => {
-       React.useEffect(() => {
-               window.document.title = 'Aos';
-       }, []);
+       const { t } = useTranslation();
+
        return <Container>
+               <Helmet>
+                       <title>AoS</title>
+                       <meta name="description" content="Castlevania: Aria of Sorrow" />
+               </Helmet>
                <div className="my-5 text-center">
                        <h1>Castlevania: Aria of Sorrow</h1>
                </div>
@@ -28,7 +31,7 @@ const AosFront = () => {
                                >
                                        <Icon.DISCORD />
                                        {' '}
-                                       {i18n.t('aos.randoDiscord')}
+                                       {t('aos.randoDiscord')}
                                </Button>
                        </Col>
                        <Col className="text-center mb-3" sm={6} md={4}>
@@ -38,7 +41,7 @@ const AosFront = () => {
                                        target="_blank"
                                        variant="primary"
                                >
-                                       {i18n.t('aos.randoWeb')}
+                                       {t('aos.randoWeb')}
                                </Button>
                        </Col>
                        <Col className="text-center mb-3" sm={6} md={4}>
@@ -50,7 +53,7 @@ const AosFront = () => {
                                >
                                        <Icon.DISCORD />
                                        {' '}
-                                       {i18n.t('aos.tourneyDiscord')}
+                                       {t('aos.tourneyDiscord')}
                                </Button>
                        </Col>
                        <Col className="text-center mb-3" sm={6} md={4}>
@@ -62,7 +65,7 @@ const AosFront = () => {
                                >
                                        <Icon.DISCORD />
                                        {' '}
-                                       {i18n.t('aos.inviteBot')}
+                                       {t('aos.inviteBot')}
                                </Button>
                        </Col>
                        <Col className="text-center mb-3" sm={6} md={4}>
@@ -74,11 +77,11 @@ const AosFront = () => {
                                >
                                        <Icon.DISCORD />
                                        {' '}
-                                       {i18n.t('aos.inviteCommand')}
+                                       {t('aos.inviteCommand')}
                                </Button>
                        </Col>
                </Row>
        </Container>;
 };
 
-export default withTranslation()(AosFront);
+export default AosFront;
index 0c27b06377cf38660bee402a632eaeaa4dd413f5..b787848f3a826f7420ee5adfec0b5e236c5dfecd 100644 (file)
@@ -1,17 +1,20 @@
 import axios from 'axios';
 import React, { useCallback, useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
+import { useTranslation } from 'react-i18next';
 
 import Generate from '../aos-generate/Generate';
 import ErrorBoundary from '../common/ErrorBoundary';
 import ErrorMessage from '../common/ErrorMessage';
 import Loading from '../common/Loading';
-import i18n from '../../i18n';
 
 const AosGenerate = () => {
        const [error, setError] = useState(null);
        const [loading, setLoading] = useState(true);
        const [presets, setPresets] = useState([]);
 
+       const { t } = useTranslation();
+
        const loadPresets = useCallback(ctrl => {
                axios
                        .get('/api/aos-presets', { signal: ctrl.signal })
@@ -36,10 +39,6 @@ const AosGenerate = () => {
                };
        }, []);
 
-       useEffect(() => {
-               window.document.title = i18n.t('aosGenerate.heading');
-       }, [i18n.language]);
-
        if (loading) {
                return <Loading />;
        }
@@ -49,6 +48,9 @@ const AosGenerate = () => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       <title>{t('aosGenerate.heading')}</title>
+               </Helmet>
                <Generate presets={presets} />
        </ErrorBoundary>;
 };
index 165a2bedbea81e17a0576b53b6e761c578dc3e4d..ece8406ea69774b6f7965625f8e4decbe25d3e59 100644 (file)
@@ -1,5 +1,6 @@
 import axios from 'axios';
 import React, { useCallback, useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
 import { useParams } from 'react-router-dom';
 
 import NotFound from './NotFound';
@@ -24,7 +25,6 @@ const AosSeed = () => {
                                setError(null);
                                setLoading(false);
                                setSeed(response.data);
-                               window.document.title = response.data.hash;
                        })
                        .catch(error => {
                                setError(error);
@@ -96,6 +96,11 @@ const AosSeed = () => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       {seed ?
+                               <title>{seed.hash}</title>
+                       : null}
+               </Helmet>
                <Seed onRetry={retry} patch={patch} seed={seed} />
        </ErrorBoundary>;
 };
index 8eafd1f66e1dc2c3a8944395f0214a64dada2f01..5273d0bc3a40f296499230a68cdfcdcea75d86d9 100644 (file)
@@ -1,14 +1,14 @@
 import React from 'react';
 import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
 
 import Map from '../aos-tracker/Map';
 
 const AosTracker = () => {
-       React.useEffect(() => {
-               window.document.title = 'Aos Tracker';
-       }, []);
-
        return <Container fluid>
+               <Helmet>
+                       <title>AoS Tracker</title>
+               </Helmet>
                <Map />
        </Container>;
 };
index 841227cd4ac45a8c5ac0433359748b4bf0c3ca2d..cbd3581d8d4a90c1266d2c74a4bff2451a1fcc95 100644 (file)
@@ -1,5 +1,6 @@
 import React from 'react';
 import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
 import { useTranslation } from 'react-i18next';
 
 import Buttons from '../map/Buttons';
@@ -11,11 +12,11 @@ const Map = () => {
        const container = React.useRef();
        const { t } = useTranslation();
 
-       React.useEffect(() => {
-               window.document.title = t('map.heading');
-       }, []);
-
        return <Container fluid>
+               <Helmet>
+                       <title>{t('map.heading')}</title>
+                       <meta name="description" content={t('map.description')} />
+               </Helmet>
                <OpenSeadragon ref={container}>
                        <div className="d-flex align-items-center justify-content-between">
                                <h1>{t('map.heading')}</h1>
index 5e2059615231af6b77472783f3211310f3d6a779..19ccc7232ea2f8be9d4593da24f67f42a5516ba6 100644 (file)
@@ -1,8 +1,13 @@
 import React from 'react';
+import { Helmet } from 'react-helmet';
 
-const NotFound = () => <div>
-       <h1>Not Found</h1>
-       <p>Sorry</p>
-</div>;
+const NotFound = () =>
+       <div>
+               <Helmet>
+                       <title>Not Found</title>
+               </Helmet>
+               <h1>Not Found</h1>
+               <p>Sorry</p>
+       </div>;
 
 export default NotFound;
index 8c202ed44ad1fce81a80e4b1f89e16481eff8944..e38061106a5d582bf9368fe05d6f6ab34e1a8f91 100644 (file)
@@ -1,5 +1,6 @@
 import axios from 'axios';
 import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
 import { withTranslation } from 'react-i18next';
 import { useParams } from 'react-router-dom';
 
@@ -39,12 +40,6 @@ const Technique = () => {
                };
        }, [name]);
 
-       useEffect(() => {
-               if (technique) {
-                       window.document.title = getTranslation(technique, 'title', i18n.language);
-               }
-       }, [technique, i18n.language]);
-
        if (loading) {
                return <Loading />;
        }
@@ -58,6 +53,10 @@ const Technique = () => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       <title>{getTranslation(technique, 'title', i18n.language)}</title>
+                       <meta name="description" content={getTranslation(technique, 'short', i18n.language)} />
+               </Helmet>
                <Detail technique={technique} />
        </ErrorBoundary>;
 };
index ddecf5603e7752130588f2edad28fe90d9db3ebe..a89c3a252e01f560449110a2739217b2487e6d70 100644 (file)
@@ -1,6 +1,7 @@
 import axios from 'axios';
 import PropTypes from 'prop-types';
 import React from 'react';
+import { Helmet } from 'react-helmet';
 import { withTranslation } from 'react-i18next';
 
 import NotFound from './NotFound';
@@ -36,7 +37,6 @@ const Techniques = ({ namespace, type }) => {
                if (!techniques.length) {
                        setLoading(true);
                }
-               window.document.title = i18n.t(`${namespace}.heading`);
                axios
                        .get(`/api/content`, {
                                params: {
@@ -63,7 +63,6 @@ const Techniques = ({ namespace, type }) => {
        }, [filter, namespace, type]);
 
        React.useEffect(() => {
-               window.document.title = i18n.t(`${namespace}.heading`);
                setTechniques(t => [...t].sort(compareTranslation('title', i18n.language)));
        }, [namespace, i18n.language]);
 
@@ -80,6 +79,10 @@ const Techniques = ({ namespace, type }) => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       <title>{i18n.t(`${namespace}.heading`)}</title>
+                       <meta name="description" content={i18n.t(`${namespace}.description`)} />
+               </Helmet>
                <Overview
                        filter={filter}
                        namespace={namespace}
index 061db2208a8a67ba35ddd05ff9fced3afd1323bf..7abd5e437b25bf8454e49810d9a2e12cde88a30a 100644 (file)
@@ -1,5 +1,6 @@
 import axios from 'axios';
 import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
 import { useParams } from 'react-router-dom';
 
 import ErrorBoundary from '../common/ErrorBoundary';
@@ -34,7 +35,6 @@ const Tournament = () => {
                                setError(null);
                                setLoading(false);
                                setTournament(sortParticipants(response.data));
-                               window.document.title = response.data.title;
                        })
                        .catch(error => {
                                setError(error);
@@ -128,6 +128,9 @@ const Tournament = () => {
        };
 
        return <ErrorBoundary>
+               <Helmet>
+                       <title>{tournament.title}</title>
+               </Helmet>
                <Detail addRound={addRound} tournament={tournament} />
        </ErrorBoundary>;
 };
index 3e868babb57f852190bf48e5d40511f1476d1361..b481112456813a4613a572702bd9b460880969c1 100644 (file)
@@ -1,5 +1,6 @@
 import axios from 'axios';
 import React, { useEffect, useState } from 'react';
+import { Helmet } from 'react-helmet';
 import { useParams } from 'react-router-dom';
 
 import ErrorBoundary from '../common/ErrorBoundary';
@@ -25,7 +26,6 @@ const User = () => {
                                setError(null);
                                setLoading(false);
                                setUser(response.data);
-                               window.document.title = response.data.nickname || response.data.username;
                        })
                        .catch(error => {
                                setError(error);
@@ -64,6 +64,9 @@ const User = () => {
        }
 
        return <ErrorBoundary>
+               <Helmet>
+                       <title>{user.nickname || user.username}</title>
+               </Helmet>
                <Profile user={user} />
        </ErrorBoundary>;
 };
index f130c08ed094c32fad16cc6d95090cf9667cb2fe..fef88f452d8319fd3455bdb491f102813aeb36f8 100644 (file)
@@ -292,6 +292,7 @@ export default {
                },
                general: {
                        anonymous: 'Anonym',
+                       appDescription: 'Turniere und Tutorials für The Legend of Zelda: A Link to the Past Randomizer',
                        appName: 'ALttP',
                },
                icon: {
@@ -379,6 +380,7 @@ export default {
                        },
                },
                map: {
+                       description: 'Karten von The Legend of Zelda: A Link to the Past',
                        dwLong: 'Dark World',
                        dwShort: 'DW',
                        goToLocation: 'Zur Stelle springen',
@@ -509,6 +511,7 @@ export default {
                        heading: 'Regelsätze',
                },
                techniques: {
+                       description: 'Tutorials für The Legend of Zelda: A Link to the Past Randomizer',
                        heading: 'Techniken',
                        lastModified: 'Zuletzt geändert: {{ date, L }}',
                        requirements: 'Erfordert: ',
index a7ca19cc1afca5c6bd45259c3e158a711fca85a8..6820f30a3b08190e83f79a55fcbd2393cda20198 100644 (file)
@@ -292,6 +292,7 @@ export default {
                },
                general: {
                        anonymous: 'Anonym',
+                       appDescription: 'Tournaments and tutorials for The Legend of Zelda: A Link to the Past Randomizer',
                        appName: 'ALttP',
                },
                icon: {
@@ -379,6 +380,7 @@ export default {
                        },
                },
                map: {
+                       description: 'Maps of The Legend of Zelda: A Link to the Past',
                        dwLong: 'Dark World',
                        dwShort: 'DW',
                        goToLocation: 'Go to location',
@@ -509,6 +511,7 @@ export default {
                        heading: 'Rulesets',
                },
                techniques: {
+                       description: 'Tutorials for The Legend of Zelda: A Link to the Past Randomizer',
                        heading: 'Techniques',
                        lastModified: 'Last modified: {{ date, L }}',
                        requirements: 'Requires: ',