Skip to content

Commit c46a6bb

Browse files
committed
general fixes
1 parent ef65885 commit c46a6bb

8 files changed

Lines changed: 112 additions & 31 deletions

File tree

src/components/ChallengeProgress/ChallengeProgress.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export class ChallengeProgress extends Component {
212212
/>
213213
<span className="mr-pr-1" />
214214
{/* eslint-disable-next-line react/style-prop-object */}
215-
(<FormattedNumber style="percent" value={taskActions.available / taskActions.total} />){" "}
215+
(<FormattedNumber style="percent" value={taskActions.available / taskActions.total} />)
216216
<FormattedMessage {...messages.outOfTotal} values={{ totalCount: taskActions.total }} />
217217
</p>
218218
);
@@ -255,7 +255,7 @@ export class ChallengeProgress extends Component {
255255
const taskPriorityActions = this.props.taskMetricsByPriority;
256256

257257
if (!_isObject(taskActions)) {
258-
return null;
258+
return <FormattedMessage {...messages.noCompletionData} />;
259259
}
260260

261261
let calculatedSeconds = null;

src/components/ChallengeProgress/Messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export default defineMessages({
1919
defaultMessage: "Tasks Remaining: {taskCount, number}",
2020
},
2121

22+
noCompletionData: {
23+
id: "ChallengeProgress.noCompletionData",
24+
defaultMessage: "No Completion Data",
25+
},
26+
2227
outOfTotal: {
2328
id: "ChallengeProgress.tasks.totalCount",
2429
defaultMessage: " of {totalCount, number}",

src/components/LockedTasks/LockedTasksWidget.jsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { differenceInMinutes, formatDistanceToNow, parseISO } from "date-fns";
2-
import { useEffect, useState } from "react";
2+
import { useEffect, useState, useRef } from "react";
33
import { FormattedMessage } from "react-intl";
44
import { Link } from "react-router-dom";
55
import { fetchUsersLockedTasks } from "../../services/User/User";
@@ -23,13 +23,23 @@ const descriptor = {
2323
const LockedTasks = (props) => {
2424
const [lockedTasks, setLockedTasks] = useState([]);
2525
const [currentTime, setCurrentTime] = useState(new Date());
26+
const isMountedRef = useRef(true);
2627

2728
useEffect(() => {
29+
// Set isMountedRef to true when component mounts
30+
isMountedRef.current = true;
31+
2832
const intervalId = setInterval(() => {
29-
setCurrentTime(new Date());
33+
if (isMountedRef.current) {
34+
setCurrentTime(new Date());
35+
}
3036
}, 60000); // Update every minute
3137

32-
return () => clearInterval(intervalId);
38+
// Cleanup function
39+
return () => {
40+
isMountedRef.current = false;
41+
clearInterval(intervalId);
42+
};
3343
}, []);
3444

3545
const calculateElapsedTime = (startDate) => {
@@ -53,9 +63,15 @@ const LockedTasks = (props) => {
5363
};
5464

5565
const fetchLockedTasks = async () => {
56-
if (props.user) {
57-
const tasks = await fetchUsersLockedTasks(props.user.id);
58-
setLockedTasks(tasks);
66+
if (props.user && isMountedRef.current) {
67+
try {
68+
const tasks = await fetchUsersLockedTasks(props.user.id);
69+
if (isMountedRef.current) {
70+
setLockedTasks(tasks);
71+
}
72+
} catch (error) {
73+
console.error("Failed to fetch locked tasks:", error);
74+
}
5975
}
6076
};
6177

@@ -114,9 +130,11 @@ const LockedTasks = (props) => {
114130
<button
115131
onClick={() => {
116132
props.unlockTask(task);
117-
setLockedTasks((prevTasks) =>
118-
prevTasks.filter((prevTask) => prevTask.id !== task.id),
119-
);
133+
if (isMountedRef.current) {
134+
setLockedTasks((prevTasks) =>
135+
prevTasks.filter((prevTask) => prevTask.id !== task.id),
136+
);
137+
}
120138
}}
121139
className="mr-button mr-button--xsmall mr-ml-3"
122140
>

src/components/WidgetGrid/WidgetGrid.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ export class WidgetGrid extends Component {
142142
// In alternate workspaces, ensure widgets take the full width
143143
if (altWorkspaceType) {
144144
widgetLayout.w = 1; // Force full width for single column layout
145+
// Also ensure minW doesn't exceed the forced width to prevent PropTypes warnings
146+
if (widgetLayout.minW && widgetLayout.minW > 1) {
147+
widgetLayout.minW = 1;
148+
}
145149
}
146150

147151
// Hide conditional widgets that shouldn't be shown

src/pages/Metrics/Messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export default defineMessages({
5959
defaultMessage: "Top Challenges",
6060
},
6161

62+
noChallengesCompleted: {
63+
id: "Metrics.leaderboard.noChallenges.label",
64+
defaultMessage: "No challenges completed yet",
65+
},
66+
6267
approvedReview: {
6368
id: "Metrics.reviewStats.approved.label",
6469
defaultMessage: "Reviewed tasks that passed",

src/pages/Metrics/Metrics.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class Metrics extends Component {
113113
<Fragment>
114114
<div className="mr-mb-4 md:mr-mb-8 md:mr-grid md:mr-grid-gap-8 md:mr-grid-columns-2">
115115
<div className="mr-mb-4 md:mr-mb-0 mr-p-8 mr-bg-blue mr-rounded mr-shadow mr-flex mr-items-center">
116-
{!this.props.taskMetrics ? (
116+
{!totalTasks && totalTasks !== 0 ? (
117117
<div className="mr-flex-grow mr-text-center">
118118
<BusySpinner />
119119
</div>

src/pages/Metrics/blocks/LeaderboardStats.jsx

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,59 @@ import _map from "lodash/map";
22
import { Component } from "react";
33
import { FormattedMessage } from "react-intl";
44
import QuickWidget from "../../../components/QuickWidget/QuickWidget";
5+
import BusySpinner from "../../../components/BusySpinner/BusySpinner";
56
import messages from "../Messages";
67

78
export default class LeaderboardStats extends Component {
89
render() {
9-
if (!this.props.leaderboardMetrics) {
10+
// Check if user has opted out of leaderboard
11+
const userOptedOut =
12+
this.props.targetUser?.settings?.leaderboardOptOut &&
13+
this.props.targetUser?.id !== this.props.currentUser?.userId;
14+
15+
// If user opted out, don't show the leaderboard widget at all
16+
if (userOptedOut) {
1017
return null;
1118
}
1219

20+
// Show loading spinner only if we're explicitly in a loading state
21+
if (this.props.loading === true) {
22+
return (
23+
<QuickWidget
24+
{...this.props}
25+
className="mr-card-widget mr-card-widget--padded mr-mb-4"
26+
widgetTitle={<FormattedMessage {...messages.leaderboardTitle} />}
27+
noMain
28+
permanent
29+
>
30+
<div className="mr-flex mr-justify-center mr-py-8">
31+
<BusySpinner />
32+
</div>
33+
</QuickWidget>
34+
);
35+
}
36+
37+
// If leaderboardMetrics is null or undefined, show "no data" state
38+
if (!this.props.leaderboardMetrics) {
39+
return (
40+
<QuickWidget
41+
{...this.props}
42+
className="mr-card-widget mr-card-widget--padded mr-mb-4"
43+
widgetTitle={<FormattedMessage {...messages.leaderboardTitle} />}
44+
noMain
45+
permanent
46+
>
47+
<div className="mr-text-center mr-py-8 mr-text-grey-light">
48+
<FormattedMessage {...messages.noChallengesCompleted} />
49+
</div>
50+
</QuickWidget>
51+
);
52+
}
53+
54+
// At this point we have leaderboard data, so render it
55+
const leaderboardMetrics = this.props.leaderboardMetrics;
56+
const topChallenges = leaderboardMetrics.topChallenges || [];
57+
1358
return (
1459
<QuickWidget
1560
{...this.props}
@@ -21,13 +66,13 @@ export default class LeaderboardStats extends Component {
2166
<ul className="mr-list-reset mr-mt-3 mr-mb-6">
2267
<li className="mr-flex mr-items-center">
2368
<strong className="mr-font-light mr-text-4xl mr-min-w-28 mr-text-yellow">
24-
{this.props.leaderboardMetrics.rank}
69+
{leaderboardMetrics.rank || "N/A"}
2570
</strong>
2671
<FormattedMessage {...messages.globalRank} />
2772
</li>
2873
<li className="mr-flex mr-items-center">
2974
<strong className="mr-font-light mr-text-4xl mr-min-w-28 mr-text-yellow">
30-
{this.props.leaderboardMetrics.score}
75+
{leaderboardMetrics.score || "N/A"}
3176
</strong>
3277
<FormattedMessage {...messages.totalPoints} />
3378
</li>
@@ -36,13 +81,19 @@ export default class LeaderboardStats extends Component {
3681
<FormattedMessage {...messages.topChallenges} />
3782
</h3>
3883
<ol className="mr-list-reset mr-links-green-lighter">
39-
{_map(this.props.leaderboardMetrics.topChallenges.slice(0, 4), (challenge, index) => {
40-
return (
41-
<li key={index} className="mr-mt-3">
42-
<a href={"/browse/challenges/" + challenge.id}>{challenge.name}</a>
43-
</li>
44-
);
45-
})}
84+
{topChallenges.length > 0 ? (
85+
_map(topChallenges.slice(0, 4), (challenge, index) => {
86+
return (
87+
<li key={index} className="mr-mt-3">
88+
<a href={"/browse/challenges/" + challenge.id}>{challenge.name}</a>
89+
</li>
90+
);
91+
})
92+
) : (
93+
<li className="mr-mt-3 mr-text-grey-light">
94+
<FormattedMessage {...messages.noChallengesCompleted} />
95+
</li>
96+
)}
4697
</ol>
4798
</QuickWidget>
4899
);

src/pages/Metrics/blocks/TaskStats.jsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ export default class TaskStats extends Component {
2929
noMain
3030
permanent
3131
>
32-
{this.props.taskMetrics && (
33-
<ChallengeProgress
34-
className="mr-mt-4 mr-mb-12"
35-
listClassName="mr-mt-3"
36-
taskMetrics={this.props.taskMetrics}
37-
prominentCounts
38-
noteAvgExcludesSkip
39-
/>
40-
)}
32+
<ChallengeProgress
33+
className="mr-mt-4 mr-mb-12"
34+
listClassName="mr-mt-3"
35+
taskMetrics={this.props.taskMetrics || null}
36+
prominentCounts
37+
noteAvgExcludesSkip
38+
/>
4139
</QuickWidget>
4240
);
4341
}

0 commit comments

Comments
 (0)