From 5856e80fcb78446be37456ec1e5c47b2ab02201f Mon Sep 17 00:00:00 2001 From: lonkaars Date: Fri, 9 Apr 2021 17:10:50 +0200 Subject: dprint format :tada: --- api/api.ts | 38 +-- api/readme.md | 58 +++- components/account.tsx | 33 +-- components/dialogBox.tsx | 40 +-- components/footer.tsx | 52 ++-- components/gameBar.tsx | 148 ++++++----- components/gameSettings.tsx | 358 +++++++++++++++---------- components/globalState.tsx | 6 +- components/logo.tsx | 29 +- components/navbar.tsx | 192 ++++++++------ components/notificationsArea.tsx | 254 ++++++++++-------- components/page.tsx | 48 ++-- components/preferencesContext.tsx | 66 ++--- components/recentGames.tsx | 123 +++++---- components/socketContext.tsx | 11 +- components/toast.tsx | 192 ++++++++------ components/ui.tsx | 415 ++++++++++++++++------------- components/voerBord.tsx | 95 ++++--- package.json | 124 ++++----- pages/_app.tsx | 29 +- pages/blog/[post].tsx | 59 +++-- pages/game.tsx | 359 +++++++++++++------------ pages/index.tsx | 273 ++++++++++--------- pages/login.tsx | 104 ++++---- pages/register.tsx | 127 +++++---- pages/search.tsx | 136 ++++++---- pages/settings.tsx | 128 ++++----- pages/user.tsx | 544 +++++++++++++++++++++----------------- readme.md | 74 +++--- styles/readme.md | 13 +- tsconfig.json | 52 ++-- voerbak/readme.md | 39 +-- voerbak/stuk/readme.md | 4 +- 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 } ``` + @@ -48,6 +51,7 @@ API return type classes are mostly defined in api/api.ts password: string } ``` + none empty response with the set_cookie header @@ -66,6 +70,7 @@ API return type classes are mostly defined in api/api.ts password: string } ``` + none empty response with the set_cookie header @@ -83,13 +88,15 @@ API return type classes are mostly defined in api/api.ts id?: userID } ``` + none|user ```ts -userInfo +userInfo; ``` + @@ -102,6 +109,7 @@ userInfo ```ts { id?: userID } ``` + none|user @@ -112,6 +120,7 @@ userInfo totals: userGameTotals } ``` + @@ -124,6 +133,7 @@ userInfo ```ts { id?: userID } ``` + none|user PNG image @@ -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 + ```ts -{ image: base64PNG } +{ + image: + base64PNG; +} ``` + user none @@ -157,8 +172,12 @@ returns error when image is not .png or larger than 256x256 ```ts -{ preferences: userPreferences } +{ + preferences: + userPreferences; +} ``` + @@ -169,8 +188,12 @@ returns error when image is not .png or larger than 256x256 ```ts -{ newPreferences: userPreferences } +{ + newPreferences: + userPreferences; +} ``` + user none @@ -188,6 +211,7 @@ returns error when image is not .png or larger than 256x256 newPassword: string, } ``` + user none @@ -205,6 +229,7 @@ returns error when image is not .png or larger than 256x256 email: string, } ``` + user none @@ -222,6 +247,7 @@ returns error when image is not .png or larger than 256x256 username: string, } ``` + user none @@ -234,8 +260,12 @@ returns error when image is not .png or larger than 256x256 ```ts -{ status: string } +{ + status: + string; +} ``` + user none @@ -459,6 +489,7 @@ These are events that are fired by the socket.io connection ## How to test API endpoints + ```sh # If you're running the standalone flask server: curl http://localhost:5000/ @@ -471,10 +502,16 @@ curl http://localhost:2080/api/ 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
; + return
; } - - 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 + return

{props.title}

- + {props.children} -
+
; } 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 + return {props.icon} {props.children} - + ; } export function Footer() { - return
-
- + return
+
+

4 op een rij

-
-
- } href="/" children="Home"/> - } href="/game" children="Spelen"/> - } href="/" children="Puzzels"/> - } href="/search" children="Zoeken"/> +
+
+ } href='/' children='Home' /> + } href='/game' children='Spelen' /> + } href='/' children='Puzzels' /> + } href='/search' children='Zoeken' />
-
- } href="/privacy" children="Privacy"/> - } href="https://github.com/lonkaars/connect-4" children="Broncode"/> +
+ } href='/privacy' children='Privacy' /> + } href='https://github.com/lonkaars/connect-4' children='Broncode' />
-
- } href="/settings" children="Instellingen"/> - } href="/user" children="Profiel"/> - } href="/logout" children="Uitloggen"/> +
+ } href='/settings' children='Instellingen' /> + } href='/user' children='Profiel' /> + } href='/logout' children='Uitloggen' />
-
+
; } 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 {props.children} + return + {props.children} + ; } -var GameBarSpacer = () =>
; +var GameBarSpacer = () =>
; 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 -
-
-
-

