aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/api.ts38
-rw-r--r--api/readme.md58
-rw-r--r--components/account.tsx33
-rw-r--r--components/dialogBox.tsx40
-rw-r--r--components/footer.tsx52
-rw-r--r--components/gameBar.tsx148
-rw-r--r--components/gameSettings.tsx358
-rw-r--r--components/globalState.tsx6
-rw-r--r--components/logo.tsx29
-rw-r--r--components/navbar.tsx192
-rw-r--r--components/notificationsArea.tsx254
-rw-r--r--components/page.tsx48
-rw-r--r--components/preferencesContext.tsx66
-rw-r--r--components/recentGames.tsx123
-rw-r--r--components/socketContext.tsx11
-rw-r--r--components/toast.tsx192
-rw-r--r--components/ui.tsx415
-rw-r--r--components/voerBord.tsx95
-rw-r--r--package.json124
-rw-r--r--pages/_app.tsx29
-rw-r--r--pages/blog/[post].tsx59
-rw-r--r--pages/game.tsx359
-rw-r--r--pages/index.tsx273
-rw-r--r--pages/login.tsx104
-rw-r--r--pages/register.tsx127
-rw-r--r--pages/search.tsx136
-rw-r--r--pages/settings.tsx128
-rw-r--r--pages/user.tsx544
-rw-r--r--readme.md74
-rw-r--r--styles/readme.md13
-rw-r--r--tsconfig.json52
-rw-r--r--voerbak/readme.md39
-rw-r--r--voerbak/stuk/readme.md4
33 files changed, 2367 insertions, 1856 deletions
diff --git a/api/api.ts b/api/api.ts
index 320df92..95c63ba 100644
--- a/api/api.ts
+++ b/api/api.ts
@@ -1,14 +1,14 @@
export interface userInfo {
- avatar: string|null,
- status: string|null,
- coutry: string|null,
- id: string,
- registered: number,
- username: string,
- friends: number,
- relation?: "none"|"friends"|"incoming"|"outgoing"|"blocked",
- rating: number,
-};
+ avatar: string | null;
+ status: string | null;
+ coutry: string | null;
+ id: string;
+ registered: number;
+ username: string;
+ friends: number;
+ relation?: 'none' | 'friends' | 'incoming' | 'outgoing' | 'blocked';
+ rating: number;
+}
export type ruleset = {
timelimit: {
@@ -17,15 +17,15 @@ export type ruleset = {
seconds: number;
addmove: number;
shared: boolean;
- },
+ };
ranked: boolean;
-}
+};
export type userColors = {
diskA: string;
diskB: string;
background: string;
-}
+};
export interface userPreferences {
darkMode?: boolean;
@@ -34,13 +34,13 @@ export interface userPreferences {
}
export interface userGameTotals {
- draw: number;
- games: number;
- lose: number;
- win: number;
+ draw: number;
+ games: number;
+ lose: number;
+ win: number;
}
-export type outcome = "w" | "l" | "d";
+export type outcome = 'w' | 'l' | 'd';
export interface userGames {
totals: userGameTotals;
@@ -60,5 +60,5 @@ export interface gameInfo {
rating_opponent?: number;
ruleset: ruleset;
started: number;
- status: "finished"|"in_progress"|"resign"|"wait_for_opponent";
+ status: 'finished' | 'in_progress' | 'resign' | 'wait_for_opponent';
}
diff --git a/api/readme.md b/api/readme.md
index ab4e1e6..70d9221 100644
--- a/api/readme.md
+++ b/api/readme.md
@@ -1,6 +1,8 @@
# API
-This is the subdirectory for the API. You can find the API reference in [this](https://docs.google.com/spreadsheets/d/1mDN9IUqRIMjr_9RmLxKybjIgVuaUadalmPEFnG-XeJg/edit?usp=sharing) Google Docs document.
+This is the subdirectory for the API. You can find the API reference in
+[this](https://docs.google.com/spreadsheets/d/1mDN9IUqRIMjr_9RmLxKybjIgVuaUadalmPEFnG-XeJg/edit?usp=sharing)
+Google Docs document.
## Endpoint reference (WIP)
@@ -33,6 +35,7 @@ API return type classes are mostly defined in api/api.ts
games: int
}
```
+
</td>
</tr>
@@ -48,6 +51,7 @@ API return type classes are mostly defined in api/api.ts
password: string
}
```
+
</td>
<td><code>none</code></td>
<td>empty response with the set_cookie header</td>
@@ -66,6 +70,7 @@ API return type classes are mostly defined in api/api.ts
password: string
}
```
+
</td>
<td><code>none</code></td>
<td>empty response with the set_cookie header</td>
@@ -83,13 +88,15 @@ API return type classes are mostly defined in api/api.ts
id?: userID
}
```
+
</td>
<td><code>none|user</code></td>
<td>
```ts
-userInfo
+userInfo;
```
+
</td>
</tr>
@@ -102,6 +109,7 @@ userInfo
```ts
{ id?: userID }
```
+
</td>
<td><code>none|user</code></td>
<td>
@@ -112,6 +120,7 @@ userInfo
totals: userGameTotals
}
```
+
</td>
</tr>
@@ -124,6 +133,7 @@ userInfo
```ts
{ id?: userID }
```
+
</td>
<td><code>none|user</code></td>
<td>PNG image</td>
@@ -137,12 +147,17 @@ update avatar
note: avatar is a client-resized 256x256 .png base64-encoded image, request
returns error when image is not .png or larger than 256x256
+
</td>
<td>
```ts
-{ image: base64PNG }
+{
+ image:
+ base64PNG;
+}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -157,8 +172,12 @@ returns error when image is not .png or larger than 256x256
<td>
```ts
-{ preferences: userPreferences }
+{
+ preferences:
+ userPreferences;
+}
```
+
</td>
</tr>
@@ -169,8 +188,12 @@ returns error when image is not .png or larger than 256x256
<td>
```ts
-{ newPreferences: userPreferences }
+{
+ newPreferences:
+ userPreferences;
+}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -188,6 +211,7 @@ returns error when image is not .png or larger than 256x256
newPassword: string,
}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -205,6 +229,7 @@ returns error when image is not .png or larger than 256x256
email: string,
}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -222,6 +247,7 @@ returns error when image is not .png or larger than 256x256
username: string,
}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -234,8 +260,12 @@ returns error when image is not .png or larger than 256x256
<td>
```ts
-{ status: string }
+{
+ status:
+ string;
+}
```
+
</td>
<td><code>user</code></td>
<td><code>none</code></td>
@@ -459,6 +489,7 @@ These are events that are fired by the socket.io connection
</table>
## How to test API endpoints
+
```sh
# If you're running the standalone flask server:
curl http://localhost:5000/<endpoint>
@@ -471,10 +502,16 @@ curl http://localhost:2080/api/<endpoint>
Please follow these rules when creating new API endpoints:
-1. Endpoints should always return a valid JSON object and an appropriate [http code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
-2. Endpoints that are in a namespace should get their own directory in this folder, eg. http://localhost:5000/status is defined in api/status.py, http://localhost:5000/auth/signup is defined in api/auth/signup.py etc.
-3. Endpoints that take data should verify that the data is present and valid, and return an empty JSON object with http code 400 (bad request) if the data isn't valid.
-4. Endpoints that require database access should get the cursor/connection object from api/db.py
+1. Endpoints should always return a valid JSON object and an appropriate
+ [http code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)
+2. Endpoints that are in a namespace should get their own directory in this
+ folder, eg. http://localhost:5000/status is defined in api/status.py,
+ http://localhost:5000/auth/signup is defined in api/auth/signup.py etc.
+3. Endpoints that take data should verify that the data is present and valid,
+ and return an empty JSON object with http code 400 (bad request) if the data
+ isn't valid.
+4. Endpoints that require database access should get the cursor/connection
+ object from api/db.py
## Example endpoint
@@ -503,4 +540,3 @@ dynamic_route = ["/tests", status]
# |
# namespace (defined in dynamic_route variable)
```
-
diff --git a/components/account.tsx b/components/account.tsx
index 7b825cd..f24135f 100644
--- a/components/account.tsx
+++ b/components/account.tsx
@@ -1,4 +1,5 @@
-var dummy = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4z/j/PwAHAQL/gXZXNQAAAABJRU5ErkJggg==";
+var dummy =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4z/j/PwAHAQL/gXZXNQAAAABJRU5ErkJggg==';
export function AccountAvatar(props: {
size: number;
@@ -8,24 +9,24 @@ export function AccountAvatar(props: {
id?: string;
}) {
- var image = "";
- image += "/api/user/avatar";
- if (typeof props.id === "string") {
- if (!props.id) image = "";
+ var image = '';
+ image += '/api/user/avatar';
+ if (typeof props.id === 'string') {
+ if (!props.id) image = '';
else image += `?id=${props.id}`;
}
if (props.dummy) image = dummy;
- return <div style={{
- width: props.size,
- height: props.size,
- backgroundColor: props.fallbackFill || "var(--background)",
- backgroundImage: `url(${image})`,
- backgroundSize: "cover",
- display: "inline-block",
- borderRadius: props.size / 2 * Number(props.round || 0)
- }}/>;
+ return <div
+ style={{
+ width: props.size,
+ height: props.size,
+ backgroundColor: props.fallbackFill || 'var(--background)',
+ backgroundImage: `url(${image})`,
+ backgroundSize: 'cover',
+ display: 'inline-block',
+ borderRadius: props.size / 2 * Number(props.round || 0),
+ }}
+ />;
}
-
-
diff --git a/components/dialogBox.tsx b/components/dialogBox.tsx
index 5ef5c3f..7abbded 100644
--- a/components/dialogBox.tsx
+++ b/components/dialogBox.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, CSSProperties } from 'react';
+import { CSSProperties, ReactNode } from 'react';
import { Vierkant } from './ui';
@@ -10,24 +10,30 @@ export function DialogBox(props: {
style?: CSSProperties;
onclick?: () => void;
}) {
- return <Vierkant style={{
- position: "fixed",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)",
- boxShadow: "0 8px 32px -5px #0007",
- width: 392,
- ...props.style
- }}>
+ return <Vierkant
+ style={{
+ position: 'fixed',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ boxShadow: '0 8px 32px -5px #0007',
+ width: 392,
+ ...props.style,
+ }}
+ >
<h2 style={{ marginBottom: 24 }}>{props.title}</h2>
<span onClick={props.onclick}>
- <CancelIcon style={{
- position: "absolute",
- top: 25, right: 25,
- color: "var(--text)",
- opacity: .85,
- cursor: "pointer"
- }}/>
+ <CancelIcon
+ style={{
+ position: 'absolute',
+ top: 25,
+ right: 25,
+ color: 'var(--text)',
+ opacity: .85,
+ cursor: 'pointer',
+ }}
+ />
</span>
{props.children}
- </Vierkant>
+ </Vierkant>;
}
diff --git a/components/footer.tsx b/components/footer.tsx
index 2d84a1c..36ebef3 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -1,49 +1,49 @@
-import { LogoDark } from "../components/logo";
import { ReactNode } from 'react';
+import { LogoDark } from '../components/logo';
-import Home from '@material-ui/icons/Home';
-import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
+import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
import ExtensionIcon from '@material-ui/icons/Extension';
+import GitHubIcon from '@material-ui/icons/GitHub';
+import Home from '@material-ui/icons/Home';
+import LockIcon from '@material-ui/icons/Lock';
+import PersonIcon from '@material-ui/icons/Person';
import SearchIcon from '@material-ui/icons/Search';
import SettingsIcon from '@material-ui/icons/Settings';
-import PersonIcon from '@material-ui/icons/Person';
-import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
-import LockIcon from '@material-ui/icons/Lock';
-import GitHubIcon from '@material-ui/icons/GitHub';
+import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
function PageLink(props: {
icon: ReactNode;
href: string;
children: string;
}) {
- return <a href={props.href} className="pageLink">
+ return <a href={props.href} className='pageLink'>
{props.icon}
<span>{props.children}</span>
- </a>
+ </a>;
}
export function Footer() {
- return <div className="footer">
- <div className="header">
- <LogoDark/>
+ return <div className='footer'>
+ <div className='header'>
+ <LogoDark />
<h2>4 op een rij</h2>
</div>
- <div className="content">
- <div className="column">
- <PageLink icon={<Home/>} href="/" children="Home"/>
- <PageLink icon={<VideogameAssetIcon/>} href="/game" children="Spelen"/>
- <PageLink icon={<ExtensionIcon/>} href="/" children="Puzzels"/>
- <PageLink icon={<SearchIcon/>} href="/search" children="Zoeken"/>
+ <div className='content'>
+ <div className='column'>
+ <PageLink icon={<Home />} href='/' children='Home' />
+ <PageLink icon={<VideogameAssetIcon />} href='/game' children='Spelen' />
+ <PageLink icon={<ExtensionIcon />} href='/' children='Puzzels' />
+ <PageLink icon={<SearchIcon />} href='/search' children='Zoeken' />
</div>
- <div className="column">
- <PageLink icon={<LockIcon/>} href="/privacy" children="Privacy"/>
- <PageLink icon={<GitHubIcon/>} href="https://github.com/lonkaars/connect-4" children="Broncode"/>
+ <div className='column'>
+ <PageLink icon={<LockIcon />} href='/privacy' children='Privacy' />
+ <PageLink icon={<GitHubIcon />} href='https://github.com/lonkaars/connect-4' children='Broncode' />
</div>
- <div className="column">
- <PageLink icon={<SettingsIcon/>} href="/settings" children="Instellingen"/>
- <PageLink icon={<PersonIcon/>} href="/user" children="Profiel"/>
- <PageLink icon={<ExitToAppOutlinedIcon/>} href="/logout" children="Uitloggen"/>
+ <div className='column'>
+ <PageLink icon={<SettingsIcon />} href='/settings' children='Instellingen' />
+ <PageLink icon={<PersonIcon />} href='/user' children='Profiel' />
+ <PageLink icon={<ExitToAppOutlinedIcon />} href='/logout' children='Uitloggen' />
</div>
</div>
- </div>
+ </div>;
}
diff --git a/components/gameBar.tsx b/components/gameBar.tsx
index 67712e2..0d7d4d9 100644
--- a/components/gameBar.tsx
+++ b/components/gameBar.tsx
@@ -1,30 +1,35 @@
import { CSSProperties, ReactNode } from 'react';
-import { Vierkant, Bubble } from './ui';
+import { Bubble, Vierkant } from './ui';
-import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded';
import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded';
-import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded';
import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded';
+import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded';
+import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded';
function GameBarModule(props: {
children?: ReactNode;
onclick?: () => void;
}) {
- return <Vierkant style={{
- backgroundColor: "var(--background-alt)",
- padding: 12,
- borderRadius: 6,
- margin: 0,
- verticalAlign: "top",
- cursor: props.onclick ? "pointer" : "default"
- }} onclick={props.onclick}>{props.children}</Vierkant>
+ return <Vierkant
+ style={{
+ backgroundColor: 'var(--background-alt)',
+ padding: 12,
+ borderRadius: 6,
+ margin: 0,
+ verticalAlign: 'top',
+ cursor: props.onclick ? 'pointer' : 'default',
+ }}
+ onclick={props.onclick}
+ >
+ {props.children}
+ </Vierkant>;
}
-var GameBarSpacer = () => <div style={{ width: 8, display: "inline-block" }}></div>;
+var GameBarSpacer = () => <div style={{ width: 8, display: 'inline-block' }}></div>;
var GameBarAlignStyle: CSSProperties = {
- display: "inline-block"
-}
+ display: 'inline-block',
+};
export function GameBar(props: {
turn: boolean;
@@ -32,64 +37,85 @@ export function GameBar(props: {
active: boolean;
resignFunction: () => void;
}) {
- return <Vierkant className="gameBar" style={{
- padding: 8,
- width: "calc(100% - 12px)"
- }}>
- <div style={{ gridAutoColumns: "auto" }}>
- <div style={{ ...GameBarAlignStyle, float: "left" }}>
- <div style={{
- width: 32, height: 32,
- margin: 8,
- backgroundColor: props.turn ? "var(--disk-b)" : "var(--disk-a)",
- borderRadius: 16,
- display: "inline-block"
- }}/>
- <h2 style={{
- fontSize: 20,
- margin: 12,
- verticalAlign: "top",
- display: "inline-block"
- }}>{
- !props.active ? "Wachten op tegenstander..." :
- props.turn == props.player1 ?
- "Jouw beurt" : "Tegenstander"
- }</h2>
+ return <Vierkant
+ className='gameBar'
+ style={{
+ padding: 8,
+ width: 'calc(100% - 12px)',
+ }}
+ >
+ <div style={{ gridAutoColumns: 'auto' }}>
+ <div style={{ ...GameBarAlignStyle, float: 'left' }}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ margin: 8,
+ backgroundColor: props.turn ? 'var(--disk-b)' : 'var(--disk-a)',
+ borderRadius: 16,
+ display: 'inline-block',
+ }}
+ />
+ <h2
+ style={{
+ fontSize: 20,
+ margin: 12,
+ verticalAlign: 'top',
+ display: 'inline-block',
+ }}
+ >
+ {!props.active
+ ? 'Wachten op tegenstander...'
+ : props.turn == props.player1
+ ? 'Jouw beurt'
+ : 'Tegenstander'}
+ </h2>
</div>
- <div style={{
- ...GameBarAlignStyle,
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)"
- }}>
- <span style={{
- color: "var(--text)",
- fontSize: 20,
- opacity: .75 - .75
- }}>0-0</span>
+ <div
+ style={{
+ ...GameBarAlignStyle,
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ }}
+ >
+ <span
+ style={{
+ color: 'var(--text)',
+ fontSize: 20,
+ opacity: .75 - .75,
+ }}
+ >
+ 0-0
+ </span>
</div>
- <div style={{ ...GameBarAlignStyle, float: "right" }}>
+ <div style={{ ...GameBarAlignStyle, float: 'right' }}>
<GameBarModule>
- <SettingsRoundedIcon/>
+ <SettingsRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <span style={{
- margin: "0 4px",
- fontSize: 20
- }}>00:00</span>
+ <span
+ style={{
+ margin: '0 4px',
+ fontSize: 20,
+ }}
+ >
+ 00:00
+ </span>
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule onclick={props.resignFunction}>
- <ExitToAppRoundedIcon/>
+ <ExitToAppRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <NavigateBeforeRoundedIcon/>
+ <NavigateBeforeRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <NavigateNextRoundedIcon/>
+ <NavigateNextRoundedIcon />
</GameBarModule>
</div>
</div>
diff --git a/components/gameSettings.tsx b/components/gameSettings.tsx
index be45112..f562e5d 100644
--- a/components/gameSettings.tsx
+++ b/components/gameSettings.tsx
@@ -1,17 +1,16 @@
-import { ReactNode, Component, CSSProperties } from 'react';
import axios from 'axios';
+import { Component, CSSProperties, ReactNode } from 'react';
-import { Button, Vierkant, CheckBox, Input } from './ui';
-import { DialogBox } from './dialogBox';
import { ruleset, userPreferences } from '../api/api';
+import { DialogBox } from './dialogBox';
+import { Button, CheckBox, Input, Vierkant } from './ui';
import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined';
-
type CurrentGameSettingsStateType = {
editGameRulesDialogVisible: boolean;
ruleset: ruleset;
-}
+};
export class CurrentGameSettings extends Component {
state: CurrentGameSettingsStateType = {
@@ -19,25 +18,25 @@ export class CurrentGameSettings extends Component {
ruleset: {
timelimit: {
enabled: false,
- shared: false
+ shared: false,
},
- ranked: false
- }
- }
+ ranked: false,
+ },
+ };
constructor(props: {}) {
super(props);
- if (typeof window === "undefined") return; // return if run on server
+ if (typeof window === 'undefined') return; // return if run on server
axios.request<userPreferences>({
- method: "get",
+ method: 'get',
url: `/api/user/preferences`,
- headers: {"content-type": "application/json"}
+ headers: { 'content-type': 'application/json' },
})
- //FIXME: this assumes the request ruleset has all properties of a ruleset
- .then(request => this.setState({ ruleset: request.data.ruleset || this.state.ruleset }))
- .catch(() => {});
+ // FIXME: this assumes the request ruleset has all properties of a ruleset
+ .then(request => this.setState({ ruleset: request.data.ruleset || this.state.ruleset }))
+ .catch(() => {});
}
showEditGameRules = () => this.setState({ editGameRulesDialogVisible: true });
@@ -45,51 +44,67 @@ export class CurrentGameSettings extends Component {
setGameRules = (newRules: ruleset) => this.setState({ ruleset: newRules });
render() {
- var timelimit_str = this.state.ruleset.timelimit.enabled ?
- `${this.state.ruleset.timelimit.minutes}m${this.state.ruleset.timelimit.seconds}s plus ${this.state.ruleset.timelimit.addmove}` :
- "Geen tijdslimiet"
- var ranked_str = this.state.ruleset.ranked ?
- "Gerangschikt" :
- "Niet gerangschikt"
- return <div style={{
- position: "relative",
- height: 80,
- overflow: "visible",
- zIndex: 1
- }}>
- <p style={{
- opacity: .75,
- fontStyle: "italic",
- userSelect: "none",
- position: "absolute",
- top: "50%",
- left: 0,
- transform: "translateY(-50%)"
- }}>
- {timelimit_str}<br/>
+ var timelimit_str = this.state.ruleset.timelimit.enabled
+ ? `${this.state.ruleset.timelimit.minutes}m${this.state.ruleset.timelimit.seconds}s plus ${this.state.ruleset.timelimit.addmove}`
+ : 'Geen tijdslimiet';
+ var ranked_str = this.state.ruleset.ranked
+ ? 'Gerangschikt'
+ : 'Niet gerangschikt';
+ return <div
+ style={{
+ position: 'relative',
+ height: 80,
+ overflow: 'visible',
+ zIndex: 1,
+ }}
+ >
+ <p
+ style={{
+ opacity: .75,
+ fontStyle: 'italic',
+ userSelect: 'none',
+ position: 'absolute',
+ top: '50%',
+ left: 0,
+ transform: 'translateY(-50%)',
+ }}
+ >
+ {timelimit_str}
+ <br />
{ranked_str}
</p>
- <Button style={{
- width: 150,
- position: "absolute",
- top: "50%",
- right: 0,
- transform: "translateY(-50%)"
- }} onclick={this.showEditGameRules}>
- <BuildOutlinedIcon style={{ fontSize: 48 }}/>
- <span style={{
- fontWeight: 600,
- position: "absolute",
- right: 24,
- top: "50%",
- width: 85,
- verticalAlign: "middle",
- textAlign: "center",
- transform: "translateY(-50%)",
- userSelect: "none"
- }}>Spelregels aanpassen</span>
+ <Button
+ style={{
+ width: 150,
+ position: 'absolute',
+ top: '50%',
+ right: 0,
+ transform: 'translateY(-50%)',
+ }}
+ onclick={this.showEditGameRules}
+ >
+ <BuildOutlinedIcon style={{ fontSize: 48 }} />
+ <span
+ style={{
+ fontWeight: 600,
+ position: 'absolute',
+ right: 24,
+ top: '50%',
+ width: 85,
+ verticalAlign: 'middle',
+ textAlign: 'center',
+ transform: 'translateY(-50%)',
+ userSelect: 'none',
+ }}
+ >
+ Spelregels aanpassen
+ </span>
</Button>
- <EditGameSettings parentState={this.state} hideEditGameRules={this.hideEditGameRules} setGameRules={this.setGameRules}/>
+ <EditGameSettings
+ parentState={this.state}
+ hideEditGameRules={this.hideEditGameRules}
+ setGameRules={this.setGameRules}
+ />
</div>;
}
}
@@ -101,25 +116,36 @@ function GameSettingsSection(props: {
noMarginBottom?: boolean;
id: string;
}) {
- return <Vierkant id={props.id} style={{
- backgroundColor: "var(--background-alt)",
- width: "100%",
- padding: 16,
- margin: 0,
- marginBottom: props.noMarginBottom ? 0 : 24
- }}>
- <span style={{
- verticalAlign: "top",
- fontSize: 14,
- fontWeight: 600
- }}>{props.title}</span>
- <CheckBox state={props.state} id={`${props.id}_enabled`} style={{
- verticalAlign: "top",
- float: "right",
- margin: -3
- }}/>
- <div>{props.children}</div>
- </Vierkant>
+ return <Vierkant
+ id={props.id}
+ style={{
+ backgroundColor: 'var(--background-alt)',
+ width: '100%',
+ padding: 16,
+ margin: 0,
+ marginBottom: props.noMarginBottom ? 0 : 24,
+ }}
+ >
+ <span
+ style={{
+ verticalAlign: 'top',
+ fontSize: 14,
+ fontWeight: 600,
+ }}
+ >
+ {props.title}
+ </span>
+ <CheckBox
+ state={props.state}
+ id={`${props.id}_enabled`}
+ style={{
+ verticalAlign: 'top',
+ float: 'right',
+ margin: -3,
+ }}
+ />
+ <div>{props.children}</div>
+ </Vierkant>;
}
function GameRule(props: {
@@ -127,15 +153,17 @@ function GameRule(props: {
description: string;
style?: CSSProperties;
}) {
- return <div style={{
- backgroundColor: "var(--page-background)",
- borderRadius: 8,
- padding: "16px 0",
- textAlign: "center",
- ...props.style
- }}>
- <h1 style={{ color: "var(--disk-a)", fontSize: 42 }}>{props.title}</h1>
- <p style={{ color: "var(--text-alt)", maxWidth: 250, margin: "0 auto" }}>{props.description}</p>
+ return <div
+ style={{
+ backgroundColor: 'var(--page-background)',
+ borderRadius: 8,
+ padding: '16px 0',
+ textAlign: 'center',
+ ...props.style,
+ }}
+ >
+ <h1 style={{ color: 'var(--disk-a)', fontSize: 42 }}>{props.title}</h1>
+ <p style={{ color: 'var(--text-alt)', maxWidth: 250, margin: '0 auto' }}>{props.description}</p>
</div>;
}
@@ -146,66 +174,114 @@ type editGameSettingsProps = {
};
export class EditGameSettings extends Component<editGameSettingsProps> {
- render () {
- return <DialogBox title="Spelregels aanpassen" style={{
- margin: 0,
- display: this.props.parentState.editGameRulesDialogVisible ? "block" : "none"
- }} onclick={this.props.hideEditGameRules}>
- <div style={{
- marginTop: 24,
- maxHeight: 500,
- overflowY: "scroll",
- borderRadius: 8,
- }}>
- <GameSettingsSection title="Tijdslimiet" state={this.props.parentState.ruleset.timelimit.enabled} id="timelimit">
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr 1fr 1fr",
- gridGap: 16,
- margin: "16px 0"
- }}>
- <Input id="timelimit_minutes" type="number" label="min" min={0} max={60} value={this.props.parentState.ruleset.timelimit.minutes}/>
- <Input id="timelimit_seconds" type="number" label="sec" min={0} max={60} value={this.props.parentState.ruleset.timelimit.seconds}/>
- <Input id="timelimit_addmove" type="number" label="plus" min={0} value={this.props.parentState.ruleset.timelimit.addmove}/>
+ render() {
+ return <DialogBox
+ title='Spelregels aanpassen'
+ style={{
+ margin: 0,
+ display: this.props.parentState.editGameRulesDialogVisible ? 'block' : 'none',
+ }}
+ onclick={this.props.hideEditGameRules}
+ >
+ <div
+ style={{
+ marginTop: 24,
+ maxHeight: 500,
+ overflowY: 'scroll',
+ borderRadius: 8,
+ }}
+ >
+ <GameSettingsSection
+ title='Tijdslimiet'
+ state={this.props.parentState.ruleset.timelimit.enabled}
+ id='timelimit'
+ >
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr 1fr 1fr',
+ gridGap: 16,
+ margin: '16px 0',
+ }}
+ >
+ <Input
+ id='timelimit_minutes'
+ type='number'
+ label='min'
+ min={0}
+ max={60}
+ value={this.props.parentState.ruleset.timelimit.minutes}
+ />
+ <Input
+ id='timelimit_seconds'
+ type='number'
+ label='sec'
+ min={0}
+ max={60}
+ value={this.props.parentState.ruleset.timelimit.seconds}
+ />
+ <Input
+ id='timelimit_addmove'
+ type='number'
+ label='plus'
+ min={0}
+ value={this.props.parentState.ruleset.timelimit.addmove}
+ />
</div>
- <CheckBox id="timelimit_shared" state={this.props.parentState.ruleset.timelimit.shared}/>
- <span style={{
- verticalAlign: "super",
- marginLeft: 4
- }}>Timer gebruiken voor bijde spelers</span>
+ <CheckBox id='timelimit_shared' state={this.props.parentState.ruleset.timelimit.shared} />
+ <span
+ style={{
+ verticalAlign: 'super',
+ marginLeft: 4,
+ }}
+ >
+ Timer gebruiken voor bijde spelers
+ </span>
</GameSettingsSection>
- { false && <GameSettingsSection title="Regelset" state={false}>
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr 1fr",
- gridGap: 16,
- margin: "16px 0"
- }}>
- <GameRule title="+2" description="Extra kolommen"/>
- <GameRule title="+4" description="Extra kolommen"/>
+ {false && <GameSettingsSection title='Regelset' state={false}>
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr 1fr',
+ gridGap: 16,
+ margin: '16px 0',
+ }}
+ >
+ <GameRule title='+2' description='Extra kolommen' />
+ <GameRule title='+4' description='Extra kolommen' />
</div>
- <GameRule style={{ marginBottom: 16 }} title="Gravity" description="De zwaartekracht draait soms"/>
- <GameRule title="Flashlight" description="Het veld wordt opgelicht door de vallende fiches"/>
- </GameSettingsSection> }
- <GameSettingsSection title="Gerangschikt spel" state={this.props.parentState.ruleset.ranked} id="ranked" noMarginBottom/>
+ <GameRule style={{ marginBottom: 16 }} title='Gravity' description='De zwaartekracht draait soms' />
+ <GameRule title='Flashlight' description='Het veld wordt opgelicht door de vallende fiches' />
+ </GameSettingsSection>}
+ <GameSettingsSection
+ title='Gerangschikt spel'
+ state={this.props.parentState.ruleset.ranked}
+ id='ranked'
+ noMarginBottom
+ />
</div>
- <Button style={{
- textAlign: "center",
- marginTop: 24
- }} onclick={() => {
- var rules: ruleset = {
- timelimit: {
- enabled: document.getElementById("timelimit_enabled").classList.contains("on"),
- minutes: Number((document.getElementById("timelimit_minutes") as HTMLInputElement).value),
- seconds: Number((document.getElementById("timelimit_seconds") as HTMLInputElement).value),
- addmove: Number((document.getElementById("timelimit_addmove") as HTMLInputElement).value),
- shared: document.getElementById("timelimit_shared").classList.contains("on"),
- },
- ranked: document.getElementById("ranked_enabled").classList.contains("on")
- }
- this.props.setGameRules(rules);
- this.props.hideEditGameRules();
- }}>Instellingen opslaan</Button>
+ <Button
+ style={{
+ textAlign: 'center',
+ marginTop: 24,
+ }}
+ onclick={() => {
+ var rules: ruleset = {
+ timelimit: {
+ enabled: document.getElementById('timelimit_enabled').classList.contains('on'),
+ minutes: Number((document.getElementById('timelimit_minutes') as HTMLInputElement).value),
+ seconds: Number((document.getElementById('timelimit_seconds') as HTMLInputElement).value),
+ addmove: Number((document.getElementById('timelimit_addmove') as HTMLInputElement).value),
+ shared: document.getElementById('timelimit_shared').classList.contains('on'),
+ },
+ ranked: document.getElementById('ranked_enabled').classList.contains('on'),
+ };
+ this.props.setGameRules(rules);
+ this.props.hideEditGameRules();
+ }}
+ >
+ Instellingen opslaan
+ </Button>
</DialogBox>;
}
}
diff --git a/components/globalState.tsx b/components/globalState.tsx
index 5b01947..0ec7838 100644
--- a/components/globalState.tsx
+++ b/components/globalState.tsx
@@ -6,9 +6,9 @@ type globalState = {
on: boolean;
time: number;
useForBoth: boolean;
- }
+ };
rankedGame: boolean;
- }
-}
+ };
+};
export var GlobalStateContext = React.createContext();
diff --git a/components/logo.tsx b/components/logo.tsx
index b0f358f..e43aa88 100644
--- a/components/logo.tsx
+++ b/components/logo.tsx
@@ -1,12 +1,12 @@
export function LogoDark() {
return (
- <div className="noclick">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <rect width="24" height="24" fill="var(--background)"/>
- <circle cx="6.5" cy="6.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="6.5" cy="17.5" r="4.5" fill="var(--disk-a)"/>
- <circle cx="17.5" cy="17.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="17.5" cy="6.5" r="3.5" stroke="var(--text)" strokeWidth="2"/>
+ <div className='noclick'>
+ <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
+ <rect width='24' height='24' fill='var(--background)' />
+ <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
+ <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--text)' strokeWidth='2' />
</svg>
</div>
);
@@ -14,15 +14,14 @@ export function LogoDark() {
export function LogoLight() {
return (
- <div className="noclick">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <rect width="24" height="24" fill="var(--page-background)"/>
- <circle cx="6.5" cy="6.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="6.5" cy="17.5" r="4.5" fill="var(--disk-a)"/>
- <circle cx="17.5" cy="17.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="17.5" cy="6.5" r="3.5" stroke="var(--background)" strokeWidth="2"/>
+ <div className='noclick'>
+ <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
+ <rect width='24' height='24' fill='var(--page-background)' />
+ <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
+ <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--background)' strokeWidth='2' />
</svg>
</div>
);
}
-
diff --git a/components/navbar.tsx b/components/navbar.tsx
index d6775ee..70de574 100644
--- a/components/navbar.tsx
+++ b/components/navbar.tsx
@@ -1,121 +1,147 @@
-import { CSSProperties, useEffect, useState, useContext } from "react";
-import axios from "axios";
+import axios from 'axios';
+import { CSSProperties, useContext, useEffect, useState } from 'react';
-import { LogoDark } from "../components/logo";
-import { AccountAvatar } from "./account";
-import { userInfo } from "../api/api";
-import { NotificationsArea } from "./notificationsArea";
-import { SocketContext } from "./socketContext";
+import { userInfo } from '../api/api';
+import { LogoDark } from '../components/logo';
+import { AccountAvatar } from './account';
+import { NotificationsArea } from './notificationsArea';
+import { SocketContext } from './socketContext';
-import Home from '@material-ui/icons/Home';
-import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
import ExtensionIcon from '@material-ui/icons/Extension';
+import Home from '@material-ui/icons/Home';
+import NotificationsIcon from '@material-ui/icons/Notifications';
+import PersonIcon from '@material-ui/icons/Person';
import SearchIcon from '@material-ui/icons/Search';
import SettingsIcon from '@material-ui/icons/Settings';
-import PersonIcon from '@material-ui/icons/Person';
-import NotificationsIcon from '@material-ui/icons/Notifications';
+import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
var NavBarItemStyle: CSSProperties = {
margin: 12,
marginBottom: 16,
- display: "block"
-}
+ display: 'block',
+};
export function NavBar() {
- var [ loggedIn, setLoggedIn ] = useState(false);
- var [ gotData, setGotData ] = useState(false);
+ var [loggedIn, setLoggedIn] = useState(false);
+ var [gotData, setGotData] = useState(false);
- var [ friendRequests, setFriendRequests ] = useState<Array<userInfo>>(null);
+ var [friendRequests, setFriendRequests] = useState<Array<userInfo>>(null);
- var [ notificationsAreaVisible, setNotificationsAreaVisible ] = useState(false);
- var [ gotNotifications, setGotNotifications ] = useState(false);
+ var [notificationsAreaVisible, setNotificationsAreaVisible] = useState(false);
+ var [gotNotifications, setGotNotifications] = useState(false);
var { io } = useContext(SocketContext);
async function getNotifications() {
- var friendRequestsReq = await axios.request<{ requests: Array<userInfo> }>({
- method: "get",
- url: `/api/social/list/requests`
+ var friendRequestsReq = await axios.request<{ requests: Array<userInfo>; }>({
+ method: 'get',
+ url: `/api/social/list/requests`,
});
setFriendRequests(friendRequestsReq.data.requests);
setGotNotifications(friendRequestsReq.data.requests.length > 0);
}
- useEffect(() => {(async () => {
- if (gotData) return;
- if (typeof window === "undefined") return;
-
- var loggedIn = document.cookie.includes("token");
- setLoggedIn(loggedIn);
-
- if (loggedIn) {
- await getNotifications();
- io.on("incomingFriendRequest", getNotifications);
- io.on("changedRelation", getNotifications);
- }
+ useEffect(() => {
+ (async () => {
+ if (gotData) return;
+ if (typeof window === 'undefined') return;
- setGotData(true);
- })()}, []);
+ var loggedIn = document.cookie.includes('token');
+ setLoggedIn(loggedIn);
- return <div className="navbar" style={{
- width: 48,
- height: "100%",
+ if (loggedIn) {
+ await getNotifications();
+ io.on('incomingFriendRequest', getNotifications);
+ io.on('changedRelation', getNotifications);
+ }
- lineHeight: 0,
+ setGotData(true);
+ })();
+ }, []);
- backgroundColor: "var(--background)",
- display: "inline-block",
+ return <div
+ className='navbar'
+ style={{
+ width: 48,
+ height: '100%',
- position: "fixed",
- top: 0,
- left: 0,
+ lineHeight: 0,
- overflow: "visible",
- whiteSpace: "nowrap",
- zIndex: 2
- }}>
- <div style={NavBarItemStyle}><LogoDark/></div>
- <a href="/" style={NavBarItemStyle}><Home/></a>
- <a href="/game" style={NavBarItemStyle}><VideogameAssetIcon/></a>
- { false && <a href="/" style={NavBarItemStyle}><ExtensionIcon/></a> }
- <a href="/search" style={NavBarItemStyle}><SearchIcon/></a>
+ backgroundColor: 'var(--background)',
+ display: 'inline-block',
- <div style={{
- position: "absolute",
- bottom: -4,
+ position: 'fixed',
+ top: 0,
left: 0,
- backgroundColor: "var(--background)"
- }}>
- { loggedIn && <a style={{
- overflow: "visible",
- position: "relative",
- ...NavBarItemStyle
- }}>
- <div style={{ cursor: "pointer" }} onClick={() => setNotificationsAreaVisible(!notificationsAreaVisible)}>
- <NotificationsIcon/>
- { gotNotifications && <div style={{
- backgroundColor: "var(--disk-a)",
- width: 8, height: 8,
- borderRadius: 4,
- position: "absolute",
- top: 2, right: 2
- }}/> }
+
+ overflow: 'visible',
+ whiteSpace: 'nowrap',
+ zIndex: 2,
+ }}
+ >
+ <div style={NavBarItemStyle}>
+ <LogoDark />
+ </div>
+ <a href='/' style={NavBarItemStyle}>
+ <Home />
+ </a>
+ <a href='/game' style={NavBarItemStyle}>
+ <VideogameAssetIcon />
+ </a>
+ {false && <a href='/' style={NavBarItemStyle}>
+ <ExtensionIcon />
+ </a>}
+ <a href='/search' style={NavBarItemStyle}>
+ <SearchIcon />
+ </a>
+
+ <div
+ style={{
+ position: 'absolute',
+ bottom: -4,
+ left: 0,
+ backgroundColor: 'var(--background)',
+ }}
+ >
+ {loggedIn && <a
+ style={{
+ overflow: 'visible',
+ position: 'relative',
+ ...NavBarItemStyle,
+ }}
+ >
+ <div
+ style={{ cursor: 'pointer' }}
+ onClick={() => setNotificationsAreaVisible(!notificationsAreaVisible)}
+ >
+ <NotificationsIcon />
+ {gotNotifications && <div
+ style={{
+ backgroundColor: 'var(--disk-a)',
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ position: 'absolute',
+ top: 2,
+ right: 2,
+ }}
+ />}
</div>
<NotificationsArea
visible={notificationsAreaVisible}
friendRequests={friendRequests}
- rerender={getNotifications}/>
- </a> }
- <a href={loggedIn ? "/user" : "/login"} style={NavBarItemStyle}>
- {
- loggedIn ?
- <AccountAvatar size={24} round/> :
- <PersonIcon/>
- }
+ rerender={getNotifications}
+ />
+ </a>}
+ <a href={loggedIn ? '/user' : '/login'} style={NavBarItemStyle}>
+ {loggedIn
+ ? <AccountAvatar size={24} round />
+ : <PersonIcon />}
</a>
- { loggedIn && <a href="/settings" style={NavBarItemStyle}><SettingsIcon/></a> }
+ {loggedIn && <a href='/settings' style={NavBarItemStyle}>
+ <SettingsIcon />
+ </a>}
</div>
- </div>
+ </div>;
}
-
diff --git a/components/notificationsArea.tsx b/components/notificationsArea.tsx
index b427941..9573b72 100644
--- a/components/notificationsArea.tsx
+++ b/components/notificationsArea.tsx
@@ -1,13 +1,13 @@
-import { CSSProperties, ReactNode, useState, useContext, useEffect } from 'react';
import axios from 'axios';
+import { CSSProperties, ReactNode, useContext, useEffect, useState } from 'react';
-import { userInfo, gameInfo } from "../api/api";
-import { AccountAvatar } from "./account";
-import { Bubble, Vierkant, IconLabelButton } from './ui';
+import { gameInfo, userInfo } from '../api/api';
+import { AccountAvatar } from './account';
import { ToastContext } from './toast';
+import { Bubble, IconLabelButton, Vierkant } from './ui';
-import DoneIcon from '@material-ui/icons/Done';
import CloseIcon from '@material-ui/icons/Close';
+import DoneIcon from '@material-ui/icons/Done';
import NotificationsActiveOutlinedIcon from '@material-ui/icons/NotificationsActiveOutlined';
export function NotificationsArea(props: {
@@ -17,71 +17,82 @@ export function NotificationsArea(props: {
rerender: () => void;
}) {
var { toast } = useContext(ToastContext);
- var [ previousMessages, setPreviousMessages ] = useState(0);
+ var [previousMessages, setPreviousMessages] = useState(0);
var messages = (
- (props.friendRequests ? props.friendRequests.length : 0) +
- (props.gameInvites ? props.gameInvites.length : 0)
- )
+ (props.friendRequests ? props.friendRequests.length : 0)
+ + (props.gameInvites ? props.gameInvites.length : 0)
+ );
useEffect(() => {
- if(messages > previousMessages) {
- toast({ message: "Je hebt nieuwe meldingen!",
- type: "confirmation",
- icon: <NotificationsActiveOutlinedIcon/>});
+ if (messages > previousMessages) {
+ toast({
+ message: 'Je hebt nieuwe meldingen!',
+ type: 'confirmation',
+ icon: <NotificationsActiveOutlinedIcon />,
+ });
}
setPreviousMessages(messages);
- })
+ });
- return props.visible && <Bubble style={{
- left: 48 + 12,
- top: 92,
- transform: "translateY(-100%)",
- textAlign: "left",
- width: 400,
- height: 450
- }} tuitjeStyle={{
- left: 12,
- bottom: 86,
- transform: "translate(-100%, 100%) rotate(90deg)"
- }}>
+ return props.visible && <Bubble
+ style={{
+ left: 48 + 12,
+ top: 92,
+ transform: 'translateY(-100%)',
+ textAlign: 'left',
+ width: 400,
+ height: 450,
+ }}
+ tuitjeStyle={{
+ left: 12,
+ bottom: 86,
+ transform: 'translate(-100%, 100%) rotate(90deg)',
+ }}
+ >
<h2 style={{ marginBottom: 24 }}>Meldingen</h2>
- <div style={{
- overflowY: "scroll",
- whiteSpace: "normal",
- height: 450 - 24 * 4,
- borderRadius: 6
- }}>
- { props.gameInvites?.map(game => <GameInvite hide={props.rerender} game={game}/>) }
- { props.friendRequests?.map(user => <FriendRequest hide={props.rerender} user={user}/>) }
- {
- messages == 0 &&
- <div style={{
- position: "absolute",
- left: 0,
- right: 0,
- bottom: 0,
- top: 0
- }}>
- <h1 style={{
- position: "absolute",
- top: "50%",
- left: "50%",
- whiteSpace: "nowrap",
- transform: "translate(-50%, -50%)",
- opacity: .7
- }}>Geen meldingen</h1>
- </div>
- }
+ <div
+ style={{
+ overflowY: 'scroll',
+ whiteSpace: 'normal',
+ height: 450 - 24 * 4,
+ borderRadius: 6,
+ }}
+ >
+ {props.gameInvites?.map(game => <GameInvite hide={props.rerender} game={game} />)}
+ {props.friendRequests?.map(user => <FriendRequest hide={props.rerender} user={user} />)}
+ {messages == 0
+ && <div
+ style={{
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ top: 0,
+ }}
+ >
+ <h1
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ whiteSpace: 'nowrap',
+ transform: 'translate(-50%, -50%)',
+ opacity: .7,
+ }}
+ >
+ Geen meldingen
+ </h1>
+ </div>}
</div>
- </Bubble>
+ </Bubble>;
}
var FriendRequestButtonStyle: CSSProperties = {
borderRadius: 6,
- display: "inline-block",
+ display: 'inline-block',
marginLeft: 0,
- textAlign: "center"
+ textAlign: 'center',
};
function Acceptable(props: {
@@ -89,78 +100,89 @@ function Acceptable(props: {
onAccept?: () => void;
onDeny?: () => void;
}) {
- return <Vierkant style={{
- borderRadius: 8,
- background: "var(--background-alt)",
- margin: 0,
- padding: 12,
- width: "100%",
- marginBottom: 12
- }}>
- <div style={{ position: "relative" }}>
+ return <Vierkant
+ style={{
+ borderRadius: 8,
+ background: 'var(--background-alt)',
+ margin: 0,
+ padding: 12,
+ width: '100%',
+ marginBottom: 12,
+ }}
+ >
+ <div style={{ position: 'relative' }}>
{props.children}
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr, 1fr",
- gridGap: 12,
- marginTop: 12,
- gridAutoFlow: "column",
- }}>
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr, 1fr',
+ gridGap: 12,
+ marginTop: 12,
+ gridAutoFlow: 'column',
+ }}
+ >
<IconLabelButton
onclick={props.onAccept}
style={FriendRequestButtonStyle}
- icon={<DoneIcon/>}
- text="Accepteren"/>
+ icon={<DoneIcon />}
+ text='Accepteren'
+ />
<IconLabelButton
onclick={props.onDeny}
style={FriendRequestButtonStyle}
- icon={<CloseIcon/>}
- text="Verwijderen"/>
+ icon={<CloseIcon />}
+ text='Verwijderen'
+ />
</div>
</div>
- </Vierkant>
+ </Vierkant>;
}
function FriendRequest(props: {
user: userInfo;
hide: () => void;
}) {
- var [ gone, setGone ] = useState(false);
+ var [gone, setGone] = useState(false);
var hide = () => {
setGone(true);
props.hide();
- }
+ };
- return !gone && <Acceptable onAccept={() => {
- axios.request({
- method: "post",
- url: "/api/social/accept",
- headers: {"content-type": "application/json"},
- data: { "id": props.user?.id }
- })
- .then(hide);
- }} onDeny={() => {
- axios.request({
- method: "post",
- url: "/api/social/remove",
- headers: {"content-type": "application/json"},
- data: { "id": props.user?.id }
- })
- .then(hide);
- }}>
- <a href={"/user?id=" + props.user.id}>
- <AccountAvatar size={48} id={props.user.id}/>
- <div style={{
- display: "inline-block",
- verticalAlign: "top",
- marginLeft: 6
- }}>
- <i style={{ display: "block" }}>Vriendschapsverzoek</i>
+ return !gone && <Acceptable
+ onAccept={() => {
+ axios.request({
+ method: 'post',
+ url: '/api/social/accept',
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': props.user?.id },
+ })
+ .then(hide);
+ }}
+ onDeny={() => {
+ axios.request({
+ method: 'post',
+ url: '/api/social/remove',
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': props.user?.id },
+ })
+ .then(hide);
+ }}
+ >
+ <a href={'/user?id=' + props.user.id}>
+ <AccountAvatar size={48} id={props.user.id} />
+ <div
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ marginLeft: 6,
+ }}
+ >
+ <i style={{ display: 'block' }}>Vriendschapsverzoek</i>
<b>{props.user.username}</b>
</div>
</a>
- </Acceptable>
+ </Acceptable>;
}
function GameInvite(props: {
@@ -169,14 +191,20 @@ function GameInvite(props: {
}) {
return <Acceptable>
<a>
- <div style={{
- display: "inline-block",
- verticalAlign: "top",
- }}>
- <i style={{ display: "block" }}>Partijuitnodiging</i>
- <p><b><a href={"/user?id=" + props.game.opponent?.id}>{props.game.opponent?.username}</a></b> wil een potje 4 op een rij spelen!</p>
+ <div
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ }}
+ >
+ <i style={{ display: 'block' }}>Partijuitnodiging</i>
+ <p>
+ <b>
+ <a href={'/user?id=' + props.game.opponent?.id}>{props.game.opponent?.username}</a>
+ </b>{" "}
+ wil een potje 4 op een rij spelen!
+ </p>
</div>
</a>
- </Acceptable>
+ </Acceptable>;
}
-
diff --git a/components/page.tsx b/components/page.tsx
index d8a4a2b..506e2db 100644
--- a/components/page.tsx
+++ b/components/page.tsx
@@ -6,27 +6,39 @@ interface CenteredPageProps {
style?: CSSProperties;
}
-export function CenteredPage (props: CenteredPageProps) {
- return <div className="CenteredPageOuter" style={{
- maxWidth: props.width,
- margin: "0 auto"
- }}>
- <div className="CenteredPageInner" style={{
- margin: "0 6px",
- lineHeight: 0,
- ...props.style
- }}>{props.children}</div>
+export function CenteredPage(props: CenteredPageProps) {
+ return <div
+ className='CenteredPageOuter'
+ style={{
+ maxWidth: props.width,
+ margin: '0 auto',
+ }}
+ >
+ <div
+ className='CenteredPageInner'
+ style={{
+ margin: '0 6px',
+ lineHeight: 0,
+ ...props.style,
+ }}
+ >
+ {props.children}
+ </div>
</div>;
}
export class PageTitle extends Component {
- render () {
- return <h1 style={{
- color: "var(--text-alt)",
- marginLeft: 6,
- marginTop: 32,
- marginBottom: 64,
- fontSize: 25,
- }}>{this.props.children}</h1>;
+ render() {
+ return <h1
+ style={{
+ color: 'var(--text-alt)',
+ marginLeft: 6,
+ marginTop: 32,
+ marginBottom: 64,
+ fontSize: 25,
+ }}
+ >
+ {this.props.children}
+ </h1>;
}
}
diff --git a/components/preferencesContext.tsx b/components/preferencesContext.tsx
index 1b58a4f..a169be6 100644
--- a/components/preferencesContext.tsx
+++ b/components/preferencesContext.tsx
@@ -1,43 +1,48 @@
-import { useState, useEffect, createContext, ReactNode } from 'react';
import axios from 'axios';
+import { createContext, ReactNode, useEffect, useState } from 'react';
import { userPreferences } from '../api/api';
function applyPreferences(preferences: userPreferences) {
- if(typeof preferences === "undefined") return;
- if(typeof preferences.darkMode !== "undefined")
- document.getElementsByTagName("html")[0].classList[preferences.darkMode ? "add" : "remove"]("dark");
+ if (typeof preferences === 'undefined') return;
+ if (typeof preferences.darkMode !== 'undefined') {
+ document.getElementsByTagName('html')[0].classList[preferences.darkMode ? 'add' : 'remove']('dark');
+ }
}
-var PreferencesContext = createContext<{ preferences?: userPreferences; updatePreference?: (newPreference: userPreferences) => void }>({});
+var PreferencesContext = createContext<
+ { preferences?: userPreferences; updatePreference?: (newPreference: userPreferences) => void; }
+>({});
-export function PreferencesContextWrapper(props: { children?: ReactNode }) {
- var server = typeof window === "undefined";
- var loggedIn = !server && document.cookie.includes("token");
+export function PreferencesContextWrapper(props: { children?: ReactNode; }) {
+ var server = typeof window === 'undefined';
+ var loggedIn = !server && document.cookie.includes('token');
var [preferences, setPreferences] = useState<userPreferences>();
- useEffect(() => {(async() => {
- if (!loggedIn) return;
+ useEffect(() => {
+ (async () => {
+ if (!loggedIn) return;
- var local_prefs = window.localStorage.getItem("preferences");
- if (local_prefs) {
- var local_prefs_json = JSON.parse(local_prefs) as userPreferences;
- setPreferences(local_prefs_json);
- applyPreferences(local_prefs_json);
- }
+ var local_prefs = window.localStorage.getItem('preferences');
+ if (local_prefs) {
+ var local_prefs_json = JSON.parse(local_prefs) as userPreferences;
+ setPreferences(local_prefs_json);
+ applyPreferences(local_prefs_json);
+ }
- if (!preferences) {
- var preferencesReq = await axios.request<{ preferences: userPreferences; }>({
- method: "get",
- url: `/api/user/preferences`,
- headers: {"content-type": "application/json"}
- });
+ if (!preferences) {
+ var preferencesReq = await axios.request<{ preferences: userPreferences; }>({
+ method: 'get',
+ url: `/api/user/preferences`,
+ headers: { 'content-type': 'application/json' },
+ });
- window.localStorage.setItem("preferences", JSON.stringify(preferencesReq.data.preferences));
- setPreferences(preferencesReq.data.preferences);
- }
- })()}, []);
+ window.localStorage.setItem('preferences', JSON.stringify(preferencesReq.data.preferences));
+ setPreferences(preferencesReq.data.preferences);
+ }
+ })();
+ }, []);
useEffect(() => applyPreferences(preferences), [preferences]);
@@ -46,17 +51,16 @@ export function PreferencesContextWrapper(props: { children?: ReactNode }) {
setPreferences(prefs);
applyPreferences(prefs);
axios.request({
- method: "post",
+ method: 'post',
url: `/api/user/preferences`,
- headers: {"content-type": "application/json"},
- data: { "newPreferences": prefs }
+ headers: { 'content-type': 'application/json' },
+ data: { 'newPreferences': prefs },
});
}
return <PreferencesContext.Provider value={{ preferences, updatePreference }}>
{props.children}
- </PreferencesContext.Provider>
+ </PreferencesContext.Provider>;
}
export default PreferencesContext;
-
diff --git a/components/recentGames.tsx b/components/recentGames.tsx
index 150520c..988126f 100644
--- a/components/recentGames.tsx
+++ b/components/recentGames.tsx
@@ -1,79 +1,92 @@
-import { CSSProperties } from 'react';
import friendlyTime from 'friendly-time';
+import { CSSProperties } from 'react';
import { gameInfo } from '../api/api';
var LeftAlignedTableColumn: CSSProperties = {
- textAlign: "left",
- paddingLeft: 16
-}
+ textAlign: 'left',
+ paddingLeft: 16,
+};
var RightAlignedTableColumn: CSSProperties = {
- textAlign: "right",
- paddingRight: 16
-}
+ textAlign: 'right',
+ paddingRight: 16,
+};
-function GameOutcome(props: { game: gameInfo }) {
+function GameOutcome(props: { game: gameInfo; }) {
var gameStatus = (() => {
return {
- "resign": () => "Opgegeven",
- "wait_for_opponent": () => "Aan het wachten op een tegenstander",
- "in_progress": () => "Bezig",
- "finished": () => {
+ 'resign': () => 'Opgegeven',
+ 'wait_for_opponent': () => 'Aan het wachten op een tegenstander',
+ 'in_progress': () => 'Bezig',
+ 'finished': () => {
return {
- "w": "Gewonnen",
- "l": "Verloren",
- "d": "Gelijkspel"
- }[props.game.outcome]
+ 'w': 'Gewonnen',
+ 'l': 'Verloren',
+ 'd': 'Gelijkspel',
+ }[props.game.outcome];
},
}[props.game.status]();
})();
var outcome = props.game.outcome;
- return <td style={{
- color:
- outcome == "w" ? "var(--disk-b-text)" :
- outcome == "l" ? "var(--disk-a-text)" :
- "var(--text)",
- opacity: !["w", "l"].includes(outcome) ? 0.75 : 1.0
- }}>{gameStatus}</td>
+ return <td
+ style={{
+ color: outcome == 'w'
+ ? 'var(--disk-b-text)'
+ : outcome == 'l'
+ ? 'var(--disk-a-text)'
+ : 'var(--text)',
+ opacity: !['w', 'l'].includes(outcome) ? 0.75 : 1.0,
+ }}
+ >
+ {gameStatus}
+ </td>;
}
-export default function RecentGames(props: { games?: Array<gameInfo> }) {
+export default function RecentGames(props: { games?: Array<gameInfo>; }) {
return <div>
<h2>Recente partijen</h2>
- {
- props.games?.length > 0 ?
- <table width="100%" style={{ marginTop: "16px", textAlign: "center" }}>
+ {props.games?.length > 0
+ ? <table width='100%' style={{ marginTop: '16px', textAlign: 'center' }}>
<tbody>
<tr>
- <th style={{ width: "50%" }}>Tegenstander</th>
- <th style={{ width: "15%" }}>Uitkomst</th>
- <th style={{ width: "15%" }}>Zetten</th>
- <th style={{ width: "20%" }}>Datum</th>
+ <th style={{ width: '50%' }}>Tegenstander</th>
+ <th style={{ width: '15%' }}>Uitkomst</th>
+ <th style={{ width: '15%' }}>Zetten</th>
+ <th style={{ width: '20%' }}>Datum</th>
</tr>
- {
- props.games?.map(game => <tr key={game.id}>
- <td style={LeftAlignedTableColumn}>
- <a href={"/user?id=" + game.opponent?.id} style={{
- fontWeight: 500
- }}>{game.opponent?.username}</a>
- </td>
- <GameOutcome game={game}/>
- <td>{Math.max(0, game.moves.length -1)}</td>
- <td style={RightAlignedTableColumn}>{(() => {
- var timeCreated = new Date(game.created);
- return friendlyTime(timeCreated);
- })()}</td>
- </tr>)
- }
+ {props.games?.map(game =>
+ <tr key={game.id}>
+ <td style={LeftAlignedTableColumn}>
+ <a
+ href={'/user?id=' + game.opponent?.id}
+ style={{
+ fontWeight: 500,
+ }}
+ >
+ {game.opponent?.username}
+ </a>
+ </td>
+ <GameOutcome game={game} />
+ <td>{Math.max(0, game.moves.length - 1)}</td>
+ <td style={RightAlignedTableColumn}>
+ {(() => {
+ var timeCreated = new Date(game.created);
+ return friendlyTime(timeCreated);
+ })()}
+ </td>
+ </tr>
+ )}
</tbody>
- </table> :
- <h1 style={{
- textAlign: "center",
- opacity: .6,
- margin: "32px 64px"
- }}>Deze gebruiker heeft nog geen partijen gespeeld</h1>
- }
- </div>
+ </table>
+ : <h1
+ style={{
+ textAlign: 'center',
+ opacity: .6,
+ margin: '32px 64px',
+ }}
+ >
+ Deze gebruiker heeft nog geen partijen gespeeld
+ </h1>}
+ </div>;
}
-
diff --git a/components/socketContext.tsx b/components/socketContext.tsx
index f493d73..435f4a7 100644
--- a/components/socketContext.tsx
+++ b/components/socketContext.tsx
@@ -1,12 +1,11 @@
-import { ReactNode, createContext } from 'react';
+import { createContext, ReactNode } from 'react';
import { io as socket, Socket } from 'socket.io-client';
-export var SocketContext = createContext<{ io?: Socket }>({});
-export function SocketContextWrapper(props: { children?: ReactNode }) {
+export var SocketContext = createContext<{ io?: Socket; }>({});
+export function SocketContextWrapper(props: { children?: ReactNode; }) {
var io = socket();
return <SocketContext.Provider value={{ io }}>
- { props.children }
- </SocketContext.Provider>
+ {props.children}
+ </SocketContext.Provider>;
}
-
diff --git a/components/toast.tsx b/components/toast.tsx
index 91e67f7..97e17e6 100644
--- a/components/toast.tsx
+++ b/components/toast.tsx
@@ -1,114 +1,138 @@
-import { CSSProperties, ReactNode, useState, createContext } from "react";
+import { createContext, CSSProperties, ReactNode, useState } from 'react';
import CloseIcon from '@material-ui/icons/Close';
function ToastArea(props: {
- style?: CSSProperties
- children?: ReactNode
+ style?: CSSProperties;
+ children?: ReactNode;
rerender?: boolean;
}) {
- return <div id="ToastArea" style={{
- position: "fixed",
- whiteSpace: "nowrap",
- bottom: 12,
- left: "50%",
- transform: "translateX(-50%)",
- zIndex: 1,
- maxWidth: 600,
- width: "calc(100% - 48px - 48px)",
- margin: "0 24px",
- ...props.style
- }}>{props.children}</div>
+ return <div
+ id='ToastArea'
+ style={{
+ position: 'fixed',
+ whiteSpace: 'nowrap',
+ bottom: 12,
+ left: '50%',
+ transform: 'translateX(-50%)',
+ zIndex: 1,
+ maxWidth: 600,
+ width: 'calc(100% - 48px - 48px)',
+ margin: '0 24px',
+ ...props.style,
+ }}
+ >
+ {props.children}
+ </div>;
}
function Toast(props: {
- text?: string
- description?: string
- icon?: ReactNode
- children?: ReactNode
- type?: "normal"|"confirmation"|"error"
- style?: CSSProperties
+ text?: string;
+ description?: string;
+ icon?: ReactNode;
+ children?: ReactNode;
+ type?: 'normal' | 'confirmation' | 'error';
+ style?: CSSProperties;
}) {
var [visible, setVisibility] = useState(true);
setTimeout(() => setVisibility(false), 10e3);
- return visible && <div style={{
- padding: 0,
- marginBottom: 12,
- borderRadius: 6,
- boxShadow: "0 8px 12px -4px #00000033",
- color:
- props.type === "confirmation" ? "var(--background)" : "var(--text)",
- backgroundColor:
- props.type === "normal" ? "var(--background)" :
- props.type === "confirmation" ? "var(--disk-b)" :
- props.type === "error" ? "var(--disk-a)" : "var(--background)",
- ...props.style
- }}>
- {
- props.children ||
- <div style={{
- lineHeight: 0,
- padding: 12,
- minHeight: props.description ? 36 : 24,
- position: "relative"
- }}>
- <div style={{
- position: "absolute",
- left: 12,
- top: "50%",
- transform: "translateY(-50%)"
- }}>{props.icon}</div>
- <div style={{
- userSelect: "none",
- position: "absolute",
- left: props.icon ? 48 : 12,
- top: "50%",
- transform: "translateY(-50%)"
- }}>
+ return visible && <div
+ style={{
+ padding: 0,
+ marginBottom: 12,
+ borderRadius: 6,
+ boxShadow: '0 8px 12px -4px #00000033',
+ color: props.type === 'confirmation' ? 'var(--background)' : 'var(--text)',
+ backgroundColor: props.type === 'normal'
+ ? 'var(--background)'
+ : props.type === 'confirmation'
+ ? 'var(--disk-b)'
+ : props.type === 'error'
+ ? 'var(--disk-a)'
+ : 'var(--background)',
+ ...props.style,
+ }}
+ >
+ {props.children
+ || <div
+ style={{
+ lineHeight: 0,
+ padding: 12,
+ minHeight: props.description ? 36 : 24,
+ position: 'relative',
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ left: 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ >
+ {props.icon}
+ </div>
+ <div
+ style={{
+ userSelect: 'none',
+ position: 'absolute',
+ left: props.icon ? 48 : 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ >
<h2 style={{ fontSize: 16 }}>{props.text}</h2>
<p>{props.description}</p>
</div>
- <div style={{
- cursor: "pointer",
- position: "absolute",
- right: 12,
- top: "50%",
- transform: "translateY(-50%)"
- }} onClick={() => setVisibility(false)}>
- <CloseIcon style={{ fontSize: 24 }}/>
+ <div
+ style={{
+ cursor: 'pointer',
+ position: 'absolute',
+ right: 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ onClick={() => setVisibility(false)}
+ >
+ <CloseIcon style={{ fontSize: 24 }} />
</div>
- </div>
- }
- </div>
+ </div>}
+ </div>;
}
export type toastSettings = {
- message: string,
- description?: string,
- type: "confirmation"|"normal"|"error",
- icon?: ReactNode
-}
+ message: string;
+ description?: string;
+ type: 'confirmation' | 'normal' | 'error';
+ icon?: ReactNode;
+};
export type toastType = (settings: toastSettings) => void;
-export var ToastContext = createContext<{ toast?: toastType }>({});
+export var ToastContext = createContext<{ toast?: toastType; }>({});
var toasts: Array<JSX.Element> = [];
-export function ToastContextWrapper(props: { children?: ReactNode }) {
+export function ToastContextWrapper(props: { children?: ReactNode; }) {
var [dummyState, rerender] = useState(false);
- return <ToastContext.Provider value={{ toast: options => {
- toasts.push(<Toast
- type={options.type}
- text={options.message}
- description={options.description}
- icon={options.icon}/>);
- rerender(!dummyState);
- } }}>
- { props.children }
+ return <ToastContext.Provider
+ value={{
+ toast: options => {
+ toasts.push(
+ <Toast
+ type={options.type}
+ text={options.message}
+ description={options.description}
+ icon={options.icon}
+ />,
+ );
+ rerender(!dummyState);
+ },
+ }}
+ >
+ {props.children}
<ToastArea rerender={dummyState}>
{toasts}
</ToastArea>
- </ToastContext.Provider>
+ </ToastContext.Provider>;
}
-
diff --git a/components/ui.tsx b/components/ui.tsx
index 9d532f8..c3f950b 100644
--- a/components/ui.tsx
+++ b/components/ui.tsx
@@ -1,9 +1,9 @@
-import { Component, CSSProperties, ReactNode, useState, useEffect } from "react";
+import { Component, CSSProperties, ReactNode, useEffect, useState } from 'react';
-import SearchIcon from '@material-ui/icons/Search';
-import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '@material-ui/icons/CheckBox';
+import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
+import SearchIcon from '@material-ui/icons/Search';
export function Vierkant(props: {
href?: string;
@@ -16,27 +16,31 @@ export function Vierkant(props: {
fullwidth?: boolean;
onclick?: () => void;
}) {
- return <a style={{
- padding: 24,
- backgroundColor: "var(--background)",
- borderRadius: 8,
- color: "var(--text)",
- margin: 6, // geen margin collapse = 12px marge
- display: "inline-block",
- position: "relative",
- boxSizing: "border-box",
- width:
- props.width ? props.width :
- props.fullwidth ? "calc(100% - 12px)" :
- undefined,
- height: props.height ? props.height : undefined,
- ...props.style
- }}
- href={props.href}
- className={props.className}
- id={props.id}
- onClick={props.onclick}
- >{props.children}</a>
+ return <a
+ style={{
+ padding: 24,
+ backgroundColor: 'var(--background)',
+ borderRadius: 8,
+ color: 'var(--text)',
+ margin: 6, // geen margin collapse = 12px marge
+ display: 'inline-block',
+ position: 'relative',
+ boxSizing: 'border-box',
+ width: props.width
+ ? props.width
+ : props.fullwidth
+ ? 'calc(100% - 12px)'
+ : undefined,
+ height: props.height ? props.height : undefined,
+ ...props.style,
+ }}
+ href={props.href}
+ className={props.className}
+ id={props.id}
+ onClick={props.onclick}
+ >
+ {props.children}
+ </a>;
}
export function Button(props: {
@@ -47,27 +51,34 @@ export function Button(props: {
onclick?: (() => void);
id?: string;
}) {
- return <a onClick={props.onclick} href={props.href} id={props.id} style={{
- padding: props.text ? 8 : 16,
- textAlign: props.text ? "center" : "left",
- borderRadius: 8,
- backgroundColor: "var(--disk-a)",
- cursor: "pointer",
- position: "relative",
- textDecoration: "none",
- display: "block",
- userSelect: "none",
- ...props.style
- }}>
- {
- props.text ?
- <span style={{
- fontWeight: 600,
- userSelect: "none"
- }}>{props.text}</span>
- : undefined
- }
- { props.children }
+ return <a
+ onClick={props.onclick}
+ href={props.href}
+ id={props.id}
+ style={{
+ padding: props.text ? 8 : 16,
+ textAlign: props.text ? 'center' : 'left',
+ borderRadius: 8,
+ backgroundColor: 'var(--disk-a)',
+ cursor: 'pointer',
+ position: 'relative',
+ textDecoration: 'none',
+ display: 'block',
+ userSelect: 'none',
+ ...props.style,
+ }}
+ >
+ {props.text
+ ? <span
+ style={{
+ fontWeight: 600,
+ userSelect: 'none',
+ }}
+ >
+ {props.text}
+ </span>
+ : undefined}
+ {props.children}
</a>;
}
@@ -78,23 +89,33 @@ export function IconLabelButton(props: {
style?: CSSProperties;
href?: string;
}) {
- return <Button onclick={props.onclick} href={props.href} style={{
- display: "inline-block",
- verticalAlign: "top",
- padding: 8,
- float: "right",
- marginLeft: 12,
- ...props.style
- }}>
+ return <Button
+ onclick={props.onclick}
+ href={props.href}
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ padding: 8,
+ float: 'right',
+ marginLeft: 12,
+ ...props.style,
+ }}
+ >
{props.icon}
- <span style={{
- display: "inline-block",
- verticalAlign: "top",
- fontWeight: 500,
- marginLeft: 8,
- marginTop: 3, marginBottom: 3, marginRight: 3
- }}>{props.text}</span>
- </Button>
+ <span
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ fontWeight: 500,
+ marginLeft: 8,
+ marginTop: 3,
+ marginBottom: 3,
+ marginRight: 3,
+ }}
+ >
+ {props.text}
+ </span>
+ </Button>;
}
export function Input(props: {
@@ -104,61 +125,73 @@ export function Input(props: {
id?: string;
min?: number;
max?: number;
- value?: string|number;
+ value?: string | number;
dark?: boolean;
autocomplete?: string;
autofocus?: boolean;
}) {
return <input
- id={props.id}
- type={props.type || "text"}
- min={props.min} max={props.max}
- placeholder={props.label}
- spellCheck={false}
- defaultValue={props.value ? String(props.value) : ""}
- className={props.dark ? "dark" : "light"}
- autoComplete={props.autocomplete}
- autoFocus={props.autofocus}
- style={{
- padding: 12,
- border: 0,
- width: "calc(100% - 24px)",
- fontSize: 14,
- backgroundColor: "var(--page-background)",
- color: "var(--text-alt)",
- borderRadius: 8,
- fontFamily: "Inter",
- ...props.style
- }}/>
+ id={props.id}
+ type={props.type || 'text'}
+ min={props.min}
+ max={props.max}
+ placeholder={props.label}
+ spellCheck={false}
+ defaultValue={props.value ? String(props.value) : ''}
+ className={props.dark ? 'dark' : 'light'}
+ autoComplete={props.autocomplete}
+ autoFocus={props.autofocus}
+ style={{
+ padding: 12,
+ border: 0,
+ width: 'calc(100% - 24px)',
+ fontSize: 14,
+ backgroundColor: 'var(--page-background)',
+ color: 'var(--text-alt)',
+ borderRadius: 8,
+ fontFamily: 'Inter',
+ ...props.style,
+ }}
+ />;
}
-export function SearchBar(props: { label?: string }) {
- return <div style={{
- marginTop: 24,
- borderRadius: 8,
- overflow: "hidden",
- width: "100%"
- }}>
- <Input label={props.label} style={{
- width: "calc(100% - 24px - 41px)",
- borderTopRightRadius: 0,
- borderBottomRightRadius: 0
- }}/>
- <div style={{
- width: 41,
- height: 41,
- backgroundColor: "var(--disk-a)",
- display: "inline-block",
- verticalAlign: "top",
- position: "relative"
- }}>
- <SearchIcon style={{
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)"
- }}/>
+export function SearchBar(props: { label?: string; }) {
+ return <div
+ style={{
+ marginTop: 24,
+ borderRadius: 8,
+ overflow: 'hidden',
+ width: '100%',
+ }}
+ >
+ <Input
+ label={props.label}
+ style={{
+ width: 'calc(100% - 24px - 41px)',
+ borderTopRightRadius: 0,
+ borderBottomRightRadius: 0,
+ }}
+ />
+ <div
+ style={{
+ width: 41,
+ height: 41,
+ backgroundColor: 'var(--disk-a)',
+ display: 'inline-block',
+ verticalAlign: 'top',
+ position: 'relative',
+ }}
+ >
+ <SearchIcon
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ }}
+ />
</div>
- </div>
+ </div>;
}
export function CheckBox(props: {
@@ -173,25 +206,28 @@ export function CheckBox(props: {
useEffect(() => {
if (gotDefaultState) return;
setOn(props.state);
- if (typeof props.state !== "undefined") setGotDefaultState(true);
+ if (typeof props.state !== 'undefined') setGotDefaultState(true);
});
var toggle = () => {
setOn(!on);
props.onclick && props.onclick(!on);
- }
+ };
- return <div onClick={toggle} id={props.id} className={on ? "on" : "off"} style={{
- ...props.style,
- display: "inline-block",
- cursor: "pointer"
- }}>
- {
- on ?
- <CheckBoxIcon/> :
- <CheckBoxOutlineBlankIcon/>
- }
- </div>
+ return <div
+ onClick={toggle}
+ id={props.id}
+ className={on ? 'on' : 'off'}
+ style={{
+ ...props.style,
+ display: 'inline-block',
+ cursor: 'pointer',
+ }}
+ >
+ {on
+ ? <CheckBoxIcon />
+ : <CheckBoxOutlineBlankIcon />}
+ </div>;
}
export class ColorPicker extends Component<{
@@ -201,44 +237,53 @@ export class ColorPicker extends Component<{
color: string;
dark: boolean;
} = {
- color: "#012345",
- dark: true
- }
+ color: '#012345',
+ dark: true,
+ };
render() {
- return <Button style={{
- display: "inline-block",
- verticalAlign: "top",
- padding: 6,
- float: "right",
- marginLeft: 12,
- color: this.state.dark ? "var(--text)" : "var(--text-alt)",
- borderColor: this.state.dark ? "var(--text)" : "var(--text-alt)",
- borderWidth: 2,
- borderStyle: "solid",
- backgroundColor: this.state.color,
- ...this.props.style
- }}>
+ return <Button
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ padding: 6,
+ float: 'right',
+ marginLeft: 12,
+ color: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
+ borderColor: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
+ borderWidth: 2,
+ borderStyle: 'solid',
+ backgroundColor: this.state.color,
+ ...this.props.style,
+ }}
+ >
<div>
- <EditOutlinedIcon/>
- <div style={{
- width: 150,
- height: 24,
- display: "inline-block",
- textAlign: "center",
- verticalAlign: "top",
- position: "relative"
- }}>
- <span style={{
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)",
- fontWeight: 600,
- fontFeatureSettings: '"tnum", "ss01"'
- }}>{this.state.color}</span>
+ <EditOutlinedIcon />
+ <div
+ style={{
+ width: 150,
+ height: 24,
+ display: 'inline-block',
+ textAlign: 'center',
+ verticalAlign: 'top',
+ position: 'relative',
+ }}
+ >
+ <span
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ fontWeight: 600,
+ fontFeatureSettings: '"tnum", "ss01"',
+ }}
+ >
+ {this.state.color}
+ </span>
</div>
</div>
- </Button>
+ </Button>;
}
}
@@ -246,12 +291,21 @@ export function Tuitje(props: {
style?: CSSProperties;
rotation?: number;
}) {
- return <svg width="36" height="12" viewBox="0 0 36 12" fill="none" xmlns="http://www.w3.org/2000/svg" style={{
- ...props.style
- }}>
- <path d="M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z"
- fill={ props.style?.background as string || "var(--background)" }/>
- </svg>
+ return <svg
+ width='36'
+ height='12'
+ viewBox='0 0 36 12'
+ fill='none'
+ xmlns='http://www.w3.org/2000/svg'
+ style={{
+ ...props.style,
+ }}
+ >
+ <path
+ d='M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z'
+ fill={props.style?.background as string || 'var(--background)'}
+ />
+ </svg>;
}
export function Bubble(props: {
@@ -259,24 +313,27 @@ export function Bubble(props: {
style?: CSSProperties;
tuitjeStyle?: CSSProperties;
}) {
- return <Vierkant style={{
- position: "absolute",
- textAlign: "center",
- margin: 0,
- overflow: "visible",
- left: "50%",
- top: -24,
- transform: "translateY(-100%) translateX(-50%)",
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
- ...props.style
- }}>
+ return <Vierkant
+ style={{
+ position: 'absolute',
+ textAlign: 'center',
+ margin: 0,
+ overflow: 'visible',
+ left: '50%',
+ top: -24,
+ transform: 'translateY(-100%) translateX(-50%)',
+ boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
+ ...props.style,
+ }}
+ >
{props.children}
- <Tuitje style={{
- position: "absolute",
- bottom: -12,
- transform: "translate(-50%, 0%) rotate(0deg)",
- ...props.tuitjeStyle
- }}/>
- </Vierkant>
+ <Tuitje
+ style={{
+ position: 'absolute',
+ bottom: -12,
+ transform: 'translate(-50%, 0%) rotate(0deg)',
+ ...props.tuitjeStyle,
+ }}
+ />
+ </Vierkant>;
}
-
diff --git a/components/voerBord.tsx b/components/voerBord.tsx
index 946aa9c..93e350c 100644
--- a/components/voerBord.tsx
+++ b/components/voerBord.tsx
@@ -1,10 +1,16 @@
function Disc() {
- return <div className="disk" style={{
- position: "absolute",
- top: 0, left: 0, right: 0, bottom: 0,
- borderRadius: 999999,
- margin: 3
- }}/>
+ return <div
+ className='disk'
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ borderRadius: 999999,
+ margin: 3,
+ }}
+ />;
}
export function VoerBord(props: {
@@ -13,40 +19,53 @@ export function VoerBord(props: {
onMove: (move: number) => void;
active: boolean;
}) {
- return <table className="voerBord" style={{
- borderSpacing: 8,
- width: "100%"
- }}>
+ return <table
+ className='voerBord'
+ style={{
+ borderSpacing: 8,
+ width: '100%',
+ }}
+ >
<tbody>
- {
- [...Array(props.height).keys()].map((row) => (
- <tr key={`r-${row}`}>
- {[...Array(props.width).keys()].map((column) => (
- <td style={{
- position: "relative",
- width: "100%",
- padding: 0
- }} key={`c-${row}x${column}`}>
- <div style={{
- display: "block",
- marginTop: "100%"
- }}/>
- <Disc/>
- <div style={{
- position: "absolute",
- top: 0, left: 0, right: 0, bottom: 0,
+ {[...Array(props.height).keys()].map((row) => (
+ <tr key={`r-${row}`}>
+ {[...Array(props.width).keys()].map((column) => (
+ <td
+ style={{
+ position: 'relative',
+ width: '100%',
+ padding: 0,
+ }}
+ key={`c-${row}x${column}`}
+ >
+ <div
+ style={{
+ display: 'block',
+ marginTop: '100%',
+ }}
+ />
+ <Disc />
+ <div
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
borderRadius: 6,
- border: "2px solid var(--background-alt)",
+ border: '2px solid var(--background-alt)',
opacity: .5,
- cursor: props.active ? "pointer" : "default"
- }} id={`pos-${(props.height - row - 1) * props.width + column}`} onClick={event => {
- props.onMove(Number((event.target as HTMLElement).id.split("-")[1]))
- }}/>
- </td>
- ))}
- </tr>
- ))
- }
+ cursor: props.active ? 'pointer' : 'default',
+ }}
+ id={`pos-${(props.height - row - 1) * props.width + column}`}
+ onClick={event => {
+ props.onMove(Number((event.target as HTMLElement).id.split('-')[1]));
+ }}
+ />
+ </td>
+ ))}
+ </tr>
+ ))}
</tbody>
- </table>
+ </table>;
}
diff --git a/package.json b/package.json
index b0e68b5..df620cc 100644
--- a/package.json
+++ b/package.json
@@ -1,64 +1,64 @@
{
- "name": "po-4-op-een-rij",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "@material-ui/core": "^4.11.2",
- "@material-ui/icons": "^4.11.2",
- "@mdi/js": "^5.8.55",
- "@mdi/react": "^1.4.0",
- "@testing-library/jest-dom": "^5.11.4",
- "@testing-library/react": "^11.1.0",
- "@testing-library/user-event": "^12.1.10",
- "@types/pica": "^5.1.2",
- "@types/react-cookies": "^0.1.0",
- "@types/uuid": "^8.3.0",
- "axios": "^0.21.1",
- "copy-to-clipboard": "^3.3.1",
- "email-validator": "^2.0.4",
- "friendly-time": "^1.1.1",
- "image-blob-reduce": "^2.2.2",
- "micromark": "^2.11.4",
- "next": "^10.0.5",
- "pica": "^6.1.1",
- "react": "^17.0.1",
- "react-cookies": "^0.1.1",
- "react-dom": "^17.0.1",
- "react-router-dom": "^5.2.0",
- "react-scripts": "^4.0.1",
- "socket.io-client": "^3.1.1",
- "swr": "^0.4.0",
- "typescript": "^4.1.3",
- "uuid": "^8.3.2",
- "web-vitals": "^0.2.4",
- "zustand": "^3.3.1"
- },
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start"
- },
- "eslintConfig": {
- "extends": [
- "react-app",
- "react-app/jest"
- ]
- },
- "browserslist": {
- "production": [
- ">0.2%",
- "not dead",
- "not op_mini all"
- ],
- "development": [
- "last 1 chrome version",
- "last 1 firefox version",
- "last 1 safari version"
- ]
- },
- "devDependencies": {
- "@types/react": "^17.0.0",
- "@types/react-dom": "^17.0.0",
- "@types/react-router-dom": "^5.1.7"
- }
+ "name": "po-4-op-een-rij",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@material-ui/core": "^4.11.2",
+ "@material-ui/icons": "^4.11.2",
+ "@mdi/js": "^5.8.55",
+ "@mdi/react": "^1.4.0",
+ "@testing-library/jest-dom": "^5.11.4",
+ "@testing-library/react": "^11.1.0",
+ "@testing-library/user-event": "^12.1.10",
+ "@types/pica": "^5.1.2",
+ "@types/react-cookies": "^0.1.0",
+ "@types/uuid": "^8.3.0",
+ "axios": "^0.21.1",
+ "copy-to-clipboard": "^3.3.1",
+ "email-validator": "^2.0.4",
+ "friendly-time": "^1.1.1",
+ "image-blob-reduce": "^2.2.2",
+ "micromark": "^2.11.4",
+ "next": "^10.0.5",
+ "pica": "^6.1.1",
+ "react": "^17.0.1",
+ "react-cookies": "^0.1.1",
+ "react-dom": "^17.0.1",
+ "react-router-dom": "^5.2.0",
+ "react-scripts": "^4.0.1",
+ "socket.io-client": "^3.1.1",
+ "swr": "^0.4.0",
+ "typescript": "^4.1.3",
+ "uuid": "^8.3.2",
+ "web-vitals": "^0.2.4",
+ "zustand": "^3.3.1"
+ },
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "@types/react-router-dom": "^5.1.7"
+ }
}
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>;
}
-
diff --git a/readme.md b/readme.md
index 280fb7e..a32331a 100644
--- a/readme.md
+++ b/readme.md
@@ -9,8 +9,8 @@
</p>
> Some of this project's code is in Dutch (commit messages, documents etc.),
-along with the whole website. This was originally a school project, but I'm
-going to keep maintaining this project during my exams and summer break
+> along with the whole website. This was originally a school project, but I'm
+> going to keep maintaining this project during my exams and summer break
## Planned features:
@@ -36,7 +36,8 @@ going to keep maintaining this project during my exams and summer break
- [NextJS](https://nextjs.org/) for static react pages and html page routing
- [socket.io](https://socket.io/) for bidirecitonal communication during a game
- [SQLite](https://sqlite.org/index.html) for the database
-- [nginx](https://nginx.org/en/) for serving static files generated by nextjs, caching and reverse proxy
+- [nginx](https://nginx.org/en/) for serving static files generated by nextjs,
+ caching and reverse proxy
A design prototype of the website can be found on
[Figma](https://www.figma.com/file/rTciVQApAe6cwrH1Prl5Wn/4-op-een-rij?node-id=0%3A1).
@@ -47,8 +48,8 @@ moving and updating it to be in api/readme.md.
## setup
-To set up this project you'll need to install npm and pip dependencies, pull
-all git submodules and compile voerbak and the sql extensions.
+To set up this project you'll need to install npm and pip dependencies, pull all
+git submodules and compile voerbak and the sql extensions.
> I haven't figured out how to run this project on Windows, so please install
> [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) if you want
@@ -65,8 +66,8 @@ To start the setup process you only need to run the following command:
./configure
```
-The script calls sudo and apt install so some password input/manual
-confirmation is required, but most of the install should be automated.
+The script calls sudo and apt install so some password input/manual confirmation
+is required, but most of the install should be automated.
### manual setup (other distro's)
@@ -74,40 +75,44 @@ If your disto doesn't use the `apt` package manager, you can still run this
project by following these steps:
0. `git clone https://github.com/lonkaars/po-4-op-een-rij`
-1. Make sure you have [python](https://python.org/downloads) (with pip and venv) installed.
-2. Make sure you have [nodejs](https://nodejs.org/en/download) (with npm) installed.
+1. Make sure you have [python](https://python.org/downloads) (with pip and venv)
+ installed.
+2. Make sure you have [nodejs](https://nodejs.org/en/download) (with npm)
+ installed.
3. Make sure you have [nginx](https://nginx.org/en/) installed.
-4. Make sure you have [make](https://www.gnu.org/software/make/) and the gnu c compilers [gcc](https://gcc.gnu.org/) installed (most distro's will have these by default).
+4. Make sure you have [make](https://www.gnu.org/software/make/) and the gnu c
+ compilers [gcc](https://gcc.gnu.org/) installed (most distro's will have
+ these by default).
5. Install typescript, react-scripts and yarn:
- ```sh
- npm i -g typescript yarn
- ```
+ ```sh
+ npm i -g typescript yarn
+ ```
6. Create a new python virtual environment and install pip modules:
- ```sh
- python -m venv venv
- source venv/bin/activate
- pip install -r requirements.txt
- ```
+ ```sh
+ python -m venv venv
+ source venv/bin/activate
+ pip install -r requirements.txt
+ ```
7. Install node modules:
- ```sh
- yarn
- ```
+ ```sh
+ yarn
+ ```
8. Build voerbak:
- ```sh
- cd voerbak
- make
- ```
+ ```sh
+ cd voerbak
+ make
+ ```
9. Download submodules:
- ```sh
- git submodule init
- git submodule update
- ```
+ ```sh
+ git submodule init
+ git submodule update
+ ```
10. Initialize database and build SQL extensions:
- ```sh
- cd database
- ./init_db.sh
- make
- ```
+ ```sh
+ cd database
+ ./init_db.sh
+ make
+ ```
## How to start
@@ -129,4 +134,3 @@ sudo nginx -c $PWD/nginx.conf
# this command is also in ./configure
sed "s/user nobody/user $(whoami)/" -i nginx.conf
```
-
diff --git a/styles/readme.md b/styles/readme.md
index 89cb09c..1d1e96f 100644
--- a/styles/readme.md
+++ b/styles/readme.md
@@ -1,9 +1,9 @@
# Styles
-This is the folder where all website css should be. Currently most of the css
-is done using inline styles in the .tsx files, but they're being moved to
-seperate .css files in here. A redesign has also been in the making and we now
-have a style guide to follow while writing the website:
+This is the folder where all website css should be. Currently most of the css is
+done using inline styles in the .tsx files, but they're being moved to seperate
+.css files in here. A redesign has also been in the making and we now have a
+style guide to follow while writing the website:
## Dark
@@ -25,7 +25,8 @@ manually.
## Show me the css
-These will eventually be moved to global.css when the move to .css files is done.
+These will eventually be moved to global.css when the move to .css files is
+done.
```css
:root {
@@ -99,6 +100,4 @@ h2 { font-size: 20px; }
user-select: none;
font-weight: 600;
}
-
```
-
diff --git a/tsconfig.json b/tsconfig.json
index 4f38d39..283a43f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,28 +1,28 @@
{
- "compilerOptions": {
- "target": "ESNext",
- "lib": [
- "dom",
- "ESNext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "preserve"
- },
- "include": [
- "src"
- ],
- "exclude": [
- "node_modules"
- ]
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": [
+ "dom",
+ "ESNext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "src"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}
diff --git a/voerbak/readme.md b/voerbak/readme.md
index 1197ce3..74e616b 100644
--- a/voerbak/readme.md
+++ b/voerbak/readme.md
@@ -1,16 +1,19 @@
# Voerbak
-Here's the source for voerbak, this project's connect 4 engine. The name comes from an abbreviation for the Dutch word for connect 4: Vier Op Een Rij -> VOER + bak = voerbak
+Here's the source for voerbak, this project's connect 4 engine. The name comes
+from an abbreviation for the Dutch word for connect 4: Vier Op Een Rij -> VOER +
+bak = voerbak
-Voerbak uses a 1-dimensional array for storing the playfield, and it's printed after every move. The ordering is left to right, then bottom to top:
+Voerbak uses a 1-dimensional array for storing the playfield, and it's printed
+after every move. The ordering is left to right, then bottom to top:
-35|36|37|38|39|40|41
--|-|-|-|-|-|-
-28|29|30|31|32|33|34
-21|22|23|24|25|26|27
-14|15|16|17|18|19|20
-7|8|9|10|11|12|13
-0|1|2|3|4|5|6
+| 35 | 36 | 37 | 38 | 39 | 40 | 41 |
+| -- | -- | -- | -- | -- | -- | -- |
+| 28 | 29 | 30 | 31 | 32 | 33 | 34 |
+| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
+| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
+| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
+| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
Voerbak is used in this project using api/game/voerbak_connector.py
@@ -22,9 +25,10 @@ make
## Input
-Voerbak takes moves seperated by newlines from stdin. An example game would look like this:
+Voerbak takes moves seperated by newlines from stdin. An example game would look
+like this:
-``` sh
+```sh
echo "4,3,3,2,1,2,2,7,1,7,1,7,1" | sed "s/,/\n/g" | ./voerbak
# ^ convert "," to newline
```
@@ -44,12 +48,12 @@ message type
Message reference:
-type|name|messages
--|-|-
-d|draw|full = board is full
-e|errors|full = column is full
-m|move|true|false = if it's player 1's move
-w|win|int-int = board indices where 4 was connected
+| type | name | messages |
+| ---- | ------ | --------------------------------------------- |
+| d | draw | full = board is full |
+| e | errors | full = column is full |
+| m | move | true |
+| w | win | int-int = board indices where 4 was connected |
## Command-line arguments
@@ -73,4 +77,3 @@ for any corresponding short options.
Report bugs to https://github.com/lonkaars/po-4-op-een-rij/.
```
-
diff --git a/voerbak/stuk/readme.md b/voerbak/stuk/readme.md
index 67f48ad..14982c9 100644
--- a/voerbak/stuk/readme.md
+++ b/voerbak/stuk/readme.md
@@ -1,3 +1,5 @@
# Stuk
-These are games from the database that finished as won but they shouldn't've. These can be used as test cases for when I'll have enough motivation to write unit tests for voerbak.
+These are games from the database that finished as won but they shouldn't've.
+These can be used as test cases for when I'll have enough motivation to write
+unit tests for voerbak.