diff options
-rw-r--r-- | api/user/updatePreferences.py | 32 | ||||
-rw-r--r-- | components/preferencesContext.tsx | 62 | ||||
-rw-r--r-- | components/ui.tsx | 36 | ||||
-rw-r--r-- | pages/_app.tsx | 45 | ||||
-rw-r--r-- | pages/settings.tsx | 9 |
5 files changed, 121 insertions, 63 deletions
diff --git a/api/user/updatePreferences.py b/api/user/updatePreferences.py new file mode 100644 index 0000000..2eb6512 --- /dev/null +++ b/api/user/updatePreferences.py @@ -0,0 +1,32 @@ +from flask import Blueprint, request +from db import cursor, connection +from auth.login_token import token_login +import json + +def format_preferences(preferences): #TODO: remove excess properties (create preferences class?) + return json.dumps(preferences) or "" + +updatePreferences = Blueprint('updatePreferences', __name__) + +@updatePreferences.route('/updatePreferences', methods = ['POST']) +def index(): + data = request.get_json() + + new_preferences = data.get("newPreferences") or "" + token = request.cookies.get("token") or "" + + if not token: return "", 401 + login = token_login(token) or "" + + if not login: return "", 403 + + formatted_json = format_preferences(new_preferences) + if not formatted_json: return "", 400 + + cursor.execute("update users set preferences = ? where user_id = ?", [formatted_json, login]) + connection.commit() + + return "", 200 + +dynamic_route = ["/user", updatePreferences] + diff --git a/components/preferencesContext.tsx b/components/preferencesContext.tsx new file mode 100644 index 0000000..f7bc409 --- /dev/null +++ b/components/preferencesContext.tsx @@ -0,0 +1,62 @@ +import { useState, useEffect, createContext, ReactNode } from 'react'; +import axios from 'axios'; + +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"); +} + +var PreferencesContext = createContext<{ preferences?: userPreferences; updatePreference?: (newPreference: userPreferences) => void }>({}); + +export function PreferencesContextWrapper(props: { children?: ReactNode }) { + var [gotData, setGotData] = useState(false); + var [preferences, setPreferences] = useState<userPreferences>(); + + useEffect(() => {(async() => { + if (gotData) return; + if (typeof window === "undefined") return; + if (!document.cookie.includes("token")) 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 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); + + setGotData(true); + })()}); + + useEffect(() => applyPreferences(preferences), [preferences]); + + function updatePreference(newPreference: userPreferences) { + var prefs: userPreferences = Object.assign(preferences, newPreference); + setPreferences(prefs); + applyPreferences(prefs); + axios.request({ + method: "post", + url: `/api/user/updatePreferences`, + headers: {"content-type": "application/json"}, + data: { "newPreferences": prefs } + }); + } + + return <PreferencesContext.Provider value={{ preferences, updatePreference }}> + {props.children} + </PreferencesContext.Provider> +} + +export default PreferencesContext; + diff --git a/components/ui.tsx b/components/ui.tsx index e2d7f41..f17ae5d 100644 --- a/components/ui.tsx +++ b/components/ui.tsx @@ -1,4 +1,4 @@ -import { Component, CSSProperties, ReactNode } from "react"; +import { Component, CSSProperties, ReactNode, useState } from "react"; import SearchIcon from '@material-ui/icons/Search'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; @@ -160,31 +160,29 @@ export function SearchBar(props: { label?: string }) { </div> } -export class CheckBox extends Component<{ +export function CheckBox(props: { state?: boolean; style?: CSSProperties; id?: string; onclick?: (state: boolean) => void; -}> { - state = { on: this.props.state || false } - public toggle = () => { - this.setState({ on: !this.state.on }); - this.props.onclick && this.props.onclick(!this.state.on); +}) { + var [on, setOn] = useState(props.state || false); + var toggle = () => { + setOn(!on); + props.onclick && props.onclick(!on); } - render() { - return <div onClick={this.toggle} id={this.props.id} className={this.state.on ? "on" : "off"} style={{ - ...this.props.style, - display: "inline-block", - cursor: "pointer" - }}> - { - this.state.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<{ diff --git a/pages/_app.tsx b/pages/_app.tsx index 08338d0..3c238b9 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,56 +1,19 @@ import Head from 'next/head'; -import axios from 'axios'; -import { useState, useEffect } from 'react'; - -import { userPreferences } from '../api/api'; +import { PreferencesContextWrapper } from '../components/preferencesContext'; import '../styles/global.css'; import '../styles/dark.css'; import '../styles/disk.css'; -function applyPreferences(preferences: userPreferences) { - if(typeof preferences === "undefined") return; - if(typeof preferences.darkMode !== "undefined") - document.getElementsByTagName("html")[0].classList[preferences.darkMode ? "add" : "remove"]("dark"); -} - export default function VierOpEenRijWebsite({ Component, pageProps }) { - var [gotData, setGotData] = useState(false); - var [preferences, setPreferences] = useState<userPreferences>(); - - useEffect(() => {(async() => { - if (gotData) return; - if (typeof window === "undefined") return; - if (!document.cookie.includes("token")) return; - - var local_prefs = window.localStorage.getItem("preferences"); - if (local_prefs) { - var local_prefs_json = JSON.parse(local_prefs) as userPreferences; - console.log(typeof local_prefs_json); - setPreferences(local_prefs_json); - applyPreferences(local_prefs_json); - } - - 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); - - setGotData(true); - })()}) - - useEffect(() => applyPreferences(preferences), [preferences]); - return <div> <Head> <title>4 op een rij</title> <link rel="stylesheet" href="/font/stylesheet.css"/> </Head> - <Component {...pageProps}/> + <PreferencesContextWrapper> + <Component {...pageProps}/> + </PreferencesContextWrapper> </div> } diff --git a/pages/settings.tsx b/pages/settings.tsx index 5034bd7..a713a5a 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -1,4 +1,4 @@ -import { CSSProperties } from 'react'; +import { CSSProperties, useContext } from 'react'; import * as cookies from 'react-cookies'; import { NavBar } from '../components/navbar'; @@ -6,6 +6,7 @@ import { CenteredPage, PageTitle } from '../components/page'; import { Vierkant, IconLabelButton, CheckBox, ColorPicker } from '../components/ui'; import { AccountAvatar } from '../components/account'; import { CurrentGameSettings } from '../components/gameSettings'; +import PreferencesContext from '../components/preferencesContext'; import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; import VisibilityOutlinedIcon from '@material-ui/icons/VisibilityOutlined'; @@ -17,6 +18,8 @@ var SettingsSubsectionStyle: CSSProperties = { }; export default function SettingsPage() { + var { preferences, updatePreference } = useContext(PreferencesContext); + return ( <div> <NavBar/> @@ -67,8 +70,8 @@ export default function SettingsPage() { </div> <div style={SettingsSubsectionStyle}> <div style={{ float: "right" }}> - <CheckBox state={typeof window !== "undefined" && document.getElementsByTagName("html")[0].classList.contains("dark")} onclick={ - (state) => document.getElementsByTagName("html")[0].classList[state ? "add" : "remove"]("dark") + <CheckBox state={preferences?.darkMode} onclick={ + state => updatePreference({"darkMode": state}) }/> </div> <h3>Donkere modus</h3> |