{ - !props.active ? "Wachten op tegenstander..." : - props.turn == props.player1 ? - "Jouw beurt" : "Tegenstander" - }

+ return +
+
+
+

+ {!props.active + ? 'Wachten op tegenstander...' + : props.turn == props.player1 + ? 'Jouw beurt' + : 'Tegenstander'} +

-
- 0-0 +
+ + 0-0 +
-
+
- + - + - 00:00 + + 00:00 + - + - + - + - + - + - +
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({ - 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
-

- {timelimit_str}
+ 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

+

+ {timelimit_str} +
{ranked_str}

- - +
; } } @@ -101,25 +116,36 @@ function GameSettingsSection(props: { noMarginBottom?: boolean; id: string; }) { - return - {props.title} - -
{props.children}
-
+ return + + {props.title} + + +
{props.children}
+
; } function GameRule(props: { @@ -127,15 +153,17 @@ function GameRule(props: { description: string; style?: CSSProperties; }) { - return
-

{props.title}

-

{props.description}

+ return
+

{props.title}

+

{props.description}

; } @@ -146,66 +174,114 @@ type editGameSettingsProps = { }; export class EditGameSettings extends Component { - render () { - return -
- -
- - - + render() { + return +
+ +
+ + +
- - Timer gebruiken voor bijde spelers + + + Timer gebruiken voor bijde spelers +
- { false && -
- - + {false && +
+ +
- - -
} - + + + } +
- + ; } } 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 ( -
- - - - - - +
+ + + + + +
); @@ -14,15 +14,14 @@ export function LogoDark() { export function LogoLight() { return ( -
- - - - - - +
+ + + + + +
); } - 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>(null); + var [friendRequests, setFriendRequests] = useState>(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 }>({ - method: "get", - url: `/api/social/list/requests` + var friendRequestsReq = await axios.request<{ requests: Array; }>({ + 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
-
- - - { false && } - + backgroundColor: 'var(--background)', + display: 'inline-block', -
- { loggedIn && -
setNotificationsAreaVisible(!notificationsAreaVisible)}> - - { gotNotifications && ; } - 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: }); + if (messages > previousMessages) { + toast({ + message: 'Je hebt nieuwe meldingen!', + type: 'confirmation', + icon: , + }); } setPreviousMessages(messages); - }) + }); - return props.visible && + return props.visible &&

Meldingen

-
- { props.gameInvites?.map(game => ) } - { props.friendRequests?.map(user => ) } - { - messages == 0 && -
-

Geen meldingen

-
- } +
+ {props.gameInvites?.map(game => )} + {props.friendRequests?.map(user => )} + {messages == 0 + &&
+

+ Geen meldingen +

+
}
- + ; } 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 -
+ return +
{props.children} -
+
} - text="Accepteren"/> + icon={} + text='Accepteren' + /> } - text="Verwijderen"/> + icon={} + text='Verwijderen' + />
- + ; } 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 && { - 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); - }}> - - -
- Vriendschapsverzoek + return !gone && { + 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); + }} + > + + +
+ Vriendschapsverzoek {props.user.username}
- + ; } function GameInvite(props: { @@ -169,14 +191,20 @@ function GameInvite(props: { }) { return -
- Partijuitnodiging -

{props.game.opponent?.username} wil een potje 4 op een rij spelen!

+
+ Partijuitnodiging +

+ + {props.game.opponent?.username} + {" "} + wil een potje 4 op een rij spelen! +

- + ; } - 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
-
{props.children}
+export function CenteredPage(props: CenteredPageProps) { + return
+
+ {props.children} +
; } export class PageTitle extends Component { - render () { - return

{this.props.children}

; + render() { + return

+ {this.props.children} +

; } } 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(); - 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 {props.children} - + ; } 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 {gameStatus} + return + {gameStatus} + ; } -export default function RecentGames(props: { games?: Array }) { +export default function RecentGames(props: { games?: Array; }) { return

Recente partijen

- { - props.games?.length > 0 ? - + {props.games?.length > 0 + ?
- - - - + + + + - { - props.games?.map(game => - - - - - ) - } + {props.games?.map(game => + + + + + + + )} -
TegenstanderUitkomstZettenDatumTegenstanderUitkomstZettenDatum
- {game.opponent?.username} - {Math.max(0, game.moves.length -1)}{(() => { - var timeCreated = new Date(game.created); - return friendlyTime(timeCreated); - })()}
+ + {game.opponent?.username} + + {Math.max(0, game.moves.length - 1)} + {(() => { + var timeCreated = new Date(game.created); + return friendlyTime(timeCreated); + })()} +
: -

Deze gebruiker heeft nog geen partijen gespeeld

- } -
+ + :

+ Deze gebruiker heeft nog geen partijen gespeeld +

} +
; } - 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 - { props.children } - + {props.children} + ; } - 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
{props.children}
+ return
+ {props.children} +
; } 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 &&
- { - props.children || -
-
{props.icon}
-
+ return visible &&
+ {props.children + ||
+
+ {props.icon} +
+

{props.text}

{props.description}

-
setVisibility(false)}> - +
setVisibility(false)} + > +
-
- } -
+
} +
; } 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 = []; -export function ToastContextWrapper(props: { children?: ReactNode }) { +export function ToastContextWrapper(props: { children?: ReactNode; }) { var [dummyState, rerender] = useState(false); - return { - toasts.push(); - rerender(!dummyState); - } }}> - { props.children } + return { + toasts.push( + , + ); + rerender(!dummyState); + }, + }} + > + {props.children} {toasts} - + ; } - 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 {props.children} + return + {props.children} + ; } export function Button(props: { @@ -47,27 +51,34 @@ export function Button(props: { onclick?: (() => void); id?: string; }) { - return - { - props.text ? - {props.text} - : undefined - } - { props.children } + return + {props.text + ? + {props.text} + + : undefined} + {props.children} ; } @@ -78,23 +89,33 @@ export function IconLabelButton(props: { style?: CSSProperties; href?: string; }) { - return + + {props.text} + + ; } 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 + 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
- -
- +export function SearchBar(props: { label?: string; }) { + return
+ +
+
-
+
; } 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
- { - on ? - : - - } -
+ return
+ {on + ? + : } +
; } 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 + ; } } @@ -246,12 +291,21 @@ export function Tuitje(props: { style?: CSSProperties; rotation?: number; }) { - return - - + return + + ; } export function Bubble(props: { @@ -259,24 +313,27 @@ export function Bubble(props: { style?: CSSProperties; tuitjeStyle?: CSSProperties; }) { - return + return {props.children} - - + + ; } - 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
+ return
; } export function VoerBord(props: { @@ -13,40 +19,53 @@ export function VoerBord(props: { onMove: (move: number) => void; active: boolean; }) { - return + return
- { - [...Array(props.height).keys()].map((row) => ( - - {[...Array(props.width).keys()].map((column) => ( - + {[...Array(props.width).keys()].map((column) => ( + - ))} - - )) - } + 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])); + }} + /> + + ))} + + ))} -
-
- -
( +
+
+ +
{ - props.onMove(Number((event.target as HTMLElement).id.split("-")[1])) - }}/> -
+ ; } 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
4 op een rij - + - - + + - - - - - - - + + + + + + + - + -
+
; } - 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
- + - {props.post.replace(/_/g, " ")} + {props.post.replace(/_/g, ' ')} -
+
-
+
; } 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 = [...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> - boardFull: boolean - winner: number - }) => { + props.io.on('finish', (data: { + winPositions: Array>; + 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: }); + props.io.on('resign', () => { + props.toast({ message: 'Het potje is opgegeven', type: 'normal', icon: }); }); }, []); - return
+ return
{ - 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 }); + }} /> -
+
; } function GameOutcomeDialog(props: { @@ -104,180 +108,209 @@ function GameOutcomeDialog(props: { visible: boolean; }) { return { - window.history.replaceState(null, null, "/"); + window.history.replaceState(null, null, '/'); window.location.reload(); - }}> -
-

{ - props.outcome == 0 ? "Gelijkspel" : - props.outcome == props.player ? "Verloren" : - props.outcome != props.player ? "Gewonnen" : - "???" - }

- { false &&

- 0 Gemiste winstzetten
- 6 Optimale zetten
- 0 Blunders -

} - { false && } style={{ - float: "none", - marginTop: 24, - padding: "12px 32px" - }}/> } + }} + > +
+

