Skip to content

Commit

Permalink
Merge pull request #1180 from hexlet-codebattle/infinite-scroll
Browse files Browse the repository at this point in the history
Infinite scroll, v.2
  • Loading branch information
vtm9 committed Nov 26, 2022
2 parents 84dcfdc + 5e24192 commit ce475fc
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 38 deletions.
32 changes: 24 additions & 8 deletions services/app/assets/js/__tests__/LobbyWidget.test.jsx
Expand Up @@ -56,7 +56,19 @@ const tasks = [
{ name: 'task3 filtered', id: 3, tags: [''] },
];
const users = [{ name: 'user1', id: -4 }, { name: 'user2', id: -2 }];
axios.get.mockResolvedValue({ data: { tasks, users } });
const games = [];
const pageInfo = {
totalPages: 2,
nextPage: 1,
};
axios.get.mockResolvedValue({
data: {
tasks,
users,
games,
pageInfo,
},
});

jest.mock('react-select');
jest.mock('react-select/async');
Expand Down Expand Up @@ -124,13 +136,6 @@ const players = [{ id: -4 }, { id: -2 }];
const preloadedState = {
lobby: {
activeGames: [],
completedGames: [
{
id: -1,
level: 'elementary',
players,
},
],
loaded: true,
presenceList: players,
liveTournaments: [],
Expand All @@ -141,6 +146,17 @@ const preloadedState = {
opponentInfo: null,
},
},
completedGames: {
completedGames: [
{
id: -1,
level: 'elementary',
players,
},
],
nextPage: null,
totalPages: null,
},
user: {
currentUserId: 1,
users: { 1: { id: 1, is_admin: false } },
Expand Down
41 changes: 23 additions & 18 deletions services/app/assets/js/widgets/components/Game/CompletedGames.jsx
Expand Up @@ -38,7 +38,7 @@ const CompletedGamesRows = memo(({ games }) => (
</>
));

const CompletedGames = ({ games, loadNextPage = null }) => {
const CompletedGames = ({ games, loadNextPage = null, totalGames }) => {
const { nextPage, totalPages } = useSelector(state => state.completedGames);
const object = useMemo(() => ({ loading: false }), [nextPage]);
const dispatch = useDispatch();
Expand Down Expand Up @@ -70,23 +70,28 @@ const CompletedGames = ({ games, loadNextPage = null }) => {
}, [object]);

return (
<div ref={ref} className="table-responsive scroll" style={{ maxHeight: '600px' }}>
<table className="table table-sm table-striped border-gray border mb-0">
<thead>
<tr>
<th className="p-3 border-0">Level</th>
<th className="px-1 py-3 border-0 text-center" colSpan={2}>
Players
</th>
<th className="px-1 py-3 border-0">Date</th>
<th className="px-1 py-3 border-0">Actions</th>
</tr>
</thead>
<tbody>
<CompletedGamesRows {...{ games }} />
</tbody>
</table>
</div>
<>
<div ref={ref} className="table-responsive scroll" style={{ maxHeight: '600px' }}>
<table className="table table-sm table-striped border-gray border mb-0">
<thead>
<tr>
<th className="p-3 border-0">Level</th>
<th className="px-1 py-3 border-0 text-center" colSpan={2}>
Players
</th>
<th className="px-1 py-3 border-0">Date</th>
<th className="px-1 py-3 border-0">Actions</th>
</tr>
</thead>
<tbody>
<CompletedGamesRows {...{ games }} />
</tbody>
</table>
</div>
<div className="bg-white py-2 px-5 font-weight-bold border-gray border">
{`Total games: ${totalGames}`}
</div>
</>
);
};

Expand Down
12 changes: 10 additions & 2 deletions services/app/assets/js/widgets/containers/LobbyWidget.jsx
Expand Up @@ -31,6 +31,7 @@ import LobbyChat from './LobbyChat';
import levelRatio from '../config/levelRatio';
import PlayerLoading from '../components/PlayerLoading';
import hashLinkNames from '../config/hashLinkNames';
import { fetchCompletedGames, loadNextPage } from '../slices/completedGames';

const isActiveGame = game => [gameStateCodes.playing, gameStateCodes.waitingOpponent].includes(game.state);

Expand Down Expand Up @@ -401,6 +402,7 @@ const GameContainers = ({
completedGames,
liveTournaments,
completedTournaments,
totalGames,
}) => {
useEffect(() => {
if (!window.location.hash) {
Expand Down Expand Up @@ -481,7 +483,7 @@ const GameContainers = ({
role="tabpanel"
aria-labelledby="completedGames-tab"
>
<CompletedGames games={completedGames} />
<CompletedGames games={completedGames} loadNextPage={loadNextPage} totalGames={totalGames} />
</div>
</div>
</div>
Expand Down Expand Up @@ -531,14 +533,19 @@ const LobbyWidget = () => {
}
}, [currentUser, dispatch]);

useEffect(() => {
dispatch(fetchCompletedGames());
}, [dispatch]);

const {
loaded,
activeGames,
completedGames,
liveTournaments,
completedTournaments,
} = useSelector(selectors.lobbyDataSelector);

const { completedGames, totalGames } = useSelector(selectors.completedGamesData);

if (!loaded) {
return <Loading />;
}
Expand All @@ -553,6 +560,7 @@ const LobbyWidget = () => {
completedGames={completedGames}
liveTournaments={liveTournaments}
completedTournaments={completedTournaments}
totalGames={totalGames}
/>
<LobbyChat />
</div>
Expand Down
4 changes: 3 additions & 1 deletion services/app/assets/js/widgets/containers/UserProfile.jsx
Expand Up @@ -9,10 +9,11 @@ import { fetchCompletedGames, loadNextPage } from '../slices/completedGames';
import CompletedGames from '../components/Game/CompletedGames';
import Heatmap from './Heatmap';
import Loading from '../components/Loading';
import * as selectors from '../selectors';

const UserProfile = () => {
const [stats, setStats] = useState(null);
const completedGames = useSelector(state => state.completedGames.completedGames);
const { completedGames, totalGames } = useSelector(selectors.completedGamesData);

const dispatch = useDispatch();

Expand Down Expand Up @@ -120,6 +121,7 @@ const UserProfile = () => {
<CompletedGames
games={completedGames}
loadNextPage={loadNextPage}
totalGames={totalGames}
/>
</>
)}
Expand Down
2 changes: 2 additions & 0 deletions services/app/assets/js/widgets/selectors/index.js
Expand Up @@ -238,3 +238,5 @@ export const currentUserNameSelector = state => {
export const isModalShow = state => state.lobby.createGameModal.show;

export const modalSelector = state => state.lobby.createGameModal;

export const completedGamesData = state => state.completedGames;
26 changes: 21 additions & 5 deletions services/app/assets/js/widgets/slices/completedGames.js
@@ -1,12 +1,19 @@
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
import { camelizeKeys } from 'humps';
import _ from 'lodash';

import { actions as lobbyActions } from './lobby';

export const fetchCompletedGames = createAsyncThunk(
'completedGames/fetchCompletedGames',
async () => {
const userId = window.location.pathname.split('/').pop();
const response = await axios.get(`/api/v1/games/completed?user_id=${userId}&page_size=20`);
const userId = window.location.pathname.split('/').pop() || null;
const route = userId
? `/api/v1/games/completed?user_id=${userId}&page_size=20`
: '/api/v1/games/completed?page_size=20';

const response = await axios.get(route);

return camelizeKeys(response.data);
},
Expand All @@ -15,9 +22,12 @@ export const fetchCompletedGames = createAsyncThunk(
export const loadNextPage = createAsyncThunk(
'completedGames/loadNextPage',
async page => {
const userId = window.location.pathname.split('/').pop();
const userId = window.location.pathname.split('/').pop() || null;
const route = userId
? `/api/v1/games/completed?user_id=${userId}&page_size=20&page=${page}`
: `/api/v1/games/completed?page_size=20&page=${page}`;

const response = await axios.get(`/api/v1/games/completed?user_id=${userId}&page_size=20&page=${page}`);
const response = await axios.get(route);

return camelizeKeys(response.data);
},
Expand All @@ -29,6 +39,7 @@ const completedGames = createSlice({
completedGames: [],
nextPage: null,
totalPages: null,
totalGames: 0,
status: 'empty',
error: null,
},
Expand All @@ -43,6 +54,7 @@ const completedGames = createSlice({
state.completedGames = payload.games;
state.totalPages = payload.pageInfo.totalPages;
state.nextPage = payload.pageInfo.pageNumber + 1;
state.totalGames = payload.pageInfo.totalEntries;
},
[fetchCompletedGames.rejected]: (state, action) => {
state.status = 'rejected';
Expand All @@ -55,12 +67,16 @@ const completedGames = createSlice({
[loadNextPage.fulfilled]: (state, { payload }) => {
state.status = 'loaded';
state.nextPage += 1;
state.completedGames = state.completedGames.concat(payload.games);
state.completedGames = _.unionBy(state.completedGames, payload.games, 'id');
},
[loadNextPage.rejected]: (state, action) => {
state.status = 'rejected';
state.error = action.error;
},
[lobbyActions.removeGameLobby]: (state, { payload: { game } }) => {
state.completedGames = [game, ...state.completedGames];
state.totalGames += 1;
},
},
});

Expand Down
5 changes: 1 addition & 4 deletions services/app/assets/js/widgets/slices/lobby.js
Expand Up @@ -3,7 +3,6 @@ import _ from 'lodash';

const initialState = {
activeGames: [],
completedGames: null,
presenceList: [],
loaded: false,
newGame: { timeoutSeconds: null },
Expand All @@ -20,11 +19,10 @@ const lobby = createSlice({
reducers: {
initGameList: (
state,
{ payload: { activeGames, completedGames, tournaments } },
{ payload: { activeGames, tournaments } },
) => ({
...state,
activeGames,
completedGames,
liveTournaments: tournaments.filter(x => x.isLive),
completedTournaments: tournaments.filter(x => !x.isLive),
loaded: true,
Expand Down Expand Up @@ -61,7 +59,6 @@ const lobby = createSlice({
},
finishGame: (state, { payload: { game } }) => {
state.activeGames = _.reject(state.activeGames, { id: game.id });
state.completedGames = [game, ...state.completedGames];
},
showCreateGameModal: state => {
state.createGameModal.show = true;
Expand Down

0 comments on commit ce475fc

Please sign in to comment.