add Footer, add a link to TWHelp in the header, add info about the last data update to the server card
This commit is contained in:
parent
5f19ccc035
commit
cde80bd092
2
.babelrc
2
.babelrc
|
@ -5,10 +5,12 @@
|
|||
{
|
||||
"@": ["./src"],
|
||||
"alias": {
|
||||
"@": "./src",
|
||||
"@common": "./src/common",
|
||||
"@config": "./src/config",
|
||||
"@features": "./src/features",
|
||||
"@libs": "./src/libs",
|
||||
"@theme": "./src/theme",
|
||||
"@utils": "./src/utils"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
|
|
@ -4,3 +4,7 @@ export const SERVER_STATUS = {
|
|||
CLOSED: 'closed',
|
||||
OPEN: 'open',
|
||||
};
|
||||
|
||||
export const TWHELP = process.env.TWHelp ?? 'https://tribalwarshelp.com';
|
||||
|
||||
export const AUTHOR = 'Dawid Wysokiński';
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Container, Toolbar } from '@material-ui/core';
|
|||
|
||||
import Header from './components/Header/Header';
|
||||
import ServerSelection from './components/ServerSelection/ServerSelection';
|
||||
import Footer from './components/Footer/Footer';
|
||||
|
||||
export default function IndexPage() {
|
||||
const classes = useStyles();
|
||||
|
@ -18,6 +19,7 @@ export default function IndexPage() {
|
|||
<ServerSelection />
|
||||
</Container>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
21
src/features/IndexPage/components/Footer/Footer.tsx
Normal file
21
src/features/IndexPage/components/Footer/Footer.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import { AUTHOR } from '@config/app';
|
||||
|
||||
import useStyles from './styles';
|
||||
import { AppBar, Toolbar, Container, Typography } from '@material-ui/core';
|
||||
|
||||
export default function Header() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<AppBar position="static" component="footer">
|
||||
<Container>
|
||||
<Toolbar disableGutters className={classes.toolbar}>
|
||||
<Typography align="center" className={classes.copyright}>
|
||||
© {new Date().getFullYear()} {AUTHOR}
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
12
src/features/IndexPage/components/Footer/styles.ts
Normal file
12
src/features/IndexPage/components/Footer/styles.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
toolbar: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
copyright: {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { useQueryParams, StringParam, withDefault } from 'use-query-params';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { TWHELP } from '@config/app';
|
||||
|
||||
import useStyles from './styles';
|
||||
import {
|
||||
|
@ -9,6 +10,9 @@ import {
|
|||
TextField,
|
||||
InputAdornment,
|
||||
Container,
|
||||
Button,
|
||||
Hidden,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import { Search as SearchIcon } from '@material-ui/icons';
|
||||
import VersionSelector from '@common/VersionSelector/VersionSelector';
|
||||
|
@ -31,7 +35,7 @@ export default function Header() {
|
|||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search server"
|
||||
placeholder="Search"
|
||||
defaultValue={query.q}
|
||||
size="small"
|
||||
onChange={e => {
|
||||
|
@ -50,6 +54,11 @@ export default function Header() {
|
|||
<div>
|
||||
<VersionSelector />
|
||||
</div>
|
||||
<Hidden xsDown implementation="css">
|
||||
<Link href={TWHELP} underline="none">
|
||||
<Button>TWHelp</Button>
|
||||
</Link>
|
||||
</Hidden>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
|
|
|
@ -6,6 +6,9 @@ import {
|
|||
NumberParam,
|
||||
withDefault,
|
||||
} from 'use-query-params';
|
||||
import formatDistanceToNow from '@libs/date/formatDistanceToNow';
|
||||
import { Locale } from '@libs/date/locales';
|
||||
import useLanguage from '@libs/i18n/useLanguage';
|
||||
import { SERVER_STATUS } from '@config/app';
|
||||
import { ServerList } from './types';
|
||||
import { SERVERS } from './queries';
|
||||
|
@ -18,12 +21,14 @@ import Pagination, {
|
|||
} from '@common/Pagination/Pagination';
|
||||
|
||||
const PER_PAGE = 30;
|
||||
const arr = new Array(PER_PAGE).fill(0);
|
||||
|
||||
export default function ServerSelection() {
|
||||
const [query, setQuery] = useQueryParams({
|
||||
page: withDefault(NumberParam, 1),
|
||||
q: withDefault(StringParam, ''),
|
||||
});
|
||||
const lang = useLanguage();
|
||||
const { data, loading: loadingServers } = useQuery<ServerList>(SERVERS, {
|
||||
fetchPolicy: 'cache-and-network',
|
||||
variables: {
|
||||
|
@ -67,12 +72,13 @@ export default function ServerSelection() {
|
|||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderPagination(true)}
|
||||
<Grid container spacing={3}>
|
||||
{loading
|
||||
? new Array(PER_PAGE).fill(0).map((_, index) => {
|
||||
? arr.map((_, index) => {
|
||||
return (
|
||||
<Grid key={index} item xs={12} sm={6} md={4}>
|
||||
<Card>
|
||||
|
@ -101,6 +107,13 @@ export default function ServerSelection() {
|
|||
{server.numberOfTribes.toLocaleString()} tribes
|
||||
<br />
|
||||
{server.numberOfVillages.toLocaleString()} villages
|
||||
<br />
|
||||
Updated{' '}
|
||||
{formatDistanceToNow(new Date(server.dataUpdatedAt), {
|
||||
locale: lang as Locale,
|
||||
addSuffix: true,
|
||||
})}
|
||||
.
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -9,6 +9,7 @@ export const SERVERS = gql`
|
|||
numberOfPlayers
|
||||
numberOfTribes
|
||||
numberOfVillages
|
||||
dataUpdatedAt
|
||||
}
|
||||
total
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ export type Server = {
|
|||
numberOfPlayers: number;
|
||||
numberOfTribes: number;
|
||||
numberOfVillages: number;
|
||||
dataUpdatedAt: string | Date;
|
||||
};
|
||||
|
||||
export type ServerList = {
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { heights } from '@theme/toolbar';
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
main: {
|
||||
padding: theme.spacing(3, 0),
|
||||
minHeight: `calc(100vh - ${heights.tabletDesktop * 2}px)`,
|
||||
[`${theme.breakpoints.down('xs')} and (orientation: landscape)`]: {
|
||||
minHeight: `calc(100vh - ${heights.mobileLandscape * 2}px)`,
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
minHeight: `calc(100vh - ${heights.mobilePortrait * 2}px)`,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
17
src/libs/date/formatDistanceToNow.ts
Normal file
17
src/libs/date/formatDistanceToNow.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { formatDistanceToNow } from 'date-fns';
|
||||
import locales, { Locale } from './locales';
|
||||
|
||||
export type Options = {
|
||||
includeSeconds?: boolean;
|
||||
addSuffix?: boolean;
|
||||
locale: Locale;
|
||||
};
|
||||
|
||||
const _formatDistanceToNow = (date: Date | number, opts: Options) => {
|
||||
return formatDistanceToNow(date, {
|
||||
...opts,
|
||||
locale: locales[opts.locale] ?? locales.en,
|
||||
});
|
||||
};
|
||||
|
||||
export default _formatDistanceToNow;
|
11
src/libs/date/locales.ts
Normal file
11
src/libs/date/locales.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import pl from 'date-fns/locale/pl';
|
||||
import enGB from 'date-fns/locale/en-GB';
|
||||
|
||||
export type Locale = 'pl' | 'en';
|
||||
|
||||
const locales = {
|
||||
pl,
|
||||
en: enGB,
|
||||
};
|
||||
|
||||
export default locales;
|
5
src/theme/toolbar.ts
Normal file
5
src/theme/toolbar.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const heights = {
|
||||
mobilePortrait: 56,
|
||||
mobileLandscape: 48,
|
||||
tabletDesktop: 64,
|
||||
};
|
|
@ -2,31 +2,16 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@common/*": [
|
||||
"src/common/*"
|
||||
],
|
||||
"@config/*": [
|
||||
"src/config/*"
|
||||
],
|
||||
"@features/*": [
|
||||
"src/features/*"
|
||||
],
|
||||
"@libs/*": [
|
||||
"src/libs/*"
|
||||
],
|
||||
"@utils/*": [
|
||||
"src/utils/*"
|
||||
]
|
||||
"@/*": ["src/*"],
|
||||
"@common/*": ["src/common/*"],
|
||||
"@config/*": ["src/config/*"],
|
||||
"@features/*": ["src/features/*"],
|
||||
"@libs/*": ["src/libs/*"],
|
||||
"@theme/*": ["src/theme/*"],
|
||||
"@utils/*": ["src/utils/*"]
|
||||
},
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
|
@ -41,10 +26,6 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Reference in New Issue
Block a user