+ {props.outcome == 0 + ? 'Gelijkspel' + : props.outcome == props.player + ? 'Verloren' + : props.outcome != props.player + ? 'Gewonnen' + : '???'} +

+ {false &&

+ 0 Gemiste winstzetten
+ 6 Optimale zetten
+ 0 Blunders +

} + {false && } + style={{ + float: 'none', + marginTop: 24, + padding: '12px 32px', + }} + />}
- + ; } 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
- - + + + toast={toast} + /> { window.history.go(-1); }}> - -
- -
- +
-
+
; } - 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
- Log in of maak een account aan om je scores op te slaan en toegang te krijgen tot meer functies -
-
-
+
; } function AccountBox(props: { @@ -86,116 +96,123 @@ function AccountBox(props: { sumGameInfo: userGameTotals; }) { return
-
- +
+
-
-

{props.info?.username}

+
+

+ {props.info?.username} +

Score: {props.info?.rating}

-

- {props.sumGameInfo?.win} W - / - {props.sumGameInfo?.lose} V - / +

+ {props.sumGameInfo?.win} W + / + {props.sumGameInfo?.lose} V + / {props.sumGameInfo?.draw} G

-
+
; } 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(); var [gameInfo, setGameInfo] = useState(); - useEffect(() => {( async () => { - if (!loggedIn) return; - var userInfoReq = await axios.request({ - method: "get", - url: `/api/user/info`, - headers: {"content-type": "application/json"} - }); - setUserInfo(userInfoReq.data); - })()}, []); - - useEffect(() => {( async () => { - if (!loggedIn) return; - var userGamesReq = await axios.request({ - method: "get", - url: `/api/user/games`, - headers: {"content-type": "application/json"} - }); - setGameInfo(userGamesReq.data); - })()}, []); + useEffect(() => { + (async () => { + if (!loggedIn) return; + var userInfoReq = await axios.request({ + method: 'get', + url: `/api/user/info`, + headers: { 'content-type': 'application/json' }, + }); + setUserInfo(userInfoReq.data); + })(); + }, []); + + useEffect(() => { + (async () => { + if (!loggedIn) return; + var userGamesReq = await axios.request({ + method: 'get', + url: `/api/user/games`, + headers: { 'content-type': 'application/json' }, + }); + setGameInfo(userGamesReq.data); + })(); + }, []); return
- + 4 op een rij
- - + + Nieuw spel
- { - false && - - + {false + && + Puzzels
-
- } - { - false && - - + } + {false + && + Tegen computer
-
- } +
} - { - loggedIn ? - : - - } + {loggedIn + ? + : }
- { - loggedIn && - - - - } + {loggedIn + && + + }

Nieuws ofzo

-

Chess.com heeft heel veel troep waar niemand naar kijkt

+

Chess.com heeft heel veel troep waar niemand naar kijkt

-
-
+
+
; } - 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, 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: }); + if ( + !formData.email + || !formData.password + ) { + toast({ message: 'Vul alsjeblieft alle velden in!', type: 'error', icon: }); 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: }); - return; - } - toast({ message: "Er is iets fout gegaan", - type: "error", - icon: }); - }); + .then(() => window.location.pathname = '/') + .catch(error => { + if (error.response.status === 401) { + toast({ message: 'Verkeerde gebruikersnaam of wachtwoord!', type: 'error', icon: }); + return; + } + toast({ message: 'Er is iets fout gegaan', type: 'error', icon: }); + }); } export default function LoginPage() { @@ -51,29 +47,46 @@ export default function LoginPage() { return (
- - -
+ + +
submitLogin(e, toast)}> - - -
- - + + + + +
+ +
- +
@@ -81,4 +94,3 @@ export default function LoginPage() {
); } - 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, 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: }); + if ( + !formData.username + || !formData.email + || !formData.password + ) { + toast({ message: 'Vul alsjeblieft alle velden in!', type: 'error', icon: }); return; } @@ -39,47 +39,47 @@ function submitRegister(event?: FormEvent, 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: }); + 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: , + }); return; } - if ( !validateEmail(formData.email) ) { - toast({ message: "Ongeldig email-adres", - type: "error", - icon: }); + if (!validateEmail(formData.email)) { + toast({ message: 'Ongeldig email-adres', type: 'error', icon: }); 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: }); + // 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: , + }); 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: }); - 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: }); + console.log(error); + }); } export default function RegisterPage() { @@ -87,22 +87,36 @@ export default function RegisterPage() { return (
- - -
+ + +
submitRegister(e, toast)}> - - - - - + + + + + +
@@ -110,4 +124,3 @@ export default function RegisterPage() {
); } - 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) => 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 }>({ - method: "post", + axios.request<{ 'results': Array; }>({ + 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 }) { +function SearchResults(props: { userList: Array; }) { return
- { props.userList?.map(user => ) } + {props.userList?.map(user => )}
; } -function SearchResult(props: { user: userInfo }) { - return -
- -
+function SearchResult(props: { user: userInfo; }) { + return +
+ +
{props.user.username}

{props.user.status}

@@ -50,27 +58,42 @@ function SearchResult(props: { user: userInfo }) { function SearchBar(props: { searchFunction: (event?: FormEvent) => void; }) { - return + return
- - - + + +
-
+
; } export default function HomePage() { @@ -80,21 +103,24 @@ export default function HomePage() { event.preventDefault(); search(results => setResults(results)); setSearched(true); - } + }; return
- + Zoeken - - - { searched && results.length == 0 &&

Geen zoekresultaten gevonden

} + + + {searched && results.length == 0 &&

+ Geen zoekresultaten gevonden +

}
-
+
; } - 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 (
- + Instellingen

Account

- -
- }/> -
+ } /> +

Gebruikersnaam

Hier staat hij dan

- }/> - }/> -
+ } /> + } /> +

