"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",
"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",
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';
const App = () => {
const [user, setUser] = useState(null);
+ const { t } = useTranslation();
+
const checkAuth = async () => {
try {
const response = await axios.get('/api/user');
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 />} />
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';
setError(null);
setLoading(false);
setSeed(response.data);
- window.document.title = response.data.hash;
})
.catch(error => {
setError(error);
}
return <ErrorBoundary>
+ <Helmet>
+ {seed ?
+ <title>{seed.hash}</title>
+ : null}
+ </Helmet>
<Seed onRetry={retry} patch={patch} seed={seed} />
</ErrorBoundary>;
};
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';
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>
>
<Icon.DISCORD />
{' '}
- {i18n.t('aos.randoDiscord')}
+ {t('aos.randoDiscord')}
</Button>
</Col>
<Col className="text-center mb-3" sm={6} md={4}>
target="_blank"
variant="primary"
>
- {i18n.t('aos.randoWeb')}
+ {t('aos.randoWeb')}
</Button>
</Col>
<Col className="text-center mb-3" sm={6} md={4}>
>
<Icon.DISCORD />
{' '}
- {i18n.t('aos.tourneyDiscord')}
+ {t('aos.tourneyDiscord')}
</Button>
</Col>
<Col className="text-center mb-3" sm={6} md={4}>
>
<Icon.DISCORD />
{' '}
- {i18n.t('aos.inviteBot')}
+ {t('aos.inviteBot')}
</Button>
</Col>
<Col className="text-center mb-3" sm={6} md={4}>
>
<Icon.DISCORD />
{' '}
- {i18n.t('aos.inviteCommand')}
+ {t('aos.inviteCommand')}
</Button>
</Col>
</Row>
</Container>;
};
-export default withTranslation()(AosFront);
+export default AosFront;
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 })
};
}, []);
- useEffect(() => {
- window.document.title = i18n.t('aosGenerate.heading');
- }, [i18n.language]);
-
if (loading) {
return <Loading />;
}
}
return <ErrorBoundary>
+ <Helmet>
+ <title>{t('aosGenerate.heading')}</title>
+ </Helmet>
<Generate presets={presets} />
</ErrorBoundary>;
};
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';
setError(null);
setLoading(false);
setSeed(response.data);
- window.document.title = response.data.hash;
})
.catch(error => {
setError(error);
}
return <ErrorBoundary>
+ <Helmet>
+ {seed ?
+ <title>{seed.hash}</title>
+ : null}
+ </Helmet>
<Seed onRetry={retry} patch={patch} seed={seed} />
</ErrorBoundary>;
};
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>;
};
import React from 'react';
import { Container } from 'react-bootstrap';
+import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import Buttons from '../map/Buttons';
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>
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;
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';
};
}, [name]);
- useEffect(() => {
- if (technique) {
- window.document.title = getTranslation(technique, 'title', i18n.language);
- }
- }, [technique, i18n.language]);
-
if (loading) {
return <Loading />;
}
}
return <ErrorBoundary>
+ <Helmet>
+ <title>{getTranslation(technique, 'title', i18n.language)}</title>
+ <meta name="description" content={getTranslation(technique, 'short', i18n.language)} />
+ </Helmet>
<Detail technique={technique} />
</ErrorBoundary>;
};
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';
if (!techniques.length) {
setLoading(true);
}
- window.document.title = i18n.t(`${namespace}.heading`);
axios
.get(`/api/content`, {
params: {
}, [filter, namespace, type]);
React.useEffect(() => {
- window.document.title = i18n.t(`${namespace}.heading`);
setTechniques(t => [...t].sort(compareTranslation('title', i18n.language)));
}, [namespace, i18n.language]);
}
return <ErrorBoundary>
+ <Helmet>
+ <title>{i18n.t(`${namespace}.heading`)}</title>
+ <meta name="description" content={i18n.t(`${namespace}.description`)} />
+ </Helmet>
<Overview
filter={filter}
namespace={namespace}
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';
setError(null);
setLoading(false);
setTournament(sortParticipants(response.data));
- window.document.title = response.data.title;
})
.catch(error => {
setError(error);
};
return <ErrorBoundary>
+ <Helmet>
+ <title>{tournament.title}</title>
+ </Helmet>
<Detail addRound={addRound} tournament={tournament} />
</ErrorBoundary>;
};
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';
setError(null);
setLoading(false);
setUser(response.data);
- window.document.title = response.data.nickname || response.data.username;
})
.catch(error => {
setError(error);
}
return <ErrorBoundary>
+ <Helmet>
+ <title>{user.nickname || user.username}</title>
+ </Helmet>
<Profile user={user} />
</ErrorBoundary>;
};
},
general: {
anonymous: 'Anonym',
+ appDescription: 'Turniere und Tutorials für The Legend of Zelda: A Link to the Past Randomizer',
appName: 'ALttP',
},
icon: {
},
},
map: {
+ description: 'Karten von The Legend of Zelda: A Link to the Past',
dwLong: 'Dark World',
dwShort: 'DW',
goToLocation: 'Zur Stelle springen',
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: ',
},
general: {
anonymous: 'Anonym',
+ appDescription: 'Tournaments and tutorials for The Legend of Zelda: A Link to the Past Randomizer',
appName: 'ALttP',
},
icon: {
},
},
map: {
+ description: 'Maps of The Legend of Zelda: A Link to the Past',
dwLong: 'Dark World',
dwShort: 'DW',
goToLocation: 'Go to location',
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: ',