mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-03-12 06:41:50 +01:00
Fix some sonar smells in frontend code
This commit is contained in:
parent
58eae50428
commit
f43d8f89fb
@ -49,7 +49,6 @@ const Accordion = ({headers, slices, open, style}) => {
|
||||
}
|
||||
|
||||
const width = headers.length;
|
||||
|
||||
return (
|
||||
<table className={"table accordion-striped" + (nightModeEnabled ? " table-dark" : '')} id="tableAccordion"
|
||||
style={style}>
|
||||
|
@ -48,7 +48,8 @@ const QueryPath = () => {
|
||||
|
||||
return (
|
||||
<aside id={"result-path"} className={"alert shadow " + (hasResults ? "alert-success" : "alert-warning")}>
|
||||
{path.map((step, i) => <p key={i} style={{marginBottom: 0, marginLeft: i * 0.7 + "rem"}}>
|
||||
{path.map((step, i) => <p key={step.kind + step.size}
|
||||
style={{marginBottom: 0, marginLeft: i * 0.7 + "rem"}}>
|
||||
<FontAwesomeIcon
|
||||
icon={faFilter}/> '{getReadableFilterName(step.kind)}' matched {step.size} players
|
||||
</p>)}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Transition} from 'react-transition-group';
|
||||
|
||||
const reduceAnimations = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true;
|
||||
const defaultDuration = 250;
|
||||
|
||||
const LoadIn = ({children, duration}) => {
|
||||
if (!duration) duration = defaultDuration;
|
||||
const reduceAnimations = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches;
|
||||
|
||||
const defaultStyle = reduceAnimations ? {
|
||||
transition: `opacity ${duration}ms ease-in-out`,
|
||||
|
@ -22,8 +22,8 @@ const ProjectionDropDown = ({projection, setProjection}) => {
|
||||
|
||||
<Dropdown.Menu>
|
||||
<h6 className="dropdown-header">{t('html.label.geoProjection.dropdown')}</h6>
|
||||
{projectionOptions.map((option, i) => (
|
||||
<Dropdown.Item key={i} onClick={() => setProjection(option)}>
|
||||
{projectionOptions.map(option => (
|
||||
<Dropdown.Item key={option} onClick={() => setProjection(option)}>
|
||||
{t(option)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
|
@ -23,8 +23,8 @@ const SortDropDown = ({sortBy, sortReversed, setSortBy}) => {
|
||||
|
||||
<Dropdown.Menu>
|
||||
<h6 className="dropdown-header">{t('html.label.sortBy')}</h6>
|
||||
{sortOptions.map((option, i) => (
|
||||
<Dropdown.Item key={i} onClick={() => setSortBy(option)}>
|
||||
{sortOptions.map(option => (
|
||||
<Dropdown.Item key={option} onClick={() => setSortBy(option)}>
|
||||
{t(option.label)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
|
@ -27,7 +27,7 @@ const NicknamesCard = ({player}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{player.nicknames.map((nickname, i) => (<tr key={'nick-' + i}>
|
||||
{player.nicknames.map(nickname => (<tr key={JSON.stringify(nickname)}>
|
||||
<td dangerouslySetInnerHTML={{__html: nickname.nickname}}/>
|
||||
<td>{nickname.server}</td>
|
||||
<td>{nickname.date}</td>
|
||||
|
@ -51,8 +51,8 @@ const FilterDropdown = ({filterOptions, filters, setFilters}) => {
|
||||
<Dropdown.Menu popperConfig={{strategy: "absolute"}}>
|
||||
<h6 className="dropdown-header">{t('html.query.filters.add')}</h6>
|
||||
<Scrollable>
|
||||
{filterOptions.map((option, i) => (
|
||||
<Dropdown.Item key={i} onClick={() => addFilter(option)}>
|
||||
{filterOptions.map(option => (
|
||||
<Dropdown.Item key={option} onClick={() => addFilter(option)}>
|
||||
{getReadableFilterName(option)}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
|
@ -29,7 +29,7 @@ const FilterList = ({filters, setFilters, setAsInvalid, setAsValid}) => {
|
||||
|
||||
return (
|
||||
<ul id={"filters"} className={"filters"}>
|
||||
{filters.map((filter, i) => <li key={i} className={"filter"}>
|
||||
{filters.map((filter, i) => <li key={JSON.stringify(filter)} className={"filter"}>
|
||||
<Filter filter={filter} index={i}
|
||||
setFilterOptions={newOptions => updateFilterOptions(i, newOptions)}
|
||||
removeFilter={() => removeFilter(i)}
|
||||
|
@ -23,28 +23,36 @@ const PlayerbaseTrendsCard = ({data}) => {
|
||||
headers={[t('html.label.thirtyDaysAgo'), t('html.label.now'), t('html.label.trend')]}>
|
||||
<TableRow icon={faUsers} color="black" text={t('html.label.totalPlayers')}
|
||||
values={[data.total_players_then, data.total_players_now,
|
||||
<BigTrend trend={data.total_players_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.total_players_trend)}
|
||||
trend={data.total_players_trend}/>]}/>
|
||||
<TableRow icon={faUsers} color="lime" text={t('html.label.regularPlayers')}
|
||||
values={[data.regular_players_then, data.regular_players_now,
|
||||
<BigTrend trend={data.regular_players_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.regular_players_trend)}
|
||||
trend={data.regular_players_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="green"
|
||||
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
|
||||
values={[data.playtime_avg_then, data.playtime_avg_now,
|
||||
<BigTrend trend={data.playtime_avg_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.total_players_trend)}
|
||||
trend={data.playtime_avg_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="gray" text={t('html.label.afk') + ' ' + t('html.label.perPlayer')}
|
||||
values={[data.afk_then, data.afk_now, <BigTrend trend={data.afk_trend}/>]}/>
|
||||
values={[data.afk_then, data.afk_now,
|
||||
<BigTrend key={JSON.stringify(data.afk_trend)}
|
||||
trend={data.afk_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="green"
|
||||
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perRegularPlayer')}
|
||||
values={[data.regular_playtime_avg_then, data.regular_playtime_avg_now,
|
||||
<BigTrend trend={data.regular_playtime_avg_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.regular_playtime_avg_trend)}
|
||||
trend={data.regular_playtime_avg_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="teal"
|
||||
text={t('html.label.averageSessionLength') + ' ' + t('html.label.perRegularPlayer')}
|
||||
values={[data.regular_session_avg_then, data.regular_session_avg_now,
|
||||
<BigTrend trend={data.regular_session_avg_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.regular_session_avg_trend)}
|
||||
trend={data.regular_session_avg_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="gray"
|
||||
text={t('html.label.afk') + ' ' + t('html.label.perRegularPlayer')}
|
||||
values={[data.regular_afk_avg_then, data.regular_afk_avg_now,
|
||||
<BigTrend trend={data.regular_afk_avg_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.regular_afk_avg_trend)}
|
||||
trend={data.regular_afk_avg_trend}/>]}/>
|
||||
</ComparisonTable>
|
||||
</Card>
|
||||
)
|
||||
|
@ -22,30 +22,43 @@ const ServerWeekComparisonCard = ({data}) => {
|
||||
<ComparisonTable comparisonHeader={t('html.label.comparing7days')}
|
||||
headers={[data.start + ' - ' + data.midpoint, data.midpoint + ' - ' + data.end, t('html.label.trend')]}>
|
||||
<TableRow icon={faUsers} color="blue" text={t('html.label.uniquePlayers')}
|
||||
values={[data.unique_before, data.unique_after, <BigTrend trend={data.unique_trend}/>]}/>
|
||||
values={[data.unique_before, data.unique_after,
|
||||
<BigTrend key={JSON.stringify(data.unique_trend)}
|
||||
trend={data.unique_trend}/>]}/>
|
||||
<TableRow icon={faUsers} color="light-green" text={t('html.label.newPlayers')}
|
||||
values={[data.new_before, data.new_after, <BigTrend trend={data.new_trend}/>]}/>
|
||||
values={[data.new_before, data.new_after,
|
||||
<BigTrend key={JSON.stringify(data.new_trend)}
|
||||
trend={data.new_trend}/>]}/>
|
||||
<TableRow icon={faUsers} color="lime" text={t('html.label.regularPlayers')}
|
||||
values={[data.regular_before, data.regular_after, <BigTrend trend={data.regular_trend}/>]}/>
|
||||
values={[data.regular_before, data.regular_after,
|
||||
<BigTrend key={JSON.stringify(data.regular_trend)}
|
||||
trend={data.regular_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="green"
|
||||
text={t('html.label.averagePlaytime') + ' ' + t('html.label.perPlayer')}
|
||||
values={[data.average_playtime_before, data.average_playtime_after,
|
||||
<BigTrend trend={data.average_playtime_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.average_playtime_trend)}
|
||||
trend={data.average_playtime_trend}/>]}/>
|
||||
<TableRow icon={faClock} color="teal"
|
||||
text={t('html.label.averageSessionLength')}
|
||||
values={[data.session_length_average_before, data.session_length_average_after,
|
||||
<BigTrend trend={data.session_length_average_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.session_length_average_trend)}
|
||||
trend={data.session_length_average_trend}/>]}/>
|
||||
<TableRow icon={faCalendarCheck} color="teal" text={t('html.label.sessions')}
|
||||
values={[data.sessions_before, data.sessions_after,
|
||||
<BigTrend trend={data.sessions_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.sessions_trend)}
|
||||
trend={data.sessions_trend}/>]}/>
|
||||
<TableRow icon={faCrosshairs} color="red" text={t('html.label.playerKills')}
|
||||
values={[data.player_kills_before, data.player_kills_after,
|
||||
<BigTrend trend={data.player_kills_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.player_kills_trend)}
|
||||
trend={data.player_kills_trend}/>]}/>
|
||||
<TableRow icon={faCrosshairs} color="green" text={t('html.label.mobKills')}
|
||||
values={[data.mob_kills_before, data.mob_kills_after,
|
||||
<BigTrend trend={data.mob_kills_trend}/>]}/>
|
||||
<BigTrend key={JSON.stringify(data.mob_kills_trend)}
|
||||
trend={data.mob_kills_trend}/>]}/>
|
||||
<TableRow icon={faSkull} color="black" text={t('html.label.deaths')}
|
||||
values={[data.deaths_before, data.deaths_after, <BigTrend trend={data.deaths_trend}/>]}/>
|
||||
values={[data.deaths_before, data.deaths_after,
|
||||
<BigTrend key={JSON.stringify(data.deaths_trend)}
|
||||
trend={data.deaths_trend}/>]}/>
|
||||
</ComparisonTable>
|
||||
</Card>
|
||||
)
|
||||
|
@ -96,7 +96,8 @@ const ExtensionCard = ({extension}) => {
|
||||
</Card.Header>
|
||||
<ul className="nav nav-tabs tab-nav-right" role="tablist">
|
||||
{extension.onlyGenericTab ? '' :
|
||||
extension.tabs.map((tab, i) => <li key={i} role="presentation" className="nav-item col-black">
|
||||
extension.tabs.map((tab, i) => <li key={JSON.stringify(tab)} role="presentation"
|
||||
className="nav-item col-black">
|
||||
<button className={"nav-link col-black"
|
||||
+ (openTabIndex === i ? ' active' : '')} onClick={() => toggleTabIndex(i)}>
|
||||
<ExtensionIcon icon={tab.tabInformation.icon}/> {tab.tabInformation.tabName}
|
||||
|
@ -13,7 +13,7 @@ const MultiSelect = ({options, selectedIndexes, setSelectedIndexes}) => {
|
||||
onChange={handleChange}>
|
||||
{options.map((option, i) => {
|
||||
return (
|
||||
<option key={i} value={selectedIndexes.includes(i)}
|
||||
<option key={JSON.stringify(option)} value={selectedIndexes.includes(i)}
|
||||
selected={selectedIndexes.includes(i)}>{option}</option>
|
||||
)
|
||||
})}
|
||||
|
@ -58,7 +58,7 @@ const getContributionIcon = (type) => {
|
||||
|
||||
const Contributor = ({contributor}) => {
|
||||
const icons = contributor.contributed.map(
|
||||
(type, i) => <Fa key={i} icon={["fa", getContributionIcon(type)]}/>);
|
||||
type => <Fa key={"" + type} icon={["fa", getContributionIcon(type)]}/>);
|
||||
return (
|
||||
<li className="contributor">{contributor.name} {icons} </li>
|
||||
)
|
||||
@ -78,7 +78,7 @@ const Contributions = () => {
|
||||
<p>In addition following <span className="col-theme">awesome people</span> have
|
||||
contributed:</p>
|
||||
<ul className="row contributors">
|
||||
{contributors.map((contributor, i) => <Contributor key={i} contributor={contributor}/>)}
|
||||
{contributors.map((contributor, i) => <Contributor key={contributor.name} contributor={contributor}/>)}
|
||||
<li>{t('html.modal.info.contributors.bugreporters')}</li>
|
||||
</ul>
|
||||
<small>
|
||||
|
@ -22,8 +22,8 @@ const LanguageSelector = () => {
|
||||
className="form-select form-select-sm"
|
||||
id="langSelector"
|
||||
defaultValue={localeService.clientLocale}>
|
||||
{languages.map((lang, i) =>
|
||||
<option key={i} value={lang.name}>{lang.displayName}</option>)}
|
||||
{languages.map(lang =>
|
||||
<option key={lang.name} value={lang.name}>{lang.displayName}</option>)}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
@ -150,8 +150,8 @@ const SidebarCollapse = ({item, open, setOpen, collapseSidebar}) => {
|
||||
<div className="bg-white py-2 collapse-inner rounded">
|
||||
{item.contents
|
||||
.filter(content => content !== undefined)
|
||||
.map((content, i) =>
|
||||
<Item key={i}
|
||||
.map(content =>
|
||||
<Item key={JSON.stringify(content)}
|
||||
inner
|
||||
active={false}
|
||||
item={content}
|
||||
|
@ -30,7 +30,7 @@ const GroupTable = ({groups, colors}) => {
|
||||
<table className={"table mb-0" + (nightModeEnabled ? " table-dark" : '')}>
|
||||
<tbody>
|
||||
{groups.length ? groups.map((group, i) =>
|
||||
<GroupRow key={i}
|
||||
<GroupRow key={group.name}
|
||||
group={group}
|
||||
color={getColor(i)}/>) :
|
||||
<tr>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {createContext, useCallback, useContext, useEffect, useState} from "react";
|
||||
import {createContext, useCallback, useContext, useEffect, useMemo, useState} from "react";
|
||||
import {fetchWhoAmI} from "../service/authenticationService";
|
||||
|
||||
const AuthenticationContext = createContext({});
|
||||
@ -35,7 +35,8 @@ export const AuthenticationContextProvider = ({children}) => {
|
||||
updateLoginDetails();
|
||||
}, [updateLoginDetails]);
|
||||
|
||||
const sharedState = {
|
||||
const sharedState = useMemo(() => {
|
||||
return {
|
||||
authLoaded,
|
||||
authRequired,
|
||||
loggedIn,
|
||||
@ -45,6 +46,16 @@ export const AuthenticationContextProvider = ({children}) => {
|
||||
hasPermissionOtherThan,
|
||||
updateLoginDetails
|
||||
}
|
||||
}, [
|
||||
authLoaded,
|
||||
authRequired,
|
||||
loggedIn,
|
||||
user,
|
||||
loginError,
|
||||
hasPermission,
|
||||
hasPermissionOtherThan,
|
||||
updateLoginDetails
|
||||
])
|
||||
return (<AuthenticationContext.Provider value={sharedState}>
|
||||
{children}
|
||||
</AuthenticationContext.Provider>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {createContext, useContext, useEffect, useState} from "react";
|
||||
import {createContext, useContext, useEffect, useMemo, useState} from "react";
|
||||
import {useDataRequest} from "./dataFetchHook";
|
||||
import {fetchExtensionData} from "../service/serverService";
|
||||
|
||||
@ -15,7 +15,9 @@ export const ServerExtensionContextProvider = ({identifier, children}) => {
|
||||
setExtensionDataLoadingError(loadingError);
|
||||
}, [data, loadingError, setExtensionData, setExtensionDataLoadingError])
|
||||
|
||||
const sharedState = {extensionData, extensionDataLoadingError}
|
||||
const sharedState = useMemo(() => {
|
||||
return {extensionData, extensionDataLoadingError};
|
||||
}, [extensionData, extensionDataLoadingError]);
|
||||
return (<ServerExtensionContext.Provider value={sharedState}>
|
||||
{children}
|
||||
</ServerExtensionContext.Provider>
|
||||
|
@ -2187,7 +2187,7 @@ a.text-dark:hover, a.text-dark:focus {
|
||||
border: 0;
|
||||
font-weight: 900;
|
||||
content: '\f105';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 5 Free', sans-serif;
|
||||
}
|
||||
|
||||
.sidebar .nav-item.dropdown.show .dropdown-toggle::after,
|
||||
@ -2471,7 +2471,7 @@ a.text-dark:hover, a.text-dark:focus {
|
||||
.sidebar #sidebarToggle::after {
|
||||
font-weight: 900;
|
||||
content: '\f104';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 5 Free', sans-serif;
|
||||
margin-right: 0.1rem;
|
||||
}
|
||||
|
||||
@ -2490,7 +2490,7 @@ a.text-dark:hover, a.text-dark:focus {
|
||||
|
||||
.sidebar.toggled #sidebarToggle::after {
|
||||
content: '\f105';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 5 Free', sans-serif;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
@ -2611,7 +2611,7 @@ a.text-dark:hover, a.text-dark:focus {
|
||||
border: 0;
|
||||
font-weight: 900;
|
||||
content: '\f107';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 5 Free', sans-serif;
|
||||
}
|
||||
|
||||
.sidebar .nav-item .nav-link[data-bs-toggle="collapse"].collapsed::after {
|
||||
@ -2892,7 +2892,7 @@ a.text-dark:hover, a.text-dark:focus {
|
||||
line-height: 51px;
|
||||
font-weight: 900;
|
||||
content: '\f107';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-family: 'Font Awesome 5 Free', sans-serif;
|
||||
color: #d1d3e2;
|
||||
}
|
||||
|
||||
|
@ -146,28 +146,7 @@ const LoginPage = () => {
|
||||
}
|
||||
}, [setRedirectTo, setSuccessMessage, t])
|
||||
|
||||
const login = async (username, password) => {
|
||||
if (!username || username.length < 1) {
|
||||
return setFailMessage(t('html.register.error.noUsername'));
|
||||
}
|
||||
if (username.length > 50) {
|
||||
return setFailMessage(t('html.register.error.usernameLength') + username.length);
|
||||
}
|
||||
if (!password || password.length < 1) {
|
||||
return setFailMessage(t('html.register.error.noPassword'));
|
||||
}
|
||||
|
||||
const {data, error} = await fetchLogin(username, password);
|
||||
|
||||
if (error) {
|
||||
if (error.message === 'Request failed with status code 403') {
|
||||
// Too many logins, reload browser to show forbidden page
|
||||
window.location.reload();
|
||||
} else {
|
||||
setFailMessage(t('html.login.failed') + (error.data && error.data.error ? error.data.error : error.message));
|
||||
}
|
||||
} else if (data && data.success) {
|
||||
await updateLoginDetails();
|
||||
const redirectAfterLogin = () => {
|
||||
if (redirectTo && !redirectTo.startsWith('http') && !redirectTo.startsWith('file') && !redirectTo.startsWith('javascript')) {
|
||||
// Normalize the URL so that it can't redirect to different domain.
|
||||
try {
|
||||
@ -191,6 +170,31 @@ const LoginPage = () => {
|
||||
} else {
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
const login = async (username, password) => {
|
||||
if (!username || username.length < 1) {
|
||||
return setFailMessage(t('html.register.error.noUsername'));
|
||||
}
|
||||
if (username.length > 50) {
|
||||
return setFailMessage(t('html.register.error.usernameLength') + username.length);
|
||||
}
|
||||
if (!password || password.length < 1) {
|
||||
return setFailMessage(t('html.register.error.noPassword'));
|
||||
}
|
||||
|
||||
const {data, error} = await fetchLogin(username, password);
|
||||
|
||||
if (error) {
|
||||
if (error.message === 'Request failed with status code 403') {
|
||||
// Too many logins, reload browser to show forbidden page
|
||||
window.location.reload();
|
||||
} else {
|
||||
setFailMessage(t('html.login.failed') + (error.data && error.data.error ? error.data.error : error.message));
|
||||
}
|
||||
} else if (data && data.success) {
|
||||
await updateLoginDetails();
|
||||
redirectAfterLogin();
|
||||
} else {
|
||||
setFailMessage(t('html.login.failed') + data ? data.error : t('generic.noData'));
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ const ConnectionsCard = ({player}) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{player.connections.map((connection, i) => (<tr key={'connection-' + i}>
|
||||
{player.connections.map((connection, i) => (<tr key={JSON.stringify(connection)}>
|
||||
<td>{connection.geolocation}</td>
|
||||
<td>{connection.date}</td>
|
||||
</tr>))}
|
||||
|
Loading…
Reference in New Issue
Block a user