Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
6d752c99d7 | |||
d516d22411 | |||
9833bcc09a | |||
0f2d0bcf44 | |||
35bbbd902c | |||
4b301c9644 | |||
69d174e5c4 | |||
8d2b870d54 | |||
4b6f41a6c7 | |||
b41a0a9127 | |||
418d5122bc | |||
94903146c4 | |||
fb806b0991 | |||
361fc9fb97 | |||
e14399d04b | |||
b19b18da5b | |||
837e3de296 | |||
3cfd4752a1 | |||
b6956bfcfc | |||
1947859e0d | |||
a628ad5d56 | |||
9deb009d82 | |||
2ead40d6ae | |||
9b63300a0a | |||
d51d7d36a8 | |||
f8afea20df | |||
68436020c1 | |||
f4561e41c3 | |||
1e01260e82 | |||
82397be39c | |||
99dc69ee8f | |||
996f16a313 | |||
0f8b1a4729 | |||
badefb0f7f | |||
2ba037fab5 | |||
637134081a | |||
96c17ffe85 | |||
8db158afe9 | |||
6015349c6f | |||
cd191a6f61 | |||
08bfe767bb | |||
6a4ebf56c4 | |||
1e4cafc07f | |||
623bdd8d42 | |||
51b3ff2e22 | |||
816ee5c454 | |||
9a3f004c72 | |||
f528da171b | |||
b23921a28c | |||
e045ded046 | |||
859b7ab3cc | |||
34432e9622 | |||
3af371703b | |||
fcef4e428e | |||
ca8e270c5e | |||
a9d35c604e | |||
8c24a29de2 | |||
e079a1177e | |||
7bf053e452 | |||
328f665c5c | |||
05fe82eff1 | |||
3143ce1058 | |||
85d18190a7 | |||
45434f5a2d | |||
922b7ea633 | |||
f0f01ccda1 | |||
947bd5dedc | |||
a2a9fd25b5 | |||
afdc700a64 | |||
f5ba173b24 | |||
7459649829 | |||
31c1f3fc40 | |||
df1d9df212 | |||
680a18d15a | |||
40f31ba723 | |||
f9974a3430 | |||
c5249da57e | |||
85e00e8d52 | |||
5841c77913 | |||
84d2972869 | |||
94407bc7c4 | |||
47695d7e4f | |||
529d8b8020 | |||
3db6ced4d2 | |||
e5614da846 | |||
71a5b77235 | |||
7617738ab2 | |||
9c3d3e0767 | |||
294068ee97 | |||
29a7ab3a6b | |||
b4cee2447d | |||
a4091c92e4 | |||
77a9540be4 | |||
f8f5a38add | |||
4c7482b919 | |||
f10c623aa8 | |||
4e090ed281 | |||
1b9c616e16 | |||
7fe575a651 | |||
f33c54ba26 | |||
a9094fd1ed |
.drone.ymlpackage.json
packages
backend
Dockerfilegit.jsonpackage.json
scripts
src
tsconfig.jsonfrontend
Dockerfileindex.htmlpackage.jsonpostcss.config.cjs
src
App.tsx
components
mini
alerts
header
icons
loaders
modal
profile
sidebar
util
pages
css
hooks
i18n
layout
lib.d.tsmain.tsxpages
react-app-env.d.tstypes
49
.drone.yml
Normal file
49
.drone.yml
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
|
||||
steps:
|
||||
- name: build-backend
|
||||
image: plugins/docker
|
||||
environment:
|
||||
TAG:
|
||||
${DRONE_TAG}
|
||||
COMMIT:
|
||||
${DRONE_COMMIT}
|
||||
settings:
|
||||
insecure: true
|
||||
repo: 10.5.0.103:1222/simrail-backend
|
||||
registry: 10.5.0.103:1222
|
||||
context: './packages/backend'
|
||||
dockerfile: './packages/backend/Dockerfile'
|
||||
build_args_from_env:
|
||||
- TAG
|
||||
- COMMIT
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_TAG##v}
|
||||
|
||||
- name: build-frontend
|
||||
image: plugins/docker
|
||||
environment:
|
||||
VITE_API_URL:
|
||||
from_secret: VITE_API_URL
|
||||
VITE_STATS_KEY:
|
||||
from_secret: VITE_STATS_KEY
|
||||
settings:
|
||||
insecure: true
|
||||
repo: 10.5.0.103:1222/simrail-frontend
|
||||
registry: 10.5.0.103:1222
|
||||
context: './packages/frontend'
|
||||
dockerfile: './packages/frontend/Dockerfile'
|
||||
build_args_from_env:
|
||||
- VITE_API_URL
|
||||
- VITE_STATS_KEY
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_TAG##v}
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
@ -4,7 +4,6 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "yarn workspace backend build && yarn workspace frontend build",
|
||||
"postbuild": "copyfiles --error ./LICENSE.txt ./dist && copyfiles --error ./LICENSE.txt ./dist/frontend/",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn workspace backend start\" \"yarn workspace frontend dev\""
|
||||
},
|
||||
"workspaces": [
|
||||
|
24
packages/backend/Dockerfile
Normal file
24
packages/backend/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
FROM node:21-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN yarn add -D typescript
|
||||
RUN yarn rawbuild
|
||||
|
||||
ARG COMMIT
|
||||
ENV COMMIT $COMMIT
|
||||
ARG TAG
|
||||
ENV TAG $TAG
|
||||
|
||||
RUN yarn make-git-info
|
||||
|
||||
# Install Doppler CLI
|
||||
RUN wget -q -t3 'https://packages.doppler.com/public/cli/rsa.8004D9FF50437357.key' -O /etc/apk/keys/cli@doppler-8004D9FF50437357.rsa.pub && \
|
||||
echo 'https://packages.doppler.com/public/cli/alpine/any-version/main' | tee -a /etc/apk/repositories && \
|
||||
apk add doppler
|
||||
|
||||
|
||||
|
||||
ENTRYPOINT ["doppler", "run", "--"]
|
||||
CMD ["node", "/app/dist/src"]
|
4
packages/backend/git.json
Normal file
4
packages/backend/git.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"tag": "",
|
||||
"commit": ""
|
||||
}
|
@ -4,8 +4,10 @@
|
||||
"main": "../../dist/backend/index.js",
|
||||
"version": "3.0.0",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "yarn build && doppler run node ../../dist/backend/index.js"
|
||||
"build": "docker build --progress=plain -t simrailpro:backend .",
|
||||
"rawbuild": "yarn tsc",
|
||||
"start": "yarn rawbuild && node --env-file=.env dist/index.js",
|
||||
"make-git-info": "node scripts/make-git.js"
|
||||
},
|
||||
"author": "Aleksander <alekswilc> Wilczyński",
|
||||
"license": "AGPL-3.0-only",
|
||||
@ -15,6 +17,7 @@
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"tsc": "^2.0.4",
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"type": "module",
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
24
packages/backend/scripts/make-git.js
Normal file
24
packages/backend/scripts/make-git.js
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 fs from 'node:fs/promises';
|
||||
|
||||
(async () => {
|
||||
await fs.writeFile('dist/git.json', JSON.stringify({
|
||||
tag: process.env.TAG ?? "",
|
||||
commit: process.env.COMMIT?.substring(0, 7) ?? "",
|
||||
}))
|
||||
})();
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -66,10 +66,9 @@ export class ActivePlayersRoute
|
||||
|
||||
app.get("/train", async (req, res) =>
|
||||
{
|
||||
const s = req.query.q?.toString().split(",").map(x => new RegExp(escapeRegexString(x), "i"));
|
||||
const s = req.query.query?.toString().split(",").map(x => new RegExp(escapeRegexString(x), "i"));
|
||||
const sserver = req.query.server?.toString();
|
||||
|
||||
|
||||
let a: ActiveTrain[] = [];
|
||||
|
||||
for (const data of sserver ? [ client.trains[ sserver as Server["ServerCode"] ] ] : Object.values(client.trains))
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
53
packages/backend/src/http/routes/images.ts
Normal file
53
packages/backend/src/http/routes/images.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 { Router } from "express";
|
||||
import { SuccessResponseBuilder } from '../responseBuilder.js';
|
||||
import { generateUrl } from '../../util/imgproxy.js';
|
||||
import { trainsMap, stationsMap } from '../../util/contants.js';
|
||||
|
||||
export class ImagesRoute {
|
||||
static load() {
|
||||
const app = Router();
|
||||
|
||||
app.get("/", async (req, res) => {
|
||||
|
||||
const trains: Record<string, string> = {};
|
||||
|
||||
Object.keys(trainsMap).forEach(x => {
|
||||
trains[x] = generateUrl(trainsMap[x], "f:png/q:50/rs:auto:512:1024:1");
|
||||
})
|
||||
|
||||
const stations: Record<string, string> = {};
|
||||
|
||||
Object.keys(stationsMap).forEach(x => {
|
||||
stations[x] = generateUrl(stationsMap[x], "f:png/q:50/rs:auto:512:1024:1");
|
||||
})
|
||||
|
||||
res.json(
|
||||
new SuccessResponseBuilder()
|
||||
.setCode(200)
|
||||
.setData({
|
||||
trains,
|
||||
stations
|
||||
})
|
||||
.toJSON(),
|
||||
);
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -67,7 +67,7 @@ export class LogRoute
|
||||
log.player.avatar = generateUrl(log.player.avatar, "rs:auto:256:256:1/f:png");
|
||||
}
|
||||
|
||||
res.status(200).json(new SuccessResponseBuilder().setCode(200).setData(log.toJSON()));
|
||||
res.status(200).json(new SuccessResponseBuilder().setCode(200).setData(log.toJSON()).toJSON());
|
||||
});
|
||||
|
||||
return app;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -24,6 +24,7 @@ import { StatsRoute } from "./routes/stats.js";
|
||||
import { LogRoute } from "./routes/log.js";
|
||||
import { ActivePlayersRoute } from "./routes/activePlayer.js";
|
||||
import { AdminRoute } from "./routes/admin.js";
|
||||
import { ImagesRoute } from './routes/images.js';
|
||||
|
||||
export class ApiModule
|
||||
{
|
||||
@ -39,6 +40,8 @@ export class ApiModule
|
||||
router.use("/leaderboard/", LeaderboardRoute.load());
|
||||
router.use("/active/", ActivePlayersRoute.load());
|
||||
router.use("/admin/", AdminRoute.load());
|
||||
router.use("/images/", ImagesRoute.load());
|
||||
|
||||
|
||||
router.use("/stats/", StatsRoute.load());
|
||||
router.use("/log/", LogRoute.load());
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
2
packages/backend/src/types/typings.d.ts
vendored
2
packages/backend/src/types/typings.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 wcmatch from "wildcard-match";
|
||||
|
||||
/*
|
||||
@ -120,10 +137,80 @@ export const trainsList = [
|
||||
"4E/EU07-*",
|
||||
],
|
||||
},
|
||||
{
|
||||
train: 'Ty2',
|
||||
pattern: [
|
||||
'Ty2/*'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const stationsMap: Record<string, string> = {
|
||||
"Grodzisk Mazowiecki": "https://api.simrail.eu:8083/Thumbnails/Stations/gr1m.jpg",
|
||||
"Korytów": "https://api.simrail.eu:8083/Thumbnails/Stations/kr1m.jpg",
|
||||
"Szeligi": "https://api.simrail.eu:8083/Thumbnails/Stations/sz1m.jpg",
|
||||
"Włoszczowa Północ": "https://api.simrail.eu:8083/Thumbnails/Stations/wp1m.jpg",
|
||||
"Knapówka": "https://api.simrail.eu:8083/Thumbnails/Stations/kn1m.jpg",
|
||||
"Psary": "https://api.simrail.eu:8083/Thumbnails/Stations/ps1m.jpg",
|
||||
"Góra Włodowska": "https://api.simrail.eu:8083/Thumbnails/Stations/gw1m.jpg",
|
||||
"Idzikowice": "https://api.simrail.eu:8083/Thumbnails/Stations/id1m.jpg",
|
||||
"Katowice Zawodzie": "https://api.simrail.eu:8083/Thumbnails/Stations/kz1m.jpg",
|
||||
"Sosnowiec Główny": "https://api.simrail.eu:8083/Thumbnails/Stations/sg1m.jpg",
|
||||
"Dąbrowa Górnicza": "https://api.simrail.eu:8083/Thumbnails/Stations/dg1m.jpg",
|
||||
"Zawiercie": "https://api.simrail.eu:8083/Thumbnails/Stations/zw1m.jpg",
|
||||
"Będzin": "https://api.simrail.eu:8083/Thumbnails/Stations/b1m.jpg",
|
||||
"Sosnowiec Południowy": "https://api.simrail.eu:8083/Thumbnails/Stations/spl1m.jpg",
|
||||
"Opoczno Południe": "https://api.simrail.eu:8083/Thumbnails/Stations/op1m.jpg",
|
||||
"Dąbrowa Górnicza Wschodnia": "https://api.simrail.eu:8083/Thumbnails/Stations/dws1m.jpg",
|
||||
"Dorota": "https://api.simrail.eu:8083/Thumbnails/Stations/dra1m.jpg",
|
||||
"Łazy Ła": "https://api.simrail.eu:8083/Thumbnails/Stations/la1m.jpg",
|
||||
"Łazy": "https://api.simrail.eu:8083/Thumbnails/Stations/lb1m.jpg",
|
||||
"Juliusz": "https://api.simrail.eu:8083/Thumbnails/Stations/ju1m.jpg",
|
||||
"Łazy Łc": "https://api.simrail.eu:8083/Thumbnails/Stations/lc1m.jpg",
|
||||
"Katowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ko1m.jpg",
|
||||
"Dąbrowa Górnicza Ząbkowice": "https://api.simrail.eu:8083/Thumbnails/Stations/dz1m.jpg",
|
||||
"Sławków": "https://api.simrail.eu:8083/Thumbnails/Stations/sl1m.jpg",
|
||||
"Starzyny": "https://api.simrail.eu:8083/Thumbnails/Stations/str1m.jpg",
|
||||
"Bukowno": "https://api.simrail.eu:8083/Thumbnails/Stations/bo1m.jpg",
|
||||
"Tunel": "https://api.simrail.eu:8083/Thumbnails/Stations/tl1m.jpg",
|
||||
"Dąbrowa Górnicza Huta Katowice": "https://api.simrail.eu:8083/Thumbnails/Stations/dghk1m.jpg",
|
||||
"Sosnowiec Kazimierz": "https://api.simrail.eu:8083/Thumbnails/Stations/skz1m.jpg",
|
||||
"Pruszków": "https://api.simrail.eu:8083/Thumbnails/Stations/pr1m.jpg",
|
||||
"Strzałki": "https://api.simrail.eu:8083/Thumbnails/Stations/st1m.jpg",
|
||||
"Olszamowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ol1m.jpg",
|
||||
"Miechów": "https://api.simrail.eu:8083/Thumbnails/Stations/mi1m.jpg",
|
||||
"Kraków Przedmieście": "https://api.simrail.eu:8083/Thumbnails/Stations/kpm1m.jpg",
|
||||
"Kraków Batowice": "https://api.simrail.eu:8083/Thumbnails/Stations/kb1m.jpg",
|
||||
"Raciborowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ra1m.jpg",
|
||||
"Zastów": "https://api.simrail.eu:8083/Thumbnails/Stations/zs1m.jpg",
|
||||
"Niedźwiedź": "https://api.simrail.eu:8083/Thumbnails/Stations/nd1m.jpg",
|
||||
"Słomniki": "https://api.simrail.eu:8083/Thumbnails/Stations/sm1m.jpg",
|
||||
"Kozłów": "https://api.simrail.eu:8083/Thumbnails/Stations/koz1m.jpg",
|
||||
"N/A": 'https://shared.steamstatic.com/store_item_assets/steam/apps/1422130/header.jpg'
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const trainsMap: Record<string, string> = {
|
||||
"Traxx (E186)": "https://wiki.simrail.eu/vehicle/poland/trains/elec-loco/traxx/20241029163359_1.jpg",
|
||||
"Dragon2 (E6ACTa, E6ACTadb)": "https://wiki.simrail.eu/vehicle/e6acta-016.jpg",
|
||||
"Dragon2 (ET25)": "https://wiki.simrail.eu/vehicle/et25-002.jpg",
|
||||
"Pendolino (ED250)": "https://wiki.simrail.eu/vehicle/ed250-001.png",
|
||||
"EN57": "https://wiki.simrail.eu/vehicle/en57-009.png",
|
||||
"EN71": "https://wiki.simrail.eu/vehicle/en71-002.png",
|
||||
"EN76": "https://wiki.simrail.eu/vehicle/en76-006.jpg",
|
||||
"EN96": "https://wiki.simrail.eu/vehicle/en96-001.jpg",
|
||||
"EP07": "https://wiki.simrail.eu/vehicle/ep07-174.jpg",
|
||||
"EP08": "https://wiki.simrail.eu/vehicle/poland/trains/elec-loco/ep08/20241106002003_1.jpg",
|
||||
"ET22": "https://wiki.simrail.eu/vehicle/et22-243.png",
|
||||
"EU07": "https://wiki.simrail.eu/vehicle/eu07-005.jpg",
|
||||
"Ty2": "https://wiki.simrail.eu/vehicle/ty2-70.png",
|
||||
"N/A": 'https://shared.steamstatic.com/store_item_assets/steam/apps/1422130/header.jpg'
|
||||
};
|
||||
|
||||
|
||||
export const getVehicle = (name: string) =>
|
||||
{
|
||||
return trainsList.find(x => wcmatch(x.pattern)(name))?.train;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -14,7 +14,7 @@
|
||||
* See LICENSE for more.
|
||||
*/
|
||||
|
||||
import { execSync } from "child_process";
|
||||
import gitInfo from '../../git.json' with { type: "json" };
|
||||
|
||||
export class GitUtil
|
||||
{
|
||||
@ -22,26 +22,12 @@ export class GitUtil
|
||||
|
||||
private static getLatestVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
const data = execSync("git describe --tags --exact-match").toString();
|
||||
return data.replace("\n", "");
|
||||
} catch
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
return gitInfo.tag;
|
||||
}
|
||||
|
||||
private static getLatestCommit()
|
||||
{
|
||||
try
|
||||
{
|
||||
const data = execSync("git rev-parse --short HEAD").toString();
|
||||
return data.replace("\n", "");
|
||||
} catch
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
return gitInfo.commit;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -28,7 +28,7 @@ export const imgProxySign = (target: string) =>
|
||||
|
||||
export const generateUrl = (url: string, options: string = "rs:auto:128:128:1/f:png") =>
|
||||
{
|
||||
if (url.includes('https://imgproxy.alekswilc.dev/')) return url;
|
||||
if (url.includes('https://proxy.cdn.alekswilc.dev/')) return url;
|
||||
|
||||
if (process.env.NODE_ENV === "development")
|
||||
{
|
||||
@ -38,5 +38,5 @@ export const generateUrl = (url: string, options: string = "rs:auto:128:128:1/f:
|
||||
}
|
||||
|
||||
const signature = imgProxySign(`/${ options }/plain/${ url }`);
|
||||
return `https://imgproxy.alekswilc.dev/${ signature }/${ options }/plain/${ url }`;
|
||||
return `https://proxy.cdn.alekswilc.dev/${ signature }/${ options }/plain/${ url }`;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -39,7 +39,7 @@
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "../../dist/backend", /* Specify an output folder for all emitted files. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
|
23
packages/frontend/Dockerfile
Normal file
23
packages/frontend/Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
|
||||
RUN npm install
|
||||
|
||||
RUN npm i -g serve vite
|
||||
|
||||
ARG VITE_API_URL
|
||||
ENV VITE_API_URL $VITE_API_URL
|
||||
|
||||
ARG VITE_STATS_KEY
|
||||
ENV VITE_STATS_KEY $VITE_STATS_KEY
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run rawbuild
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "serve", "-s", "dist" ]
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
~ Copyright (C) 2025 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
|
||||
|
@ -5,8 +5,9 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"rawbuild": "vite build",
|
||||
"preview": "vite preview",
|
||||
"build": "docker build --progress=plain -t simrailpro:frontend ."
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.13",
|
||||
@ -35,7 +36,7 @@
|
||||
"prettier": "^3.0.0",
|
||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^4.4.7",
|
||||
"vite": "^6.2.0",
|
||||
"webpack": "^5.88.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 { Dispatch, SetStateAction } from "react";
|
||||
|
||||
export const ConfirmModal = ({ showModal, setShowModal, onConfirm, title, description }: {
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 { useTranslation } from 'react-i18next';
|
||||
import { formatTime } from '../../../util/time.ts'
|
||||
|
||||
export const StationStat = ({ stationName, time, image }: { stationName: string, time: number, image: string }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
stationName = stationName === 'N/A'
|
||||
? "Untracked" : stationName
|
||||
|
||||
return <div
|
||||
key={stationName}
|
||||
className="flex flex-col align-center items-center rounded border border-stroke bg-stroke shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||
<div className="p-4">
|
||||
<img className=""
|
||||
src={image}
|
||||
alt="station icon" />
|
||||
</div>
|
||||
<div className="flex flex-col p-2 align-center items-center">
|
||||
<h4 className="mb-3 text-xl font-semibold text-black dark:text-white">
|
||||
{stationName}
|
||||
</h4>
|
||||
<p className={'break-words'}>{t('profile.stations.time', { time: formatTime(time) })}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
43
packages/frontend/src/components/mini/profile/TrainStat.tsx
Normal file
43
packages/frontend/src/components/mini/profile/TrainStat.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 { useTranslation } from 'react-i18next'
|
||||
import { formatTime } from '../../../util/time.ts'
|
||||
|
||||
export const TrainStat = ({ trainName, time, distance, score, image }: { trainName: string, time: number, distance: number, score: number, image: string }) => {
|
||||
const { t } = useTranslation();
|
||||
trainName = trainName === 'N/A'
|
||||
? "Untracked" : trainName
|
||||
|
||||
return <div
|
||||
key={trainName}
|
||||
className="flex flex-col align-center items-center rounded border border-stroke bg-stroke shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||
<div className="p-4">
|
||||
<img className=""
|
||||
src={image}
|
||||
alt="train icon" />
|
||||
</div>
|
||||
<div className="flex flex-col p-2 align-center items-center">
|
||||
<h4 className="mb-3 text-xl font-semibold text-black dark:text-white">
|
||||
{trainName}
|
||||
</h4>
|
||||
<p className={'break-words'}>{t('profile.trains.time', { time: formatTime(time) })}</p>
|
||||
<p className={'break-words'}>{t('profile.trains.distance', { distance: Math.floor(distance / 1000) })}m</p>
|
||||
<p className={'break-words pb-4'}>{t('profile.trains.score', { score: score })}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -16,59 +16,58 @@
|
||||
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
|
||||
|
||||
const getPaginationNums = (page: number, pages: number) => {
|
||||
if (pages <= 5)
|
||||
return Array.from({ length: pages }, (_, i) => i + 1);
|
||||
|
||||
const numbers = [1];
|
||||
if (page <= 3) {
|
||||
numbers.push(2, 3, 4);
|
||||
} else if (page >= pages - 2) {
|
||||
numbers.push(pages - 3, pages - 2, pages - 1);
|
||||
} else {
|
||||
numbers.push(page - 1, page, page + 1);
|
||||
}
|
||||
|
||||
numbers.push(pages);
|
||||
return [...new Set(numbers)].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
export const Paginator = ({ page, setPage, pages }: {
|
||||
page: number,
|
||||
pages: number,
|
||||
setPage: Dispatch<SetStateAction<number>>
|
||||
}) =>
|
||||
{
|
||||
let numbers = [ 1, page - 2, page - 1, page, page + 1, page + 2 ];
|
||||
|
||||
page === 1 && (numbers = [ page, page + 1, page + 2, page + 3, page + 4 ]);
|
||||
|
||||
page === 2 && (numbers = [ page - 1, page, page + 1, page + 2, page + 3 ]);
|
||||
|
||||
page === 3 && (numbers = [ page - 2, page - 1, page, page + 1, page + 2 ]);
|
||||
|
||||
(page === pages) && (numbers = [ 1, page - 4, page - 3, page - 2, page - 1 ]);
|
||||
|
||||
(page === (pages - 1)) && (numbers = [ 1, page - 3, page - 2, page - 1, page ]);
|
||||
|
||||
(page === (pages - 2)) && (numbers = [ 1, page - 2, page - 1, page, page + 1 ]);
|
||||
|
||||
numbers = numbers.filter(x => (pages + 1) >= x && x > 0);
|
||||
}) => {
|
||||
// todo: rewrite this shit XDDDDDDDD
|
||||
const numbers = getPaginationNums(page, pages);
|
||||
|
||||
return <div
|
||||
className="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark flex flex-row align-center justify-center p-2">
|
||||
className="rounded-sm flex flex-row align-center justify-center p-2">
|
||||
<ul className="flex flex-wrap items-center">
|
||||
<li>
|
||||
<a className="cursor-pointer flex h-9 w-9 items-center justify-center rounded-l-md border border-stroke hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark"
|
||||
onClick={ () => setPage(page => (page - 1) < 1 ? 1 : page - 1) }>
|
||||
onClick={() => setPage(page => (page - 1) < 1 ? 1 : page - 1)}>
|
||||
<svg className="fill-current" width="8" height="16" viewBox="0 0 8 16" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.17578 15.1156C7.00703 15.1156 6.83828 15.0593 6.72578 14.9187L0.369531 8.44995C0.116406 8.19683 0.116406 7.80308 0.369531 7.54995L6.72578 1.0812C6.97891 0.828076 7.37266 0.828076 7.62578 1.0812C7.87891 1.33433 7.87891 1.72808 7.62578 1.9812L1.71953 7.99995L7.65391 14.0187C7.90703 14.2718 7.90703 14.6656 7.65391 14.9187C7.48516 15.0312 7.34453 15.1156 7.17578 15.1156Z"
|
||||
fill=""></path>
|
||||
fill=""></path>
|
||||
</svg>
|
||||
</a></li>
|
||||
|
||||
{ numbers.map(num =>
|
||||
{
|
||||
{numbers.map(num => {
|
||||
return <li>
|
||||
<a onClick={ () => setPage(num) }
|
||||
className={ `cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${ page === num && "text-primary border-primary dark:border-primary" }` }>{ num }</a>
|
||||
<a onClick={() => setPage(num)}
|
||||
className={`cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${page === num && "text-primary border-primary dark:border-primary"}`}>{num}</a>
|
||||
</li>;
|
||||
}) }
|
||||
|
||||
{ !!pages && <li>
|
||||
<a className={ `cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${ page === pages && "text-primary border-primary dark:border-primary" }` }
|
||||
onClick={ () => setPage(pages) }>{ pages }</a></li> }
|
||||
})}
|
||||
<li>
|
||||
<a className="cursor-pointer flex h-9 w-9 items-center justify-center rounded-r-md border border-stroke border-l-transparent hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none"
|
||||
onClick={ () => setPage(page => (page + 1) > pages ? pages : page + 1) }>
|
||||
onClick={() => setPage(page => (page + 1) > pages ? pages : page + 1)}>
|
||||
<svg className="fill-current" width="8" height="16" viewBox="0 0 8 16" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.819531 15.1156C0.650781 15.1156 0.510156 15.0593 0.369531 14.9468C0.116406 14.6937 0.116406 14.3 0.369531 14.0468L6.27578 7.99995L0.369531 1.9812C0.116406 1.72808 0.116406 1.33433 0.369531 1.0812C0.622656 0.828076 1.01641 0.828076 1.26953 1.0812L7.62578 7.54995C7.87891 7.80308 7.87891 8.19683 7.62578 8.44995L1.26953 14.9187C1.15703 15.0312 0.988281 15.1156 0.819531 15.1156Z"
|
||||
fill="">
|
||||
fill="">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -15,9 +15,8 @@
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { TProfileData } from "../../../types/profile.ts";
|
||||
import { TProfileData, TProfilePlayer } from "../../../types/profile.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
||||
import { formatTime } from "../../../util/time.ts";
|
||||
import { useAuth } from "../../../hooks/useAuth.tsx";
|
||||
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
|
||||
@ -25,258 +24,253 @@ import { post } from "../../../util/fetcher.ts";
|
||||
import { toast } from "react-toastify";
|
||||
import dayjs from "dayjs";
|
||||
import { UserIcons } from "../../mini/icons/UserIcons.tsx";
|
||||
import { StationStat } from '../../mini/profile/StationStat.tsx';
|
||||
import { chunk } from '../../../util/chunk.ts';
|
||||
import { Paginator } from '../../mini/util/Paginator.tsx';
|
||||
import { TrainStat } from '../../mini/profile/TrainStat.tsx';
|
||||
import { TImagesData } from '../../../types/images.ts';
|
||||
|
||||
export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
||||
{
|
||||
const sortTrainsByList: Record<number, string> = {
|
||||
[0]: 'time',
|
||||
[1]: 'score',
|
||||
[2]: 'distance',
|
||||
}
|
||||
|
||||
const [ showTrains, setShowTrains ] = useState(false);
|
||||
const [ showStations, setShowStations ] = useState(false);
|
||||
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
|
||||
const [ hideLeaderboardStatsModal, setHideLeaderboardStatsModal ] = useState(false);
|
||||
const [ hideProfileModal, setHideProfileModal ] = useState(false);
|
||||
export const ProfileCard = ({ data, images }: { data: TProfileData, images: TImagesData }) => {
|
||||
const [sortTrainsBy, setSortTrainsBy] = useState(2);
|
||||
const [sortTrainsBy2, setSortTrainsBy2] = useState(0);
|
||||
const [sortStationsBy, setSortStationsBy] = useState(0);
|
||||
const [hideLeaderboardStatsModal, setHideLeaderboardStatsModal] = useState(false);
|
||||
const [hideProfileModal, setHideProfileModal] = useState(false);
|
||||
|
||||
const { isAdmin, token } = useAuth();
|
||||
|
||||
const adminToggleHideLeaderboardPlayerProfile = () =>
|
||||
{
|
||||
post(`/admin/profile/${ data.player.id }/${ data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard" }`, {}, { "X-Auth-Token": token })
|
||||
.then((response) =>
|
||||
{
|
||||
if (response.code === 200)
|
||||
{
|
||||
toast.success(t("admin.hideLeaderboard.alert"));
|
||||
}
|
||||
});
|
||||
// #region ADMIN
|
||||
const adminToggleHideLeaderboardPlayerProfile = () => {
|
||||
post(`/admin/profile/${data.player.id}/${data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard"}`, {}, { "X-Auth-Token": token })
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
toast.success(t("admin.hideLeaderboard.alert"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const adminHidePlayerProfile = () =>
|
||||
{
|
||||
post(`/admin/profile/${ data.player.id }/hide`, {}, { "X-Auth-Token": token })
|
||||
.then((response) =>
|
||||
{
|
||||
if (response.code === 200)
|
||||
{
|
||||
toast.success(t("admin.hide.alert"));
|
||||
}
|
||||
});
|
||||
const adminHidePlayerProfile = () => {
|
||||
post(`/admin/profile/${data.player.id}/hide`, {}, { "X-Auth-Token": token })
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
toast.success(t("admin.hide.alert"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const adminForceUpdate = () =>
|
||||
{
|
||||
post(`/admin/profile/${ data.player.id }/forceUpdate`, {}, { "X-Auth-Token": token })
|
||||
.then((response) =>
|
||||
{
|
||||
if (response.code === 200)
|
||||
{
|
||||
toast.success(t("admin.update.alert"));
|
||||
}
|
||||
});
|
||||
const adminForceUpdate = () => {
|
||||
post(`/admin/profile/${data.player.id}/forceUpdate`, {}, { "X-Auth-Token": token })
|
||||
.then((response) => {
|
||||
if (response.code === 200) {
|
||||
toast.success(t("admin.update.alert"));
|
||||
}
|
||||
});
|
||||
};
|
||||
// #endregion
|
||||
|
||||
|
||||
const sortStations = (a: keyof TProfilePlayer['dispatcherStats'], b: keyof TProfilePlayer['dispatcherStats']) => {
|
||||
if (sortStationsBy) {
|
||||
const _a = a;
|
||||
a = b;
|
||||
b = _a;
|
||||
}
|
||||
|
||||
return data.player.dispatcherStats[b].time - data.player.dispatcherStats[a].time
|
||||
}
|
||||
|
||||
const sortTrains = (a: keyof TProfilePlayer['trainStats'], b: keyof TProfilePlayer['trainStats']) => {
|
||||
if (sortTrainsBy2) {
|
||||
const _a = a;
|
||||
a = b;
|
||||
b = _a;
|
||||
}
|
||||
|
||||
return data.player.trainStats[b][(sortTrainsByList[sortTrainsBy] ?? 'distance') as 'distance'] - data.player.trainStats[a][(sortTrainsByList[sortTrainsBy] ?? 'distance') as 'distance'];
|
||||
}
|
||||
|
||||
const dispatcherStats = [...chunk(Object.keys(data.player.dispatcherStats).sort(sortStations), 8)];
|
||||
const [dispatcherPage, setDispatcherPage] = useState(1);
|
||||
|
||||
const trainStats = [...chunk(Object.keys(data.player.trainStats).sort(sortTrains), 8)];
|
||||
const [trainPage, setTrainPage] = useState(1);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
return <>
|
||||
<ConfirmModal showModal={ hideLeaderboardStatsModal } setShowModal={ setHideLeaderboardStatsModal }
|
||||
onConfirm={ adminToggleHideLeaderboardPlayerProfile }
|
||||
title={ t("admin.hideLeaderboard.modal.title") }
|
||||
description={ t("admin.hideLeaderboard.modal.description") }/>
|
||||
<ConfirmModal showModal={ hideProfileModal } setShowModal={ setHideProfileModal }
|
||||
onConfirm={ adminHidePlayerProfile } title={ t("admin.hide.modal.title") }
|
||||
description={ t("admin.hide.modal.description") }/>
|
||||
<ConfirmModal showModal={hideLeaderboardStatsModal} setShowModal={setHideLeaderboardStatsModal}
|
||||
onConfirm={adminToggleHideLeaderboardPlayerProfile}
|
||||
title={t("admin.hideLeaderboard.modal.title")}
|
||||
description={t("admin.hideLeaderboard.modal.description")} />
|
||||
<ConfirmModal showModal={hideProfileModal} setShowModal={setHideProfileModal}
|
||||
onConfirm={adminHidePlayerProfile} title={t("admin.hide.modal.title")}
|
||||
description={t("admin.hide.modal.description")} />
|
||||
<div
|
||||
className="overflow-hidden rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||
className="overflow-hidden ">
|
||||
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
|
||||
<div
|
||||
className="mx-auto max-w-44 rounded-full">
|
||||
className="mx-auto max-w-44 rounded-full">
|
||||
<div className="relative rounded-full">
|
||||
<img className="rounded-full" src={ data.player.avatar } alt="profile"/>
|
||||
{ data.active &&
|
||||
<span className="absolute w-full rounded-full border-white bg-[#219653] dark:border-black max-w-5.5 right-0 top-0 h-5.5 border-[3px]"></span> }
|
||||
<img className="rounded-full" src={data.player.avatar} alt="profile" />
|
||||
{data.active &&
|
||||
<span className="absolute w-full rounded-full border-white bg-[#219653] dark:border-black max-w-5.5 right-0 top-0 h-5.5 border-[3px]"></span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<h3 className="text-2xl font-semibold text-black dark:text-white">
|
||||
{ data.player.username } <UserIcons flags={ data.player.flags }/>
|
||||
{data.player.username} <UserIcons flags={data.player.flags} />
|
||||
</h3>
|
||||
|
||||
<div
|
||||
className="mx-auto mt-4.5 mb-5.5 grid max-w-94 grid-cols-2 rounded-md border border-stroke py-2.5 shadow-1 dark:border-strokedark dark:bg-[#37404F]">
|
||||
className="mx-auto mt-4.5 mb-5.5 grid max-w-94 grid-cols-2 rounded-md border border-stroke py-2.5 shadow-1 dark:border-strokedark dark:bg-[#37404F]">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
|
||||
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
|
||||
<span className="font-semibold text-black dark:text-white">
|
||||
{ Math.floor(data.player.trainDistance / 1000) }km
|
||||
{Math.floor(data.player.trainDistance / 1000)}km
|
||||
</span>
|
||||
<span className="text-sm text-wrap">{ t("profile.stats.distance") }</span>
|
||||
<span className="text-sm text-wrap">{t("profile.stats.distance")}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
|
||||
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
|
||||
<span className="font-semibold text-black dark:text-white">
|
||||
{ formatTime(data.player.dispatcherTime) }
|
||||
{formatTime(data.player.dispatcherTime)}
|
||||
</span>
|
||||
<span className="text-sm text-wrap">{ t("profile.stats.time") }</span>
|
||||
<span className="text-sm text-wrap">{t("profile.stats.time")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ data.active && data.active.type === "train" &&
|
||||
<div className="mx-auto text-center">
|
||||
<h4 className="font-semibold text-black dark:text-white">{ t("profile.active.train", { train: `${ data.active.trainName } - ${ data.active.trainNumber }`, server: data.active.server.toUpperCase() }) }</h4>
|
||||
</div> }
|
||||
{data.active && data.active.type === "train" &&
|
||||
<div className="mx-auto text-center">
|
||||
<h4 className="font-semibold text-black dark:text-white">{t("profile.active.train", { train: `${data.active.trainName} - ${data.active.trainNumber}`, server: data.active.server.toUpperCase() })}</h4>
|
||||
</div>}
|
||||
|
||||
{ data.active && data.active.type === "station" &&
|
||||
<div className="mx-auto text-center">
|
||||
<h4 className="font-semibold text-black dark:text-white">{ t("profile.active.station", { station: `${ data.active.stationName } - ${ data.active.stationShort }`, server: data.active.server.toUpperCase() }) }</h4>
|
||||
</div> }
|
||||
{data.active && data.active.type === "station" &&
|
||||
<div className="mx-auto text-center">
|
||||
<h4 className="font-semibold text-black dark:text-white">{t("profile.active.station", { station: `${data.active.stationName} - ${data.active.stationShort}`, server: data.active.server.toUpperCase() })}</h4>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
|
||||
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.stations.header")}</h1>
|
||||
<div className="flex flex-row gap-4">
|
||||
<a className='cursor-pointer' onClick={() => setSortStationsBy((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
|
||||
<strong>{t('profile.stations.sortby.title')}</strong> {sortStationsBy === 0 ? t('profile.stations.sortby.max') : t('profile.stations.sortby.min')}
|
||||
</p></a>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
|
||||
{dispatcherStats[dispatcherPage - 1].map(stationName => {
|
||||
const station = data.player.dispatcherStats[stationName];
|
||||
|
||||
{ Object.keys(data.player.trainStats || {}).length > 0 &&
|
||||
<div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
|
||||
<div className="group relative cursor-pointer" onClick={ () => setShowTrains(val => !val) }>
|
||||
<h1 className="text-xl text-black dark:text-white pb-5">{ t("profile.trains.header") }</h1>
|
||||
<ArrowIcon rotated={ showTrains }/>
|
||||
return <StationStat stationName={stationName} time={station.time} image={images.stations[stationName] ?? images.stations['N/A']} />
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-col pt-4">
|
||||
<Paginator setPage={setDispatcherPage} page={dispatcherPage} pages={dispatcherStats.length} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
|
||||
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.trains.header")}</h1>
|
||||
<div className="flex flex-col">
|
||||
<a className='cursor-pointer' onClick={() => setSortTrainsBy2((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
|
||||
<strong>{t('profile.trains.sortby.title')}</strong> {sortTrainsBy2 === 0 ? t('profile.trains.sortby.max') : t('profile.trains.sortby.min')}
|
||||
</p></a>
|
||||
<a className='cursor-pointer' onClick={() => setSortTrainsBy((prev) => {
|
||||
prev++;
|
||||
if (prev > 2) prev = 0;
|
||||
return prev;
|
||||
})}><p className='text-base'>
|
||||
<strong>{t('profile.trains.sortby.title')}</strong> {t('profile.trains.sortby.' + sortTrainsByList[sortTrainsBy])}
|
||||
</p></a>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
|
||||
{trainStats[trainPage - 1].map(trainName => {
|
||||
const train = data.player.trainStats[trainName];
|
||||
return <TrainStat trainName={trainName} time={train.time} distance={train.distance} score={train.score} image={images.trains[trainName] ?? images.trains['N/A']} />
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-col pt-4">
|
||||
<Paginator setPage={setTrainPage} page={trainPage} pages={trainStats.length} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
|
||||
|
||||
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
|
||||
<div className="grid grid-cols-2 rounded-sm bg-gray-2 dark:bg-meta-4">
|
||||
<div className="p-2.5 text-center xl:p-5">
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{t("profile.stations.station")}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
{ showTrains &&
|
||||
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
|
||||
<div className="grid grid-cols-3 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-3">
|
||||
<div className="p-2.5 text-center xl:p-5">
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{ t("profile.trains.train") }
|
||||
</h5>
|
||||
</div>
|
||||
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
|
||||
onClick={ () => setSortTrainsBy("distance") }>
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{ t("profile.trains.distance") }
|
||||
</h5>
|
||||
<FlexArrowIcon rotated={ sortTrainsBy === "distance" || !sortTrainsBy }/>
|
||||
</div>
|
||||
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
|
||||
onClick={ () => setSortTrainsBy("score") }>
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{ t("profile.trains.points") }
|
||||
</h5>
|
||||
<FlexArrowIcon rotated={ sortTrainsBy === "score" }/>
|
||||
</div>
|
||||
{/*<div className="hidden sm:flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"*/ }
|
||||
{/* onClick={ () => setSortTrainsBy("time") }>*/ }
|
||||
{/* <h5 className="text-sm font-medium uppercase xsm:text-base">*/ }
|
||||
{/* { t("profile.trains.time") }*/ }
|
||||
{/* </h5>*/ }
|
||||
{/* <FlexArrowIcon rotated={ sortTrainsBy === "time" }/>*/ }
|
||||
{/*</div>*/ }
|
||||
</div>
|
||||
|
||||
{ Object.keys(data.player.trainStats).sort((a, b) => data.player.trainStats[ b ][ sortTrainsBy ] - data.player.trainStats[ a ][ sortTrainsBy ]).map(trainName =>
|
||||
{
|
||||
const train = data.player.trainStats[ trainName ];
|
||||
|
||||
return <div
|
||||
className={ `grid grid-cols-3 sm:grid-cols-3 border-t border-t-stroke dark:border-t-strokedark` }
|
||||
key={ trainName }
|
||||
>
|
||||
<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">
|
||||
{ trainName }
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
||||
<p className="text-meta-6 sm:block break-all">{ Math.floor(train.distance / 1000) }km</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
||||
<p className="text-meta-3">{ train.score }</p>
|
||||
</div>
|
||||
|
||||
{/*<div className="hidden sm:flex items-center justify-center p-2.5 lg:p-5">*/ }
|
||||
{/* <p className="text-meta-3">{ formatTime(train.time) }</p>*/ }
|
||||
{/*</div>*/ }
|
||||
</div>;
|
||||
}) }
|
||||
|
||||
|
||||
</div> }
|
||||
|
||||
</div> }
|
||||
{ Object.keys(data.player.dispatcherStats || {}).length > 0 &&
|
||||
<div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
|
||||
<div className="group relative cursor-pointer" onClick={ () => setShowStations(val => !val) }>
|
||||
<h1 className="text-xl text-black dark:text-white pb-5">{ t("profile.stations.header") }</h1>
|
||||
<ArrowIcon rotated={ showStations }/>
|
||||
<div className="p-2.5 text-center xl:p-5">
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{t("profile.stations.time")}
|
||||
</h5>
|
||||
</div>
|
||||
{ showStations &&
|
||||
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
|
||||
<div className="grid grid-cols-2 rounded-sm bg-gray-2 dark:bg-meta-4">
|
||||
<div className="p-2.5 text-center xl:p-5">
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{ t("profile.stations.station") }
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[b].time - data.player.dispatcherStats[a].time).map(stationName => {
|
||||
const station = data.player.dispatcherStats[stationName];
|
||||
return <div
|
||||
className={`grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark`}
|
||||
key={stationName}
|
||||
>
|
||||
<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">
|
||||
{stationName}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="p-2.5 text-center xl:p-5">
|
||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||
{ t("profile.stations.time") }
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
{ Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[ b ].time - data.player.dispatcherStats[ a ].time).map(stationName =>
|
||||
{
|
||||
const station = data.player.dispatcherStats[ stationName ];
|
||||
return <div
|
||||
className={ `grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark` }
|
||||
key={ stationName }
|
||||
>
|
||||
<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">
|
||||
{ stationName }
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
||||
<p className="text-meta-3">{formatTime(station.time)}</p>
|
||||
</div>
|
||||
</div>;
|
||||
})}
|
||||
|
||||
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
||||
<p className="text-meta-3">{ formatTime(station.time) }</p>
|
||||
</div>
|
||||
</div>;
|
||||
}) }
|
||||
</div>
|
||||
|
||||
</div> }
|
||||
|
||||
</div> }
|
||||
|
||||
|
||||
{ isAdmin && <>
|
||||
<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">{ t("admin.header") }</h1>
|
||||
</div> */}
|
||||
{isAdmin && <>
|
||||
<div className="shadow-default items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||
<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">
|
||||
|
||||
{ data.player.flags.includes("leaderboard_hidden") ?
|
||||
<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" }
|
||||
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> }
|
||||
{data.player.flags.includes("leaderboard_hidden") ?
|
||||
<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"}
|
||||
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"
|
||||
onClick={ () => setHideProfileModal(true) }>
|
||||
{ t("admin.hide.button") }
|
||||
onClick={() => setHideProfileModal(true)}>
|
||||
{t("admin.hide.button")}
|
||||
</button>
|
||||
|
||||
<button className="inline-flex items-center justify-center rounded-md bg-primary py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
||||
onClick={ () => adminForceUpdate() }>
|
||||
{ t("admin.update.button") }
|
||||
onClick={() => adminForceUpdate()}>
|
||||
{t("admin.update.button")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</> }
|
||||
</>}
|
||||
|
||||
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||
<div className="items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 { useContext, createContext, ReactNode } from "react";
|
||||
import useSWR from "swr";
|
||||
import { get } from "../util/fetcher.ts";
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -35,7 +35,7 @@ function useLocalStorage<T>(
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch
|
||||
{
|
||||
return item ? item : initialValue;
|
||||
return (item && item !== "undefined") ? item : initialValue;
|
||||
}
|
||||
}
|
||||
} catch (error)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -20,7 +20,7 @@
|
||||
"footer": {
|
||||
"license": "Licence:",
|
||||
"powered": "Založeno na:",
|
||||
"thanks": "Speciální poděkování: <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, komunita SimRail a moje přítelkyně",
|
||||
"thanks": "Speciální poděkování: <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, komunita SimRail",
|
||||
"author": "Pro komunitu SimRail vytvořeno s ❤️ uživatelem <anchor>{{author}}</anchor> "
|
||||
}
|
||||
},
|
||||
@ -63,14 +63,27 @@
|
||||
"trains": {
|
||||
"header": "Statistiky vlaků",
|
||||
"train": "Vlak",
|
||||
"distance": "Vzdálenost",
|
||||
"points": "Body",
|
||||
"time": "Čas"
|
||||
"distance": "Vzdálenost: {{distance}}km",
|
||||
"score": "Body: {{score}",
|
||||
"time": "Čas: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Řadit podle: ",
|
||||
"min": "Nejnižší",
|
||||
"max": "Nejvyšší",
|
||||
"time": "Čas",
|
||||
"distance": "Vzdálenost",
|
||||
"score": "Body"
|
||||
}
|
||||
},
|
||||
"stations": {
|
||||
"header": "Statistiky stanic",
|
||||
"station": "Stanice",
|
||||
"time": "Čas"
|
||||
"time": "Čas: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Řadit podle: ",
|
||||
"min": "Nejnižší",
|
||||
"max": "Nejvyšší"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"notfound": {
|
||||
|
@ -20,7 +20,7 @@
|
||||
"footer": {
|
||||
"license": "License:",
|
||||
"powered": "Based on:",
|
||||
"thanks": "Special thanks to <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, Simrail community and my girlfriend",
|
||||
"thanks": "Special thanks to <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite> and Simrail community",
|
||||
"author": "Created by <anchor>{{author}}</anchor> with ❤️ for the Simrail community"
|
||||
}
|
||||
},
|
||||
@ -63,14 +63,27 @@
|
||||
"trains": {
|
||||
"header": "Train Statistics",
|
||||
"train": "Train",
|
||||
"distance": "Distance",
|
||||
"points": "Points",
|
||||
"time": "Time"
|
||||
"distance": "Distance: {{distance}}km",
|
||||
"score": "Points: {{score}}",
|
||||
"time": "Time: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Sort: ",
|
||||
"min": "Lowest",
|
||||
"max": "Highest",
|
||||
"time": "Time",
|
||||
"distance": "Distance",
|
||||
"score": "Points"
|
||||
}
|
||||
},
|
||||
"stations": {
|
||||
"header": "Station Statistics",
|
||||
"station": "Station",
|
||||
"time": "Time"
|
||||
"time": "Time: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Sort: ",
|
||||
"min": "Lowest",
|
||||
"max": "Highest"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"notfound": {
|
||||
|
@ -20,7 +20,7 @@
|
||||
"footer": {
|
||||
"license": "Licencja:",
|
||||
"powered": "Oparte na:",
|
||||
"thanks": "Specjalne podziękowania dla <bahu>serwerowni BAHU.PRO</bahu>, <simrailelite>discorda Simrail ELITE</simrailelite>, społeczności Simrail i mojej dziewczyny",
|
||||
"thanks": "Specjalne podziękowania dla <bahu>serwerowni BAHU.PRO</bahu>, <simrailelite>discorda Simrail ELITE</simrailelite> i społeczności Simrail",
|
||||
"author": "Stworzone przez <anchor>{{author}}</anchor> z ❤️ dla społeczności Simrail"
|
||||
}
|
||||
},
|
||||
@ -63,14 +63,27 @@
|
||||
"trains": {
|
||||
"header": "Statystyki pociągów",
|
||||
"train": "Pociąg",
|
||||
"distance": "Dystans",
|
||||
"points": "Punkty",
|
||||
"time": "Czas"
|
||||
"distance": "Dystans: {{distance}}km",
|
||||
"score": "Punkty: {{score}}",
|
||||
"time": "Czas: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Sortowanie: ",
|
||||
"min": "Od najniższej",
|
||||
"max": "Od najwyższej",
|
||||
"time": "Czas",
|
||||
"distance": "Dystans",
|
||||
"score": "Punkty"
|
||||
}
|
||||
},
|
||||
"stations": {
|
||||
"header": "Statystyki stacji",
|
||||
"station": "Stacja",
|
||||
"time": "Czas"
|
||||
"time": "Czas: {{time}}",
|
||||
"sortby": {
|
||||
"title": "Sortowanie: ",
|
||||
"min": "Od najniższej",
|
||||
"max": "Od najwyższej"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"notfound": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
2
packages/frontend/src/lib.d.ts
vendored
2
packages/frontend/src/lib.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
@ -27,37 +27,37 @@ import useSWR from "swr";
|
||||
import { get } from "../../util/fetcher.ts";
|
||||
|
||||
|
||||
export const Profile = () =>
|
||||
{
|
||||
export const Profile = () => {
|
||||
const { id } = useParams();
|
||||
const { data, error, isLoading } = useSWR(`/profiles/${ id }`, get, { refreshInterval: 5_000, errorRetryCount: 5 });
|
||||
const { data, error, isLoading } = useSWR(`/profiles/${id}`, get, { refreshInterval: 5_000, errorRetryCount: 5 });
|
||||
const images = useSWR(`/images/`, get);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* LOADING */ }
|
||||
{ isLoading && <ContentLoader/> }
|
||||
{/* ERROR */ }
|
||||
{ error && <LoadError/> }
|
||||
{/* BLACKLISTED */ }
|
||||
{ data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
|
||||
description="The player's profile could not be displayed due to active moderator actions."/> }
|
||||
{ data && data.code === 403 && <WarningAlert title={ t("profile.errors.blacklist.title") }
|
||||
description={ t("profile.errors.blacklist.description") }/> }
|
||||
{/* NOT FOUND */ }
|
||||
{ data && data.code === 404 && <PageMeta title="simrail.pro | Profile not found"
|
||||
description="Player's profile could not be found or the player has a private Steam profile."/> }
|
||||
{ data && data.code === 404 && <WarningAlert title={ t("profile.errors.notfound.title") }
|
||||
description={ t("profile.errors.notfound.description") }/> }
|
||||
<>
|
||||
{/* LOADING */}
|
||||
{(isLoading || images.isLoading) && <ContentLoader />}
|
||||
{/* ERROR */}
|
||||
{(error || images.error) && <LoadError />}
|
||||
{/* BLACKLISTED */}
|
||||
{data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
|
||||
description="The player's profile could not be displayed due to active moderator actions." />}
|
||||
{data && data.code === 403 && <WarningAlert title={t("profile.errors.blacklist.title")}
|
||||
description={t("profile.errors.blacklist.description")} />}
|
||||
{/* NOT FOUND */}
|
||||
{data && data.code === 404 && <PageMeta title="simrail.pro | Profile not found"
|
||||
description="Player's profile could not be found or the player has a private Steam profile." />}
|
||||
{data && data.code === 404 && <WarningAlert title={t("profile.errors.notfound.title")}
|
||||
description={t("profile.errors.notfound.description")} />}
|
||||
|
||||
{/* SUCCESS */ }
|
||||
{ data && data.code === 200 && <PageMeta image={ data.data.player.username }
|
||||
title={ `simrail.pro | ${ data.data.player.username }'s profile` }
|
||||
description={ `${ data.data.player.trainDistance ? 0 : ((data.data.player.trainDistance / 1000).toFixed(2)) } driving experience |
|
||||
${ data.data.player.dispatcherTime ? 0 : formatTime(data.data.player.dispatcherTime) } dispatcher experience` }/> }
|
||||
{ data && data.code === 200 && <ProfileCard data={ data.data }/> }
|
||||
</>
|
||||
{/* SUCCESS */}
|
||||
{data && data.code === 200 && images.data && images.data.code === 200 && <PageMeta image={data.data.player.username}
|
||||
title={`simrail.pro | ${data.data.player.username}'s profile`}
|
||||
description={`${data.data.player.trainDistance ? 0 : ((data.data.player.trainDistance / 1000).toFixed(2))} driving experience |
|
||||
${data.data.player.dispatcherTime ? 0 : formatTime(data.data.player.dispatcherTime)} dispatcher experience`} />}
|
||||
{data && data.code === 200 && images.data && images.data.code === 200 && <ProfileCard data={data.data} images={images.data.data} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
2
packages/frontend/src/react-app-env.d.ts
vendored
2
packages/frontend/src/react-app-env.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
10
packages/frontend/src/types/images.ts
Normal file
10
packages/frontend/src/types/images.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface TImagesResponse {
|
||||
success: boolean;
|
||||
data: TImagesData;
|
||||
code: number;
|
||||
}
|
||||
|
||||
export interface TImagesData {
|
||||
trains: Record<string, string>
|
||||
stations: Record<string, string>
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
|
||||
* Copyright (C) 2025 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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user