From 901cb049559cdd3348453755e33758cfb3ace569 Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Mon, 25 May 2020 22:54:15 +0200 Subject: [PATCH] Profile page shows guests and lets you delete them. History page shows times in the appropriate format. IntlDatetime set up for each locale. --- .babelrc | 7 ++- package-lock.json | 68 ++++++++++++++++++++++--- package.json | 25 +++++---- public/index.html | 4 +- src/App.tsx | 11 ++-- src/Components/GamesList.tsx | 31 ++++++++++++ src/Components/GuestList.tsx | 39 ++++++++++++++ src/Components/HistoryPage.tsx | 23 ++------- src/Components/ProfilePage.tsx | 49 ++++++++++++++++-- src/Contexts/UserContext.ts | 4 +- src/enums.ts | 8 ++- src/filetypes.d.ts | 0 src/index.tsx | 3 +- src/static/strings.ts | 18 +++++++ tslint.json | 2 +- webpack.config.js | 92 +++++++++++++++++----------------- 16 files changed, 287 insertions(+), 97 deletions(-) create mode 100644 src/Components/GamesList.tsx create mode 100644 src/Components/GuestList.tsx mode change 100755 => 100644 src/filetypes.d.ts diff --git a/.babelrc b/.babelrc index cae5209..0e8ee5e 100755 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,8 @@ { - "presets": ["@babel/env", "@babel/preset-react"] + "presets": [ + "@babel/preset-typescript", + "@babel/env", + "@babel/preset-react" + ], + "plugins": ["@babel/plugin-proposal-class-properties"] } diff --git a/package-lock.json b/package-lock.json index 6743ab2..8d843a9 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "kadi-frontend", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -271,6 +271,20 @@ "semver": "^5.5.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.6.tgz", + "integrity": "sha512-6N9IeuyHvMBRyjNYOMJHrhwtu4WJMrYf8hVbEHD3pbbbmNOk1kmXSQs7bA4dYDUaIx4ZEzdnvo6NwC3WHd/Qow==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.9.6", + "@babel/helper-split-export-declaration": "^7.8.3" + } + }, "@babel/helper-create-regexp-features-plugin": { "version": "7.8.8", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz", @@ -547,6 +561,16 @@ "@babel/plugin-syntax-async-generators": "^7.8.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", + "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-proposal-dynamic-import": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", @@ -718,6 +742,15 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.8.3.tgz", + "integrity": "sha512-GO1MQ/SGGGoiEXY0e0bSpHimJvxqB7lktLLIq2pv8xG7WZ8IMEle74jIe1FhprHBWjwjZtXHkycDLZXIWM5Wfg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", @@ -1066,6 +1099,17 @@ "@babel/helper-plugin-utils": "^7.8.3" } }, + "@babel/plugin-transform-typescript": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.6.tgz", + "integrity": "sha512-8OvsRdvpt3Iesf2qsAn+YdlwAJD7zJ+vhFZmDCa4b8dTp7MmHtKk5FF2mCsGxjZwuwsy/yIIay/nLmxST1ctVQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.9.6", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-typescript": "^7.8.3" + } + }, "@babel/plugin-transform-unicode-regex": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", @@ -1190,6 +1234,16 @@ "@babel/plugin-transform-react-jsx-source": "^7.9.0" } }, + "@babel/preset-typescript": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz", + "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-typescript": "^7.9.0" + } + }, "@babel/runtime": { "version": "7.9.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", @@ -3053,9 +3107,9 @@ "dev": true }, "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", "dev": true }, "events": { @@ -3834,9 +3888,9 @@ } }, "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { "eventemitter3": "^4.0.0", diff --git a/package.json b/package.json index 6d8b65f..8d54fa3 100755 --- a/package.json +++ b/package.json @@ -6,33 +6,28 @@ "license": "ISC", "author": "Daniel Ledda", "scripts": { - "build": "webpack --mode development", + "build-dev": "webpack --mode development && npm postbuild", + "build": "webpack --mode production", "postbuild": "rsync -avu --delete dist/ ../kadi_backend/static/frontend", "start": "webpack-dev-server --mode development", "test": "echo \"Error: no test specified\" && exit 1" }, - "dependencies": { - "axios": "^0.19.2", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-router-dom": "^5.1.2", - "semantic-ui-css": "^2.4.1", - "semantic-ui-react": "^0.88.2" - }, "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.9.6", + "@babel/plugin-proposal-class-properties": "^7.8.3", "@babel/preset-env": "^7.9.6", "@babel/preset-react": "^7.9.4", + "@babel/preset-typescript": "^7.9.0", "@types/node": "^13.11.1", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.7", "@types/react-router-dom": "^5.1.5", - "ignore-loader": "^0.1.2", - "font-loader": "^0.1.2", "babel-loader": "^8.1.0", "css-loader": "^3.5.3", "file-loader": "^6.0.0", + "font-loader": "^0.1.2", + "ignore-loader": "^0.1.2", "react-hot-loader": "^4.12.21", "style-loader": "^1.2.1", "ts-loader": "^7.0.3", @@ -42,5 +37,13 @@ "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0" + }, + "dependencies": { + "axios": "^0.19.2", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router-dom": "^5.1.2", + "semantic-ui-css": "^2.4.1", + "semantic-ui-react": "^0.88.2" } } diff --git a/public/index.html b/public/index.html index 3a4647d..53a926c 100755 --- a/public/index.html +++ b/public/index.html @@ -2,7 +2,7 @@ - + You need to enable JavaScript to run this app. - + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 8ecffcb..eef6711 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import React, {ReactNode} from "react"; -import {BrowserRouter as Router, Link, Route} from "react-router-dom"; -import {Redirect, Switch} from "react-router"; +import {BrowserRouter as Router, Route} from "react-router-dom"; +import {Redirect} from "react-router"; import {IntlStrings} from "./static/strings"; -import {PageId, SupportedLang} from "./enums"; +import {PageId, SupportedLang, supportedLangToIntlDTF} from "./enums"; import {pageComponentFromId} from "./pageListings"; import KadiPage from "./Components/KadiPage"; import HomePage from "./Components/HomePage"; @@ -27,6 +27,7 @@ class App extends React.Component { username: username, loggedIn: loggedIn, updateUserContext: this.updateUserContext, + dateTimeFormatter: this.state.userContext.dateTimeFormatter, currentLang: this.state.userContext.currentLang, strings: this.state.userContext.strings, changeLang: this.state.userContext.changeLang, @@ -35,6 +36,7 @@ class App extends React.Component { this.changeLang = (lang: SupportedLang, submit=true) => { this.setState({userContext: { + dateTimeFormatter: supportedLangToIntlDTF[lang], strings: IntlStrings[lang], currentLang: lang, changeLang: this.changeLang, @@ -52,6 +54,7 @@ class App extends React.Component { username: "", loggedIn: false, updateUserContext: this.updateUserContext, + dateTimeFormatter: supportedLangToIntlDTF[SupportedLang.gb], currentLang: SupportedLang.gb, strings: IntlStrings[SupportedLang.gb], changeLang: this.changeLang, @@ -76,7 +79,7 @@ class App extends React.Component { } submitLanguagePreference(lang: SupportedLang) { - axios.post(SERVER_BASE_NAME + "/api/changeLang", + axios.put(SERVER_BASE_NAME + "/api/lang", {lang: lang}, {headers: {"Content-Type": "application/json"}} ); diff --git a/src/Components/GamesList.tsx b/src/Components/GamesList.tsx new file mode 100644 index 0000000..36eeb95 --- /dev/null +++ b/src/Components/GamesList.tsx @@ -0,0 +1,31 @@ +import {List, ListItem} from "semantic-ui-react"; +import React from "react"; +import UserContext from "../Contexts/UserContext"; + +interface GamesListProps { + loading: boolean; + gamesList: any[]; +} + +const GamesList: React.FunctionComponent = (props) => { + const {loading, gamesList} = props; + const Uctx = React.useContext(UserContext); + const listItems = gamesList.map(listing => + + Game played on: {Uctx.dateTimeFormatter.format(new Date(listing.createdAt))} + + ); + return ( + <> + {loading ? ( +

{Uctx.strings.historyPage.loading}

+ ) : ( + + {listItems} + + )} + + ); +}; + +export default GamesList; \ No newline at end of file diff --git a/src/Components/GuestList.tsx b/src/Components/GuestList.tsx new file mode 100644 index 0000000..ba33bec --- /dev/null +++ b/src/Components/GuestList.tsx @@ -0,0 +1,39 @@ +import {Header, List, ListItem} from "semantic-ui-react"; +import React from "react"; +import UserContext from "../Contexts/UserContext"; +import {Guest} from "./ProfilePage"; +import HeaderSubHeader from "semantic-ui-react/dist/commonjs/elements/Header/HeaderSubheader"; +import {SERVER_BASE_NAME} from "../index"; + + +interface GuestListProps { + loading: boolean; + guestList: Guest[]; + deleteGuest: (id: string) => any; +} + +const GuestList: React.FunctionComponent = (props) => { + const {loading, guestList, deleteGuest} = props; + const Uctx = React.useContext(UserContext); + const listItems = guestList.map(guest => + + {guest.nick} - deleteGuest(guest.id)}>{Uctx.strings.general.deleteCommand} + + ); + return ( + <> +
+ {Uctx.strings.profilePage.guestsHeader} +
+ {loading ? ( +

{Uctx.strings.profilePage.loadingGuests}

+ ) : ( + + {listItems} + + )} + + ); +}; + +export default GuestList; \ No newline at end of file diff --git a/src/Components/HistoryPage.tsx b/src/Components/HistoryPage.tsx index 2ddc4ea..2714141 100755 --- a/src/Components/HistoryPage.tsx +++ b/src/Components/HistoryPage.tsx @@ -3,6 +3,7 @@ import {Header, List, ListItem} from "semantic-ui-react"; import axios from "axios"; import {SERVER_BASE_NAME} from "../index"; import UserContext from "../Contexts/UserContext"; +import GamesList from "./GamesList"; interface HistoryPageProps { } @@ -27,11 +28,9 @@ class HistoryPage extends React.Component { .then(response => this.setState({gameListings: response.data.games})) .catch(error => this.handleError(error)) .finally(() => this.setState({ loadingGames: false })); - console.log(this.state.gameListings); } handleError = (error: any) => void { - }; render(): ReactElement { @@ -41,22 +40,10 @@ class HistoryPage extends React.Component {
{Locale.historyPage.title}
- { - this.state.loadingGames ? ( -

- Loading games... -

- ) : - ( - - { - this.state.gameListings.map(listing => { - return Game played on: {listing.createdAt}; - }) - } - - ) - } + ); } diff --git a/src/Components/ProfilePage.tsx b/src/Components/ProfilePage.tsx index 79379a0..5842d5a 100755 --- a/src/Components/ProfilePage.tsx +++ b/src/Components/ProfilePage.tsx @@ -1,10 +1,20 @@ import React, {ReactElement} from "react"; import {Header} from "semantic-ui-react"; import UserContext from "../Contexts/UserContext"; +import axios from "axios"; +import {SERVER_BASE_NAME} from "../index"; +import GuestList from "./GuestList"; interface ProfilePageProps {} interface ProfilePageState { + loadingGuests: boolean; + guests: Guest[]; +} + +export interface Guest { + id: string; + nick: string; } class ProfilePage extends React.Component { @@ -12,15 +22,48 @@ class ProfilePage extends React.Component { super(props); this.state = { + loadingGuests: false, + guests: [], }; } + componentDidMount(): void { + this.loadGuests(); + } + + loadGuests(): void { + this.setState({loadingGuests: true}, () => { + axios.get(SERVER_BASE_NAME + "/api/guests") + .then(response => this.setState({guests: response.data.guests})) + .catch(error => this.handleError(error)) + .finally(() => this.setState({ loadingGuests: false })); + }); + } + + deleteGuest(id: string): void { + console.log("delete with url", SERVER_BASE_NAME + "/api/guest/" + id); + axios.delete(SERVER_BASE_NAME + "/api/guest/" + id) + .then(response => this.loadGuests()) + .catch(error => this.handleError(error)) + } + + handleError(error: any): void { + console.log(error); + } + render(): ReactElement { const Locale = this.context.strings; return ( -
- {Locale.profilePage.title} -
+ <> +
+ {Locale.profilePage.title} +
+ this.deleteGuest(id)} + loading={this.state.loadingGuests} + guestList={this.state.guests} + /> + ); } } diff --git a/src/Contexts/UserContext.ts b/src/Contexts/UserContext.ts index 7ab3c06..007288f 100755 --- a/src/Contexts/UserContext.ts +++ b/src/Contexts/UserContext.ts @@ -1,11 +1,12 @@ import React from "react"; -import {SupportedLang} from "../enums"; +import {supportedLangToIntlDTF, SupportedLang} from "../enums"; import {IntlStrings} from "../static/strings"; export interface IUserContext { username: string; loggedIn: boolean; updateUserContext: (username: string, loggedIn: boolean) => void; + dateTimeFormatter: Intl.DateTimeFormat; currentLang: SupportedLang; strings: any; changeLang: (lang: SupportedLang, submit?: boolean) => void; @@ -15,6 +16,7 @@ const userDefaultVal = { loggedIn: false, username: "", updateUserContext: () => {}, + dateTimeFormatter: supportedLangToIntlDTF[SupportedLang.gb], currentLang: SupportedLang.gb, strings: IntlStrings[SupportedLang.gb as SupportedLang], changeLang: () => {}, diff --git a/src/enums.ts b/src/enums.ts index 89939a5..da5b527 100755 --- a/src/enums.ts +++ b/src/enums.ts @@ -11,4 +11,10 @@ export enum PageId { stats = "stats", home = "home", history = "history", -} \ No newline at end of file +} + +export const supportedLangToIntlDTF: Record = { + gb: Intl.DateTimeFormat('en-AU'), + de: Intl.DateTimeFormat('de-DE'), + it: Intl.DateTimeFormat('it-IT'), +}; \ No newline at end of file diff --git a/src/filetypes.d.ts b/src/filetypes.d.ts old mode 100755 new mode 100644 diff --git a/src/index.tsx b/src/index.tsx index 91f3fee..f5f0feb 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,8 +3,7 @@ import ReactDOM from "react-dom"; import "semantic-ui-css/semantic.min.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; - -export const SERVER_BASE_NAME = "/kadi"; +export {homepage as SERVER_BASE_NAME} from "../package.json"; ReactDOM.render(( diff --git a/src/static/strings.ts b/src/static/strings.ts index 43ff8bf..0ac0e75 100755 --- a/src/static/strings.ts +++ b/src/static/strings.ts @@ -28,6 +28,9 @@ export const LanguageNames: Record = { export const IntlStrings = { gb: { + general: { + deleteCommand: "Delete", + }, menu: { profileTab: "Profile", statsTab: "Stats", @@ -44,6 +47,8 @@ export const IntlStrings = { }, profilePage: { title: "Profile", + guestsHeader: "Guests", + loadingGuests: "Loading guests..." }, statsPage: { title: "Stats", @@ -56,9 +61,13 @@ export const IntlStrings = { }, historyPage: { title: "History", + loading: "Loading games...", }, }, de: { + general: { + deleteCommand: "Löschen", + }, menu: { profileTab: "Profil", statsTab: "Statistiken", @@ -75,6 +84,8 @@ export const IntlStrings = { }, profilePage: { title: "Profil", + guestsHeader: "Gäste", + loadingGuests: "Gäste werden geladen...", }, statsPage: { title: "Statistiken", @@ -87,9 +98,13 @@ export const IntlStrings = { }, historyPage: { title: "Spielverlauf", + loading: "Spielverlauf wird geladen...", }, }, it: { + general: { + deleteCommand: "Cancella", + }, menu: { profileTab: "Profilo", statsTab: "Statistiche", @@ -106,6 +121,8 @@ export const IntlStrings = { }, profilePage: { title: "Profilo", + guestsHeader: "===TRANSLATE ME===", + loadingGuests: "===TRANSLATE ME===", }, statsPage: { title: "Statistiche", @@ -118,6 +135,7 @@ export const IntlStrings = { }, historyPage: { title: "Storia", + loading: "Caricando storia giochi..." }, }, } as const; \ No newline at end of file diff --git a/tslint.json b/tslint.json index 32538c5..06739e6 100755 --- a/tslint.json +++ b/tslint.json @@ -25,7 +25,7 @@ ], "linterOptions": { "exclude": [ - "build/**/*.js", + "dist/**/*.js", "config/**/*.js", "node_modules/**/*.ts" ] diff --git a/webpack.config.js b/webpack.config.js index 7dee35a..69dfa0c 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,51 +1,51 @@ const path = require("path"); const webpack = require("webpack"); +const SERVER_ROOT = require("./package.json").homepage; + module.exports = { - entry: "./src/index.tsx", - mode: "development", - module: { - rules: [ - { - test: /\.tsx?$/, - exclude: /(node_modules|bower_components|\.d\.ts$)/, - use: [ - { - loader: "babel-loader", - options: { presets: ["@babel/env"] }, - }, - { loader: "ts-loader" }, - ], - }, - { - test: /\.d\.ts$/, - loader: 'ignore-loader' - }, - { - test: /\.css$/, - use: ["style-loader", "css-loader"] - }, - { - test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i, - use: [ - { - loader: 'file-loader', - }, - ], - } - ] - }, - resolve: { extensions: [".tsx", ".ts", ".js", "*"] }, - output: { - path: path.resolve(__dirname, "dist/"), - publicPath: "/kadi/static/frontend/", - filename: "bundle.js" - }, - devServer: { - contentBase: path.join(__dirname, "public/"), - port: 3000, - publicPath: "http://localhost:3000/dist/", - hotOnly: true - }, - plugins: [new webpack.HotModuleReplacementPlugin()] + entry: "./src/index.tsx", + mode: "production", + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /(node_modules|bower_components|\.d\.ts$)/, + use: [ + "react-hot-loader/webpack", + "babel-loader", + ], + }, + { + test: /\.d\.ts$/, + loader: 'ignore-loader' + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + }, + { + test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i, + use: [ + { + loader: 'file-loader', + }, + ], + } + ] + }, + resolve: { extensions: [".tsx", ".ts", ".js", "*"] }, + output: { + path: path.resolve(__dirname, "dist/"), + publicPath: SERVER_ROOT + "/static/frontend/", + filename: "bundle.js" + }, + devServer: { + contentBase: path.join(__dirname, "public/"), + contentBasePublicPath: SERVER_ROOT + "/", + port: 3000, + publicPath: "http://localhost:3000" + SERVER_ROOT + "/static/frontend/", + hotOnly: true + }, + plugins: [new webpack.HotModuleReplacementPlugin()] };