mirror of
https://github.com/Unkn0wnCat/data-toolbox-site.git
synced 2025-05-25 21:56:11 +02:00
Bootstrap Kevin's Data-Toolbox
This commit is contained in:
parent
4da936864f
commit
3f4d6da00b
30 changed files with 1255 additions and 120 deletions
|
@ -6,8 +6,16 @@
|
|||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"i18next": "^20.2.1",
|
||||
"i18next-browser-languagedetector": "^6.1.0",
|
||||
"i18next-http-backend": "^1.2.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^11.8.12",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
|
|
|
@ -7,37 +7,19 @@
|
|||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Tools crafted for your enjoyment."
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Kevin's Data-Toolbox</title>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700;800&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<noscript>You need to enable JavaScript to run Kevin's Data-Toolbox.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
Hello you source-dwelling human. If you like source-code, you should check out my GitHub-Account at https://github.com/Unkn0wnCat
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
|
34
public/locales/de/translation.json
Normal file
34
public/locales/de/translation.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"site": {
|
||||
"title": "Kevins Datenkasten",
|
||||
"navigation": {
|
||||
"tools": "Werkzeuge",
|
||||
"about": "Über"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"heroPretitle": "Kevins",
|
||||
"heroTitle": "Datenkasten",
|
||||
"heroSubtitle": "Dein 1-Stopp-Daten-Shop!"
|
||||
},
|
||||
"tools": {
|
||||
"toolCategories": "Kategorien",
|
||||
"toolList": "Liste von Werkzeugen",
|
||||
"noresults": "Keine Ergebnisse.",
|
||||
"categories": {
|
||||
"everything": "Alles"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Über Kevins Datenkasten",
|
||||
"p1": "Kevins Datenkasten ist meine Kollektion von kleinen, nützlichen Werkzeugen. Schau doch mal ob du was nüzliches findest!",
|
||||
"p2": "Ich werde mehr Werkzeuge hinzufügen sobald ich diese fertig habe, also schau regelmäßig wieder rein!",
|
||||
"morebyme": "Mehr von mir",
|
||||
"visitKevinKdev": "Schau dir meine Website unter <1>KevinK.dev</1> an!"
|
||||
|
||||
},
|
||||
"system": {
|
||||
"notfound": "Seite nicht gefunden",
|
||||
"language": "Sprache"
|
||||
}
|
||||
}
|
34
public/locales/en/translation.json
Normal file
34
public/locales/en/translation.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"site": {
|
||||
"title": "Kevin's Data-Toolbox",
|
||||
"navigation": {
|
||||
"tools": "Tools",
|
||||
"about": "About"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"heroPretitle": "Kevin's",
|
||||
"heroTitle": "Data-Toolbox",
|
||||
"heroSubtitle": "Your One-Stop-Data-Shop!"
|
||||
},
|
||||
"tools": {
|
||||
"toolCategories": "Categories",
|
||||
"toolList": "List of tools",
|
||||
"noresults": "No results have been found.",
|
||||
"categories": {
|
||||
"everything": "Everything"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "About Kevin's Data-Toolbox",
|
||||
"p1": "Kevin's Data-Toolbox is my collection of useful small tools. Feel free to look through them to see if there anything is of use to you!",
|
||||
"p2": "There will be more tools added over time as I create them, so check back regularly to learn about new tools!",
|
||||
"morebyme": "More By Me",
|
||||
"visitKevinKdev": "Check out my website <1>KevinK.dev</1>!"
|
||||
|
||||
},
|
||||
"system": {
|
||||
"notfound": "Page Not Found",
|
||||
"language": "Language"
|
||||
}
|
||||
}
|
38
src/App.css
38
src/App.css
|
@ -1,38 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
49
src/App.js
49
src/App.js
|
@ -1,24 +1,37 @@
|
|||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import React, { lazy, Suspense } from "react";
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
Route
|
||||
} from "react-router-dom";
|
||||
import Navigation from "./components/Navigation";
|
||||
|
||||
import * as styles from "./App.module.scss";
|
||||
import NotFoundPage from "./pages/NotFound";
|
||||
import ToolLoader from "./tools/ToolLoader";
|
||||
|
||||
const HomePage = lazy(() => import('./pages/Home'));
|
||||
const ToolsPage = lazy(() => import('./pages/Tools'));
|
||||
const AboutPage = lazy(() => import('./pages/About'));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<Suspense fallback="Kevin's Data-Toolbox is loading...">
|
||||
<Router>
|
||||
<div className={styles.appContainer}>
|
||||
<Navigation/>
|
||||
<Suspense fallback="Kevin's Data-Toolbox is loading...">
|
||||
<Switch>
|
||||
<Route path="/about" component={AboutPage} />
|
||||
<Route path="/tools/:category?" component={ToolsPage} />
|
||||
<Route path="/tool/:tool" component={ToolLoader} />
|
||||
<Route path="/" exact component={HomePage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</div>
|
||||
</Router>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
13
src/App.module.scss
Normal file
13
src/App.module.scss
Normal file
|
@ -0,0 +1,13 @@
|
|||
@import "./common";
|
||||
|
||||
.appContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
|
||||
padding-top: $layoutNavigationHeight;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
/*test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
const linkElement = screen.getByText(/about/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
*/
|
36
src/_common.scss
Normal file
36
src/_common.scss
Normal file
|
@ -0,0 +1,36 @@
|
|||
$fontFamily: 'Open Sans', sans-serif;
|
||||
|
||||
$colorAccent: #f58428;
|
||||
|
||||
$layoutWidth: 1200px;
|
||||
$layoutPadding: 20px;
|
||||
$layoutNavigationHeight: 50px;
|
||||
|
||||
@mixin layoutBox() {
|
||||
max-width: $layoutWidth;
|
||||
padding: 0 $layoutPadding;
|
||||
margin: 0 auto;
|
||||
|
||||
}
|
||||
|
||||
.layoutBox {
|
||||
@include layoutBox();
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
font-weight: 800;
|
||||
font-size: 3em;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.flexList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
> * {
|
||||
margin: $layoutPadding;
|
||||
}
|
||||
}
|
25
src/components/LanguageChooser.js
Normal file
25
src/components/LanguageChooser.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as styles from "./LanguageChooser.module.scss";
|
||||
|
||||
const LanguageChooser = (props) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div className={styles.lChooser + " " + (props.active ? styles.active : "")}>
|
||||
<div>
|
||||
<span className={styles.title}>{t("system.language")}</span>
|
||||
<Link to={"#"} onClick={() => {i18n.changeLanguage("en"); props.onDone()}}>English</Link>
|
||||
<Link to={"#"} onClick={() => {i18n.changeLanguage("de"); props.onDone()}}>Deutsch</Link>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
export default LanguageChooser;
|
||||
|
45
src/components/LanguageChooser.module.scss
Normal file
45
src/components/LanguageChooser.module.scss
Normal file
|
@ -0,0 +1,45 @@
|
|||
@import "../common";
|
||||
|
||||
.lChooser {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
background-color: #000000e0;
|
||||
backdrop-filter: blur(5px);
|
||||
color: white;
|
||||
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
transition: opacity .25s;
|
||||
|
||||
&.active {
|
||||
|
||||
opacity: 1;
|
||||
pointer-events: visible;
|
||||
|
||||
}
|
||||
|
||||
> div {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
display: flex;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: $layoutPadding;
|
||||
}
|
||||
}
|
35
src/components/LinkBox.js
Normal file
35
src/components/LinkBox.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import * as styles from "./LinkBox.module.scss";
|
||||
|
||||
const LinkBox = (props) => {
|
||||
return (!props.external ?
|
||||
<Link className={styles.linkBox + (props.small ? " "+styles.small : "") + (props.highlight ? " "+styles.highlight : "")} to={props.to}>
|
||||
<div className={styles.lbIcon}><props.icon/></div>
|
||||
<span className={styles.lbText}>{props.text}</span>
|
||||
</Link> :
|
||||
<a className={styles.linkBox + (props.small ? " "+styles.small : "") + (props.highlight ? " "+styles.highlight : "")} href={props.to}>
|
||||
<div className={styles.lbIcon}><props.icon/></div>
|
||||
<span className={styles.lbText}>{props.text}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
LinkBox.defaultProps = {
|
||||
"small": false,
|
||||
"highlight": false,
|
||||
"external": false
|
||||
}
|
||||
|
||||
LinkBox.props = {
|
||||
"to": PropTypes.string,
|
||||
"text": PropTypes.string.isRequired,
|
||||
"icon": PropTypes.object.isRequired,
|
||||
"small": PropTypes.bool,
|
||||
"highlight": PropTypes.bool,
|
||||
"external": PropTypes.bool
|
||||
};
|
||||
|
||||
export default LinkBox;
|
56
src/components/LinkBox.module.scss
Normal file
56
src/components/LinkBox.module.scss
Normal file
|
@ -0,0 +1,56 @@
|
|||
@import "../common";
|
||||
|
||||
.linkBox {
|
||||
padding: 30px;
|
||||
color: $colorAccent;
|
||||
|
||||
text-decoration: none;
|
||||
box-shadow: 0 0 10px rgba(black, .25);
|
||||
border-radius: 10px;
|
||||
width: 250px;
|
||||
|
||||
.lbIcon {
|
||||
svg {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.lbText {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.highlight {
|
||||
background-color: $colorAccent;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: unset;
|
||||
border-radius: 100px;
|
||||
display: flex;
|
||||
padding: 5px 15px;
|
||||
line-height: 25px;
|
||||
align-items: center;
|
||||
|
||||
.lbIcon {
|
||||
height: 25px;
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 2.5px;
|
||||
}
|
||||
|
||||
margin-bottom: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.lbText {
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
}
|
29
src/components/Navigation.js
Normal file
29
src/components/Navigation.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as styles from "./Navigation.module.scss";
|
||||
import { Globe } from "react-feather";
|
||||
import LanguageChooser from "./LanguageChooser";
|
||||
|
||||
const Navigation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [langChooserActive, setLangChooserActive] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={styles.navigation}>
|
||||
<nav>
|
||||
<Link to={"/"}>{t("site.title")}</Link>
|
||||
<span className={styles.spacer}></span>
|
||||
<Link to={"/tools"}>{t("site.navigation.tools")}</Link>
|
||||
<Link to={"/about"}>{t("site.navigation.about")}</Link>
|
||||
<Link to={"#"} onClick={() => {setLangChooserActive(true)}}><Globe/></Link>
|
||||
<LanguageChooser active={langChooserActive} onDone={() => {setLangChooserActive(false)}} />
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Navigation;
|
39
src/components/Navigation.module.scss
Normal file
39
src/components/Navigation.module.scss
Normal file
|
@ -0,0 +1,39 @@
|
|||
@import "../common";
|
||||
|
||||
.navigation {
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $layoutNavigationHeight;
|
||||
background-color: #1c1c1c;
|
||||
z-index: 100;
|
||||
|
||||
@supports(backdrop-filter: blur(5px)) {
|
||||
background-color: #000000e0;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
|
||||
> nav {
|
||||
max-width: $layoutWidth;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
height: $layoutNavigationHeight;
|
||||
align-items: stretch;
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> a {
|
||||
padding: 0 $layoutPadding;
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
31
src/i18n.js
Normal file
31
src/i18n.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import Backend from 'i18next-http-backend';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
// don't want to use this?
|
||||
// have a look at the Quick start guide
|
||||
// for passing in lng and translations on init
|
||||
|
||||
i18n
|
||||
// load translation using http -> see /public/locales (i.e. https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
|
||||
// learn more: https://github.com/i18next/i18next-http-backend
|
||||
.use(Backend)
|
||||
// detect user language
|
||||
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
||||
.use(LanguageDetector)
|
||||
// pass the i18n instance to react-i18next.
|
||||
.use(initReactI18next)
|
||||
// init i18next
|
||||
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default i18n;
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import './index.scss';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
import './i18n';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
|
|
18
src/index.scss
Normal file
18
src/index.scss
Normal file
|
@ -0,0 +1,18 @@
|
|||
@import "./common";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, #root {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: $fontFamily;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $colorAccent;
|
||||
text-decoration: underline dotted currentColor;
|
||||
}
|
27
src/pages/About.js
Normal file
27
src/pages/About.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
|
||||
import * as styles from "./About.module.scss";
|
||||
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
|
||||
const AboutPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return ([
|
||||
<div>
|
||||
<div className={styles.layoutBox}>
|
||||
<h1>{t("about.title")}</h1>
|
||||
|
||||
<p>{t("about.p1")}</p>
|
||||
|
||||
<p>{t("about.p2")}</p>
|
||||
|
||||
<h2>{t("about.morebyme")}</h2>
|
||||
|
||||
<p><Trans i18nKey={"about.visitKevinKdev"}> <a href="https://kevink.dev"> </a> </Trans></p>
|
||||
</div>
|
||||
</div>
|
||||
]);
|
||||
}
|
||||
|
||||
export default AboutPage;
|
1
src/pages/About.module.scss
Normal file
1
src/pages/About.module.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "../common";
|
33
src/pages/Home.js
Normal file
33
src/pages/Home.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from "react";
|
||||
import { List } from 'react-feather';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as styles from "./Home.module.scss";
|
||||
import LinkBox from "../components/LinkBox";
|
||||
|
||||
const HomePage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return ([
|
||||
<div className={styles.heroBox}>
|
||||
<div className={styles.layoutBox}>
|
||||
<span className={styles.heroPretitle}>{t("home.heroPretitle")}</span>
|
||||
<span className={styles.heroTitle}>{t("home.heroTitle")}</span>
|
||||
<span className={styles.heroSubtitle}>{t("home.heroSubtitle")}</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div className={styles.categoryBox}>
|
||||
<div className={styles.layoutBox}>
|
||||
<span className={styles.title}>{t("tools.toolCategories")}</span>
|
||||
|
||||
<div className={styles.flexList}>
|
||||
<LinkBox to={"/tools"} text={t("tools.categories.everything")} icon={List} />
|
||||
{/*<LinkBox to={"/tools/osm"} text={"OSM"} icon={Map} />*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
]);
|
||||
}
|
||||
|
||||
export default HomePage;
|
41
src/pages/Home.module.scss
Normal file
41
src/pages/Home.module.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
@import "../common";
|
||||
|
||||
.heroBox {
|
||||
margin-top: -$layoutNavigationHeight;
|
||||
|
||||
background-color: #1c1c1c;
|
||||
background-image: url(https://source.unsplash.com/uq5RMAZdZG4/1920x1080);
|
||||
|
||||
> div {
|
||||
padding-top: 100px;
|
||||
padding-bottom: 100px;
|
||||
min-height: 600px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
span {
|
||||
color: white;
|
||||
text-shadow: 0 0 10px black, 0 0 10px black;
|
||||
|
||||
&.heroPretitle {
|
||||
font-size: 2em;
|
||||
font-weight: 100;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
|
||||
&.heroTitle {
|
||||
font-size: 4em;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
&.heroSubtitle {
|
||||
font-size: 1.5em;
|
||||
font-weight: 400;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
21
src/pages/NotFound.js
Normal file
21
src/pages/NotFound.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import { Frown } from 'react-feather';
|
||||
|
||||
import * as styles from "./NotFound.module.scss";
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NotFoundPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return ([
|
||||
<div>
|
||||
<div className={styles.layoutBox}>
|
||||
<h1>{t("system.notfound")} <Frown/></h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
]);
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
1
src/pages/NotFound.module.scss
Normal file
1
src/pages/NotFound.module.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "../common";
|
45
src/pages/Tools.js
Normal file
45
src/pages/Tools.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import * as icons from 'react-feather';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import * as styles from "./Tools.module.scss";
|
||||
import LinkBox from "../components/LinkBox";
|
||||
|
||||
import { tools } from "../tools/tools.json";
|
||||
|
||||
const ToolsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
let { category } = useParams();
|
||||
|
||||
if(category) category = category.toLowerCase();
|
||||
|
||||
let toolList = tools.filter((tool) => {
|
||||
return !tool.hidden && (category == null || tool.category === category);
|
||||
});
|
||||
|
||||
return ([
|
||||
<div className={styles.categoryBox}>
|
||||
<div className={styles.layoutBox}>
|
||||
<span className={styles.title}>{t("tools.toolList")}</span>
|
||||
|
||||
<div className={styles.flexList}>
|
||||
<LinkBox to={"/tools"} text={t("tools.categories.everything")} icon={icons["List"]} small={true} highlight={category == null} />
|
||||
{/*<LinkBox to={"/tools/osm"} text={"OSM"} icon={icons["Map"]} small={true} highlight={category === "osm"} />*/}
|
||||
</div>
|
||||
|
||||
<div className={styles.flexList}>
|
||||
{toolList.map((tool, i) => {
|
||||
return (<LinkBox key={"tool"+i} external={tool.external} to={tool.external ? tool.url : "/tool/"+tool.urlname} text={tool.name} icon={icons[tool.icon]} />);
|
||||
})}
|
||||
|
||||
{toolList.length === 0 ? <span>{t("tools.noresults")}</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
]);
|
||||
}
|
||||
|
||||
export default ToolsPage;
|
1
src/pages/Tools.module.scss
Normal file
1
src/pages/Tools.module.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@import "../common";
|
19
src/tools/ToolLoader.js
Normal file
19
src/tools/ToolLoader.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React, { lazy } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import NotFoundPage from "../pages/NotFound";
|
||||
|
||||
const HomePage = lazy(() => import('../pages/Home'));
|
||||
|
||||
const ToolLoader = () => {
|
||||
const {tool} = useParams();
|
||||
|
||||
switch(tool) {
|
||||
case "test":
|
||||
return <HomePage/>;
|
||||
|
||||
default:
|
||||
return <NotFoundPage/>;
|
||||
}
|
||||
}
|
||||
|
||||
export default ToolLoader;
|
20
src/tools/tools.json
Normal file
20
src/tools/tools.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"tools": [
|
||||
{
|
||||
"name": "Test01",
|
||||
"external": true,
|
||||
"url": "https://kevink.dev",
|
||||
"icon": "Smile",
|
||||
"category": "osm",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Test02",
|
||||
"external": false,
|
||||
"urlname": "test",
|
||||
"icon": "Smile",
|
||||
"category": "something",
|
||||
"hidden": true
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue