Skip to content

Commit 23b9e34

Browse files
committed
Add loading indicator
1 parent 4020d71 commit 23b9e34

File tree

5 files changed

+161
-39
lines changed

5 files changed

+161
-39
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from "react";
2+
import "./styles.module.scss";
3+
4+
export default function ChallengeLoading() {
5+
return (
6+
<div styleName="challenge-loading">
7+
<div styleName="track placeholder-template"></div>
8+
<div styleName="main">
9+
<div styleName="title placeholder-template"></div>
10+
<div styleName="info placeholder-template"></div>
11+
<div styleName="footer placeholder-template"></div>
12+
</div>
13+
<div>
14+
<div styleName="prize placeholder-template"></div>
15+
<div styleName="prize-nominal placeholder-template"></div>
16+
</div>
17+
</div>
18+
)
19+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@import "~styles/mixins";
2+
3+
@keyframes placeholderAnim {
4+
0% {
5+
background-position: -$base-unit * 94 0;
6+
}
7+
8+
100% {
9+
background-position: $base-unit * 94 0;
10+
}
11+
}
12+
13+
.animated-placeholder-template {
14+
animation-duration: 1.25s;
15+
animation-fill-mode: forwards;
16+
animation-iteration-count: infinite;
17+
animation-name: placeholderAnim;
18+
animation-timing-function: linear;
19+
background: $tc-gray-neutral-dark;
20+
background: linear-gradient(to right, $tc-gray-neutral-dark 8%, $tc-gray-10 18%, $tc-gray-neutral-dark 33%);
21+
background-size: $base-unit * 256 $base-unit * 20;
22+
position: relative;
23+
}
24+
25+
.placeholder-template {
26+
border-radius: $corner-radius;
27+
28+
@extend .animated-placeholder-template;
29+
}
30+
31+
.challenge-loading {
32+
height: 126px;
33+
padding: 16px;
34+
margin: 0 16px;
35+
display: flex;
36+
border-bottom: 1px solid #E9E9E9;
37+
border-top: 1px solid #E9E9E9;
38+
39+
> div {
40+
margin-right: 16px;
41+
}
42+
43+
&:nth-child(even) {
44+
background: #FBFBFB;
45+
}
46+
.track {
47+
flex: 1 0 46px;
48+
width: 46px;
49+
height: 44px;
50+
}
51+
52+
.main {
53+
flex: 1 1 70%;
54+
}
55+
56+
.title {
57+
height: 22px;
58+
width: 100px;
59+
margin-bottom: 8px;
60+
}
61+
62+
.info {
63+
height: 18px;
64+
width: 200px;
65+
margin-bottom: 16px;
66+
}
67+
68+
.footer {
69+
height: 18px;
70+
width: 70%;
71+
}
72+
73+
.prize {
74+
height: 18px;
75+
width: 60px;
76+
margin-bottom: 8px;
77+
margin-right: 40px;
78+
}
79+
80+
.prize-nominal {
81+
height: 42px;
82+
width: 40px;
83+
}
84+
}

src/containers/Challenges/Listing/index.jsx

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ import ChallengeItem from "./ChallengeItem";
99
import TextInput from "../../../components/TextInput";
1010
import Dropdown from "../../../components/Dropdown";
1111
import DateRangePicker from "../../../components/DateRangePicker";
12+
import ChallengeLoading from "../../../components/challenge-listing/ChallengeLoading";
1213
import * as utils from "../../../utils";
14+
1315
import * as constants from "../../../constants";
1416
import IconSearch from "../../../assets/icons/search.svg";
15-
1617
import "./styles.scss";
1718

1819
const Listing = ({
1920
challenges,
21+
loadingChallenges,
2022
search,
2123
page,
2224
perPage,
@@ -104,45 +106,50 @@ const Listing = ({
104106
</div>
105107
</div>
106108
</Panel.Header>
107-
{challenges.length ?
108-
<Panel.Body>
109-
{challenges.map((challenge, index) => (
110-
<div key={challenge.id} styleName={index % 2 === 0 ? "even" : "odd"}>
111-
<ChallengeItem
112-
challenge={challenge}
113-
onClickTag={(tag) => {
114-
const filterChange = { tags: [tag] };
115-
updateFilter(filterChange);
116-
}}
117-
onClickTrack={(track) => {
118-
const filterChange = { tracks: [track] };
119-
updateFilter(filterChange);
120-
}}
121-
isLoggedIn={isLoggedIn}
122-
/>
123-
</div>
124-
))}
125-
<div styleName="pagination">
126-
<Pagination
127-
length={total}
128-
pageSize={perPage}
129-
pageIndex={utils.pagination.pageToPageIndex(page)}
130-
onChange={(event) => {
131-
const filterChange = {
132-
page: utils.pagination.pageIndexToPage(event.pageIndex),
133-
perPage: event.pageSize,
134-
};
135-
updateFilter(filterChange);
136-
}}
137-
/>
138-
</div>
139-
</Panel.Body> : <ChallengeError /> }
109+
{
110+
loadingChallenges ?
111+
_.times(3, () => <ChallengeLoading />) :
112+
challenges.length ?
113+
<Panel.Body>
114+
{challenges.map((challenge, index) => (
115+
<div key={challenge.id} styleName={index % 2 === 0 ? "even" : "odd"}>
116+
<ChallengeItem
117+
challenge={challenge}
118+
onClickTag={(tag) => {
119+
const filterChange = { tags: [tag] };
120+
updateFilter(filterChange);
121+
}}
122+
onClickTrack={(track) => {
123+
const filterChange = { tracks: [track] };
124+
updateFilter(filterChange);
125+
}}
126+
isLoggedIn={isLoggedIn}
127+
/>
128+
</div>
129+
))}
130+
<div styleName="pagination">
131+
<Pagination
132+
length={total}
133+
pageSize={perPage}
134+
pageIndex={utils.pagination.pageToPageIndex(page)}
135+
onChange={(event) => {
136+
const filterChange = {
137+
page: utils.pagination.pageIndexToPage(event.pageIndex),
138+
perPage: event.pageSize,
139+
};
140+
updateFilter(filterChange);
141+
}}
142+
/>
143+
</div>
144+
</Panel.Body> : <ChallengeError />
145+
}
140146
</Panel>
141147
);
142148
};
143149

144150
Listing.propTypes = {
145151
challenges: PT.arrayOf(PT.shape()),
152+
loadingChallenges: PT.bool,
146153
search: PT.string,
147154
page: PT.number,
148155
perPage: PT.number,

src/containers/Challenges/index.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { connect } from "react-redux";
44
import Listing from "./Listing";
55
import actions from "../../actions";
66
// import ChallengeRecommendedError from "./Listing/errors/ChallengeRecommendedError";
7+
import LoadingIndicator from "../../components/LoadingIndicator";
8+
import Panel from "../../components/Panel";
79
import * as constants from "../../constants";
810
import IconListView from "../../assets/icons/list-view.svg";
911
import IconCardView from "../../assets/icons/card-view.svg";
1012
import { Banner } from "@topcoder/micro-frontends-earn-app";
11-
import * as utils from "../../utils";
1213

14+
import * as utils from "../../utils";
1315
import "./styles.scss";
1416

1517
const Challenges = ({
@@ -28,6 +30,7 @@ const Challenges = ({
2830
initialized,
2931
updateQuery,
3032
tags,
33+
loadingChallenges,
3134
}) => {
3235
const [isLoggedIn, setIsLoggedIn] = useState(null);
3336

@@ -69,11 +72,12 @@ const Challenges = ({
6972
</button>
7073
</span>
7174
</h1>
72-
{initialized && (
75+
{initialized ? (
7376
<>
7477
{/*noRecommendedChallenges && <ChallengeRecommendedError />*/}
7578
<Listing
7679
challenges={challenges}
80+
loadingChallenges={loadingChallenges}
7781
search={search}
7882
page={page}
7983
perPage={perPage}
@@ -91,6 +95,12 @@ const Challenges = ({
9195
isLoggedIn={isLoggedIn}
9296
/>
9397
</>
98+
) : (
99+
<Panel>
100+
<Panel.Body>
101+
<LoadingIndicator />
102+
</Panel.Body>
103+
</Panel>
94104
)}
95105
</div>
96106
);
@@ -112,6 +122,7 @@ Challenges.propTypes = {
112122
initialized: PT.bool,
113123
updateQuery: PT.func,
114124
tags: PT.arrayOf(PT.string),
125+
loadingChallenges: PT.bool,
115126
};
116127

117128
const mapStateToProps = (state) => ({
@@ -129,6 +140,7 @@ const mapStateToProps = (state) => ({
129140
recommendedChallenges: state.challenges.recommendedChallenges,
130141
initialized: state.challenges.initialized,
131142
tags: state.filter.challenge.tags,
143+
loadingChallenges: state.challenges.loadingChallenges
132144
});
133145

134146
const mapDispatchToProps = {

src/reducers/challenges.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { handleActions } from "redux-actions";
22

33
const defaultState = {
4-
loadingChallenges: false,
4+
loadingChallenges: true,
55
loadingChallengesError: null,
66
challenges: [],
77
total: 0,
@@ -13,7 +13,7 @@ const defaultState = {
1313
};
1414

1515
function onGetChallengesInit(state) {
16-
return { ...state, loadingChallenges: true, loadingChallengesError: null };
16+
return { ...state, challenges: [], loadingChallenges: true, loadingChallengesError: null };
1717
}
1818

1919
function onGetChallengesDone(state, { payload }) {
@@ -60,7 +60,7 @@ function onGetChallengesDone(state, { payload }) {
6060

6161
export default handleActions(
6262
{
63-
GET_CHALLENGE_INIT: onGetChallengesInit,
63+
GET_CHALLENGES_INIT: onGetChallengesInit,
6464
GET_CHALLENGES_DONE: onGetChallengesDone,
6565
// GET_CHALLENGES_FAILURE: onGetChallengesFailure,
6666
},

0 commit comments

Comments
 (0)