@selfcommunity/react-ui 1.2.14-alpha.0 → 1.2.14-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/components/LeaderboardWidget/LeaderboardWidget.d.ts +2 -1
- package/lib/cjs/components/LeaderboardWidget/LeaderboardWidget.js +35 -4
- package/lib/esm/components/LeaderboardWidget/LeaderboardWidget.d.ts +2 -1
- package/lib/esm/components/LeaderboardWidget/LeaderboardWidget.js +37 -6
- package/lib/umd/react-ui.js +1 -1
- package/package.json +3 -3
|
@@ -34,7 +34,8 @@ export interface LeaderboardWidgetProps extends WidgetProps {
|
|
|
34
34
|
*
|
|
35
35
|
*
|
|
36
36
|
* This component renders a widget showing the top 3 users of the leaderboard for the given period (podium) and, optionally, the general ranking list.
|
|
37
|
-
* Leaderboard entries
|
|
37
|
+
* Leaderboard entries can be provided via the `entries` prop; if `mode` is `full` and no `entries` are provided, the component fetches the general
|
|
38
|
+
* ranking on its own.
|
|
38
39
|
* The widget is hidden if the leaderboard feature is disabled or no podium data is available.
|
|
39
40
|
|
|
40
41
|
#### Import
|
|
@@ -11,8 +11,12 @@ const system_1 = require("@mui/system");
|
|
|
11
11
|
const constants_1 = require("./constants");
|
|
12
12
|
const crown_1 = tslib_1.__importDefault(require("../../assets/leaderboard/crown"));
|
|
13
13
|
const react_core_1 = require("@selfcommunity/react-core");
|
|
14
|
+
const api_services_1 = require("@selfcommunity/api-services");
|
|
15
|
+
const utils_1 = require("@selfcommunity/utils");
|
|
16
|
+
const Errors_1 = require("../../constants/Errors");
|
|
14
17
|
const HiddenPlaceholder_1 = tslib_1.__importDefault(require("../../shared/HiddenPlaceholder"));
|
|
15
18
|
const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
|
|
19
|
+
const RANKING_LIMIT = 5;
|
|
16
20
|
const classes = {
|
|
17
21
|
root: `${constants_1.PREFIX}-root`,
|
|
18
22
|
podiumHeader: `${constants_1.PREFIX}-podium-header`,
|
|
@@ -45,7 +49,8 @@ const Root = (0, material_1.styled)(Widget_1.default, {
|
|
|
45
49
|
*
|
|
46
50
|
*
|
|
47
51
|
* This component renders a widget showing the top 3 users of the leaderboard for the given period (podium) and, optionally, the general ranking list.
|
|
48
|
-
* Leaderboard entries
|
|
52
|
+
* Leaderboard entries can be provided via the `entries` prop; if `mode` is `full` and no `entries` are provided, the component fetches the general
|
|
53
|
+
* ranking on its own.
|
|
49
54
|
* The widget is hidden if the leaderboard feature is disabled or no podium data is available.
|
|
50
55
|
|
|
51
56
|
#### Import
|
|
@@ -92,11 +97,37 @@ function LeaderboardWidget(inProps) {
|
|
|
92
97
|
props: inProps,
|
|
93
98
|
name: constants_1.PREFIX
|
|
94
99
|
});
|
|
95
|
-
const { className, mode = 'full', period = 'month', entries
|
|
100
|
+
const { className, mode = 'full', period = 'month', entries: entriesProp, isLoading: isLoadingProp = true, onSeeMoreClick } = props, rest = tslib_1.__rest(props, ["className", "mode", "period", "entries", "isLoading", "onSeeMoreClick"]);
|
|
96
101
|
// CONTEXT
|
|
97
102
|
const scUserContext = (0, react_1.useContext)(react_core_1.SCUserContext);
|
|
98
103
|
// PREFERENCES
|
|
99
104
|
const leaderboardsEnabled = (0, react_core_1.useSCPreferenceEnabled)(react_core_1.SCPreferences.CONFIGURATIONS_LEADERBOARDS_ENABLED);
|
|
105
|
+
// Self-fetches the leaderboard data when running in `full` mode and no entries are provided by the parent
|
|
106
|
+
const shouldFetch = mode === 'full' && !entriesProp;
|
|
107
|
+
const [fetchedEntries, setFetchedEntries] = (0, react_1.useState)([]);
|
|
108
|
+
const [isFetching, setIsFetching] = (0, react_1.useState)(shouldFetch);
|
|
109
|
+
const isMountedRef = (0, react_core_1.useIsComponentMountedRef)();
|
|
110
|
+
(0, react_1.useEffect)(() => {
|
|
111
|
+
if (!shouldFetch) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setIsFetching(true);
|
|
115
|
+
api_services_1.ScoreService.getLeaderboards({ limit: RANKING_LIMIT })
|
|
116
|
+
.then((data) => {
|
|
117
|
+
if (isMountedRef.current) {
|
|
118
|
+
setFetchedEntries(data.results);
|
|
119
|
+
setIsFetching(false);
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
|
|
124
|
+
if (isMountedRef.current) {
|
|
125
|
+
setIsFetching(false);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}, [shouldFetch, period]);
|
|
129
|
+
const entries = shouldFetch ? fetchedEntries : entriesProp || [];
|
|
130
|
+
const isLoading = shouldFetch ? isFetching : isLoadingProp;
|
|
100
131
|
/**
|
|
101
132
|
* Renders nothing if the leaderboard feature is disabled
|
|
102
133
|
*/
|
|
@@ -116,8 +147,8 @@ function LeaderboardWidget(inProps) {
|
|
|
116
147
|
if (!podium.length) {
|
|
117
148
|
return (0, jsx_runtime_1.jsx)(HiddenPlaceholder_1.default, {});
|
|
118
149
|
}
|
|
119
|
-
return ((0, jsx_runtime_1.jsx)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest, { children: (0, jsx_runtime_1.jsxs)(material_1.CardContent, { children: [(0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.podiumHeader }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h4", className: classes.podiumTitle }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.podium.title", defaultMessage: "ui.leaderboardWidget.podium.title", values: { period } }) })) })), (0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "center", alignItems: "flex-end", className: classes.podium }, { children: podium.map((entry) => ((0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ alignItems: "center", className: (0, classnames_1.default)(classes.podiumItem, `${classes.podiumItem}-${entry.position}`) }, { children: [entry.position === 1 && ((0, jsx_runtime_1.jsx)(material_1.Box, { component: "span", className: classes.podiumCrown, style: { maskImage: `url(${crown_1.default})`, WebkitMaskImage: `url(${crown_1.default})` } })), (0, jsx_runtime_1.
|
|
120
|
-
const isMe = Boolean(scUserContext.user) && entry.user.id === scUserContext.user.id;
|
|
150
|
+
return ((0, jsx_runtime_1.jsx)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest, { children: (0, jsx_runtime_1.jsxs)(material_1.CardContent, { children: [(0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.podiumHeader }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h4", className: classes.podiumTitle }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.podium.title", defaultMessage: "ui.leaderboardWidget.podium.title", values: { period } }) })) })), (0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "center", alignItems: "flex-end", className: classes.podium }, { children: podium.map((entry) => ((0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ alignItems: "center", className: (0, classnames_1.default)(classes.podiumItem, `${classes.podiumItem}-${entry.position}`) }, { children: [(0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ className: classes.podiumAvatarWrap }, { children: [entry.position === 1 && ((0, jsx_runtime_1.jsx)(material_1.Box, { component: "span", className: classes.podiumCrown, style: { maskImage: `url(${crown_1.default})`, WebkitMaskImage: `url(${crown_1.default})` } })), (0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: entry.user.username, src: entry.user.avatar, className: classes.podiumAvatar }), (0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ component: "span", className: classes.podiumBadge }, { children: entry.position }))] })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", fontWeight: 600, className: classes.podiumName }, { children: entry.user.real_name || entry.user.username })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "caption", className: classes.podiumScore }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.podium.score", defaultMessage: "ui.leaderboardWidget.podium.score", values: { score: entry.total_score } }) }))] }), entry.user.id))) })), mode === 'full' && entries.length > 0 && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.rankingHeader }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h4", className: classes.rankingTitle }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.ranking.title", defaultMessage: "ui.leaderboardWidget.ranking.title" }) })) })), (0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ spacing: 1, className: classes.rankingList }, { children: entries.map((entry) => {
|
|
151
|
+
const isMe = Boolean(scUserContext.user) && entry.user.id === scUserContext.user.id && entry.position <= RANKING_LIMIT;
|
|
121
152
|
return ((0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", className: (0, classnames_1.default)(classes.rankingItem, { [classes.rankingItemActive]: isMe }) }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", className: classes.rankingPosition }, { children: entry.position })), (0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: entry.user.username, src: entry.user.avatar, className: classes.rankingAvatar }), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", fontWeight: isMe ? 600 : 400, className: classes.rankingName }, { children: isMe ? ((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.ranking.you", defaultMessage: "ui.leaderboardWidget.ranking.you" })) : (entry.user.real_name || entry.user.username) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", className: classes.rankingScore }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.ranking.score", defaultMessage: "ui.leaderboardWidget.ranking.score", values: { score: entry.total_score } }) }))] }), entry.user.id));
|
|
122
153
|
}) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ component: "span", role: "button", tabIndex: 0, onClick: onSeeMoreClick, className: classes.seeMore }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardWidget.ranking.seeMore", defaultMessage: "ui.leaderboardWidget.ranking.seeMore" }) }))] }))] }) })));
|
|
123
154
|
}
|
|
@@ -34,7 +34,8 @@ export interface LeaderboardWidgetProps extends WidgetProps {
|
|
|
34
34
|
*
|
|
35
35
|
*
|
|
36
36
|
* This component renders a widget showing the top 3 users of the leaderboard for the given period (podium) and, optionally, the general ranking list.
|
|
37
|
-
* Leaderboard entries
|
|
37
|
+
* Leaderboard entries can be provided via the `entries` prop; if `mode` is `full` and no `entries` are provided, the component fetches the general
|
|
38
|
+
* ranking on its own.
|
|
38
39
|
* The widget is hidden if the leaderboard feature is disabled or no podium data is available.
|
|
39
40
|
|
|
40
41
|
#### Import
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __rest } from "tslib";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useContext } from 'react';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
4
4
|
import { Avatar, Box, CardContent, Stack, Typography, styled } from '@mui/material';
|
|
5
5
|
import { FormattedMessage } from 'react-intl';
|
|
6
6
|
import classNames from 'classnames';
|
|
@@ -8,9 +8,13 @@ import Widget from '../Widget';
|
|
|
8
8
|
import { useThemeProps } from '@mui/system';
|
|
9
9
|
import { PREFIX } from './constants';
|
|
10
10
|
import crownIcon from '../../assets/leaderboard/crown';
|
|
11
|
-
import { SCPreferences, SCUserContext, useSCPreferenceEnabled } from '@selfcommunity/react-core';
|
|
11
|
+
import { SCPreferences, SCUserContext, useIsComponentMountedRef, useSCPreferenceEnabled } from '@selfcommunity/react-core';
|
|
12
|
+
import { ScoreService } from '@selfcommunity/api-services';
|
|
13
|
+
import { Logger } from '@selfcommunity/utils';
|
|
14
|
+
import { SCOPE_SC_UI } from '../../constants/Errors';
|
|
12
15
|
import HiddenPlaceholder from '../../shared/HiddenPlaceholder';
|
|
13
16
|
import LeaderboardWidgetSkeleton from './Skeleton';
|
|
17
|
+
const RANKING_LIMIT = 5;
|
|
14
18
|
const classes = {
|
|
15
19
|
root: `${PREFIX}-root`,
|
|
16
20
|
podiumHeader: `${PREFIX}-podium-header`,
|
|
@@ -43,7 +47,8 @@ const Root = styled(Widget, {
|
|
|
43
47
|
*
|
|
44
48
|
*
|
|
45
49
|
* This component renders a widget showing the top 3 users of the leaderboard for the given period (podium) and, optionally, the general ranking list.
|
|
46
|
-
* Leaderboard entries
|
|
50
|
+
* Leaderboard entries can be provided via the `entries` prop; if `mode` is `full` and no `entries` are provided, the component fetches the general
|
|
51
|
+
* ranking on its own.
|
|
47
52
|
* The widget is hidden if the leaderboard feature is disabled or no podium data is available.
|
|
48
53
|
|
|
49
54
|
#### Import
|
|
@@ -90,11 +95,37 @@ export default function LeaderboardWidget(inProps) {
|
|
|
90
95
|
props: inProps,
|
|
91
96
|
name: PREFIX
|
|
92
97
|
});
|
|
93
|
-
const { className, mode = 'full', period = 'month', entries
|
|
98
|
+
const { className, mode = 'full', period = 'month', entries: entriesProp, isLoading: isLoadingProp = true, onSeeMoreClick } = props, rest = __rest(props, ["className", "mode", "period", "entries", "isLoading", "onSeeMoreClick"]);
|
|
94
99
|
// CONTEXT
|
|
95
100
|
const scUserContext = useContext(SCUserContext);
|
|
96
101
|
// PREFERENCES
|
|
97
102
|
const leaderboardsEnabled = useSCPreferenceEnabled(SCPreferences.CONFIGURATIONS_LEADERBOARDS_ENABLED);
|
|
103
|
+
// Self-fetches the leaderboard data when running in `full` mode and no entries are provided by the parent
|
|
104
|
+
const shouldFetch = mode === 'full' && !entriesProp;
|
|
105
|
+
const [fetchedEntries, setFetchedEntries] = useState([]);
|
|
106
|
+
const [isFetching, setIsFetching] = useState(shouldFetch);
|
|
107
|
+
const isMountedRef = useIsComponentMountedRef();
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!shouldFetch) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setIsFetching(true);
|
|
113
|
+
ScoreService.getLeaderboards({ limit: RANKING_LIMIT })
|
|
114
|
+
.then((data) => {
|
|
115
|
+
if (isMountedRef.current) {
|
|
116
|
+
setFetchedEntries(data.results);
|
|
117
|
+
setIsFetching(false);
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.catch((error) => {
|
|
121
|
+
Logger.error(SCOPE_SC_UI, error);
|
|
122
|
+
if (isMountedRef.current) {
|
|
123
|
+
setIsFetching(false);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}, [shouldFetch, period]);
|
|
127
|
+
const entries = shouldFetch ? fetchedEntries : entriesProp || [];
|
|
128
|
+
const isLoading = shouldFetch ? isFetching : isLoadingProp;
|
|
98
129
|
/**
|
|
99
130
|
* Renders nothing if the leaderboard feature is disabled
|
|
100
131
|
*/
|
|
@@ -114,8 +145,8 @@ export default function LeaderboardWidget(inProps) {
|
|
|
114
145
|
if (!podium.length) {
|
|
115
146
|
return _jsx(HiddenPlaceholder, {});
|
|
116
147
|
}
|
|
117
|
-
return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(CardContent, { children: [_jsx(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.podiumHeader }, { children: _jsx(Typography, Object.assign({ variant: "h4", className: classes.podiumTitle }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.podium.title", defaultMessage: "ui.leaderboardWidget.podium.title", values: { period } }) })) })), _jsx(Stack, Object.assign({ direction: "row", justifyContent: "center", alignItems: "flex-end", className: classes.podium }, { children: podium.map((entry) => (_jsxs(Stack, Object.assign({ alignItems: "center", className: classNames(classes.podiumItem, `${classes.podiumItem}-${entry.position}`) }, { children: [entry.position === 1 && (_jsx(Box, { component: "span", className: classes.podiumCrown, style: { maskImage: `url(${crownIcon})`, WebkitMaskImage: `url(${crownIcon})` } })),
|
|
118
|
-
const isMe = Boolean(scUserContext.user) && entry.user.id === scUserContext.user.id;
|
|
148
|
+
return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(CardContent, { children: [_jsx(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.podiumHeader }, { children: _jsx(Typography, Object.assign({ variant: "h4", className: classes.podiumTitle }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.podium.title", defaultMessage: "ui.leaderboardWidget.podium.title", values: { period } }) })) })), _jsx(Stack, Object.assign({ direction: "row", justifyContent: "center", alignItems: "flex-end", className: classes.podium }, { children: podium.map((entry) => (_jsxs(Stack, Object.assign({ alignItems: "center", className: classNames(classes.podiumItem, `${classes.podiumItem}-${entry.position}`) }, { children: [_jsxs(Box, Object.assign({ className: classes.podiumAvatarWrap }, { children: [entry.position === 1 && (_jsx(Box, { component: "span", className: classes.podiumCrown, style: { maskImage: `url(${crownIcon})`, WebkitMaskImage: `url(${crownIcon})` } })), _jsx(Avatar, { alt: entry.user.username, src: entry.user.avatar, className: classes.podiumAvatar }), _jsx(Box, Object.assign({ component: "span", className: classes.podiumBadge }, { children: entry.position }))] })), _jsx(Typography, Object.assign({ variant: "body2", fontWeight: 600, className: classes.podiumName }, { children: entry.user.real_name || entry.user.username })), _jsx(Typography, Object.assign({ variant: "caption", className: classes.podiumScore }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.podium.score", defaultMessage: "ui.leaderboardWidget.podium.score", values: { score: entry.total_score } }) }))] }), entry.user.id))) })), mode === 'full' && entries.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.rankingHeader }, { children: _jsx(Typography, Object.assign({ variant: "h4", className: classes.rankingTitle }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.ranking.title", defaultMessage: "ui.leaderboardWidget.ranking.title" }) })) })), _jsx(Stack, Object.assign({ spacing: 1, className: classes.rankingList }, { children: entries.map((entry) => {
|
|
149
|
+
const isMe = Boolean(scUserContext.user) && entry.user.id === scUserContext.user.id && entry.position <= RANKING_LIMIT;
|
|
119
150
|
return (_jsxs(Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", className: classNames(classes.rankingItem, { [classes.rankingItemActive]: isMe }) }, { children: [_jsx(Typography, Object.assign({ variant: "body2", className: classes.rankingPosition }, { children: entry.position })), _jsx(Avatar, { alt: entry.user.username, src: entry.user.avatar, className: classes.rankingAvatar }), _jsx(Typography, Object.assign({ variant: "body2", fontWeight: isMe ? 600 : 400, className: classes.rankingName }, { children: isMe ? (_jsx(FormattedMessage, { id: "ui.leaderboardWidget.ranking.you", defaultMessage: "ui.leaderboardWidget.ranking.you" })) : (entry.user.real_name || entry.user.username) })), _jsx(Typography, Object.assign({ variant: "body2", className: classes.rankingScore }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.ranking.score", defaultMessage: "ui.leaderboardWidget.ranking.score", values: { score: entry.total_score } }) }))] }), entry.user.id));
|
|
120
151
|
}) })), _jsx(Typography, Object.assign({ component: "span", role: "button", tabIndex: 0, onClick: onSeeMoreClick, className: classes.seeMore }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardWidget.ranking.seeMore", defaultMessage: "ui.leaderboardWidget.ranking.seeMore" }) }))] }))] }) })));
|
|
121
152
|
}
|