diff options
Diffstat (limited to 'pages')
-rw-r--r-- | pages/_app.tsx | 29 | ||||
-rw-r--r-- | pages/blog/[post].tsx | 59 | ||||
-rw-r--r-- | pages/game.tsx | 359 | ||||
-rw-r--r-- | pages/index.tsx | 273 | ||||
-rw-r--r-- | pages/login.tsx | 104 | ||||
-rw-r--r-- | pages/register.tsx | 127 | ||||
-rw-r--r-- | pages/search.tsx | 136 | ||||
-rw-r--r-- | pages/settings.tsx | 128 | ||||
-rw-r--r-- | pages/user.tsx | 544 |
9 files changed, 968 insertions, 791 deletions
diff --git a/pages/_app.tsx b/pages/_app.tsx index fad7c33..0682a4d 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,38 +1,37 @@ import Head from 'next/head'; import { PreferencesContextWrapper } from '../components/preferencesContext'; -import { ToastContextWrapper } from '../components/toast'; import { SocketContextWrapper } from '../components/socketContext'; +import { ToastContextWrapper } from '../components/toast'; -import '../styles/global.css'; import '../styles/dark.css'; import '../styles/disk.css'; import '../styles/footer.css'; +import '../styles/global.css'; export default function VierOpEenRijWebsite({ Component, pageProps }) { return <div> <Head> <title>4 op een rij</title> - <link rel="stylesheet" href="/font/stylesheet.css"/> + <link rel='stylesheet' href='/font/stylesheet.css' /> - <link rel="icon" href="/favicon.svg" type="image/svg+xml"/> - <link rel="icon" href="/favicon.png" type="image/png"/> + <link rel='icon' href='/favicon.svg' type='image/svg+xml' /> + <link rel='icon' href='/favicon.png' type='image/png' /> - <meta property="og:site_name" content="4 op een rij"/> - <meta property="og:url" content="http://connect4.pipeframe.xyz/"/> - <meta property="og:title" content="Loek's epische vier op een rij website"/> - <meta property="og:description" content="Kom vier op een rij spelen NU"/> - <meta property="og:type" content="website"/> - <meta name="og:image" itemProp="image" content="/favicon.png"/> - <meta name="theme-color" content="#e16d82"/> + <meta property='og:site_name' content='4 op een rij' /> + <meta property='og:url' content='http://connect4.pipeframe.xyz/' /> + <meta property='og:title' content="Loek's epische vier op een rij website" /> + <meta property='og:description' content='Kom vier op een rij spelen NU' /> + <meta property='og:type' content='website' /> + <meta name='og:image' itemProp='image' content='/favicon.png' /> + <meta name='theme-color' content='#e16d82' /> </Head> <PreferencesContextWrapper> <ToastContextWrapper> <SocketContextWrapper> - <Component {...pageProps}/> + <Component {...pageProps} /> </SocketContextWrapper> </ToastContextWrapper> </PreferencesContextWrapper> - </div> + </div>; } - diff --git a/pages/blog/[post].tsx b/pages/blog/[post].tsx index 9588517..9ebd3ad 100644 --- a/pages/blog/[post].tsx +++ b/pages/blog/[post].tsx @@ -1,50 +1,52 @@ -import micromark from 'micromark'; import { readdirSync, readFileSync } from 'fs'; -import { join } from 'path' +import micromark from 'micromark'; +import { join } from 'path'; import { NavBar } from '../../components/navbar'; import { CenteredPage, PageTitle } from '../../components/page'; import { Vierkant } from '../../components/ui'; export default function Post(props: { - post: string, - content: string, - tags: string + post: string; + content: string; + tags: string; }) { return <div> - <NavBar/> + <NavBar /> <CenteredPage width={802}> - <PageTitle>{props.post.replace(/_/g, " ")}</PageTitle> + <PageTitle>{props.post.replace(/_/g, ' ')}</PageTitle> <Vierkant fullwidth> - <div dangerouslySetInnerHTML={{__html: props.content}}> + <div dangerouslySetInnerHTML={{ __html: props.content }}> </div> </Vierkant> </CenteredPage> - </div> + </div>; } function parseTags(fileContent: string) { - var fileAsArr = fileContent.split("\n"); - var lastLine = fileAsArr[fileAsArr.length-1] - if (!lastLine.startsWith(";tags:")) return { - tags: [], - result: "" + var fileAsArr = fileContent.split('\n'); + var lastLine = fileAsArr[fileAsArr.length - 1]; + if (!lastLine.startsWith(';tags:')) { + return { + tags: [], + result: '', + }; } - var tags = lastLine.replace(";tags:", "").trim().split(" "); + var tags = lastLine.replace(';tags:', '').trim().split(' '); - fileAsArr.pop() - var result = fileAsArr.join("\n").trim() + fileAsArr.pop(); + var result = fileAsArr.join('\n').trim(); - return { tags, result } + return { tags, result }; } -export function getStaticProps(props: {params: { post: string }}) { - var filename = join("news/", props.params.post + ".md") - var filecontent = readFileSync(filename).toString().trim() +export function getStaticProps(props: { params: { post: string; }; }) { + var filename = join('news/', props.params.post + '.md'); + var filecontent = readFileSync(filename).toString().trim(); var parsed = parseTags(filecontent); - var content = micromark(parsed.result) + var content = micromark(parsed.result); return { props: { @@ -52,21 +54,20 @@ export function getStaticProps(props: {params: { post: string }}) { content, tags: parsed.tags, }, - } + }; } export function getStaticPaths() { - var files = readdirSync("news").filter(f => f.endsWith(".md")); + var files = readdirSync('news').filter(f => f.endsWith('.md')); return { paths: files.map((f) => { return { params: { - post: f.substr(0, f.length - 3) - } - } + post: f.substr(0, f.length - 3), + }, + }; }), fallback: false, - } + }; } - diff --git a/pages/game.tsx b/pages/game.tsx index 4fa58a9..b5200a7 100644 --- a/pages/game.tsx +++ b/pages/game.tsx @@ -1,24 +1,24 @@ -import { CSSProperties, useState, useEffect, useContext } from 'react'; +import Icon from '@mdi/react'; import axios from 'axios'; +import copy from 'copy-to-clipboard'; +import { CSSProperties, useContext, useEffect, useState } from 'react'; import * as cookies from 'react-cookies'; -import { SocketContext } from '../components/socketContext'; import { Socket } from 'socket.io-client'; -import Icon from '@mdi/react'; -import copy from 'copy-to-clipboard'; +import { SocketContext } from '../components/socketContext'; -import { NavBar } from '../components/navbar'; -import { CenteredPage } from '../components/page'; -import { VoerBord } from '../components/voerBord'; import { DialogBox } from '../components/dialogBox'; -import { CurrentGameSettings } from '../components/gameSettings'; -import { Button, SearchBar, IconLabelButton } from '../components/ui'; import { GameBar } from '../components/gameBar'; +import { CurrentGameSettings } from '../components/gameSettings'; +import { NavBar } from '../components/navbar'; +import { CenteredPage } from '../components/page'; import { ToastContext, toastType } from '../components/toast'; +import { Button, IconLabelButton, SearchBar } from '../components/ui'; +import { VoerBord } from '../components/voerBord'; -import WifiTetheringRoundedIcon from '@material-ui/icons/WifiTetheringRounded'; +import FlagOutlinedIcon from '@material-ui/icons/FlagOutlined'; import LinkRoundedIcon from '@material-ui/icons/LinkRounded'; import RefreshIcon from '@material-ui/icons/Refresh'; -import FlagOutlinedIcon from '@material-ui/icons/FlagOutlined'; +import WifiTetheringRoundedIcon from '@material-ui/icons/WifiTetheringRounded'; import { mdiContentCopy } from '@mdi/js'; function VoerGame(props: { @@ -37,49 +37,51 @@ function VoerGame(props: { var board: Array<number> = [...Array(width * height)].map(() => 0); useEffect(() => { - props.io.on("connect", () => console.log("connect")); - props.io.on("disconnect", () => console.log("disconnect")); + props.io.on('connect', () => console.log('connect')); + props.io.on('disconnect', () => console.log('disconnect')); - props.io.on("fieldUpdate", (data: { field: string }) => { - board = data.field.split("").map(i => Number(i)); - for(let i = 0; i < data.field.length; i++) + props.io.on('fieldUpdate', (data: { field: string; }) => { + board = data.field.split('').map(i => Number(i)); + for (let i = 0; i < data.field.length; i++) { document.getElementById(`pos-${i}`).parentNode.children.item(1).classList.add(`state-${data.field[i]}`); + } }); - props.io.on("turnUpdate", (data: { player1: boolean }) => setTurn(data.player1)); + props.io.on('turnUpdate', (data: { player1: boolean; }) => setTurn(data.player1)); - props.io.on("finish", (data: { - winPositions: Array<Array<number>> - boardFull: boolean - winner: number - }) => { + props.io.on('finish', (data: { + winPositions: Array<Array<number>>; + boardFull: boolean; + winner: number; + }) => { // setWinPositions(data.winPositions); if (data.boardFull) setOutcome(0); if (data.winPositions.length > 0) setOutcome(board[data.winPositions[0][0]]); }); - props.io.on("resign", () => { - props.toast({ message: "Het potje is opgegeven", - type: "normal", - icon: <FlagOutlinedIcon/>}); + props.io.on('resign', () => { + props.toast({ message: 'Het potje is opgegeven', type: 'normal', icon: <FlagOutlinedIcon /> }); }); }, []); - return <div style={{ - position: "relative", - top: "50%", - transform: "translateY(-50%)", - maxWidth: "100vh", - margin: "0 auto" - }}> + return <div + style={{ + position: 'relative', + top: '50%', + transform: 'translateY(-50%)', + maxWidth: '100vh', + margin: '0 auto', + }} + > <VoerBord - width={width} height={height} + width={width} + height={height} onMove={move => { - props.io.emit("newMove", { + props.io.emit('newMove', { move: move % width + 1, - token: cookies.load("token"), //TODO: get token from request - game_id: props.gameID + token: cookies.load('token'), // TODO: get token from request + game_id: props.gameID, }); }} active={props.active && outcome == -1} @@ -88,14 +90,16 @@ function VoerGame(props: { turn={turn} player1={props.player1} active={props.active} - resignFunction={() => { props.io.emit("resign", { game_id: props.gameID }) }} + resignFunction={() => { + props.io.emit('resign', { game_id: props.gameID }); + }} /> <GameOutcomeDialog outcome={outcome} player={props.player1 ? 1 : 2} visible={outcome != -1} /> - </div> + </div>; } function GameOutcomeDialog(props: { @@ -104,180 +108,209 @@ function GameOutcomeDialog(props: { visible: boolean; }) { return <DialogBox - title="Speluitkomst" - style={{ display: props.visible ? "inline-block" : "none" }} + title='Speluitkomst' + style={{ display: props.visible ? 'inline-block' : 'none' }} onclick={() => { - window.history.replaceState(null, null, "/"); + window.history.replaceState(null, null, '/'); window.location.reload(); - }}> - <div style={{ - width: "100%", - textAlign: "center" - }}> - <h2 style={{ - color: - props.outcome == 0 ? "var(--text)" : - props.outcome == props.player ? "var(--disk-a-text)" : - props.outcome != props.player ? "var(--disk-b-text)" : - "var(--text)", - opacity: props.outcome == 0 ? .75 : 1, - marginTop: 8 - }}>{ - props.outcome == 0 ? "Gelijkspel" : - props.outcome == props.player ? "Verloren" : - props.outcome != props.player ? "Gewonnen" : - "???" - }</h2> - { false && <p style={{ marginTop: 24 }}> - 0 Gemiste winstzetten<br/> - 6 Optimale zetten<br/> - 0 Blunders - </p> } - { false && <IconLabelButton text="Opnieuw spelen" icon={<RefreshIcon/>} style={{ - float: "none", - marginTop: 24, - padding: "12px 32px" - }}/> } + }} + > + <div + style={{ + width: '100%', + textAlign: 'center', + }} + > + <h2 + style={{ + color: props.outcome == 0 + ? 'var(--text)' + : props.outcome == props.player + ? 'var(--disk-a-text)' + : props.outcome != props.player + ? 'var(--disk-b-text)' + : 'var(--text)', + opacity: props.outcome == 0 ? .75 : 1, + marginTop: 8, + }} + > + {props.outcome == 0 + ? 'Gelijkspel' + : props.outcome == props.player + ? 'Verloren' + : props.outcome != props.player + ? 'Gewonnen' + : '???'} + </h2> + {false && <p style={{ marginTop: 24 }}> + 0 Gemiste winstzetten<br /> + 6 Optimale zetten<br /> + 0 Blunders + </p>} + {false && <IconLabelButton + text='Opnieuw spelen' + icon={<RefreshIcon />} + style={{ + float: 'none', + marginTop: 24, + padding: '12px 32px', + }} + />} </div> - </DialogBox> + </DialogBox>; } var InviteButtonStyle: CSSProperties = { - backgroundColor: "var(--page-background)", + backgroundColor: 'var(--page-background)', height: 160, - padding: 12 -} + padding: 12, +}; var InviteButtonIconStyle: CSSProperties = { fontSize: 100, - position: "absolute", + position: 'absolute', top: 12, - left: "50%", - transform: "translateX(-50%)" -} + left: '50%', + transform: 'translateX(-50%)', +}; var InviteButtonLabelStyle: CSSProperties = { - position: "absolute", + position: 'absolute', bottom: 12, - left: "50%", - transform: "translateX(-50%)", - textAlign: "center", - color: "var(--text-alt)", + left: '50%', + transform: 'translateX(-50%)', + textAlign: 'center', + color: 'var(--text-alt)', width: 136, fontSize: 20, - userSelect: "none" -} + userSelect: 'none', +}; export default function GamePage() { - var [gameID, setGameID] = useState(""); + var [gameID, setGameID] = useState(''); var [player1, setPlayer1] = useState(true); var [active, setActive] = useState(false); - var [gameIDUrl, setGameIDUrl] = useState(""); + var [gameIDUrl, setGameIDUrl] = useState(''); var { io } = useContext(SocketContext); var { toast } = useContext(ToastContext); useEffect(() => { - var gameIDUrl = new URLSearchParams(window.location.search).get("id") || ""; + var gameIDUrl = new URLSearchParams(window.location.search).get('id') || ''; setGameIDUrl(gameIDUrl); if (!gameIDUrl || gameIDUrl == gameID) return; - axios.request<{ id: string, player_1: boolean, game_started: boolean }>({ - method: "post", - url: "/api/game/accept", - headers: {"content-type": "application/json"}, - data: { id: gameIDUrl } - }) - .then(response => { - setGameID(response.data.id); - setPlayer1(response.data.player_1); - io.emit("registerGameListener", { game_id: response.data.id }); - setActive(true); + axios.request<{ id: string; player_1: boolean; game_started: boolean; }>({ + method: 'post', + url: '/api/game/accept', + headers: { 'content-type': 'application/json' }, + data: { id: gameIDUrl }, }) - .catch(err => { - toast({ message: "error", - type: "confirmation", - description: err.toString() }); - }); + .then(response => { + setGameID(response.data.id); + setPlayer1(response.data.player_1); + io.emit('registerGameListener', { game_id: response.data.id }); + setActive(true); + }) + .catch(err => { + toast({ message: 'error', type: 'confirmation', description: err.toString() }); + }); setGameID(gameIDUrl); }, []); useEffect(() => { - io.on("gameStart", () => setActive(true)); + io.on('gameStart', () => setActive(true)); }, []); return <div> - <NavBar/> - <CenteredPage width={900} style={{ height: "100vh" }}> + <NavBar /> + <CenteredPage width={900} style={{ height: '100vh' }}> <VoerGame active={active} gameID={gameID} player1={player1} io={io} - toast={toast}/> + toast={toast} + /> <DialogBox - title="Nieuw spel" - style={{ display: gameIDUrl || gameID ? "none" : "inline-block" }} - onclick={() => { window.history.go(-1); }}> - <CurrentGameSettings/> - <div style={{ - marginTop: 24, - display: "grid", - gridTemplateColumns: "1fr 1fr", - gridGap: 24 - }}> - <Button style={InviteButtonStyle} onclick={() => { - axios.request<{ id: string, player_1: boolean, game_started: boolean }>({ - url: "/api/game/random", - }) - .then(response => { - setGameID(response.data.id); - window.history.replaceState(null, null, "?id=" + response.data.id); - setPlayer1(response.data.player_1); - io.emit("registerGameListener", { game_id: response.data.id }); - if (response.data.game_started) setActive(true); - }) - .catch(() => {}); - }}> - <WifiTetheringRoundedIcon style={{ - color: "var(--disk-b)", - ...InviteButtonIconStyle - }}/> + title='Nieuw spel' + style={{ display: gameIDUrl || gameID ? 'none' : 'inline-block' }} + onclick={() => { + window.history.go(-1); + }} + > + <CurrentGameSettings /> + <div + style={{ + marginTop: 24, + display: 'grid', + gridTemplateColumns: '1fr 1fr', + gridGap: 24, + }} + > + <Button + style={InviteButtonStyle} + onclick={() => { + axios.request<{ id: string; player_1: boolean; game_started: boolean; }>({ + url: '/api/game/random', + }) + .then(response => { + setGameID(response.data.id); + window.history.replaceState(null, null, '?id=' + response.data.id); + setPlayer1(response.data.player_1); + io.emit('registerGameListener', { game_id: response.data.id }); + if (response.data.game_started) setActive(true); + }) + .catch(() => {}); + }} + > + <WifiTetheringRoundedIcon + style={{ + color: 'var(--disk-b)', + ...InviteButtonIconStyle, + }} + /> <h2 style={InviteButtonLabelStyle}>Willekeurige speler</h2> </Button> - <Button style={InviteButtonStyle} onclick={() => { - axios.request<{ id: string }>({ - method: "post", - url: "/api/game/new", - headers: {"content-type": "application/json"}, - data: {} - }) - .then(response => { - setGameID(response.data.id); - window.history.replaceState(null, null, "?id=" + response.data.id); - setPlayer1(true); - io.emit("registerGameListener", { game_id: response.data.id }); - setActive(false); + <Button + style={InviteButtonStyle} + onclick={() => { + axios.request<{ id: string; }>({ + method: 'post', + url: '/api/game/new', + headers: { 'content-type': 'application/json' }, + data: {}, + }) + .then(response => { + setGameID(response.data.id); + window.history.replaceState(null, null, '?id=' + response.data.id); + setPlayer1(true); + io.emit('registerGameListener', { game_id: response.data.id }); + setActive(false); - copy(window.location.href); - toast({ message: "Link gekopiƫerd naar klembord", - type: "confirmation", - icon: <Icon size={1} path={mdiContentCopy}/> }); - }) - .catch(() => {}); - }}> - <LinkRoundedIcon style={{ - color: "var(--disk-a)", - ...InviteButtonIconStyle - }}/> + copy(window.location.href); + toast({ + message: 'Link gekopiƫerd naar klembord', + type: 'confirmation', + icon: <Icon size={1} path={mdiContentCopy} />, + }); + }) + .catch(() => {}); + }} + > + <LinkRoundedIcon + style={{ + color: 'var(--disk-a)', + ...InviteButtonIconStyle, + }} + /> <h2 style={InviteButtonLabelStyle}>Uitnodigen via link</h2> </Button> </div> - <SearchBar label="Zoeken in vriendenlijst"/> + <SearchBar label='Zoeken in vriendenlijst' /> </DialogBox> </CenteredPage> - </div> + </div>; } - diff --git a/pages/index.tsx b/pages/index.tsx index 9bfe2a7..354efc5 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,84 +1,94 @@ -import { CSSProperties, useState, useEffect, useContext } from 'react'; import axios from 'axios'; -import { userInfo, userGameTotals, userGames } from '../api/api'; -import { SocketContext } from '../components/socketContext'; +import { CSSProperties, useContext, useEffect, useState } from 'react'; +import { userGames, userGameTotals, userInfo } from '../api/api'; import { Footer } from '../components/footer'; +import { SocketContext } from '../components/socketContext'; +import { AccountAvatar } from '../components/account'; import { NavBar } from '../components/navbar'; import { CenteredPage, PageTitle } from '../components/page'; -import { Vierkant, Button } from '../components/ui'; -import { AccountAvatar } from '../components/account'; import RecentGames from '../components/recentGames'; +import { Button, Vierkant } from '../components/ui'; -import VideogameAssetIcon from '@material-ui/icons/VideogameAsset'; import ExtensionIcon from '@material-ui/icons/Extension'; +import VideogameAssetIcon from '@material-ui/icons/VideogameAsset'; -import Icon from '@mdi/react'; import { mdiRobotExcited } from '@mdi/js'; +import Icon from '@mdi/react'; var GameModeIconStyle: CSSProperties = { fontSize: 64, width: 64, height: 64, - display: "inline-block", - position: "absolute", + display: 'inline-block', + position: 'absolute', top: 24, - left: "50%", - transform: "translateX(-50%)" -} + left: '50%', + transform: 'translateX(-50%)', +}; var GameModeTextStyle: CSSProperties = { - whiteSpace: "nowrap", - display: "inline-block", - position: "absolute", + whiteSpace: 'nowrap', + display: 'inline-block', + position: 'absolute', bottom: 24, - left: "50%", - transform: "translateX(-50%)", - userSelect: "none", - fontWeight: 500 -} + left: '50%', + transform: 'translateX(-50%)', + userSelect: 'none', + fontWeight: 500, +}; var SquareSize: CSSProperties = { width: 90, - height: 90 -} + height: 90, +}; var LoginOrRegisterBoxStyle: CSSProperties = { - verticalAlign: "top", + verticalAlign: 'top', height: `calc(${SquareSize.height}px + 24px * 2)`, - width: "100%", - maxWidth: `calc(100% - ${SquareSize.width}px - 12px * 2 - 24px * 2)` -} + width: '100%', + maxWidth: `calc(100% - ${SquareSize.width}px - 12px * 2 - 24px * 2)`, +}; var InnerLoginOrRegisterBoxStyle: CSSProperties = { - position: "relative", - width: "100%", - height: "100%" -} + position: 'relative', + width: '100%', + height: '100%', +}; function LoginOrRegisterBox() { - return <div style={{ ...InnerLoginOrRegisterBoxStyle, textAlign: "center" }}> - <span style={{ - userSelect: "none", - display: "inline-block", - position: "absolute", - fontSize: 14, - left: 0, right: 0, top: 0, - margin: "0 auto", - minWidth: 240, - maxWidth: 350 - }}>Log in of maak een account aan om je scores op te slaan en toegang te krijgen tot meer functies</span> - <div style={{ - display: "grid", - gridGap: 24, - gridTemplateColumns: "1fr 1fr", - position: "absolute", - left: 0, right: 0, bottom: 0 - }}> - <Button href="/register" text="Registreren" style={{ backgroundColor: "var(--background-alt)" }}/> - <Button href="/login" text="Inloggen"/> + return <div style={{ ...InnerLoginOrRegisterBoxStyle, textAlign: 'center' }}> + <span + style={{ + userSelect: 'none', + display: 'inline-block', + position: 'absolute', + fontSize: 14, + left: 0, + right: 0, + top: 0, + margin: '0 auto', + minWidth: 240, + maxWidth: 350, + }} + > + Log in of maak een account aan om je scores op te slaan en toegang te krijgen tot meer functies + </span> + <div + style={{ + display: 'grid', + gridGap: 24, + gridTemplateColumns: '1fr 1fr', + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }} + > + <Button href='/register' text='Registreren' style={{ backgroundColor: 'var(--background-alt)' }} /> + <Button href='/login' text='Inloggen' /> </div> - </div> + </div>; } function AccountBox(props: { @@ -86,116 +96,123 @@ function AccountBox(props: { sumGameInfo: userGameTotals; }) { return <div style={InnerLoginOrRegisterBoxStyle}> - <div style={{ - position: "absolute", - top: 0, left: 0, - ...SquareSize - }}> - <AccountAvatar size={90}/> + <div + style={{ + position: 'absolute', + top: 0, + left: 0, + ...SquareSize, + }} + > + <AccountAvatar size={90} /> </div> - <div style={{ - position: "absolute", - top: 0, left: 102, - width: "calc(100% - 90px - 12px)", - height: "100%" - }}> - <h2 style={{ - maxWidth: 178, - fontSize: 20, - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - }}>{props.info?.username}</h2> + <div + style={{ + position: 'absolute', + top: 0, + left: 102, + width: 'calc(100% - 90px - 12px)', + height: '100%', + }} + > + <h2 + style={{ + maxWidth: 178, + fontSize: 20, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} + > + {props.info?.username} + </h2> <p style={{ marginTop: 6 }}>Score: {props.info?.rating}</p> - <p style={{ position: "absolute", bottom: 0, left: 0 }}> - <span style={{ color: "var(--disk-b-text)" }}>{props.sumGameInfo?.win} W</span> - <span style={{ margin: "0 3px" }}>/</span> - <span style={{ color: "var(--disk-a-text)" }}>{props.sumGameInfo?.lose} V</span> - <span style={{ margin: "0 3px" }}>/</span> + <p style={{ position: 'absolute', bottom: 0, left: 0 }}> + <span style={{ color: 'var(--disk-b-text)' }}>{props.sumGameInfo?.win} W</span> + <span style={{ margin: '0 3px' }}>/</span> + <span style={{ color: 'var(--disk-a-text)' }}>{props.sumGameInfo?.lose} V</span> + <span style={{ margin: '0 3px' }}>/</span> <span style={{ opacity: .75 }}>{props.sumGameInfo?.draw} G</span> </p> </div> - </div> + </div>; } export default function HomePage() { - var server = typeof window === "undefined"; - var loggedIn = !server && document.cookie.includes("token"); + var server = typeof window === 'undefined'; + var loggedIn = !server && document.cookie.includes('token'); var { io } = useContext(SocketContext); useEffect(() => { - io.on("connect", () => { console.log("gert") }); + io.on('connect', () => { + console.log('gert'); + }); }, []); var [userInfo, setUserInfo] = useState<userInfo>(); var [gameInfo, setGameInfo] = useState<userGames>(); - useEffect(() => {( async () => { - if (!loggedIn) return; - var userInfoReq = await axios.request<userInfo>({ - method: "get", - url: `/api/user/info`, - headers: {"content-type": "application/json"} - }); - setUserInfo(userInfoReq.data); - })()}, []); - - useEffect(() => {( async () => { - if (!loggedIn) return; - var userGamesReq = await axios.request<userGames>({ - method: "get", - url: `/api/user/games`, - headers: {"content-type": "application/json"} - }); - setGameInfo(userGamesReq.data); - })()}, []); + useEffect(() => { + (async () => { + if (!loggedIn) return; + var userInfoReq = await axios.request<userInfo>({ + method: 'get', + url: `/api/user/info`, + headers: { 'content-type': 'application/json' }, + }); + setUserInfo(userInfoReq.data); + })(); + }, []); + + useEffect(() => { + (async () => { + if (!loggedIn) return; + var userGamesReq = await axios.request<userGames>({ + method: 'get', + url: `/api/user/games`, + headers: { 'content-type': 'application/json' }, + }); + setGameInfo(userGamesReq.data); + })(); + }, []); return <div> - <NavBar/> + <NavBar /> <CenteredPage width={802}> <PageTitle>4 op een rij</PageTitle> <div> - <Vierkant href="/game"> - <VideogameAssetIcon style={GameModeIconStyle}/> + <Vierkant href='/game'> + <VideogameAssetIcon style={GameModeIconStyle} /> <span style={GameModeTextStyle}>Nieuw spel</span> <div style={SquareSize}></div> </Vierkant> - { - false && - <Vierkant href="/"> - <ExtensionIcon style={GameModeIconStyle}/> + {false + && <Vierkant href='/'> + <ExtensionIcon style={GameModeIconStyle} /> <span style={GameModeTextStyle}>Puzzels</span> <div style={SquareSize}></div> - </Vierkant> - } - { - false && - <Vierkant href="/"> - <Icon path={mdiRobotExcited} style={GameModeIconStyle}/> + </Vierkant>} + {false + && <Vierkant href='/'> + <Icon path={mdiRobotExcited} style={GameModeIconStyle} /> <span style={GameModeTextStyle}>Tegen computer</span> <div style={SquareSize}></div> - </Vierkant> - } + </Vierkant>} <Vierkant style={LoginOrRegisterBoxStyle}> - { - loggedIn ? - <AccountBox info={userInfo} sumGameInfo={gameInfo?.totals}/> : - <LoginOrRegisterBox/> - } + {loggedIn + ? <AccountBox info={userInfo} sumGameInfo={gameInfo?.totals} /> + : <LoginOrRegisterBox />} </Vierkant> </div> - { - loggedIn && - <Vierkant fullwidth> - <RecentGames games={gameInfo?.games}/> - </Vierkant> - } + {loggedIn + && <Vierkant fullwidth> + <RecentGames games={gameInfo?.games} /> + </Vierkant>} <Vierkant fullwidth> <h2>Nieuws ofzo</h2> - <p style={{ margin: "6px 0" }}>Chess.com heeft heel veel troep waar niemand naar kijkt</p> + <p style={{ margin: '6px 0' }}>Chess.com heeft heel veel troep waar niemand naar kijkt</p> </Vierkant> </CenteredPage> - <Footer/> - </div> + <Footer /> + </div>; } - diff --git a/pages/login.tsx b/pages/login.tsx index abfca04..1e14573 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -3,47 +3,43 @@ import { FormEvent, useContext } from 'react'; import { NavBar } from '../components/navbar'; import { CenteredPage } from '../components/page'; -import { Vierkant, Input, Button } from '../components/ui'; import { ToastContext, toastType } from '../components/toast'; +import { Button, Input, Vierkant } from '../components/ui'; +import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; -import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; function submitLogin(event?: FormEvent<HTMLFormElement>, toast?: toastType) { event?.preventDefault(); var formData = { - email: (document.getElementById("email") as HTMLInputElement).value, - password: (document.getElementById("password") as HTMLInputElement).value - } + email: (document.getElementById('email') as HTMLInputElement).value, + password: (document.getElementById('password') as HTMLInputElement).value, + }; - if ( !formData.email || - !formData.password ) { - toast({ message: "Vul alsjeblieft alle velden in!", - type: "error", - icon: <ReportProblemOutlinedIcon/>}); + if ( + !formData.email + || !formData.password + ) { + toast({ message: 'Vul alsjeblieft alle velden in!', type: 'error', icon: <ReportProblemOutlinedIcon /> }); return; } axios({ - method: "post", + method: 'post', url: `${window.location.origin}/api/auth/login`, - headers: {"content-type": "application/json"}, - data: formData + headers: { 'content-type': 'application/json' }, + data: formData, }) - .then(() => window.location.pathname = "/") - .catch(error => { - if (error.response.status === 401) { - toast({ message: "Verkeerde gebruikersnaam of wachtwoord!", - type: "error", - icon: <VpnKeyIcon/>}); - return; - } - toast({ message: "Er is iets fout gegaan", - type: "error", - icon: <ErrorOutlineIcon/>}); - }); + .then(() => window.location.pathname = '/') + .catch(error => { + if (error.response.status === 401) { + toast({ message: 'Verkeerde gebruikersnaam of wachtwoord!', type: 'error', icon: <VpnKeyIcon /> }); + return; + } + toast({ message: 'Er is iets fout gegaan', type: 'error', icon: <ErrorOutlineIcon /> }); + }); } export default function LoginPage() { @@ -51,29 +47,46 @@ export default function LoginPage() { return ( <div> - <NavBar/> - <CenteredPage width={500} style={{ height: "100vh" }}> - <div style={{ - position: "relative", - top: "50%", - transform: "translateY(-50%)", - margin: "0 auto", - textAlign: "center" - }}> + <NavBar /> + <CenteredPage width={500} style={{ height: '100vh' }}> + <div + style={{ + position: 'relative', + top: '50%', + transform: 'translateY(-50%)', + margin: '0 auto', + textAlign: 'center', + }} + > <Vierkant> <form onSubmit={(e) => submitLogin(e, toast)}> - <Input autofocus autocomplete="username" id="email" label="email of gebruikersnaam" style={{ marginBottom: 12 }}></Input> - <Input autocomplete="current-password" id="password" label="wachtwoord" type="password"></Input> - <div style={{ - marginTop: 24, - gridGap: 24, - display: "grid", - gridTemplateColumns: "1fr 1fr" - }}> - <Button href="/register" text="Registreren" style={{ backgroundColor: "var(--background-alt)"}}></Button> - <Button text="Inloggen" onclick={() => submitLogin(null, toast)}></Button> + <Input + autofocus + autocomplete='username' + id='email' + label='email of gebruikersnaam' + style={{ marginBottom: 12 }} + > + </Input> + <Input autocomplete='current-password' id='password' label='wachtwoord' type='password'> + </Input> + <div + style={{ + marginTop: 24, + gridGap: 24, + display: 'grid', + gridTemplateColumns: '1fr 1fr', + }} + > + <Button + href='/register' + text='Registreren' + style={{ backgroundColor: 'var(--background-alt)' }} + > + </Button> + <Button text='Inloggen' onclick={() => submitLogin(null, toast)}></Button> </div> - <input type="submit" style={{ display: "none" }}/> + <input type='submit' style={{ display: 'none' }} /> </form> </Vierkant> </div> @@ -81,4 +94,3 @@ export default function LoginPage() { </div> ); } - diff --git a/pages/register.tsx b/pages/register.tsx index 5c0e37b..f78d092 100644 --- a/pages/register.tsx +++ b/pages/register.tsx @@ -4,27 +4,27 @@ import { FormEvent, useContext } from 'react'; import { NavBar } from '../components/navbar'; import { CenteredPage } from '../components/page'; -import { Vierkant, Input, Button } from '../components/ui'; import { ToastContext, toastType } from '../components/toast'; +import { Button, Input, Vierkant } from '../components/ui'; -import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; import ErrorOutlineIcon from '@material-ui/icons/ErrorOutline'; +import ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined'; function submitRegister(event?: FormEvent<HTMLFormElement>, toast?: toastType) { event?.preventDefault(); var formData = { - username: (document.getElementById("username") as HTMLInputElement).value, - email: (document.getElementById("email") as HTMLInputElement).value, - password: (document.getElementById("password") as HTMLInputElement).value - } + username: (document.getElementById('username') as HTMLInputElement).value, + email: (document.getElementById('email') as HTMLInputElement).value, + password: (document.getElementById('password') as HTMLInputElement).value, + }; - if ( !formData.username || - !formData.email || - !formData.password ) { - toast({ message: "Vul alsjeblieft alle velden in!", - type: "error", - icon: <ReportProblemOutlinedIcon/>}); + if ( + !formData.username + || !formData.email + || !formData.password + ) { + toast({ message: 'Vul alsjeblieft alle velden in!', type: 'error', icon: <ReportProblemOutlinedIcon /> }); return; } @@ -39,47 +39,47 @@ function submitRegister(event?: FormEvent<HTMLFormElement>, toast?: toastType) { * https://stackoverflow.com/questions/5142103/regex-to-validate-password-strength */ - if ( formData.username.length < 3 || formData.username.length > 35 ) { - toast({ message: "Ongeldige gebruikersnaam", - description: "Je gebruikersnaam moet tussen de 3 en 35 letters zijn", - type: "error", - icon: <ReportProblemOutlinedIcon/>}); + if (formData.username.length < 3 || formData.username.length > 35) { + toast({ + message: 'Ongeldige gebruikersnaam', + description: 'Je gebruikersnaam moet tussen de 3 en 35 letters zijn', + type: 'error', + icon: <ReportProblemOutlinedIcon />, + }); return; } - if ( !validateEmail(formData.email) ) { - toast({ message: "Ongeldig email-adres", - type: "error", - icon: <ReportProblemOutlinedIcon/>}); + if (!validateEmail(formData.email)) { + toast({ message: 'Ongeldig email-adres', type: 'error', icon: <ReportProblemOutlinedIcon /> }); return; } - //TODO: wachtwoord max 72 tekens ivm bcrypt - if ( !formData.password.match(passwordRegex) ) { - toast({ message: "Ongeldig wachtwoord", - description: "Je wachtwoord moet een hoofdletter, kleine letter en een getal bevatten", - type: "error", - icon: <ReportProblemOutlinedIcon/>}); + // TODO: wachtwoord max 72 tekens ivm bcrypt + if (!formData.password.match(passwordRegex)) { + toast({ + message: 'Ongeldig wachtwoord', + description: 'Je wachtwoord moet een hoofdletter, kleine letter en een getal bevatten', + type: 'error', + icon: <ReportProblemOutlinedIcon />, + }); return; } - + axios({ - method: "post", + method: 'post', url: `${window.location.origin}/api/auth/signup`, - headers: {"content-type": "application/json"}, - data: formData - }) - .then(() => { - //TODO: email verificatie - // redirect naar home, automatische login - window.location.pathname = "/"; + headers: { 'content-type': 'application/json' }, + data: formData, }) - .catch(error => { - toast({ message: "Er is iets fout gegaan", - type: "error", - icon: <ErrorOutlineIcon/>}); - console.log(error); - }); + .then(() => { + // TODO: email verificatie + // redirect naar home, automatische login + window.location.pathname = '/'; + }) + .catch(error => { + toast({ message: 'Er is iets fout gegaan', type: 'error', icon: <ErrorOutlineIcon /> }); + console.log(error); + }); } export default function RegisterPage() { @@ -87,22 +87,36 @@ export default function RegisterPage() { return ( <div> - <NavBar/> - <CenteredPage width={500} style={{ height: "100vh" }}> - <div style={{ - position: "relative", - top: "50%", - transform: "translateY(-50%)", - margin: "0 auto", - textAlign: "center" - }}> + <NavBar /> + <CenteredPage width={500} style={{ height: '100vh' }}> + <div + style={{ + position: 'relative', + top: '50%', + transform: 'translateY(-50%)', + margin: '0 auto', + textAlign: 'center', + }} + > <Vierkant> <form onSubmit={(e) => submitRegister(e, toast)}> - <Input autofocus autocomplete="username" id="username" label="gebruikersnaam" style={{ marginBottom: 12 }}></Input> - <Input autocomplete="email" id="email" label="email" style={{ marginBottom: 12 }}></Input> - <Input autocomplete="new-password" id="password" label="wachtwoord" type="password"></Input> - <Button text="Registreren" style={{ marginTop: 24 }} onclick={() => submitRegister(null, toast)}></Button> - <input type="submit" style={{ display: "none" }}/> + <Input + autofocus + autocomplete='username' + id='username' + label='gebruikersnaam' + style={{ marginBottom: 12 }} + > + </Input> + <Input autocomplete='email' id='email' label='email' style={{ marginBottom: 12 }}></Input> + <Input autocomplete='new-password' id='password' label='wachtwoord' type='password'></Input> + <Button + text='Registreren' + style={{ marginTop: 24 }} + onclick={() => submitRegister(null, toast)} + > + </Button> + <input type='submit' style={{ display: 'none' }} /> </form> </Vierkant> </div> @@ -110,4 +124,3 @@ export default function RegisterPage() { </div> ); } - diff --git a/pages/search.tsx b/pages/search.tsx index 50a2410..2b8668a 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -1,45 +1,53 @@ -import { FormEvent, useState } from 'react'; import axios from 'axios'; +import { FormEvent, useState } from 'react'; +import { userInfo } from '../api/api'; +import { AccountAvatar } from '../components/account'; import { NavBar } from '../components/navbar'; -import { Vierkant, Button, Input } from '../components/ui'; import { CenteredPage, PageTitle } from '../components/page'; -import { AccountAvatar } from '../components/account'; -import { userInfo } from '../api/api'; +import { Button, Input, Vierkant } from '../components/ui'; import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined'; function search(callback: (results: Array<userInfo>) => void) { - var query: string = (document.getElementById("searchBar") as HTMLInputElement).value; + var query: string = (document.getElementById('searchBar') as HTMLInputElement).value; if (query.length < 3) return; - axios.request<{ "results": Array<userInfo> }>({ - method: "post", + axios.request<{ 'results': Array<userInfo>; }>({ + method: 'post', url: `${window.location.origin}/api/social/search`, - headers: {"content-type": "application/json"}, - data: { "query": query } + headers: { 'content-type': 'application/json' }, + data: { 'query': query }, }) - .then(response => callback(response.data.results)) - .catch(() => {}); + .then(response => callback(response.data.results)) + .catch(() => {}); } -function SearchResults(props: { userList: Array<userInfo> }) { +function SearchResults(props: { userList: Array<userInfo>; }) { return <div> - { props.userList?.map(user => <SearchResult user={user} key={user.id}/>) } + {props.userList?.map(user => <SearchResult user={user} key={user.id} />)} </div>; } -function SearchResult(props: { user: userInfo }) { - return <Vierkant style={{ - padding: 12 - }} fullwidth href={"/user?id=" + props.user.id}> - <div style={{ position: "relative" }}> - <AccountAvatar size={48} id={props.user.id}/> - <div style={{ - position: "absolute", - top: 0, right: 0, bottom: 0, - left: 48 + 12 - }}> +function SearchResult(props: { user: userInfo; }) { + return <Vierkant + style={{ + padding: 12, + }} + fullwidth + href={'/user?id=' + props.user.id} + > + <div style={{ position: 'relative' }}> + <AccountAvatar size={48} id={props.user.id} /> + <div + style={{ + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 48 + 12, + }} + > <b>{props.user.username}</b> <p>{props.user.status}</p> </div> @@ -50,27 +58,42 @@ function SearchResult(props: { user: userInfo }) { function SearchBar(props: { searchFunction: (event?: FormEvent<HTMLFormElement>) => void; }) { - return <Vierkant fullwidth style={{ - padding: 8, - marginBottom: 24 - }}> + return <Vierkant + fullwidth + style={{ + padding: 8, + marginBottom: 24, + }} + > <form onSubmit={props.searchFunction}> - <Input id="searchBar" label="Zoeken voor gebruikers..." autocomplete="off" dark autofocus style={{ - backgroundColor: "var(--background)", - color: "var(--text)", - padding: 14, - fontSize: 16, - width: "calc(100% - 48px - 14px * 2)" - }}/> - <Button style={{ - padding: 12, - float: "right", - display: "inline-block", - borderRadius: 4 - }} onclick={props.searchFunction}><SearchOutlinedIcon/></Button> - <input type="submit" style={{ display: "none" }}/> + <Input + id='searchBar' + label='Zoeken voor gebruikers...' + autocomplete='off' + dark + autofocus + style={{ + backgroundColor: 'var(--background)', + color: 'var(--text)', + padding: 14, + fontSize: 16, + width: 'calc(100% - 48px - 14px * 2)', + }} + /> + <Button + style={{ + padding: 12, + float: 'right', + display: 'inline-block', + borderRadius: 4, + }} + onclick={props.searchFunction} + > + <SearchOutlinedIcon /> + </Button> + <input type='submit' style={{ display: 'none' }} /> </form> - </Vierkant> + </Vierkant>; } export default function HomePage() { @@ -80,21 +103,24 @@ export default function HomePage() { event.preventDefault(); search(results => setResults(results)); setSearched(true); - } + }; return <div> - <NavBar/> + <NavBar /> <CenteredPage width={802}> <PageTitle>Zoeken</PageTitle> - <SearchBar searchFunction={getSearchResults}/> - <SearchResults userList={results}/> - { searched && results.length == 0 && <h1 style={{ - opacity: .6, - color: "var(--text)", - textAlign: "center", - margin: "24px 32px" - }}>Geen zoekresultaten gevonden</h1> } + <SearchBar searchFunction={getSearchResults} /> + <SearchResults userList={results} /> + {searched && results.length == 0 && <h1 + style={{ + opacity: .6, + color: 'var(--text)', + textAlign: 'center', + margin: '24px 32px', + }} + > + Geen zoekresultaten gevonden + </h1>} </CenteredPage> - </div> + </div>; } - diff --git a/pages/settings.tsx b/pages/settings.tsx index dcaa866..0f40a90 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -1,35 +1,35 @@ -import { CSSProperties, useContext } from 'react'; -import * as cookies from 'react-cookies'; import axios from 'axios'; import reduce from 'image-blob-reduce'; +import { CSSProperties, useContext } from 'react'; +import * as cookies from 'react-cookies'; -import { NavBar } from '../components/navbar'; -import { CenteredPage, PageTitle } from '../components/page'; -import { Vierkant, IconLabelButton, CheckBox, ColorPicker } from '../components/ui'; import { AccountAvatar } from '../components/account'; +import { Footer } from '../components/footer'; import { CurrentGameSettings } from '../components/gameSettings'; +import { NavBar } from '../components/navbar'; +import { CenteredPage, PageTitle } from '../components/page'; import PreferencesContext from '../components/preferencesContext'; -import { Footer } from '../components/footer'; +import { CheckBox, ColorPicker, IconLabelButton, Vierkant } from '../components/ui'; import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; -import VisibilityOutlinedIcon from '@material-ui/icons/VisibilityOutlined'; import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined'; import PublishOutlinedIcon from '@material-ui/icons/PublishOutlined'; +import VisibilityOutlinedIcon from '@material-ui/icons/VisibilityOutlined'; var SettingsSubsectionStyle: CSSProperties = { marginTop: 24, - minHeight: 40 + minHeight: 40, }; async function uploadNewProfileImage() { if (!this.result) return; - var result = this.result.split(";"); + var result = this.result.split(';'); var mimeType = result[0].substr(5); - if (!["image/png", "image/jpeg"].includes(mimeType)) return; + if (!['image/png', 'image/jpeg'].includes(mimeType)) return; - var blob = await (await fetch(this.result)).blob() + var blob = await (await fetch(this.result)).blob(); var image = await new reduce().toBlob(blob, { max: 256 }); var reader = new FileReader(); @@ -37,13 +37,13 @@ async function uploadNewProfileImage() { reader.readAsBinaryString(image); reader.onload = async () => { await axios.request({ - method: "post", + method: 'post', url: `/api/user/avatar`, - headers: {"content-type": "image/png"}, - data: btoa(reader.result as string) + headers: { 'content-type': 'image/png' }, + data: btoa(reader.result as string), }); - window.location.reload(); //TODO: this is straight garbage - } + window.location.reload(); // TODO: this is straight garbage + }; } export default function SettingsPage() { @@ -51,48 +51,49 @@ export default function SettingsPage() { return ( <div> - <NavBar/> + <NavBar /> <CenteredPage width={802}> <PageTitle>Instellingen</PageTitle> <Vierkant fullwidth> <h2>Account</h2> <div style={SettingsSubsectionStyle}> - <AccountAvatar size={100}/> - <label htmlFor="pfUpload"> - <IconLabelButton text="Nieuwe profielfoto uploaden" icon={<PublishOutlinedIcon/>}/> + <AccountAvatar size={100} /> + <label htmlFor='pfUpload'> + <IconLabelButton text='Nieuwe profielfoto uploaden' icon={<PublishOutlinedIcon />} /> </label> <input - type="file" - id="pfUpload" - accept=".png,.jpg,.jpeg" - style={{ display: "none" }} + type='file' + id='pfUpload' + accept='.png,.jpg,.jpeg' + style={{ display: 'none' }} onChange={event => { - var file = event.target.files[0]; - if (!file) return; + var file = event.target.files[0]; + if (!file) return; - var reader = new FileReader(); - reader.onload = uploadNewProfileImage; - reader.readAsDataURL(file); - }}/> + var reader = new FileReader(); + reader.onload = uploadNewProfileImage; + reader.readAsDataURL(file); + }} + /> </div> <div style={SettingsSubsectionStyle}> - <IconLabelButton text="Bewerken" icon={<EditOutlinedIcon/>}/> - <div style={{ display: "block" }}> + <IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} /> + <div style={{ display: 'block' }}> <h3>Gebruikersnaam</h3> <p>Hier staat hij dan</p> </div> </div> <div style={SettingsSubsectionStyle}> - <IconLabelButton text="Bewerken" icon={<EditOutlinedIcon/>}/> - <IconLabelButton text="Onthullen" icon={<VisibilityOutlinedIcon/>}/> - <div style={{ display: "block" }}> + <IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} /> + <IconLabelButton text='Onthullen' icon={<VisibilityOutlinedIcon />} /> + <div style={{ display: 'block' }}> <h3>Email</h3> <p>******@example.com</p> </div> </div> <div style={SettingsSubsectionStyle}> - <IconLabelButton text="Bewerken" icon={<EditOutlinedIcon/>}/> - <div style={{ display: "block" }}> + <IconLabelButton text='Bewerken' icon={<EditOutlinedIcon />} /> + <div style={{ display: 'block' }}> <h3>Wachtwoord</h3> </div> </div> @@ -100,23 +101,24 @@ export default function SettingsPage() { <Vierkant fullwidth> <h2>Kleuren</h2> <div style={SettingsSubsectionStyle}> - <ColorPicker/> - <ColorPicker/> - <div style={{ display: "block" }}> + <ColorPicker /> + <ColorPicker /> + <div style={{ display: 'block' }}> <h3>Schijfjes</h3> </div> </div> <div style={SettingsSubsectionStyle}> - <ColorPicker/> - <div style={{ display: "block" }}> + <ColorPicker /> + <div style={{ display: 'block' }}> <h3>Achtergrond</h3> </div> </div> <div style={SettingsSubsectionStyle}> - <div style={{ float: "right" }}> - <CheckBox state={preferences?.darkMode} onclick={ - state => updatePreference({"darkMode": state}) - }/> + <div style={{ float: 'right' }}> + <CheckBox + state={preferences?.darkMode} + onclick={state => updatePreference({ 'darkMode': state })} + /> </div> <h3>Donkere modus</h3> </div> @@ -124,27 +126,33 @@ export default function SettingsPage() { <Vierkant fullwidth> <h2>Standaard spelregels</h2> <div style={SettingsSubsectionStyle}> - <CurrentGameSettings/> + <CurrentGameSettings /> </div> </Vierkant> <Vierkant fullwidth> <h2>Uitloggen</h2> - <div style={{ - width: "100%", - textAlign: "center" - }}> - <IconLabelButton icon={<ExitToAppOutlinedIcon/>} text="Uitloggen" style={{ - float: "none", - marginLeft: 0 - }} onclick={() => { - cookies.remove("token") - window.location.pathname = "/"; - }}/> + <div + style={{ + width: '100%', + textAlign: 'center', + }} + > + <IconLabelButton + icon={<ExitToAppOutlinedIcon />} + text='Uitloggen' + style={{ + float: 'none', + marginLeft: 0, + }} + onclick={() => { + cookies.remove('token'); + window.location.pathname = '/'; + }} + /> </div> </Vierkant> </CenteredPage> - <Footer/> + <Footer /> </div> ); } - diff --git a/pages/user.tsx b/pages/user.tsx index 5f5b1eb..4f7331c 100644 --- a/pages/user.tsx +++ b/pages/user.tsx @@ -1,89 +1,106 @@ -import { ReactNode, Children, useState, useEffect, useContext } from 'react'; import Icon from '@mdi/react'; import axios from 'axios'; +import { Children, ReactNode, useContext, useEffect, useState } from 'react'; +import { userGames, userInfo } from '../api/api'; +import { AccountAvatar } from '../components/account'; +import { Footer } from '../components/footer'; import { NavBar } from '../components/navbar'; import { CenteredPage, PageTitle } from '../components/page'; -import { Vierkant, IconLabelButton } from '../components/ui'; -import { AccountAvatar } from '../components/account'; -import { userInfo, userGames } from '../api/api'; import RecentGames from '../components/recentGames'; -import { ToastContext } from '../components/toast'; import { SocketContext } from '../components/socketContext'; -import { Footer } from '../components/footer'; +import { ToastContext } from '../components/toast'; +import { IconLabelButton, Vierkant } from '../components/ui'; -import PersonAddOutlinedIcon from '@material-ui/icons/PersonAddOutlined'; -import AssignmentIndOutlinedIcon from '@material-ui/icons/AssignmentIndOutlined'; import ArrowDownwardOutlinedIcon from '@material-ui/icons/ArrowDownwardOutlined'; import ArrowUpwardOutlinedIcon from '@material-ui/icons/ArrowUpwardOutlined'; -import PeopleOutlineOutlinedIcon from '@material-ui/icons/PeopleOutlineOutlined'; +import AssignmentIndOutlinedIcon from '@material-ui/icons/AssignmentIndOutlined'; +import DoneOutlinedIcon from '@material-ui/icons/DoneOutlined'; import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; +import PeopleOutlineOutlinedIcon from '@material-ui/icons/PeopleOutlineOutlined'; +import PersonAddOutlinedIcon from '@material-ui/icons/PersonAddOutlined'; import SettingsOutlinedIcon from '@material-ui/icons/SettingsOutlined'; -import DoneOutlinedIcon from '@material-ui/icons/DoneOutlined'; import { mdiAccountCancelOutline, - mdiEqual, + mdiAccountMinusOutline, + mdiAccountRemoveOutline, mdiCheckboxBlankCircle, mdiClipboardTextOutline, - mdiGamepadSquareOutline, mdiEarth, - mdiAccountMinusOutline, - mdiAccountRemoveOutline } from '@mdi/js'; + mdiEqual, + mdiGamepadSquareOutline, +} from '@mdi/js'; function InfoModule(props: { label: string; icon: ReactNode; }) { - return <div style={{ - position: "relative", - height: "100%" - }}> - <div style={{ - position: "absolute", - left: "50%", - transform: "translateX(-50%)" - }}>{props.icon}</div> - <div style={{ - position: "absolute", - top: 24 + 6, - left: 0, right: 0, bottom: 0, - }}> - <span style={{ - position: "absolute", - top: "50%", - transform: "translateY(-50%)", - width: "100%", - textAlign: "center" - }}>{props.label}</span> + return <div + style={{ + position: 'relative', + height: '100%', + }} + > + <div + style={{ + position: 'absolute', + left: '50%', + transform: 'translateX(-50%)', + }} + > + {props.icon} + </div> + <div + style={{ + position: 'absolute', + top: 24 + 6, + left: 0, + right: 0, + bottom: 0, + }} + > + <span + style={{ + position: 'absolute', + top: '50%', + transform: 'translateY(-50%)', + width: '100%', + textAlign: 'center', + }} + > + {props.label} + </span> </div> - </div> + </div>; } -function InfoSection(props: { children: ReactNode }) { +function InfoSection(props: { children: ReactNode; }) { return <Vierkant fullwidth> - <div style={{ - display: "grid", - gridTemplateColumns: `repeat(${Children.count(props.children)}, 1fr)`, - gridGap: 12, - height: 64 - }}> + <div + style={{ + display: 'grid', + gridTemplateColumns: `repeat(${Children.count(props.children)}, 1fr)`, + gridGap: 12, + height: 64, + }} + > {props.children} </div> - </Vierkant> + </Vierkant>; } export default function AccountPage() { - var server = typeof window === "undefined"; - var loggedIn = !server && document.cookie.includes("token"); - var pageID = server ? "" : new URLSearchParams(window.location.search).get("id"); + var server = typeof window === 'undefined'; + var loggedIn = !server && document.cookie.includes('token'); + var pageID = server ? '' : new URLSearchParams(window.location.search).get('id'); if (!loggedIn && !pageID) !server && window.history.go(-1); - var reqData = loggedIn && pageID ? { "id": pageID } : undefined; + var reqData = loggedIn && pageID ? { 'id': pageID } : undefined; var [user, setUser] = useState<userInfo>(); var [gameInfo, setGameInfo] = useState<userGames>(); var [editingStatus, setEditingStatus] = useState(false); - var [relation, setRelation] = useState<userInfo["relation"]>("none"); + var [relation, setRelation] = useState<userInfo['relation']>('none'); var [ownPage, setOwnPage] = useState(loggedIn && !pageID); var { toast } = useContext(ToastContext); @@ -91,237 +108,288 @@ export default function AccountPage() { async function getUserData(): Promise<userInfo> { var userReq = await axios.request<userInfo>({ - method: "post", + method: 'post', url: `/api/user/info`, - headers: {"content-type": "application/json"}, - data: reqData + headers: { 'content-type': 'application/json' }, + data: reqData, }); setUser(userReq.data); - return userReq.data + return userReq.data; } async function getRelationTo(user: userInfo) { var user = await getUserData(); - setRelation(user.relation || "none"); + setRelation(user.relation || 'none'); } function setIOListeners(user: userInfo) { - io.on("changedRelation", (data: { id: string }) => { + io.on('changedRelation', (data: { id: string; }) => { if (data.id != user.id) return; getRelationTo(user); }); - io.on("incomingFriendRequest", getRelationTo); + io.on('incomingFriendRequest', getRelationTo); } - useEffect(() => {(async() => { - var user = await getUserData(); + useEffect(() => { + (async () => { + var user = await getUserData(); - getRelationTo(user); - setIOListeners(user); - })()}, []); + getRelationTo(user); + setIOListeners(user); + })(); + }, []); - useEffect(() => {(async() => { - var userReq = await axios.request<userInfo>({ - method: "post", - url: `/api/user/info`, - headers: {"content-type": "application/json"} - }); - setOwnPage(ownPage || userReq.data.id == pageID); - })()}, []); + useEffect(() => { + (async () => { + var userReq = await axios.request<userInfo>({ + method: 'post', + url: `/api/user/info`, + headers: { 'content-type': 'application/json' }, + }); + setOwnPage(ownPage || userReq.data.id == pageID); + })(); + }, []); // Get recent games - useEffect(() => {(async() => { - var userGamesReq = await axios.request<userGames>({ - method: "post", - url: `/api/user/games`, - headers: {"content-type": "application/json"}, - data: reqData - }); - setGameInfo(userGamesReq.data); - })()}, []); + useEffect(() => { + (async () => { + var userGamesReq = await axios.request<userGames>({ + method: 'post', + url: `/api/user/games`, + headers: { 'content-type': 'application/json' }, + data: reqData, + }); + setGameInfo(userGamesReq.data); + })(); + }, []); return <div> - <NavBar/> + <NavBar /> <CenteredPage width={802}> <PageTitle>Profiel</PageTitle> <Vierkant fullwidth> - <AccountAvatar size={128} id={user?.id || ""}/> - <div style={{ - display: "inline-block", - verticalAlign: "top", - marginLeft: 12, - width: "calc(100% - 128px - 12px)" - }}> + <AccountAvatar size={128} id={user?.id || ''} /> + <div + style={{ + display: 'inline-block', + verticalAlign: 'top', + marginLeft: 12, + width: 'calc(100% - 128px - 12px)', + }} + > <h2 style={{ fontSize: 32 }}>{user?.username}</h2> - <p id="status" contentEditable={editingStatus ? "true" : "false"} style={{ - marginTop: 6, - transitionDuration: ".3s" - }} suppressContentEditableWarning={true}>{user?.status}</p> + <p + id='status' + contentEditable={editingStatus ? 'true' : 'false'} + style={{ + marginTop: 6, + transitionDuration: '.3s', + }} + suppressContentEditableWarning={true} + > + {user?.status} + </p> </div> - <div style={{ - position: "absolute", - backgroundColor: "var(--background)", - height: "40px", - bottom: 24, left: 24 + 12 + 128, right: 24 - }}> - { loggedIn && <div> { - ownPage ? - <div> - <IconLabelButton icon={<SettingsOutlinedIcon/>} href="/settings" text="Instellingen"/> - { - !editingStatus ? - <IconLabelButton - icon={<EditOutlinedIcon/>} - text="Status bewerken" - onclick={() => setEditingStatus(true)}/> : - <IconLabelButton - icon={<DoneOutlinedIcon/>} - text="Status opslaan" - onclick={() => { - setEditingStatus(false) - axios.request({ - method: "post", - url: `/api/user/status`, - headers: {"content-type": "application/json"}, - data: { "status": document.getElementById("status").innerText } - }); - }}/> - } - </div> : - <div> - {(() => { - var icon = { - "blocked": <Icon size={1} path={mdiAccountCancelOutline}/> - }[relation] || <Icon size={1} path={mdiAccountCancelOutline}/> + <div + style={{ + position: 'absolute', + backgroundColor: 'var(--background)', + height: '40px', + bottom: 24, + left: 24 + 12 + 128, + right: 24, + }} + > + {loggedIn && <div> + {ownPage + ? <div> + <IconLabelButton icon={<SettingsOutlinedIcon />} href='/settings' text='Instellingen' /> + {!editingStatus + ? <IconLabelButton + icon={<EditOutlinedIcon />} + text='Status bewerken' + onclick={() => setEditingStatus(true)} + /> + : <IconLabelButton + icon={<DoneOutlinedIcon />} + text='Status opslaan' + onclick={() => { + setEditingStatus(false); + axios.request({ + method: 'post', + url: `/api/user/status`, + headers: { 'content-type': 'application/json' }, + data: { 'status': document.getElementById('status').innerText }, + }); + }} + />} + </div> + : <div> + {(() => { + var icon = { + 'blocked': <Icon size={1} path={mdiAccountCancelOutline} />, + }[relation] || <Icon size={1} path={mdiAccountCancelOutline} />; - var text = { - "blocked": "Deblokkeren" - }[relation] || "Blokkeren" + var text = { + 'blocked': 'Deblokkeren', + }[relation] || 'Blokkeren'; - return <IconLabelButton icon={icon} text={text} onclick={() => { - var nextRelation = { - "blocked": { - "endpoint": "/api/social/unblock", - "action": `${user.username} gedeblokkeerd`, - "relation": "none", - "icon": <Icon size={1} path={mdiAccountCancelOutline}/>, - } - }[relation] || { - "endpoint": "/api/social/block", - "action": `${user.username} geblokkeerd`, - "relation": "blocked", - "icon": <Icon size={1} path={mdiAccountCancelOutline}/>, - } + return <IconLabelButton + icon={icon} + text={text} + onclick={() => { + var nextRelation = { + 'blocked': { + 'endpoint': '/api/social/unblock', + 'action': `${user.username} gedeblokkeerd`, + 'relation': 'none', + 'icon': <Icon size={1} path={mdiAccountCancelOutline} />, + }, + }[relation] || { + 'endpoint': '/api/social/block', + 'action': `${user.username} geblokkeerd`, + 'relation': 'blocked', + 'icon': <Icon size={1} path={mdiAccountCancelOutline} />, + }; - axios.request({ - method: "post", - url: nextRelation.endpoint, - headers: {"content-type": "application/json"}, - data: { "id": user?.id } - }) - .then(() => { - toast({ message: nextRelation.action, - type: "confirmation", - icon: nextRelation.icon }); - setRelation(nextRelation.relation); - }); - }}/> - })()} - {(() => { - var icon = { - "friends": <Icon size={1} path={mdiAccountMinusOutline}/>, - "outgoing": <Icon size={1} path={mdiAccountRemoveOutline}/>, - "incoming": <PersonAddOutlinedIcon/> - }[relation] || <PersonAddOutlinedIcon/> + axios.request({ + method: 'post', + url: nextRelation.endpoint, + headers: { 'content-type': 'application/json' }, + data: { 'id': user?.id }, + }) + .then(() => { + toast({ + message: nextRelation.action, + type: 'confirmation', + icon: nextRelation.icon, + }); + setRelation(nextRelation.relation); + }); + }} + />; + })()} + {(() => { + var icon = { + 'friends': <Icon size={1} path={mdiAccountMinusOutline} />, + 'outgoing': <Icon size={1} path={mdiAccountRemoveOutline} />, + 'incoming': <PersonAddOutlinedIcon />, + }[relation] || <PersonAddOutlinedIcon />; - var text = { - "friends": "Vriend verwijderen", - "outgoing": "Vriendschapsverzoek annuleren", - "incoming": "Vriendschapsverzoek accepteren" - }[relation] || "Vriendschapsverzoek sturen" + var text = { + 'friends': 'Vriend verwijderen', + 'outgoing': 'Vriendschapsverzoek annuleren', + 'incoming': 'Vriendschapsverzoek accepteren', + }[relation] || 'Vriendschapsverzoek sturen'; - return <IconLabelButton icon={icon} text={text} onclick={() => { - var nextRelation = { - "friends": { - "endpoint": "/api/social/remove", - "action": `${user.username} succesvol verwijderd als vriend`, - "relation": "none", - "icon": <Icon size={1} path={mdiAccountMinusOutline}/>, - }, - "outgoing": { - "endpoint": "/api/social/remove", - "action": `Vriendschapsverzoek naar ${user.username} geannuleerd`, - "relation": "none", - "icon": <Icon size={1} path={mdiAccountMinusOutline}/>, - }, - "incoming": { - "endpoint": "/api/social/accept", - "action": `Vriendschapsverzoek van ${user.username} geaccepteerd`, - "relation": "friends", - "icon": <PersonAddOutlinedIcon/>, - }, - }[relation] || { - "endpoint": "/api/social/request", - "action": `Vriendschapsverzoek gestuurd naar ${user.username}`, - "relation": "outgoing", - "icon": <PersonAddOutlinedIcon/>, - } + return <IconLabelButton + icon={icon} + text={text} + onclick={() => { + var nextRelation = { + 'friends': { + 'endpoint': '/api/social/remove', + 'action': `${user.username} succesvol verwijderd als vriend`, + 'relation': 'none', + 'icon': <Icon size={1} path={mdiAccountMinusOutline} />, + }, + 'outgoing': { + 'endpoint': '/api/social/remove', + 'action': `Vriendschapsverzoek naar ${user.username} geannuleerd`, + 'relation': 'none', + 'icon': <Icon size={1} path={mdiAccountMinusOutline} />, + }, + 'incoming': { + 'endpoint': '/api/social/accept', + 'action': `Vriendschapsverzoek van ${user.username} geaccepteerd`, + 'relation': 'friends', + 'icon': <PersonAddOutlinedIcon />, + }, + }[relation] || { + 'endpoint': '/api/social/request', + 'action': `Vriendschapsverzoek gestuurd naar ${user.username}`, + 'relation': 'outgoing', + 'icon': <PersonAddOutlinedIcon />, + }; - axios.request({ - method: "post", - url: nextRelation.endpoint, - headers: {"content-type": "application/json"}, - data: { "id": user?.id } - }) - .then(() => { - toast({ message: nextRelation.action, - type: "confirmation", - icon: nextRelation.icon }); - setRelation(nextRelation.relation); - }); - }}/> - })()} - </div> - }</div>} + axios.request({ + method: 'post', + url: nextRelation.endpoint, + headers: { 'content-type': 'application/json' }, + data: { 'id': user?.id }, + }) + .then(() => { + toast({ + message: nextRelation.action, + type: 'confirmation', + icon: nextRelation.icon, + }); + setRelation(nextRelation.relation); + }); + }} + />; + })()} + </div>} + </div>} </div> </Vierkant> <InfoSection> - <InfoModule icon={<Icon size={1} path={mdiCheckboxBlankCircle} color="var(--disk-b-text)"/>} label="Online"/> - <InfoModule icon={<AssignmentIndOutlinedIcon/>} label={ (() => { - var memberSince = "Lid sinds"; + <InfoModule + icon={<Icon size={1} path={mdiCheckboxBlankCircle} color='var(--disk-b-text)' />} + label='Online' + /> + <InfoModule + icon={<AssignmentIndOutlinedIcon />} + label={(() => { + var memberSince = 'Lid sinds'; - var registered = new Date(user?.registered); - memberSince += " " + registered.toLocaleString("nl-nl", { month: "long", day: "numeric" }); + var registered = new Date(user?.registered); + memberSince += ' ' + registered.toLocaleString('nl-nl', { month: 'long', day: 'numeric' }); - var currentYear = new Date().getFullYear(); - var memberYear = registered.getFullYear(); - if (currentYear != memberYear) memberSince += " " + memberYear; + var currentYear = new Date().getFullYear(); + var memberYear = registered.getFullYear(); + if (currentYear != memberYear) memberSince += ' ' + memberYear; - return memberSince; - })() }/> - <InfoModule icon={<PeopleOutlineOutlinedIcon/>} label={(() => { - var label = user?.friends.toString() + " "; - label += user?.friends == 1 ? "vriend" : "vrienden"; - return label; - })()}/> - <InfoModule icon={<Icon size={1} path={mdiEarth}/>} label="Nederland"/> + return memberSince; + })()} + /> + <InfoModule + icon={<PeopleOutlineOutlinedIcon />} + label={(() => { + var label = user?.friends.toString() + ' '; + label += user?.friends == 1 ? 'vriend' : 'vrienden'; + return label; + })()} + /> + <InfoModule icon={<Icon size={1} path={mdiEarth} />} label='Nederland' /> </InfoSection> <InfoSection> - <InfoModule icon={<ArrowUpwardOutlinedIcon style={{ color: "var(--disk-b-text)" }}/>} label={ gameInfo?.totals.win + " keer gewonnen" }/> - <InfoModule icon={<Icon size={1} path={mdiEqual}/>} label={ gameInfo?.totals.draw + " keer gelijkspel" }/> - <InfoModule icon={<ArrowDownwardOutlinedIcon style={{ color: "var(--disk-a-text)" }}/>} label={ gameInfo?.totals.lose + " keer verloren" }/> - <InfoModule icon={<Icon size={1} path={mdiClipboardTextOutline}/>} label={ "Score: " + user?.rating }/> - <InfoModule icon={<Icon size={1} path={mdiGamepadSquareOutline}/>} label={(() => { - var label = gameInfo?.totals.games.toString() + " "; - label += gameInfo?.totals.games == 1 ? "potje" : "potjes"; - return label; - })()}/> + <InfoModule + icon={<ArrowUpwardOutlinedIcon style={{ color: 'var(--disk-b-text)' }} />} + label={gameInfo?.totals.win + ' keer gewonnen'} + /> + <InfoModule + icon={<Icon size={1} path={mdiEqual} />} + label={gameInfo?.totals.draw + ' keer gelijkspel'} + /> + <InfoModule + icon={<ArrowDownwardOutlinedIcon style={{ color: 'var(--disk-a-text)' }} />} + label={gameInfo?.totals.lose + ' keer verloren'} + /> + <InfoModule icon={<Icon size={1} path={mdiClipboardTextOutline} />} label={'Score: ' + user?.rating} /> + <InfoModule + icon={<Icon size={1} path={mdiGamepadSquareOutline} />} + label={(() => { + var label = gameInfo?.totals.games.toString() + ' '; + label += gameInfo?.totals.games == 1 ? 'potje' : 'potjes'; + return label; + })()} + /> </InfoSection> <Vierkant> - <RecentGames games={gameInfo?.games}/> + <RecentGames games={gameInfo?.games} /> </Vierkant> </CenteredPage> - <Footer/> - </div> + <Footer /> + </div>; } - |