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:
Aleksander Wilczyński 2024-12-16 19:57:22 +01:00
commit 6c43ff7209
Signed by: gitea
GPG Key ID: CECFC30736A3D1C8
16 changed files with 171 additions and 69 deletions

View File

@ -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,
}); });

View File

@ -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"] }
} }
} }
]; ];

View File

@ -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");
} }

View File

@ -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");
} }

View 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" }/> }</>;
};

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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": {

View File

@ -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": {