Email

******@example.com

- }/> -
+ } /> +

Wachtwoord

@@ -100,23 +101,24 @@ export default function SettingsPage() {

Kleuren

- - -
+ + +

Schijfjes

- -
+ +

Achtergrond

-
- updatePreference({"darkMode": state}) - }/> +
+ updatePreference({ 'darkMode': state })} + />

Donkere modus

@@ -124,27 +126,33 @@ export default function SettingsPage() {

Standaard spelregels

- +

Uitloggen

-
- } text="Uitloggen" style={{ - float: "none", - marginLeft: 0 - }} onclick={() => { - cookies.remove("token") - window.location.pathname = "/"; - }}/> +
+ } + text='Uitloggen' + style={{ + float: 'none', + marginLeft: 0, + }} + onclick={() => { + cookies.remove('token'); + window.location.pathname = '/'; + }} + />
-
+
); } - 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
-
{props.icon}
-
- {props.label} + return
+
+ {props.icon} +
+
+ + {props.label} +
-
+
; } -function InfoSection(props: { children: ReactNode }) { +function InfoSection(props: { children: ReactNode; }) { return -
+
{props.children}
- + ; } 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(); var [gameInfo, setGameInfo] = useState(); var [editingStatus, setEditingStatus] = useState(false); - var [relation, setRelation] = useState("none"); + var [relation, setRelation] = useState('none'); var [ownPage, setOwnPage] = useState(loggedIn && !pageID); var { toast } = useContext(ToastContext); @@ -91,237 +108,288 @@ export default function AccountPage() { async function getUserData(): Promise { var userReq = await axios.request({ - 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({ - method: "post", - url: `/api/user/info`, - headers: {"content-type": "application/json"} - }); - setOwnPage(ownPage || userReq.data.id == pageID); - })()}, []); + useEffect(() => { + (async () => { + var userReq = await axios.request({ + 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({ - method: "post", - url: `/api/user/games`, - headers: {"content-type": "application/json"}, - data: reqData - }); - setGameInfo(userGamesReq.data); - })()}, []); + useEffect(() => { + (async () => { + var userGamesReq = await axios.request({ + method: 'post', + url: `/api/user/games`, + headers: { 'content-type': 'application/json' }, + data: reqData, + }); + setGameInfo(userGamesReq.data); + })(); + }, []); return
- + Profiel - -
+ +

{user?.username}

-

{user?.status}

+

+ {user?.status} +

-
- { loggedIn &&
{ - ownPage ? -
- } href="/settings" text="Instellingen"/> - { - !editingStatus ? - } - text="Status bewerken" - onclick={() => setEditingStatus(true)}/> : - } - 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 } - }); - }}/> - } -
: -
- {(() => { - var icon = { - "blocked": - }[relation] || +
+ {loggedIn &&
+ {ownPage + ?
+ } href='/settings' text='Instellingen' /> + {!editingStatus + ? } + text='Status bewerken' + onclick={() => setEditingStatus(true)} + /> + : } + 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 }, + }); + }} + />} +
+ :
+ {(() => { + var icon = { + 'blocked': , + }[relation] || ; - var text = { - "blocked": "Deblokkeren" - }[relation] || "Blokkeren" + var text = { + 'blocked': 'Deblokkeren', + }[relation] || 'Blokkeren'; - return { - var nextRelation = { - "blocked": { - "endpoint": "/api/social/unblock", - "action": `${user.username} gedeblokkeerd`, - "relation": "none", - "icon": , - } - }[relation] || { - "endpoint": "/api/social/block", - "action": `${user.username} geblokkeerd`, - "relation": "blocked", - "icon": , - } + return { + var nextRelation = { + 'blocked': { + 'endpoint': '/api/social/unblock', + 'action': `${user.username} gedeblokkeerd`, + 'relation': 'none', + 'icon': , + }, + }[relation] || { + 'endpoint': '/api/social/block', + 'action': `${user.username} geblokkeerd`, + 'relation': 'blocked', + 'icon': , + }; - 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": , - "outgoing": , - "incoming": - }[relation] || + 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': , + 'outgoing': , + 'incoming': , + }[relation] || ; - 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 { - var nextRelation = { - "friends": { - "endpoint": "/api/social/remove", - "action": `${user.username} succesvol verwijderd als vriend`, - "relation": "none", - "icon": , - }, - "outgoing": { - "endpoint": "/api/social/remove", - "action": `Vriendschapsverzoek naar ${user.username} geannuleerd`, - "relation": "none", - "icon": , - }, - "incoming": { - "endpoint": "/api/social/accept", - "action": `Vriendschapsverzoek van ${user.username} geaccepteerd`, - "relation": "friends", - "icon": , - }, - }[relation] || { - "endpoint": "/api/social/request", - "action": `Vriendschapsverzoek gestuurd naar ${user.username}`, - "relation": "outgoing", - "icon": , - } + return { + var nextRelation = { + 'friends': { + 'endpoint': '/api/social/remove', + 'action': `${user.username} succesvol verwijderd als vriend`, + 'relation': 'none', + 'icon': , + }, + 'outgoing': { + 'endpoint': '/api/social/remove', + 'action': `Vriendschapsverzoek naar ${user.username} geannuleerd`, + 'relation': 'none', + 'icon': , + }, + 'incoming': { + 'endpoint': '/api/social/accept', + 'action': `Vriendschapsverzoek van ${user.username} geaccepteerd`, + 'relation': 'friends', + 'icon': , + }, + }[relation] || { + 'endpoint': '/api/social/request', + 'action': `Vriendschapsverzoek gestuurd naar ${user.username}`, + 'relation': 'outgoing', + 'icon': , + }; - 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); - }); - }}/> - })()} -
- }
} + 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); + }); + }} + />; + })()} +
} +
}
- } label="Online"/> - } label={ (() => { - var memberSince = "Lid sinds"; + } + label='Online' + /> + } + 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; - })() }/> - } label={(() => { - var label = user?.friends.toString() + " "; - label += user?.friends == 1 ? "vriend" : "vrienden"; - return label; - })()}/> - } label="Nederland"/> + return memberSince; + })()} + /> + } + label={(() => { + var label = user?.friends.toString() + ' '; + label += user?.friends == 1 ? 'vriend' : 'vrienden'; + return label; + })()} + /> + } label='Nederland' /> - } label={ gameInfo?.totals.win + " keer gewonnen" }/> - } label={ gameInfo?.totals.draw + " keer gelijkspel" }/> - } label={ gameInfo?.totals.lose + " keer verloren" }/> - } label={ "Score: " + user?.rating }/> - } label={(() => { - var label = gameInfo?.totals.games.toString() + " "; - label += gameInfo?.totals.games == 1 ? "potje" : "potjes"; - return label; - })()}/> + } + label={gameInfo?.totals.win + ' keer gewonnen'} + /> + } + label={gameInfo?.totals.draw + ' keer gelijkspel'} + /> + } + label={gameInfo?.totals.lose + ' keer verloren'} + /> + } label={'Score: ' + user?.rating} /> + } + label={(() => { + var label = gameInfo?.totals.games.toString() + ' '; + label += gameInfo?.totals.games == 1 ? 'potje' : 'potjes'; + return label; + })()} + /> - + -
-
+
+
; } - diff --git a/readme.md b/readme.md index 280fb7e..a32331a 100644 --- a/readme.md +++ b/readme.md @@ -9,8 +9,8 @@

> 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. -- cgit v1.2.3