@selfcommunity/react-ui 1.3.0-alpha.0 → 1.3.0-alpha.1
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/LeaderboardPositionWidget/LeaderboardPositionWidget.d.ts +21 -1
- package/lib/cjs/components/LeaderboardPositionWidget/LeaderboardPositionWidget.js +32 -2
- package/lib/cjs/components/LeaderboardWidget/LeaderboardWidget.js +5 -1
- package/lib/esm/components/LeaderboardPositionWidget/LeaderboardPositionWidget.d.ts +21 -1
- package/lib/esm/components/LeaderboardPositionWidget/LeaderboardPositionWidget.js +34 -4
- package/lib/esm/components/LeaderboardWidget/LeaderboardWidget.js +5 -1
- package/lib/umd/react-ui.js +1 -1
- package/package.json +3 -3
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import { WidgetProps } from '../Widget';
|
|
2
2
|
import { SCLeaderboardEntry } from '@selfcommunity/types';
|
|
3
|
+
import { LeaderboardPeriod } from '@selfcommunity/utils';
|
|
4
|
+
export type LeaderboardPositionWidgetPeriod = LeaderboardPeriod;
|
|
3
5
|
export interface LeaderboardPositionWidgetProps extends WidgetProps {
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The leaderboard entry of the logged user. If not provided, the component fetches it on its own.
|
|
8
|
+
*/
|
|
9
|
+
position?: SCLeaderboardEntry;
|
|
10
|
+
/**
|
|
11
|
+
* The period the position refers to.
|
|
12
|
+
* @default 'month'
|
|
13
|
+
*/
|
|
14
|
+
period?: LeaderboardPositionWidgetPeriod;
|
|
15
|
+
/**
|
|
16
|
+
* If true, shows the skeleton in place of the widget content.
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
5
19
|
isLoading?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The url the widget links to.
|
|
22
|
+
* @default the profile of the logged user
|
|
23
|
+
*/
|
|
24
|
+
to?: string;
|
|
6
25
|
}
|
|
7
26
|
/**
|
|
8
27
|
* > API documentation for the Community-JS Leaderboard Position Widget component. Learn about the available props and the CSS API.
|
|
9
28
|
*
|
|
10
29
|
*
|
|
11
30
|
* This component renders a widget showing the current logged user position in the leaderboard.
|
|
31
|
+
* The position can be provided via the `position` prop; if it is not provided, the component fetches it on its own.
|
|
12
32
|
* The widget is hidden if the user is not authenticated, the leaderboard feature is disabled or no position data is available.
|
|
13
33
|
|
|
14
34
|
#### Import
|
|
@@ -10,6 +10,9 @@ const Widget_1 = tslib_1.__importDefault(require("../Widget"));
|
|
|
10
10
|
const system_1 = require("@mui/system");
|
|
11
11
|
const constants_1 = require("./constants");
|
|
12
12
|
const react_core_1 = require("@selfcommunity/react-core");
|
|
13
|
+
const api_services_1 = require("@selfcommunity/api-services");
|
|
14
|
+
const utils_1 = require("@selfcommunity/utils");
|
|
15
|
+
const Errors_1 = require("../../constants/Errors");
|
|
13
16
|
const HiddenPlaceholder_1 = tslib_1.__importDefault(require("../../shared/HiddenPlaceholder"));
|
|
14
17
|
const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
|
|
15
18
|
const classes = {
|
|
@@ -31,6 +34,7 @@ const Root = (0, material_1.styled)(Widget_1.default, {
|
|
|
31
34
|
*
|
|
32
35
|
*
|
|
33
36
|
* This component renders a widget showing the current logged user position in the leaderboard.
|
|
37
|
+
* The position can be provided via the `position` prop; if it is not provided, the component fetches it on its own.
|
|
34
38
|
* The widget is hidden if the user is not authenticated, the leaderboard feature is disabled or no position data is available.
|
|
35
39
|
|
|
36
40
|
#### Import
|
|
@@ -64,12 +68,38 @@ function LeaderboardPositionWidget(inProps) {
|
|
|
64
68
|
props: inProps,
|
|
65
69
|
name: constants_1.PREFIX
|
|
66
70
|
});
|
|
67
|
-
const { className, position, isLoading = true } = props, rest = tslib_1.__rest(props, ["className", "position", "isLoading"]);
|
|
71
|
+
const { className, position: positionProp, period = 'month', isLoading: isLoadingProp = true, to } = props, rest = tslib_1.__rest(props, ["className", "position", "period", "isLoading", "to"]);
|
|
68
72
|
// CONTEXT
|
|
69
73
|
const scUserContext = (0, react_1.useContext)(react_core_1.SCUserContext);
|
|
70
74
|
const scRoutingContext = (0, react_core_1.useSCRouting)();
|
|
71
75
|
// PREFERENCES
|
|
72
76
|
const leaderboardsEnabled = (0, react_core_1.useSCPreferenceEnabled)(react_core_1.SCPreferences.CONFIGURATIONS_LEADERBOARDS_ENABLED);
|
|
77
|
+
// Self-fetches the logged user position when it is not provided by the parent
|
|
78
|
+
const shouldFetch = Boolean(scUserContext.user) && leaderboardsEnabled && !positionProp;
|
|
79
|
+
const [fetchedPosition, setFetchedPosition] = (0, react_1.useState)(null);
|
|
80
|
+
const [isFetching, setIsFetching] = (0, react_1.useState)(shouldFetch);
|
|
81
|
+
const isMountedRef = (0, react_core_1.useIsComponentMountedRef)();
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
if (!shouldFetch) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setIsFetching(true);
|
|
87
|
+
api_services_1.ScoreService.getLeaderboards(Object.assign({ limit: 1 }, (0, utils_1.getPeriodRange)(period)))
|
|
88
|
+
.then((data) => {
|
|
89
|
+
if (isMountedRef.current) {
|
|
90
|
+
setFetchedPosition(data.my_position);
|
|
91
|
+
setIsFetching(false);
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.catch((error) => {
|
|
95
|
+
utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
|
|
96
|
+
if (isMountedRef.current) {
|
|
97
|
+
setIsFetching(false);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}, [shouldFetch, period]);
|
|
101
|
+
const position = shouldFetch ? fetchedPosition : positionProp;
|
|
102
|
+
const isLoading = shouldFetch ? isFetching : isLoadingProp;
|
|
73
103
|
/**
|
|
74
104
|
* Renders nothing if the user is not authenticated or the leaderboard feature is disabled
|
|
75
105
|
*/
|
|
@@ -88,6 +118,6 @@ function LeaderboardPositionWidget(inProps) {
|
|
|
88
118
|
if (!position) {
|
|
89
119
|
return (0, jsx_runtime_1.jsx)(HiddenPlaceholder_1.default, {});
|
|
90
120
|
}
|
|
91
|
-
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.Typography, Object.assign({ variant: "h4", className: classes.title }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.title", defaultMessage: "ui.leaderboardPositionWidget.title" }) })), (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", component: react_core_1.Link, to: scRoutingContext.url(react_core_1.SCRoutes.USER_PROFILE_ROUTE_NAME, { id: position.user.id }), className: classes.user }, { children: [(0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: position.user.username, src: position.user.avatar, className: classes.userAvatar }), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", fontWeight: 600, className: classes.userName }, { children: position.user.real_name || position.user.username }))] })), (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.footer }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h3", className: classes.position }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.position", defaultMessage: "ui.leaderboardPositionWidget.position", values: { position: position.position } }) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", className: classes.score }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.score", defaultMessage: "ui.leaderboardPositionWidget.score", values: { score: position.total_score } }) }))] }))] }) })));
|
|
121
|
+
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.Typography, Object.assign({ variant: "h4", className: classes.title }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.title", defaultMessage: "ui.leaderboardPositionWidget.title" }) })), (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", component: react_core_1.Link, to: to !== null && to !== void 0 ? to : scRoutingContext.url(react_core_1.SCRoutes.USER_PROFILE_ROUTE_NAME, { id: position.user.id }), className: classes.user }, { children: [(0, jsx_runtime_1.jsx)(material_1.Avatar, { alt: position.user.username, src: position.user.avatar, className: classes.userAvatar }), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", fontWeight: 600, className: classes.userName }, { children: position.user.real_name || position.user.username }))] })), (0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.footer }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h3", className: classes.position }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.position", defaultMessage: "ui.leaderboardPositionWidget.position", values: { position: position.position } }) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", className: classes.score }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.leaderboardPositionWidget.score", defaultMessage: "ui.leaderboardPositionWidget.score", values: { score: position.total_score } }) }))] }))] }) })));
|
|
92
122
|
}
|
|
93
123
|
exports.default = LeaderboardPositionWidget;
|
|
@@ -17,6 +17,7 @@ const Errors_1 = require("../../constants/Errors");
|
|
|
17
17
|
const HiddenPlaceholder_1 = tslib_1.__importDefault(require("../../shared/HiddenPlaceholder"));
|
|
18
18
|
const Skeleton_1 = tslib_1.__importDefault(require("./Skeleton"));
|
|
19
19
|
const RANKING_LIMIT = 5;
|
|
20
|
+
const PODIUM_LIMIT = 3;
|
|
20
21
|
const classes = {
|
|
21
22
|
root: `${constants_1.PREFIX}-root`,
|
|
22
23
|
podiumHeader: `${constants_1.PREFIX}-podium-header`,
|
|
@@ -140,7 +141,10 @@ function LeaderboardWidget(inProps) {
|
|
|
140
141
|
if (isLoading) {
|
|
141
142
|
return (0, jsx_runtime_1.jsx)(Skeleton_1.default, { mode: mode });
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
+
// Entries can share the same position: the podium shows only the first one of each of the first three positions, while the ranking list below shows them all
|
|
145
|
+
const podium = entries
|
|
146
|
+
.filter((entry, index) => entry.position <= PODIUM_LIMIT && entries.findIndex((e) => e.position === entry.position) === index)
|
|
147
|
+
.slice(0, PODIUM_LIMIT);
|
|
144
148
|
/**
|
|
145
149
|
* Renders nothing if there's no podium data
|
|
146
150
|
*/
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import { WidgetProps } from '../Widget';
|
|
2
2
|
import { SCLeaderboardEntry } from '@selfcommunity/types';
|
|
3
|
+
import { LeaderboardPeriod } from '@selfcommunity/utils';
|
|
4
|
+
export type LeaderboardPositionWidgetPeriod = LeaderboardPeriod;
|
|
3
5
|
export interface LeaderboardPositionWidgetProps extends WidgetProps {
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The leaderboard entry of the logged user. If not provided, the component fetches it on its own.
|
|
8
|
+
*/
|
|
9
|
+
position?: SCLeaderboardEntry;
|
|
10
|
+
/**
|
|
11
|
+
* The period the position refers to.
|
|
12
|
+
* @default 'month'
|
|
13
|
+
*/
|
|
14
|
+
period?: LeaderboardPositionWidgetPeriod;
|
|
15
|
+
/**
|
|
16
|
+
* If true, shows the skeleton in place of the widget content.
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
5
19
|
isLoading?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The url the widget links to.
|
|
22
|
+
* @default the profile of the logged user
|
|
23
|
+
*/
|
|
24
|
+
to?: string;
|
|
6
25
|
}
|
|
7
26
|
/**
|
|
8
27
|
* > API documentation for the Community-JS Leaderboard Position Widget component. Learn about the available props and the CSS API.
|
|
9
28
|
*
|
|
10
29
|
*
|
|
11
30
|
* This component renders a widget showing the current logged user position in the leaderboard.
|
|
31
|
+
* The position can be provided via the `position` prop; if it is not provided, the component fetches it on its own.
|
|
12
32
|
* The widget is hidden if the user is not authenticated, the leaderboard feature is disabled or no position data is available.
|
|
13
33
|
|
|
14
34
|
#### Import
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { __rest } from "tslib";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useContext } from 'react';
|
|
3
|
+
import { useContext, useEffect, useState } from 'react';
|
|
4
4
|
import { Avatar, CardContent, Stack, Typography, styled } from '@mui/material';
|
|
5
5
|
import { FormattedMessage } from 'react-intl';
|
|
6
6
|
import classNames from 'classnames';
|
|
7
7
|
import Widget from '../Widget';
|
|
8
8
|
import { useThemeProps } from '@mui/system';
|
|
9
9
|
import { PREFIX } from './constants';
|
|
10
|
-
import { Link, SCPreferences, SCRoutes, SCUserContext, useSCPreferenceEnabled, useSCRouting } from '@selfcommunity/react-core';
|
|
10
|
+
import { Link, SCPreferences, SCRoutes, SCUserContext, useIsComponentMountedRef, useSCPreferenceEnabled, useSCRouting } from '@selfcommunity/react-core';
|
|
11
|
+
import { ScoreService } from '@selfcommunity/api-services';
|
|
12
|
+
import { getPeriodRange, Logger } from '@selfcommunity/utils';
|
|
13
|
+
import { SCOPE_SC_UI } from '../../constants/Errors';
|
|
11
14
|
import HiddenPlaceholder from '../../shared/HiddenPlaceholder';
|
|
12
15
|
import LeaderboardPositionWidgetSkeleton from './Skeleton';
|
|
13
16
|
const classes = {
|
|
@@ -29,6 +32,7 @@ const Root = styled(Widget, {
|
|
|
29
32
|
*
|
|
30
33
|
*
|
|
31
34
|
* This component renders a widget showing the current logged user position in the leaderboard.
|
|
35
|
+
* The position can be provided via the `position` prop; if it is not provided, the component fetches it on its own.
|
|
32
36
|
* The widget is hidden if the user is not authenticated, the leaderboard feature is disabled or no position data is available.
|
|
33
37
|
|
|
34
38
|
#### Import
|
|
@@ -62,12 +66,38 @@ export default function LeaderboardPositionWidget(inProps) {
|
|
|
62
66
|
props: inProps,
|
|
63
67
|
name: PREFIX
|
|
64
68
|
});
|
|
65
|
-
const { className, position, isLoading = true } = props, rest = __rest(props, ["className", "position", "isLoading"]);
|
|
69
|
+
const { className, position: positionProp, period = 'month', isLoading: isLoadingProp = true, to } = props, rest = __rest(props, ["className", "position", "period", "isLoading", "to"]);
|
|
66
70
|
// CONTEXT
|
|
67
71
|
const scUserContext = useContext(SCUserContext);
|
|
68
72
|
const scRoutingContext = useSCRouting();
|
|
69
73
|
// PREFERENCES
|
|
70
74
|
const leaderboardsEnabled = useSCPreferenceEnabled(SCPreferences.CONFIGURATIONS_LEADERBOARDS_ENABLED);
|
|
75
|
+
// Self-fetches the logged user position when it is not provided by the parent
|
|
76
|
+
const shouldFetch = Boolean(scUserContext.user) && leaderboardsEnabled && !positionProp;
|
|
77
|
+
const [fetchedPosition, setFetchedPosition] = useState(null);
|
|
78
|
+
const [isFetching, setIsFetching] = useState(shouldFetch);
|
|
79
|
+
const isMountedRef = useIsComponentMountedRef();
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!shouldFetch) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setIsFetching(true);
|
|
85
|
+
ScoreService.getLeaderboards(Object.assign({ limit: 1 }, getPeriodRange(period)))
|
|
86
|
+
.then((data) => {
|
|
87
|
+
if (isMountedRef.current) {
|
|
88
|
+
setFetchedPosition(data.my_position);
|
|
89
|
+
setIsFetching(false);
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.catch((error) => {
|
|
93
|
+
Logger.error(SCOPE_SC_UI, error);
|
|
94
|
+
if (isMountedRef.current) {
|
|
95
|
+
setIsFetching(false);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}, [shouldFetch, period]);
|
|
99
|
+
const position = shouldFetch ? fetchedPosition : positionProp;
|
|
100
|
+
const isLoading = shouldFetch ? isFetching : isLoadingProp;
|
|
71
101
|
/**
|
|
72
102
|
* Renders nothing if the user is not authenticated or the leaderboard feature is disabled
|
|
73
103
|
*/
|
|
@@ -86,5 +116,5 @@ export default function LeaderboardPositionWidget(inProps) {
|
|
|
86
116
|
if (!position) {
|
|
87
117
|
return _jsx(HiddenPlaceholder, {});
|
|
88
118
|
}
|
|
89
|
-
return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(CardContent, { children: [_jsx(Typography, Object.assign({ variant: "h4", className: classes.title }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.title", defaultMessage: "ui.leaderboardPositionWidget.title" }) })), _jsxs(Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", component: Link, to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, { id: position.user.id }), className: classes.user }, { children: [_jsx(Avatar, { alt: position.user.username, src: position.user.avatar, className: classes.userAvatar }), _jsx(Typography, Object.assign({ variant: "body1", fontWeight: 600, className: classes.userName }, { children: position.user.real_name || position.user.username }))] })), _jsxs(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.footer }, { children: [_jsx(Typography, Object.assign({ variant: "h3", className: classes.position }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.position", defaultMessage: "ui.leaderboardPositionWidget.position", values: { position: position.position } }) })), _jsx(Typography, Object.assign({ variant: "body1", className: classes.score }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.score", defaultMessage: "ui.leaderboardPositionWidget.score", values: { score: position.total_score } }) }))] }))] }) })));
|
|
119
|
+
return (_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(CardContent, { children: [_jsx(Typography, Object.assign({ variant: "h4", className: classes.title }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.title", defaultMessage: "ui.leaderboardPositionWidget.title" }) })), _jsxs(Stack, Object.assign({ direction: "row", spacing: 1.5, alignItems: "center", component: Link, to: to !== null && to !== void 0 ? to : scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, { id: position.user.id }), className: classes.user }, { children: [_jsx(Avatar, { alt: position.user.username, src: position.user.avatar, className: classes.userAvatar }), _jsx(Typography, Object.assign({ variant: "body1", fontWeight: 600, className: classes.userName }, { children: position.user.real_name || position.user.username }))] })), _jsxs(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", className: classes.footer }, { children: [_jsx(Typography, Object.assign({ variant: "h3", className: classes.position }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.position", defaultMessage: "ui.leaderboardPositionWidget.position", values: { position: position.position } }) })), _jsx(Typography, Object.assign({ variant: "body1", className: classes.score }, { children: _jsx(FormattedMessage, { id: "ui.leaderboardPositionWidget.score", defaultMessage: "ui.leaderboardPositionWidget.score", values: { score: position.total_score } }) }))] }))] }) })));
|
|
90
120
|
}
|
|
@@ -15,6 +15,7 @@ import { SCOPE_SC_UI } from '../../constants/Errors';
|
|
|
15
15
|
import HiddenPlaceholder from '../../shared/HiddenPlaceholder';
|
|
16
16
|
import LeaderboardWidgetSkeleton from './Skeleton';
|
|
17
17
|
const RANKING_LIMIT = 5;
|
|
18
|
+
const PODIUM_LIMIT = 3;
|
|
18
19
|
const classes = {
|
|
19
20
|
root: `${PREFIX}-root`,
|
|
20
21
|
podiumHeader: `${PREFIX}-podium-header`,
|
|
@@ -138,7 +139,10 @@ export default function LeaderboardWidget(inProps) {
|
|
|
138
139
|
if (isLoading) {
|
|
139
140
|
return _jsx(LeaderboardWidgetSkeleton, { mode: mode });
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
// Entries can share the same position: the podium shows only the first one of each of the first three positions, while the ranking list below shows them all
|
|
143
|
+
const podium = entries
|
|
144
|
+
.filter((entry, index) => entry.position <= PODIUM_LIMIT && entries.findIndex((e) => e.position === entry.position) === index)
|
|
145
|
+
.slice(0, PODIUM_LIMIT);
|
|
142
146
|
/**
|
|
143
147
|
* Renders nothing if there's no podium data
|
|
144
148
|
*/
|