forked from simrail/simrail.pro
Merge pull request 'feat(backend, frontend): fix stats from singleplayer, fix admin men.' (#76) from v3.0.1 into main
Reviewed-on: simrail/simrail.pro#76 Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
This commit is contained in:
commit
6c43ff7209
@ -59,9 +59,9 @@ export class AdminRoute
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/profile/:playerId/clear", async (req, res) =>
|
app.post("/profile/:playerId/hideLeaderboard", async (req, res) =>
|
||||||
{
|
{
|
||||||
const token = req.headers["x-auth-token"];
|
const token = req.headers[ "x-auth-token" ];
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
{
|
{
|
||||||
@ -93,14 +93,58 @@ export class AdminRoute
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await MProfile.updateOne({id: player.id}, {
|
player.flags.push("leaderboard_hidden");
|
||||||
dispatcherTime: 0,
|
|
||||||
trainTime: 0,
|
|
||||||
trainDistance: 0,
|
|
||||||
trainPoints: 0,
|
|
||||||
|
|
||||||
trainStats: {},
|
await MProfile.updateOne({ id: player.id }, {
|
||||||
dispatcherStats: {},
|
flags: player.flags,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(
|
||||||
|
new SuccessResponseBuilder()
|
||||||
|
.setCode(200)
|
||||||
|
.setData({})
|
||||||
|
.toJSON(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/profile/:playerId/showLeaderboard", async (req, res) =>
|
||||||
|
{
|
||||||
|
const token = req.headers[ "x-auth-token" ];
|
||||||
|
|
||||||
|
if (!token)
|
||||||
|
{
|
||||||
|
res.status(400).json(new ErrorResponseBuilder()
|
||||||
|
.setCode(400)
|
||||||
|
.setData("Missing token query").toJSON());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = await MAdmin.findOne({ token });
|
||||||
|
|
||||||
|
if (!admin)
|
||||||
|
{
|
||||||
|
res.status(401).json(new ErrorResponseBuilder()
|
||||||
|
.setCode(401)
|
||||||
|
.setData("Invalid token").toJSON());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = await MProfile.findOne({
|
||||||
|
id: req.params.playerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!player)
|
||||||
|
{
|
||||||
|
res.status(401).json(new ErrorResponseBuilder()
|
||||||
|
.setCode(401)
|
||||||
|
.setData("Invalid playerId").toJSON());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player.flags = player.flags.filter(x => x !== "leaderboard_hidden");
|
||||||
|
|
||||||
|
await MProfile.updateOne({ id: player.id }, {
|
||||||
|
flags: player.flags,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json(
|
res.json(
|
||||||
@ -113,7 +157,7 @@ export class AdminRoute
|
|||||||
|
|
||||||
app.post("/profile/:playerId/hide", async (req, res) =>
|
app.post("/profile/:playerId/hide", async (req, res) =>
|
||||||
{
|
{
|
||||||
const token = req.headers["x-auth-token"];
|
const token = req.headers[ "x-auth-token" ];
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
{
|
{
|
||||||
@ -147,7 +191,7 @@ export class AdminRoute
|
|||||||
|
|
||||||
player.flags.push("hidden");
|
player.flags.push("hidden");
|
||||||
|
|
||||||
await MProfile.updateOne({id: player.id}, {
|
await MProfile.updateOne({ id: player.id }, {
|
||||||
flags: player.flags,
|
flags: player.flags,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ export class LeaderboardRoute
|
|||||||
const filter: PipelineStage[] = [
|
const filter: PipelineStage[] = [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
flags: { $nin: ["hidden"] }
|
flags: { $nin: ["hidden", "leaderboard_hidden"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -83,7 +83,7 @@ export class LeaderboardRoute
|
|||||||
const filter: PipelineStage[] = [
|
const filter: PipelineStage[] = [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
flags: { $nin: ["hidden"] }
|
flags: { $nin: ["hidden", "leaderboard_hidden"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -85,6 +85,23 @@ export class StationsModule
|
|||||||
player.steamTrainScore = stats?.stats?.find(x => x.name === "SCORE")?.value ?? 0;
|
player.steamTrainScore = stats?.stats?.find(x => x.name === "SCORE")?.value ?? 0;
|
||||||
|
|
||||||
|
|
||||||
|
if ((player.steamTrainDistance > player.trainDistance) || (player.trainPoints > player.steamTrainScore))
|
||||||
|
{
|
||||||
|
player.trainStats[ "N/A" ] = {
|
||||||
|
time: 0, distance: player.steamTrainDistance > player.trainDistance ? player.steamTrainDistance - player.trainDistance : player.trainDistance,
|
||||||
|
score: player.trainPoints > player.steamTrainScore ? player.steamTrainScore - player.trainPoints : player.trainPoints,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (player.steamTrainDistance > player.trainDistance)
|
||||||
|
{
|
||||||
|
player.trainDistance = player.steamTrainDistance;
|
||||||
|
}
|
||||||
|
if (player.trainPoints > player.steamTrainScore)
|
||||||
|
{
|
||||||
|
player.trainPoints = player.steamTrainScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player.flags = player.flags.filter(x => x !== "private");
|
player.flags = player.flags.filter(x => x !== "private");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +114,23 @@ export class TrainsModule
|
|||||||
player.steamDispatcherTime = stats?.stats?.find(x => x.name === "DISPATCHER_TIME")?.value ?? 0;
|
player.steamDispatcherTime = stats?.stats?.find(x => x.name === "DISPATCHER_TIME")?.value ?? 0;
|
||||||
player.steamTrainScore = stats?.stats?.find(x => x.name === "SCORE")?.value ?? 0;
|
player.steamTrainScore = stats?.stats?.find(x => x.name === "SCORE")?.value ?? 0;
|
||||||
|
|
||||||
|
if ((player.steamTrainDistance > player.trainDistance) || (player.trainPoints > player.steamTrainScore))
|
||||||
|
{
|
||||||
|
player.trainStats[ "N/A" ] = {
|
||||||
|
time: 0, distance: player.steamTrainDistance > player.trainDistance ? player.steamTrainDistance - player.trainDistance : player.trainDistance,
|
||||||
|
score: player.trainPoints > player.steamTrainScore ? player.steamTrainScore - player.trainPoints : player.trainPoints,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (player.steamTrainDistance > player.trainDistance)
|
||||||
|
{
|
||||||
|
player.trainDistance = player.steamTrainDistance;
|
||||||
|
}
|
||||||
|
if (player.trainPoints > player.steamTrainScore)
|
||||||
|
{
|
||||||
|
player.trainPoints = player.steamTrainScore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player.flags = player.flags.filter(x => x !== "private");
|
player.flags = player.flags.filter(x => x !== "private");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
packages/frontend/src/components/mini/util/UserIcons.tsx
Normal file
24
packages/frontend/src/components/mini/util/UserIcons.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* See LICENSE for more.
|
||||||
|
*/
|
||||||
|
import { FaUserShield, FaUserSlash, FaUserLock } from "react-icons/fa6";
|
||||||
|
|
||||||
|
export const UserIcons = ({ flags }: { flags: string[] }) =>
|
||||||
|
{
|
||||||
|
return <> { flags.includes("administrator") &&
|
||||||
|
<FaUserShield className={ "inline text-meta-1 ml-1" }/> } { flags.includes("leaderboard_hidden") &&
|
||||||
|
<FaUserLock className={ "inline text-meta-6 ml-1" }/> } { flags.includes("hidden") &&
|
||||||
|
<FaUserSlash className={ "inline text-meta-1 ml-1" }/> }</>;
|
||||||
|
};
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
|
||||||
import { TActiveStationPlayersData } from "../../../types/active.ts";
|
import { TActiveStationPlayersData } from "../../../types/active.ts";
|
||||||
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
export const ActiveStationTable = ({ stations }: { stations: TActiveStationPlayersData[] }) =>
|
export const ActiveStationTable = ({ stations }: { stations: TActiveStationPlayersData[] }) =>
|
||||||
{
|
{
|
||||||
@ -65,8 +65,8 @@ export const ActiveStationTable = ({ stations }: { stations: TActiveStationPlaye
|
|||||||
<div className="flex justify-center items-center gap-3 p-5 lg:p-5">
|
<div className="flex justify-center items-center gap-3 p-5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + station.steam }
|
<Link to={ "/profile/" + station.steam }
|
||||||
className="color-orchid">{ station.username }</Link> { station.player.flags.includes("verified") &&
|
className="color-orchid">{ station.username }</Link> <UserIcons
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
flags={ station.player.flags }/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
|
||||||
import { TActiveTrainPlayersData } from "../../../types/active.ts";
|
import { TActiveTrainPlayersData } from "../../../types/active.ts";
|
||||||
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
export const ActiveTrainTable = ({ trains }: {
|
export const ActiveTrainTable = ({ trains }: {
|
||||||
trains: TActiveTrainPlayersData[],
|
trains: TActiveTrainPlayersData[],
|
||||||
}) =>
|
}) =>
|
||||||
@ -70,8 +71,8 @@ export const ActiveTrainTable = ({ trains }: {
|
|||||||
<div className="flex items-center justify-center gap-3 p-5 lg:p-5">
|
<div className="flex items-center justify-center gap-3 p-5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + train.steam }
|
<Link to={ "/profile/" + train.steam }
|
||||||
className="color-orchid">{ train.username }</Link> { train.player.flags.includes("verified") &&
|
className="color-orchid">{ train.username }</Link> <UserIcons
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
flags={ train.player.flags }/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { TLeaderboardRecord } from "../../../types/leaderboard.ts";
|
import { TLeaderboardRecord } from "../../../types/leaderboard.ts";
|
||||||
import { formatTime } from "../../../util/time.ts";
|
import { formatTime } from "../../../util/time.ts";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
export const StationTable = ({ stations }: { stations: TLeaderboardRecord[] }) =>
|
export const StationTable = ({ stations }: { stations: TLeaderboardRecord[] }) =>
|
||||||
{
|
{
|
||||||
@ -58,8 +58,8 @@ export const StationTable = ({ stations }: { stations: TLeaderboardRecord[] }) =
|
|||||||
<div className="flex justify-center items-center gap-3 p-5 lg:p-5">
|
<div className="flex justify-center items-center gap-3 p-5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + station.id }
|
<Link to={ "/profile/" + station.id }
|
||||||
className="color-orchid">{ station.username }</Link> { station.flags.includes("verified") &&
|
className="color-orchid">{ station.username }</Link> <UserIcons flags={station.flags} />
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { TLeaderboardRecord } from "../../../types/leaderboard.ts";
|
import { TLeaderboardRecord } from "../../../types/leaderboard.ts";
|
||||||
import { formatTime } from "../../../util/time.ts";
|
import { formatTime } from "../../../util/time.ts";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import { FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
import { FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
||||||
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
export const TrainTable = ({ trains, setSortBy, sortBy }: {
|
export const TrainTable = ({ trains, setSortBy, sortBy }: {
|
||||||
trains: TLeaderboardRecord[],
|
trains: TLeaderboardRecord[],
|
||||||
@ -80,8 +80,7 @@ export const TrainTable = ({ trains, setSortBy, sortBy }: {
|
|||||||
<div className="flex items-center justify-center gap-3 p-5 lg:p-5">
|
<div className="flex items-center justify-center gap-3 p-5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + train.id }
|
<Link to={ "/profile/" + train.id }
|
||||||
className="color-orchid">{ train.username }</Link> { train.flags.includes("verified") &&
|
className="color-orchid">{ train.username }</Link> <UserIcons flags={train.flags} />
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ import { TLogStationData } from "../../../types/log.ts";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { FaCheck } from 'react-icons/fa6';
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
|
|
||||||
export const StationLog = ({ data }: { data: TLogStationData }) =>
|
export const StationLog = ({ data }: { data: TLogStationData }) =>
|
||||||
{
|
{
|
||||||
@ -50,7 +51,7 @@ export const StationLog = ({ data }: { data: TLogStationData }) =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
||||||
{ data.player.username }{ data.player.flags.includes('verified') && <FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
{ data.player.username } <UserIcons flags={data.player.flags} />
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@ import { TLogTrainData } from "../../../types/log.ts";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
|
|
||||||
export const TrainLog = ({ data }: { data: TLogTrainData }) =>
|
export const TrainLog = ({ data }: { data: TLogTrainData }) =>
|
||||||
@ -51,8 +51,7 @@ export const TrainLog = ({ data }: { data: TLogTrainData }) =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
||||||
{ data.player.username } { data.player.flags.includes("verified") &&
|
{ data.player.username } <UserIcons flags={data.player.flags} />
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,11 +17,8 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { TStationRecord } from "../../../types/station.ts";
|
import { TStationRecord } from "../../../types/station.ts";
|
||||||
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
|
||||||
|
|
||||||
|
|
||||||
// setSearchItem: Dispatch<SetStateAction<string>>
|
// setSearchItem: Dispatch<SetStateAction<string>>
|
||||||
export const StationTable = ({ stations }: {
|
export const StationTable = ({ stations }: {
|
||||||
@ -68,8 +65,7 @@ export const StationTable = ({ stations }: {
|
|||||||
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + (station.steam ?? station.player.id) }
|
<Link to={ "/profile/" + (station.steam ?? station.player.id) }
|
||||||
className="color-orchid">{ station.username ?? station.player.username }</Link> { station.player.flags.includes("verified") &&
|
className="color-orchid">{ station.username ?? station.player.username }</Link> <UserIcons flags={station.player.flags} />
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { TTrainRecord } from "../../../types/train.ts";
|
import { TTrainRecord } from "../../../types/train.ts";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
// setSearchItem: Dispatch<SetStateAction<string>>
|
// setSearchItem: Dispatch<SetStateAction<string>>
|
||||||
export const TrainTable = ({ trains }: {
|
export const TrainTable = ({ trains }: {
|
||||||
@ -75,8 +75,7 @@ export const TrainTable = ({ trains }: {
|
|||||||
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
<Link to={ "/profile/" + (train.steam ?? train.player.id) }
|
<Link to={ "/profile/" + (train.steam ?? train.player.id) }
|
||||||
className="color-orchid">{ train.username ?? train.player.username }</Link> { train.player.flags.includes("verified") &&
|
className="color-orchid">{ train.username ?? train.player.username }</Link> <UserIcons flags={train.player.flags} />
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ import { TProfileData } from "../../../types/profile.ts";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
||||||
import { formatTime } from "../../../util/time.ts";
|
import { formatTime } from "../../../util/time.ts";
|
||||||
import { FaCheck } from "react-icons/fa6";
|
|
||||||
import { useAuth } from "../../../hooks/useAuth.tsx";
|
import { useAuth } from "../../../hooks/useAuth.tsx";
|
||||||
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
|
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
|
||||||
import { post } from "../../../util/fetcher.ts";
|
import { post } from "../../../util/fetcher.ts";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
|
import { UserIcons } from "../../mini/util/UserIcons.tsx";
|
||||||
|
|
||||||
export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
||||||
{
|
{
|
||||||
@ -32,19 +32,19 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
const [ showTrains, setShowTrains ] = useState(false);
|
const [ showTrains, setShowTrains ] = useState(false);
|
||||||
const [ showStations, setShowStations ] = useState(false);
|
const [ showStations, setShowStations ] = useState(false);
|
||||||
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
|
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
|
||||||
const [ clearStatsModal, setClearStatsModal ] = useState(false);
|
const [ hideLeaderboardStatsModal, setHideLeaderboardStatsModal ] = useState(false);
|
||||||
const [ hideProfileModal, setHideProfileModal ] = useState(false);
|
const [ hideProfileModal, setHideProfileModal ] = useState(false);
|
||||||
|
|
||||||
const { isAdmin, token } = useAuth();
|
const { isAdmin, token } = useAuth();
|
||||||
|
|
||||||
const adminClearPlayerStats = () =>
|
const adminToggleHideLeaderboardPlayerProfile = () =>
|
||||||
{
|
{
|
||||||
post(`/admin/profile/${ data.player.id }/clear`, {}, { "X-Auth-Token": token })
|
post(`/admin/profile/${ data.player.id }/${ data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard" }`, {}, { "X-Auth-Token": token })
|
||||||
.then((response) =>
|
.then((response) =>
|
||||||
{
|
{
|
||||||
if (response.code === 200)
|
if (response.code === 200)
|
||||||
{
|
{
|
||||||
toast.success(t("admin.clear.alert"));
|
toast.success(t("admin.hideLeaderboard.alert"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -64,14 +64,12 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return <>
|
return <>
|
||||||
<ConfirmModal showModal={ clearStatsModal } setShowModal={ setClearStatsModal }
|
<ConfirmModal showModal={ hideLeaderboardStatsModal } setShowModal={ setHideLeaderboardStatsModal }
|
||||||
onConfirm={ adminClearPlayerStats } title={ t("admin.clear.modal.title") }
|
onConfirm={ adminToggleHideLeaderboardPlayerProfile } title={ t("admin.hideLeaderboard.modal.title") }
|
||||||
description={ t("admin.clear.modal.description") }/>
|
description={ t("admin.hideLeaderboard.modal.description") }/>
|
||||||
|
|
||||||
<ConfirmModal showModal={ hideProfileModal } setShowModal={ setHideProfileModal }
|
<ConfirmModal showModal={ hideProfileModal } setShowModal={ setHideProfileModal }
|
||||||
onConfirm={ adminHidePlayerProfile } title={ t("admin.hide.modal.title") }
|
onConfirm={ adminHidePlayerProfile } title={ t("admin.hide.modal.title") }
|
||||||
description={ t("admin.hide.modal.description") }/>
|
description={ t("admin.hide.modal.description") }/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="overflow-hidden rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
className="overflow-hidden rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||||
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
|
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
|
||||||
@ -83,8 +81,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
<h3 className="mb-1.5 text-2xl font-semibold text-black dark:text-white">
|
||||||
{ data.player.username } { data.player.flags.includes("verified") &&
|
{ data.player.username } <UserIcons flags={ data.player.flags }/>
|
||||||
<FaCheck className={ "inline text-meta-3 ml-1" }/> }
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -223,20 +220,24 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
|
|
||||||
|
|
||||||
{ isAdmin && <>
|
{ isAdmin && <>
|
||||||
|
|
||||||
|
|
||||||
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||||
<h1 className="text-xl text-black dark:text-white">Moderator actions</h1>
|
<h1 className="text-xl text-black dark:text-white">{ t("admin.header") }</h1>
|
||||||
|
|
||||||
<div className="items-center justify-center p-2.5 flex xl:p-5 gap-2 flex-wrap sm:flex-nowrap">
|
<div className="items-center justify-center p-2.5 flex xl:p-5 gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
|
||||||
onClick={ () => setClearStatsModal(true) }>
|
{ data.player.flags.includes("leaderboard_hidden") ?
|
||||||
{t("admin.clear.button")}
|
<button className={ "inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-success" }
|
||||||
</button>
|
onClick={ () => adminToggleHideLeaderboardPlayerProfile() }>
|
||||||
|
{ t("admin.hideLeaderboard.button2") }
|
||||||
|
</button> :
|
||||||
|
<button className={ "inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-danger" }
|
||||||
|
onClick={ () => setHideLeaderboardStatsModal(true) }>
|
||||||
|
{ t("admin.hideLeaderboard.button") }
|
||||||
|
</button> }
|
||||||
|
|
||||||
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
||||||
onClick={ () => setHideProfileModal(true) }>
|
onClick={ () => setHideProfileModal(true) }>
|
||||||
{t("admin.hide.button")}
|
{ t("admin.hide.button") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -244,7 +245,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
|
|
||||||
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||||
<h1 className="text-sm text-black dark:text-white">
|
<h1 className="text-sm text-black dark:text-white">
|
||||||
{t("profile.info", { date: dayjs(data.player.createdAt).format('DD/MM/YYYY') })}
|
{ t("profile.info", { date: dayjs(data.player.createdAt).format("DD/MM/YYYY") }) }
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -154,13 +154,15 @@
|
|||||||
"logout": "Log out"
|
"logout": "Log out"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"clear": {
|
"header": "Akcje moderacyjne",
|
||||||
|
"hideLeaderboard": {
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Are you sure?",
|
"title": "Are you sure?",
|
||||||
"description": "This action will permanently clear user statistics."
|
"description": "This action will hide user profile from leaderboard."
|
||||||
},
|
},
|
||||||
"button": "Clear profile",
|
"button": "Hide profile in leaderboard",
|
||||||
"alert": "Player stats cleared."
|
"button2": "Show profile in leaderboard",
|
||||||
|
"alert": "Player profile hidden."
|
||||||
},
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"modal": {
|
"modal": {
|
||||||
|
@ -154,13 +154,15 @@
|
|||||||
"logout": "Wyloguj"
|
"logout": "Wyloguj"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"clear": {
|
"header": "Moderator actions",
|
||||||
|
"hideLeaderboard": {
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Czy jesteś pewien?",
|
"title": "Czy jesteś pewien?",
|
||||||
"description": "Ta akcja permanentnie wyczyści wszystkie statystyki gracza."
|
"description": "Ta akcja ukryje profil gracza w tablicy wyników."
|
||||||
},
|
},
|
||||||
"button": "Wyczyść profil",
|
"button": "Ukryj profil w tablicy wyników",
|
||||||
"alert": "Wyczyszczono statystyki gracza."
|
"button2": "Pokaż profil w tablicy wyników",
|
||||||
|
"alert": "Ukryto profil gracza w tablicy wyników."
|
||||||
},
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"modal": {
|
"modal": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user