feature(database-connection): Add routing for dynamic seasons

This commit is contained in:
2021-01-11 15:46:09 +01:00
parent ff1c56c791
commit 29b6974714
17 changed files with 699 additions and 166 deletions

View File

@@ -1,51 +1,15 @@
import React from 'react';
import './App.scss';
import UserStatsMockService from "./mock/UserStatsMockService";
import UserStatsService from "./services/UserStatsService";
import {findDOMNode} from "react-dom";
import TableEntry from "./models/TableEntry";
import UserList from './components/UserList/UserList';
import ErrorContainer from './components/ErrorContainer/ErrorContainer';
import Footer from './components/Footer/Footer';
import Header from './components/Header/Header';
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
interface State {
error?: string,
loading: boolean,
users: TableEntry[],
mock: TableEntry[]
}
import {library} from "@fortawesome/fontawesome-svg-core";
import {faArrowCircleLeft, faArrowCircleRight} from "@fortawesome/free-solid-svg-icons";
import MainPage, {IMainPageProps} from "./pages/MainPage/MainPage";
library.add(faArrowCircleLeft, faArrowCircleRight)
export default class App extends React.Component {
private apiService: UserStatsService = new UserStatsService();
private mockService = new UserStatsMockService();
state: State;
constructor(props: Readonly<{}>) {
super(props);
this.state = {
error: undefined,
loading: false,
users: [],
mock: this.mockService.getStatsWithoutPromise()
}
}
componentWillMount() {
this.fetchData();
}
private fetchData() {
this.setState({ loading: true })
this.apiService.getStats()
.then(data => this.setState({
loading: false,
users: data
}))
}
// componentDidMount() {
// this.setState({loading: false, mock: this.mockService.getStatsWithoutPromise()});
// this.apiService.getStats()
@@ -62,28 +26,14 @@ export default class App extends React.Component {
// });
// }
componentDidUpdate() {
const element = findDOMNode(this);
if (element != null) {
window.scrollTo(0, 0);
}
}
render() {
return (
<div className="App">
<Header />
{/* { this.state.error != null ? <p className="error-message"> { this.state.error } Please try again later!</p> : null} */}
{
(!this.state.error && this.state.loading)
? <UserList users={this.state.mock} />
: <UserList users={this.state.users} />
}
{
this.state.error && <ErrorContainer message={this.state.error} />
}
<Footer />
</div>
<Router>
<Switch>
<Route exact path={'/'} component={(props: IMainPageProps) => (<MainPage {...props}/>)} />
<Route path={'/season/:id'} component={(props: IMainPageProps) => (<MainPage {...props}/>)} />
</Switch>
</Router>
);
}
}

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import ErrorContainer from './ErrorContainer';
describe('<ErrorContainer />', () => {
test('it should mount', () => {
render(<ErrorContainer />);
const errorContainer = screen.getByTestId('ErrorContainer');
expect(errorContainer).toBeInTheDocument();
});
});

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Footer from './Footer';
describe('<Footer />', () => {
test('it should mount', () => {
render(<Footer />);
const footer = screen.getByTestId('Footer');
expect(footer).toBeInTheDocument();
});
});

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Header from './Header';
describe('<Header />', () => {
test('it should mount', () => {
render(<Header />);
const header = screen.getByTestId('Header');
expect(header).toBeInTheDocument();
});
});

View File

@@ -0,0 +1 @@
.SeasonDetail {}

View File

@@ -0,0 +1,24 @@
import React from 'react';
import './SeasonDetail.scss';
export interface SeasonDetailProperties {
seasonId: string,
dates: {
start: Date,
end: Date
}
}
class SeasonDetail extends React.Component<SeasonDetailProperties> {
render() {
const { seasonId, dates } = this.props
return (
<span className="SeasonDetail" data-testid="SeasonDetail">
Season {seasonId} - Duration: {dates.start.toDateString()} - {dates.end.toDateString()}
</span>
)
}
}
export default SeasonDetail;

View File

@@ -0,0 +1 @@
.SeasonSwitch {}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import './SeasonSwitch.scss';
import SeasonDetail, { SeasonDetailProperties } from '../SeasonDetail/SeasonDetail';
import {withRouter} from "react-router";
class SeasonSwitch extends React.Component<SeasonDetailProperties> {
render() {
let seasonId = Number(this.props.seasonId)
return(
<div className="SeasonSwitch" data-testid="SeasonSwitch">
<Link to={"/season/" + (seasonId - 1)}>
<FontAwesomeIcon icon="arrow-circle-left" />
</Link>
<SeasonDetail {...this.props} />
<Link to={"/season/" + (seasonId + 1)}>
<FontAwesomeIcon icon="arrow-circle-right" />
</Link>
</div>
)
}
}
export default SeasonSwitch;

View File

@@ -1,14 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import UserList from './UserList';
describe('<UserList />', () => {
test('it should mount', () => {
render(<UserList />);
const userList = screen.getByTestId('UserList');
expect(userList).toBeInTheDocument();
});
});

View File

@@ -1,6 +1,8 @@
import React from 'react';
import TableEntry from '../../models/TableEntry';
import './UserList.scss';
import SeasonSwitch from "../SeasonSwitch/SeasonSwitch";
import UserStatsResponse from "../../models/UserStatsResponse";
// const UserList: React.FC = () => (
// <div className="UserList" data-testid="UserList">
@@ -8,17 +10,15 @@ import './UserList.scss';
// </div>
// );
interface UserListProperties {
users: TableEntry[]
}
export default class UserList extends React.Component<IUserListProperties, IUserListState> {
class UserList extends React.Component<UserListProperties> {
constructor(props: Readonly<IUserListProperties> | IUserListProperties) {
super(props);
constructor(props: Readonly<UserListProperties>) {
super(props);
}
this.state = { mocked: this.props.mocked }
}
private createTableEntries(entries: TableEntry[]) {
private createTableEntries(entries: TableEntry[]) {
return entries.map((entry, index) => {
const placement = index + 1;
const placementClassName = placement === 1 ? "first-place"
@@ -51,6 +51,7 @@ class UserList extends React.Component<UserListProperties> {
render() {
return (
<div className="UserList" data-testid="UserList">
<SeasonSwitch seasonId={this.props.userStats.seasonId} dates={this.props.userStats.dates} />
<table>
<thead>
<tr>
@@ -61,7 +62,7 @@ class UserList extends React.Component<UserListProperties> {
</tr>
</thead>
<tbody>
{this.createTableEntries(this.props.users)}
{this.createTableEntries(this.props.userStats.stats)}
</tbody>
</table>
</div>
@@ -69,4 +70,11 @@ class UserList extends React.Component<UserListProperties> {
}
}
export default UserList;
interface IUserListProperties {
userStats: UserStatsResponse
mocked: boolean
}
interface IUserListState {
mocked: boolean
}

View File

@@ -1,26 +1,46 @@
import TableEntry from "../models/TableEntry";
import UserStatsService from "../services/UserStatsService";
import UserStatsResponse from "../models/UserStatsResponse";
export default class UserStatsMockService extends UserStatsService {
private readonly entries: TableEntry[];
private readonly mocks: UserStatsResponse[] = [
{
seasonId: "1",
dates: {
start: new Date(2019, 12, 5),
end: new Date()
},
stats: [
{ name: "Humen", rank: "Overwatch Noob 2", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Random Rank 3 4", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Kas is cool 5", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Bremsspu 6r", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "127 3", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "ok343", onlineTime: "0d 1h 0m 0s" }
]
},
{
seasonId: "2",
dates: {
start: new Date(new Date().getFullYear(), 0, 1),
end: new Date()
},
stats: [
{ name: "Humen", rank: "Overwatch Noob", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Random Rank 3", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Kas is cool", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Bremsspur", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "12", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "ok", onlineTime: "0d 1h 0m 0s" }
]
}
]
constructor() {
super();
this.entries = [
{ name: "Humen", rank: "Overwatch Noob", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Random Rank 3", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Kas is cool", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "Bremsspur", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "12", onlineTime: "0d 1h 0m 0s" },
{ name: "Humen", rank: "ok", onlineTime: "0d 1h 0m 0s" }
];
async getStats(seasonId: string): Promise<UserStatsResponse> {
return Promise.resolve(this.mocks[Number(seasonId) - 1])
}
async getStats(): Promise<TableEntry[]> {
return Promise.resolve(this.entries);
}
getStatsWithoutPromise(): TableEntry[] {
return this.entries;
getStatsWithoutPromise(seasonId: string): UserStatsResponse {
return this.mocks[Number(seasonId) - 1]
}
}

View File

@@ -0,0 +1,6 @@
import TableEntry from "./TableEntry";
import {SeasonDetailProperties} from "../components/SeasonDetail/SeasonDetail";
export default interface UserStatsResponse extends SeasonDetailProperties {
stats: TableEntry[]
}

View File

@@ -0,0 +1,3 @@
.MainPage {
}

View File

@@ -0,0 +1,96 @@
import React, {useEffect} from "react";
import UserStatsService from "../../services/UserStatsService";
import UserStatsMockService from "../../mock/UserStatsMockService";
import {findDOMNode} from "react-dom";
import Header from "../../components/Header/Header";
import UserList from "../../components/UserList/UserList";
import ErrorContainer from "../../components/ErrorContainer/ErrorContainer";
import Footer from "../../components/Footer/Footer";
import {RouteComponentProps} from "react-router/index";
import UserStatsResponse from "../../models/UserStatsResponse";
import {withRouter} from "react-router";
class MainPage extends React.Component<IMainPageProps, IMainPageState> {
private apiService: UserStatsService = new UserStatsService();
private mockService = new UserStatsMockService();
constructor(props: IMainPageProps) {
super(props)
console.log("this.props.match.params.id = " + this.props.match.params.id)
let seasonId = !this.props.match.params.id ? "1" : this.props.match.params.id
console.log("seasonId = " + seasonId)
this.state = {
id: seasonId,
error: undefined,
loading: false,
users: this.mockService.getStatsWithoutPromise("1")
}
}
componentWillMount() {
this.fetchData();
}
componentDidUpdate(prevProps: Readonly<IMainPageProps>) {
const element = findDOMNode(this);
if (element != null) {
window.scrollTo(0, 0);
}
// if(prevProps.match.params.id !== this.props.match.params.id) {
// this.setState({ id: this.props.match.params.id })
// }
}
private fetchData() {
this.setState({ loading: true })
// this.apiService.getStats(this.state.id)
// .then(data => this.setState({
// loading: false,
// users: data
// }))
this.mockService.getStats(this.state.id)
.then(data => this.setState({
loading: false,
users: data
}))
}
render() {
return (
<div className="MainPage">
<Header />
{
this.state.error && <ErrorContainer message={this.state.error} />
}
{
(this.state.loading)
? <UserList userStats={this.state.users} mocked={true} />
: <UserList userStats={this.state.users} mocked={!(!this.state.error)} />
}
{/* { this.state.error != null ? <p className="error-message"> { this.state.error } Please try again later!</p> : null} */}
<Footer />
</div>
)
}
}
export interface IMainPageProps extends RouteComponentProps<{ id: string }> {
}
interface IMainPageState {
id: string;
error?: string;
loading: boolean;
users: UserStatsResponse;
}
export default withRouter(MainPage)

View File

@@ -1,5 +1,5 @@
import TableEntry from "../models/TableEntry";
import RequestError from "../models/RequestError";
import UserStatsResponse from "../models/UserStatsResponse";
export default class UserStatsService {
@@ -13,7 +13,7 @@ export default class UserStatsService {
}
};
async getStats(): Promise<TableEntry[]> {
async getStats(seasonId: string): Promise<UserStatsResponse> {
return fetch(this.apiURL, this.requestInit)
.then(res => UserStatsService.checkResponse(res))
.then(data => data.json());