Skip to content

Commit 9ffc7b0

Browse files
committed
fix pagination logic
1 parent 7e60154 commit 9ffc7b0

4 files changed

Lines changed: 129 additions & 68 deletions

File tree

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,63 @@
11
import React from 'react';
2+
import { useSelector, useDispatch } from 'react-redux';
23
import moment from 'moment';
34
import ResultIcon from './ResultIcon';
45
import UserInfo from '../../containers/UserInfo';
56
import GameLevelBadge from '../GameLevelBadge';
67

7-
const CompletedGames = ({ games, onShowMoreButtonClick = null }) => (
8-
<div className="table-responsive">
9-
<table className="table table-sm table-striped border-gray border mb-0">
10-
<thead>
11-
<tr>
12-
<th className="p-3 border-0">Level</th>
13-
<th className="p-3 border-0 text-center" colSpan={2}>
14-
Players
15-
</th>
16-
<th className="p-3 border-0">Date</th>
17-
<th className="p-3 border-0">Actions</th>
18-
</tr>
19-
</thead>
20-
<tbody>
21-
{games.map(game => (
22-
<tr key={game.id}>
23-
<td className="p-3 align-middle text-nowrap">
24-
<GameLevelBadge level={game.level} />
25-
</td>
26-
<td className="p-3 align-middle text-nowrap cb-username-td text-truncate">
27-
<div className="d-flex align-items-center">
28-
<ResultIcon gameId={game.id} player1={game.players[0]} player2={game.players[1]} />
29-
<UserInfo user={game.players[0]} />
30-
</div>
31-
</td>
32-
<td className="p-3 align-middle text-nowrap cb-username-td text-truncate">
33-
<div className="d-flex align-items-center">
34-
<ResultIcon gameId={game.id} player1={game.players[1]} player2={game.players[0]} />
35-
<UserInfo user={game.players[1]} />
36-
</div>
37-
</td>
38-
<td className="p-3 align-middle text-nowrap">{moment.utc(game.finishsAt).local().format('MM.DD HH:mm')}</td>
39-
<td className="p-3 align-middle">
40-
<a type="button" className="btn btn-outline-orange btn-sm" href={`/games/${game.id}`}>
41-
Show
42-
</a>
43-
</td>
8+
const CompletedGames = ({ games, loadNextPage = null }) => {
9+
const { nextPage, totalPages } = useSelector(state => state.completedGames);
10+
const dispatch = useDispatch();
11+
12+
return (
13+
<div className="table-responsive">
14+
<table className="table table-sm table-striped border-gray border mb-0">
15+
<thead>
16+
<tr>
17+
<th className="p-3 border-0">Level</th>
18+
<th className="p-3 border-0 text-center" colSpan={2}>
19+
Players
20+
</th>
21+
<th className="p-3 border-0">Date</th>
22+
<th className="p-3 border-0">Actions</th>
4423
</tr>
24+
</thead>
25+
<tbody>
26+
{games.map(game => (
27+
<tr key={game.id}>
28+
<td className="p-3 align-middle text-nowrap">
29+
<GameLevelBadge level={game.level} />
30+
</td>
31+
<td className="p-3 align-middle text-nowrap cb-username-td text-truncate">
32+
<div className="d-flex align-items-center">
33+
<ResultIcon gameId={game.id} player1={game.players[0]} player2={game.players[1]} />
34+
<UserInfo user={game.players[0]} />
35+
</div>
36+
</td>
37+
<td className="p-3 align-middle text-nowrap cb-username-td text-truncate">
38+
<div className="d-flex align-items-center">
39+
<ResultIcon gameId={game.id} player1={game.players[1]} player2={game.players[0]} />
40+
<UserInfo user={game.players[1]} />
41+
</div>
42+
</td>
43+
<td className="p-3 align-middle text-nowrap">{moment.utc(game.finishsAt).local().format('MM.DD HH:mm')}</td>
44+
<td className="p-3 align-middle">
45+
<a type="button" className="btn btn-outline-orange btn-sm" href={`/games/${game.id}`}>
46+
Show
47+
</a>
48+
</td>
49+
</tr>
4550
))}
46-
</tbody>
47-
</table>
48-
{ onShowMoreButtonClick
51+
</tbody>
52+
</table>
53+
{ loadNextPage !== null && nextPage < totalPages
4954
? (
50-
<button type="button" className="mt-2 btn btn-light" onClick={() => onShowMoreButtonClick()}>
55+
<button type="button" className="mt-2 btn btn-light" onClick={() => dispatch(loadNextPage(nextPage))}>
5156
Show More
5257
</button>
5358
) : null}
54-
</div>
55-
);
59+
</div>
60+
);
61+
};
5662

5763
export default CompletedGames;

services/app/assets/js/widgets/containers/UserProfile.jsx

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { camelizeKeys } from 'humps';
2-
import { useDispatch } from 'react-redux';
2+
import { useDispatch, useSelector } from 'react-redux';
33
import React, { useState, useEffect } from 'react';
44
import axios from 'axios';
55

66
import { actions } from '../slices';
7+
import { fetchCompletedGames, loadNextPage } from '../slices/completedGames';
78
import CompletedGames from '../components/Game/CompletedGames';
89
import Heatmap from './Heatmap';
910
import Loading from '../components/Loading';
@@ -22,9 +23,7 @@ const getUserAvatarUrl = ({ githubId, discordId, discordAvatar }) => {
2223

2324
const UserProfile = () => {
2425
const [stats, setStats] = useState(null);
25-
const [completedGames, setCompletedGames] = useState([]);
26-
const [currentPage, setCurrenPage] = useState(1);
27-
const [totalPages, setTotalPages] = useState(1);
26+
const completedGames = useSelector(state => state.completedGames.completedGames);
2827

2928
const dispatch = useDispatch();
3029

@@ -41,24 +40,11 @@ const UserProfile = () => {
4140
});
4241
}, [dispatch]);
4342

44-
const dateParse = date => new Date(date).toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
45-
46-
const loadCompletedGames = () => {
47-
const userId = window.location.pathname.split('/').pop();
43+
useEffect(() => {
44+
dispatch(fetchCompletedGames());
45+
}, [dispatch]);
4846

49-
if (currentPage <= totalPages) {
50-
axios
51-
.get(`/api/v1/user/${userId}/completed_games?page_size=3&page=${currentPage}`)
52-
.then(response => {
53-
setTotalPages(response.data.page_info.total_pages);
54-
setCurrenPage(currentPage + 1);
55-
setCompletedGames(completedGames.concat(response.data.games));
56-
})
57-
.catch(error => {
58-
dispatch(actions.setError(error));
59-
});
60-
}
61-
};
47+
const dateParse = date => new Date(date).toLocaleString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
6248

6349
const renderAchievemnt = achievement => {
6450
if (achievement.includes('win_games_with')) {
@@ -142,9 +128,7 @@ const UserProfile = () => {
142128
<>
143129
<CompletedGames
144130
games={completedGames}
145-
onShowMoreButtonClick={
146-
currentPage <= totalPages ? loadCompletedGames : null
147-
}
131+
loadNextPage={loadNextPage}
148132
/>
149133
</>
150134
)}
@@ -183,7 +167,6 @@ const UserProfile = () => {
183167
role="tab"
184168
aria-controls="completedGames"
185169
aria-selected="false"
186-
onClick={loadCompletedGames}
187170
>
188171
Completed games
189172
</a>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2+
import axios from 'axios';
3+
4+
export const fetchCompletedGames = createAsyncThunk(
5+
'completedGames/fetchCompletedGames',
6+
async () => {
7+
const userId = window.location.pathname.split('/').pop();
8+
9+
const response = await axios.get(`/api/v1/user/${userId}/completed_games?page_size=7`);
10+
11+
return response.data;
12+
},
13+
);
14+
15+
export const loadNextPage = createAsyncThunk(
16+
'completedGames/loadNextPage',
17+
async page => {
18+
const userId = window.location.pathname.split('/').pop();
19+
20+
const response = await axios.get(`/api/v1/user/${userId}/completed_games?page_size=7&page=${page}`);
21+
22+
return response.data;
23+
},
24+
);
25+
26+
const completedGames = createSlice({
27+
name: 'completedGames',
28+
initialState: {
29+
completedGames: [],
30+
nextPage: null,
31+
totalPages: null,
32+
status: 'empty',
33+
error: null,
34+
},
35+
reducers: {},
36+
extraReducers: {
37+
[fetchCompletedGames.pending]: state => {
38+
state.status = 'loading';
39+
state.error = null;
40+
},
41+
[fetchCompletedGames.fulfilled]: (state, { payload }) => {
42+
state.status = 'loaded';
43+
state.completedGames = payload.games;
44+
state.totalPages = payload.page_info.total_pages;
45+
state.nextPage = payload.page_info.page_number + 1;
46+
},
47+
[fetchCompletedGames.rejected]: (state, action) => {
48+
state.status = 'rejected';
49+
state.error = action.error;
50+
},
51+
[loadNextPage.pending]: state => {
52+
state.status = 'loading';
53+
state.error = null;
54+
},
55+
[loadNextPage.fulfilled]: (state, { payload }) => {
56+
state.status = 'loaded';
57+
state.nextPage += 1;
58+
state.completedGames = state.completedGames.concat(payload.games);
59+
},
60+
[loadNextPage.rejected]: (state, action) => {
61+
state.status = 'rejected';
62+
state.error = action.error;
63+
},
64+
},
65+
});
66+
67+
const { actions, reducer } = completedGames;
68+
export { actions };
69+
export default reducer;

services/app/assets/js/widgets/slices/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import chat, { actions as chatActions } from './chat';
2+
import completedGames, { actions as completedGamesActions } from './completedGames';
23
import editor, { actions as editorActions } from './editor';
34
import storeLoaded, { actions as storeLoadedActions } from './store';
45
import usersInfo, { actions as usersInfoActions } from './usersInfo';
@@ -21,6 +22,7 @@ const setError = error => ({
2122
export const actions = {
2223
setError,
2324
...chatActions,
25+
...completedGamesActions,
2426
...editorActions,
2527
...gameActions,
2628
...storeLoadedActions,
@@ -47,6 +49,7 @@ export default {
4749
playbook,
4850
user,
4951
chat,
52+
completedGames,
5053
lobby,
5154
storeLoaded,
5255
executionOutput,

0 commit comments

Comments
 (0)