Compare commits
105 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 | |||
47d98aba82 | |||
c8efbb92f5 | |||
e460fdefe8 | |||
7ed9df1e9d |
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",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "yarn workspace backend build && yarn workspace frontend build",
|
"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\""
|
"start": "concurrently --kill-others-on-fail \"yarn workspace backend start\" \"yarn workspace frontend dev\""
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"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",
|
"main": "../../dist/backend/index.js",
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "docker build --progress=plain -t simrailpro:backend .",
|
||||||
"start": "yarn build && doppler run node ../../dist/backend/index.js"
|
"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",
|
"author": "Aleksander <alekswilc> Wilczyński",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
@ -15,6 +17,7 @@
|
|||||||
"@types/node": "^22.10.1",
|
"@types/node": "^22.10.1",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
|
"tsc": "^2.0.4",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.5.4"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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) =>
|
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();
|
const sserver = req.query.server?.toString();
|
||||||
|
|
||||||
|
|
||||||
let a: ActiveTrain[] = [];
|
let a: ActiveTrain[] = [];
|
||||||
|
|
||||||
for (const data of sserver ? [ client.trains[ sserver as Server["ServerCode"] ] ] : Object.values(client.trains))
|
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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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");
|
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;
|
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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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 { LogRoute } from "./routes/log.js";
|
||||||
import { ActivePlayersRoute } from "./routes/activePlayer.js";
|
import { ActivePlayersRoute } from "./routes/activePlayer.js";
|
||||||
import { AdminRoute } from "./routes/admin.js";
|
import { AdminRoute } from "./routes/admin.js";
|
||||||
|
import { ImagesRoute } from './routes/images.js';
|
||||||
|
|
||||||
export class ApiModule
|
export class ApiModule
|
||||||
{
|
{
|
||||||
@ -39,6 +40,8 @@ export class ApiModule
|
|||||||
router.use("/leaderboard/", LeaderboardRoute.load());
|
router.use("/leaderboard/", LeaderboardRoute.load());
|
||||||
router.use("/active/", ActivePlayersRoute.load());
|
router.use("/active/", ActivePlayersRoute.load());
|
||||||
router.use("/admin/", AdminRoute.load());
|
router.use("/admin/", AdminRoute.load());
|
||||||
|
router.use("/images/", ImagesRoute.load());
|
||||||
|
|
||||||
|
|
||||||
router.use("/stats/", StatsRoute.load());
|
router.use("/stats/", StatsRoute.load());
|
||||||
router.use("/log/", LogRoute.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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -18,7 +18,7 @@ import { IPlayerPayload, IPlayerStatsPayload } from "../types/player.js";
|
|||||||
import { MProfile } from "../mongo/profile.js";
|
import { MProfile } from "../mongo/profile.js";
|
||||||
import { assert } from "node:console";
|
import { assert } from "node:console";
|
||||||
|
|
||||||
const STEAM_API_KEY = process.env.STEAM_APIKEY;
|
const steamKeys: string[] = JSON.parse(process.env.STEAM_APIKEY!);
|
||||||
|
|
||||||
const steamFetch = (url: string) =>
|
const steamFetch = (url: string) =>
|
||||||
{
|
{
|
||||||
@ -28,12 +28,13 @@ const steamFetch = (url: string) =>
|
|||||||
{
|
{
|
||||||
const req = () =>
|
const req = () =>
|
||||||
{
|
{
|
||||||
|
const steamKey = steamKeys[ Math.floor(Math.random() * steamKeys.length) ];
|
||||||
|
|
||||||
fetch(url, { signal: AbortSignal.timeout(10000) }).then(x => x.json())
|
fetch(url.replace("[STEAMKEY]", steamKey), { signal: AbortSignal.timeout(10000) }).then(x => x.json())
|
||||||
.then(x => res(x))
|
.then(x => res(x))
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
{
|
{
|
||||||
console.log("STEAM request failed! ", url.replace(STEAM_API_KEY!, "[XXX]"), retries);
|
console.log("STEAM request failed! ", url.replace("[STEAMKEY]", steamKey), retries);
|
||||||
|
|
||||||
retries++;
|
retries++;
|
||||||
setTimeout(() => req(), retries * 1000);
|
setTimeout(() => req(), retries * 1000);
|
||||||
@ -54,7 +55,7 @@ export class PlayerUtil
|
|||||||
|
|
||||||
if (!player)
|
if (!player)
|
||||||
{
|
{
|
||||||
const data = await steamFetch(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=${ STEAM_API_KEY }&format=json&steamids=${ steamId }`) as IPlayerPayload;
|
const data = await steamFetch(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=[STEAMKEY]&format=json&steamids=${ steamId }`) as IPlayerPayload;
|
||||||
|
|
||||||
assert(data.response.players, "Expected data.response.players to be truthy");
|
assert(data.response.players, "Expected data.response.players to be truthy");
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ export class PlayerUtil
|
|||||||
|
|
||||||
public static async getPlayerSteamData(steamId: string)
|
public static async getPlayerSteamData(steamId: string)
|
||||||
{
|
{
|
||||||
const data = await steamFetch(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=${ STEAM_API_KEY }&format=json&steamids=${ steamId }`) as IPlayerPayload;
|
const data = await steamFetch(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/?key=[STEAMKEY]&format=json&steamids=${ steamId }`) as IPlayerPayload;
|
||||||
|
|
||||||
if (!data?.response?.players?.length)
|
if (!data?.response?.players?.length)
|
||||||
{
|
{
|
||||||
@ -161,7 +162,7 @@ export class PlayerUtil
|
|||||||
|
|
||||||
public static async getPlayerStats(steamId: string)
|
public static async getPlayerStats(steamId: string)
|
||||||
{
|
{
|
||||||
const data = await steamFetch(`https://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=1422130&key=${ STEAM_API_KEY }&steamid=${ steamId }`) as IPlayerStatsPayload;
|
const data = await steamFetch(`https://api.steampowered.com/ISteamUserStats/GetUserStatsForGame/v0002/?appid=1422130&key=[STEAMKEY]&steamid=${ steamId }`) as IPlayerStatsPayload;
|
||||||
|
|
||||||
if (!data.playerstats?.stats)
|
if (!data.playerstats?.stats)
|
||||||
{
|
{
|
||||||
|
@ -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
|
* 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
|
* 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";
|
import wcmatch from "wildcard-match";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -120,10 +137,80 @@ export const trainsList = [
|
|||||||
"4E/EU07-*",
|
"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) =>
|
export const getVehicle = (name: string) =>
|
||||||
{
|
{
|
||||||
return trainsList.find(x => wcmatch(x.pattern)(name))?.train;
|
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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -14,7 +14,7 @@
|
|||||||
* See LICENSE for more.
|
* See LICENSE for more.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync } from "child_process";
|
import gitInfo from '../../git.json' with { type: "json" };
|
||||||
|
|
||||||
export class GitUtil
|
export class GitUtil
|
||||||
{
|
{
|
||||||
@ -22,26 +22,12 @@ export class GitUtil
|
|||||||
|
|
||||||
private static getLatestVersion()
|
private static getLatestVersion()
|
||||||
{
|
{
|
||||||
try
|
return gitInfo.tag;
|
||||||
{
|
|
||||||
const data = execSync("git describe --tags --exact-match").toString();
|
|
||||||
return data.replace("\n", "");
|
|
||||||
} catch
|
|
||||||
{
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getLatestCommit()
|
private static getLatestCommit()
|
||||||
{
|
{
|
||||||
try
|
return gitInfo.commit;
|
||||||
{
|
|
||||||
const data = execSync("git rev-parse --short HEAD").toString();
|
|
||||||
return data.replace("\n", "");
|
|
||||||
} catch
|
|
||||||
{
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
* 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
|
* 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") =>
|
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")
|
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 }`);
|
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
|
* 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
|
* 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. */
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving 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. */
|
// "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. */
|
// "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. */
|
// "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. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
// "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. */
|
// "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. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
// "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. */
|
// "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
|
~ 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
|
~ it under the terms of the GNU Affero General Public License as published
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"rawbuild": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"build": "docker build --progress=plain -t simrailpro:frontend ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
@ -35,7 +36,7 @@
|
|||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"vite": "^4.4.7",
|
"vite": "^6.2.0",
|
||||||
"webpack": "^5.88.2"
|
"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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -76,10 +76,13 @@ export const Header = (props: {
|
|||||||
<div className="flex items-center gap-3 2xsm:gap-7">
|
<div className="flex items-center gap-3 2xsm:gap-7">
|
||||||
<ul className="flex items-center gap-2 2xsm:gap-4">
|
<ul className="flex items-center gap-2 2xsm:gap-4">
|
||||||
<a className="cursor-pointer" onClick={ () => i18n.changeLanguage("pl") }>
|
<a className="cursor-pointer" onClick={ () => i18n.changeLanguage("pl") }>
|
||||||
<ReactCountryFlag countryCode={ "PL" } svg alt={'PL'} />
|
<ReactCountryFlag countryCode={ "PL" } svg alt={ "PL" }/>
|
||||||
</a>
|
</a>
|
||||||
<a className="cursor-pointer" onClick={ () => i18n.changeLanguage("en") }>
|
<a className="cursor-pointer" onClick={ () => i18n.changeLanguage("en") }>
|
||||||
<ReactCountryFlag countryCode={ "US" } svg alt={'EN'}/>
|
<ReactCountryFlag countryCode={ "US" } svg alt={ "EN" }/>
|
||||||
|
</a>
|
||||||
|
<a className="cursor-pointer" onClick={ () => i18n.changeLanguage("cs") }>
|
||||||
|
<ReactCountryFlag countryCode={ "CZ" } svg alt={ "CZ" }/>
|
||||||
</a>
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="flex items-center gap-2 2xsm:gap-4">
|
<ul className="flex items-center gap-2 2xsm:gap-4">
|
||||||
|
@ -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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
export const ConfirmModal = ({ showModal, setShowModal, onConfirm, title, description }: {
|
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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -16,34 +16,38 @@
|
|||||||
|
|
||||||
import { Dispatch, SetStateAction } from "react";
|
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 }: {
|
export const Paginator = ({ page, setPage, pages }: {
|
||||||
page: number,
|
page: number,
|
||||||
pages: number,
|
pages: number,
|
||||||
setPage: Dispatch<SetStateAction<number>>
|
setPage: Dispatch<SetStateAction<number>>
|
||||||
}) =>
|
}) => {
|
||||||
{
|
// todo: rewrite this shit XDDDDDDDD
|
||||||
let numbers = [ 1, page - 2, page - 1, page, page + 1, page + 2 ];
|
const numbers = getPaginationNums(page, pages);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
return <div
|
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">
|
<ul className="flex flex-wrap items-center">
|
||||||
<li>
|
<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"
|
<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"
|
<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"
|
<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"
|
||||||
@ -51,20 +55,15 @@ export const Paginator = ({ page, setPage, pages }: {
|
|||||||
</svg>
|
</svg>
|
||||||
</a></li>
|
</a></li>
|
||||||
|
|
||||||
{ numbers.map(num =>
|
{numbers.map(num => {
|
||||||
{
|
|
||||||
return <li>
|
return <li>
|
||||||
<a onClick={ () => setPage(num) }
|
<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>
|
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>;
|
</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>
|
<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"
|
<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"
|
<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"
|
<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"
|
||||||
|
@ -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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -15,9 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { TProfileData } from "../../../types/profile.ts";
|
import { TProfileData, TProfilePlayer } from "../../../types/profile.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
|
|
||||||
import { formatTime } from "../../../util/time.ts";
|
import { formatTime } from "../../../util/time.ts";
|
||||||
import { useAuth } from "../../../hooks/useAuth.tsx";
|
import { useAuth } from "../../../hooks/useAuth.tsx";
|
||||||
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
|
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
|
||||||
@ -25,78 +24,108 @@ import { post } from "../../../util/fetcher.ts";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { UserIcons } from "../../mini/icons/UserIcons.tsx";
|
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);
|
export const ProfileCard = ({ data, images }: { data: TProfileData, images: TImagesData }) => {
|
||||||
const [ showStations, setShowStations ] = useState(false);
|
const [sortTrainsBy, setSortTrainsBy] = useState(2);
|
||||||
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
|
const [sortTrainsBy2, setSortTrainsBy2] = useState(0);
|
||||||
const [ hideLeaderboardStatsModal, setHideLeaderboardStatsModal ] = useState(false);
|
const [sortStationsBy, setSortStationsBy] = useState(0);
|
||||||
const [ hideProfileModal, setHideProfileModal ] = useState(false);
|
const [hideLeaderboardStatsModal, setHideLeaderboardStatsModal] = useState(false);
|
||||||
|
const [hideProfileModal, setHideProfileModal] = useState(false);
|
||||||
|
|
||||||
const { isAdmin, token } = useAuth();
|
const { isAdmin, token } = useAuth();
|
||||||
|
|
||||||
const adminToggleHideLeaderboardPlayerProfile = () =>
|
// #region ADMIN
|
||||||
{
|
const adminToggleHideLeaderboardPlayerProfile = () => {
|
||||||
post(`/admin/profile/${ data.player.id }/${ data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard" }`, {}, { "X-Auth-Token": token })
|
post(`/admin/profile/${data.player.id}/${data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard"}`, {}, { "X-Auth-Token": token })
|
||||||
.then((response) =>
|
.then((response) => {
|
||||||
{
|
if (response.code === 200) {
|
||||||
if (response.code === 200)
|
|
||||||
{
|
|
||||||
toast.success(t("admin.hideLeaderboard.alert"));
|
toast.success(t("admin.hideLeaderboard.alert"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const adminHidePlayerProfile = () =>
|
const adminHidePlayerProfile = () => {
|
||||||
{
|
post(`/admin/profile/${data.player.id}/hide`, {}, { "X-Auth-Token": token })
|
||||||
post(`/admin/profile/${ data.player.id }/hide`, {}, { "X-Auth-Token": token })
|
.then((response) => {
|
||||||
.then((response) =>
|
if (response.code === 200) {
|
||||||
{
|
|
||||||
if (response.code === 200)
|
|
||||||
{
|
|
||||||
toast.success(t("admin.hide.alert"));
|
toast.success(t("admin.hide.alert"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const adminForceUpdate = () =>
|
const adminForceUpdate = () => {
|
||||||
{
|
post(`/admin/profile/${data.player.id}/forceUpdate`, {}, { "X-Auth-Token": token })
|
||||||
post(`/admin/profile/${ data.player.id }/forceUpdate`, {}, { "X-Auth-Token": token })
|
.then((response) => {
|
||||||
.then((response) =>
|
if (response.code === 200) {
|
||||||
{
|
|
||||||
if (response.code === 200)
|
|
||||||
{
|
|
||||||
toast.success(t("admin.update.alert"));
|
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();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ConfirmModal showModal={ hideLeaderboardStatsModal } setShowModal={ setHideLeaderboardStatsModal }
|
<ConfirmModal showModal={hideLeaderboardStatsModal} setShowModal={setHideLeaderboardStatsModal}
|
||||||
onConfirm={ adminToggleHideLeaderboardPlayerProfile }
|
onConfirm={adminToggleHideLeaderboardPlayerProfile}
|
||||||
title={ t("admin.hideLeaderboard.modal.title") }
|
title={t("admin.hideLeaderboard.modal.title")}
|
||||||
description={ t("admin.hideLeaderboard.modal.description") }/>
|
description={t("admin.hideLeaderboard.modal.description")} />
|
||||||
<ConfirmModal showModal={ hideProfileModal } setShowModal={ setHideProfileModal }
|
<ConfirmModal showModal={hideProfileModal} setShowModal={setHideProfileModal}
|
||||||
onConfirm={ adminHidePlayerProfile } title={ t("admin.hide.modal.title") }
|
onConfirm={adminHidePlayerProfile} title={t("admin.hide.modal.title")}
|
||||||
description={ t("admin.hide.modal.description") }/>
|
description={t("admin.hide.modal.description")} />
|
||||||
<div
|
<div
|
||||||
className="overflow-hidden rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
className="overflow-hidden ">
|
||||||
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
|
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
|
||||||
<div
|
<div
|
||||||
className="mx-auto max-w-44 rounded-full">
|
className="mx-auto max-w-44 rounded-full">
|
||||||
<div className="relative rounded-full">
|
<div className="relative rounded-full">
|
||||||
<img className="rounded-full" src={ data.player.avatar } alt="profile"/>
|
<img className="rounded-full" src={data.player.avatar} alt="profile" />
|
||||||
{ data.active &&
|
{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> }
|
<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>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<h3 className="text-2xl font-semibold text-black dark:text-white">
|
<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>
|
</h3>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -104,179 +133,144 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
|
|||||||
<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">
|
<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>
|
||||||
<span className="text-sm text-wrap">{ t("profile.stats.distance") }</span>
|
<span className="text-sm text-wrap">{t("profile.stats.distance")}</span>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
<span className="font-semibold text-black dark:text-white">
|
||||||
{ formatTime(data.player.dispatcherTime) }
|
{formatTime(data.player.dispatcherTime)}
|
||||||
</span>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ data.active && data.active.type === "train" &&
|
{data.active && data.active.type === "train" &&
|
||||||
<div className="mx-auto text-center">
|
<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>
|
<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> }
|
</div>}
|
||||||
|
|
||||||
{ data.active && data.active.type === "station" &&
|
{data.active && data.active.type === "station" &&
|
||||||
<div className="mx-auto text-center">
|
<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>
|
<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>
|
</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 &&
|
return <StationStat stationName={stationName} time={station.time} image={images.stations[stationName] ?? images.stations['N/A']} />
|
||||||
<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) }>
|
</div>
|
||||||
<h1 className="text-xl text-black dark:text-white pb-5">{ t("profile.trains.header") }</h1>
|
<div className="flex flex-col pt-4">
|
||||||
<ArrowIcon rotated={ showTrains }/>
|
<Paginator setPage={setDispatcherPage} page={dispatcherPage} pages={dispatcherStats.length} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ showTrains &&
|
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
|
||||||
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
|
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.trains.header")}</h1>
|
||||||
<div className="grid grid-cols-3 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-3">
|
<div className="flex flex-col">
|
||||||
<div className="p-2.5 text-center xl:p-5">
|
<a className='cursor-pointer' onClick={() => setSortTrainsBy2((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
|
||||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
<strong>{t('profile.trains.sortby.title')}</strong> {sortTrainsBy2 === 0 ? t('profile.trains.sortby.max') : t('profile.trains.sortby.min')}
|
||||||
{ t("profile.trains.train") }
|
</p></a>
|
||||||
</h5>
|
<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>
|
||||||
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
|
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
|
||||||
onClick={ () => setSortTrainsBy("distance") }>
|
{trainStats[trainPage - 1].map(trainName => {
|
||||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
const train = data.player.trainStats[trainName];
|
||||||
{ t("profile.trains.distance") }
|
return <TrainStat trainName={trainName} time={train.time} distance={train.distance} score={train.score} image={images.trains[trainName] ?? images.trains['N/A']} />
|
||||||
</h5>
|
})}
|
||||||
<FlexArrowIcon rotated={ sortTrainsBy === "distance" || !sortTrainsBy }/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
|
<div className="flex flex-col pt-4">
|
||||||
onClick={ () => setSortTrainsBy("score") }>
|
<Paginator setPage={setTrainPage} page={trainPage} pages={trainStats.length} />
|
||||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
|
||||||
{ t("profile.trains.points") }
|
|
||||||
</h5>
|
|
||||||
<FlexArrowIcon rotated={ sortTrainsBy === "score" }/>
|
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{ Object.keys(data.player.trainStats).sort((a, b) => data.player.trainStats[ b ][ sortTrainsBy ] - data.player.trainStats[ a ][ sortTrainsBy ]).map(trainName =>
|
{/* <div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
|
||||||
{
|
|
||||||
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>
|
|
||||||
{ showStations &&
|
|
||||||
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
|
<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="grid grid-cols-2 rounded-sm bg-gray-2 dark:bg-meta-4">
|
||||||
<div className="p-2.5 text-center xl:p-5">
|
<div className="p-2.5 text-center xl:p-5">
|
||||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||||
{ t("profile.stations.station") }
|
{t("profile.stations.station")}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-2.5 text-center xl:p-5">
|
<div className="p-2.5 text-center xl:p-5">
|
||||||
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
<h5 className="text-sm font-medium uppercase xsm:text-base">
|
||||||
{ t("profile.stations.time") }
|
{t("profile.stations.time")}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[ b ].time - data.player.dispatcherStats[ a ].time).map(stationName =>
|
{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];
|
||||||
const station = data.player.dispatcherStats[ stationName ];
|
|
||||||
return <div
|
return <div
|
||||||
className={ `grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark` }
|
className={`grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark`}
|
||||||
key={ stationName }
|
key={stationName}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
|
||||||
<p className="text-black dark:text-white sm:block break-all">
|
<p className="text-black dark:text-white sm:block break-all">
|
||||||
{ stationName }
|
{stationName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
<div className="flex items-center justify-center p-2.5 lg:p-5">
|
||||||
<p className="text-meta-3">{ formatTime(station.time) }</p>
|
<p className="text-meta-3">{formatTime(station.time)}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}) }
|
})}
|
||||||
|
|
||||||
</div> }
|
</div>
|
||||||
|
|
||||||
</div> }
|
</div> */}
|
||||||
|
{isAdmin && <>
|
||||||
|
<div className="shadow-default items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
|
||||||
{ isAdmin && <>
|
<h1 className="text-xl text-black dark:text-white">{t("admin.header")}</h1>
|
||||||
<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 className="items-center justify-center p-2.5 flex xl:p-5 gap-2 flex-wrap sm:flex-nowrap">
|
<div className="items-center justify-center p-2.5 flex xl:p-5 gap-2 flex-wrap sm:flex-nowrap">
|
||||||
|
|
||||||
{ data.player.flags.includes("leaderboard_hidden") ?
|
{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" }
|
<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() }>
|
onClick={() => adminToggleHideLeaderboardPlayerProfile()}>
|
||||||
{ t("admin.hideLeaderboard.button2") }
|
{t("admin.hideLeaderboard.button2")}
|
||||||
</button> :
|
</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" }
|
<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) }>
|
onClick={() => setHideLeaderboardStatsModal(true)}>
|
||||||
{ t("admin.hideLeaderboard.button") }
|
{t("admin.hideLeaderboard.button")}
|
||||||
</button> }
|
</button>}
|
||||||
|
|
||||||
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
|
||||||
onClick={ () => setHideProfileModal(true) }>
|
onClick={() => setHideProfileModal(true)}>
|
||||||
{ t("admin.hide.button") }
|
{t("admin.hide.button")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<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"
|
<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() }>
|
onClick={() => adminForceUpdate()}>
|
||||||
{ t("admin.update.button") }
|
{t("admin.update.button")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<h1 className="text-sm text-black dark:text-white">
|
||||||
{ t("profile.info", { date: dayjs(data.player.createdAt).format("DD/MM/YYYY") }) }
|
{t("profile.info", { date: dayjs(data.player.createdAt).format("DD/MM/YYYY") })}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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 { useContext, createContext, ReactNode } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { get } from "../util/fetcher.ts";
|
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
|
* 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
|
* 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
|
* 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
|
* 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;
|
return item ? JSON.parse(item) : initialValue;
|
||||||
} catch
|
} catch
|
||||||
{
|
{
|
||||||
return item ? item : initialValue;
|
return (item && item !== "undefined") ? item : initialValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error)
|
} 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -19,7 +19,7 @@ import { initReactI18next } from "react-i18next";
|
|||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import translationsInEng from "./languages/en.json";
|
import translationsInEng from "./languages/en.json";
|
||||||
import translationsInPl from "./languages/pl.json";
|
import translationsInPl from "./languages/pl.json";
|
||||||
// import translationsInCz from "./languages/cs.json";
|
import translationsInCs from "./languages/cs.json";
|
||||||
|
|
||||||
const resources = {
|
const resources = {
|
||||||
en: {
|
en: {
|
||||||
@ -28,9 +28,9 @@ const resources = {
|
|||||||
pl: {
|
pl: {
|
||||||
translation: translationsInPl,
|
translation: translationsInPl,
|
||||||
},
|
},
|
||||||
// cs: {
|
cs: {
|
||||||
// translation: translationsInCz,
|
translation: translationsInCs,
|
||||||
// }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void i18n
|
void i18n
|
||||||
|
186
packages/frontend/src/i18n/languages/cs.json
Normal file
186
packages/frontend/src/i18n/languages/cs.json
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"placeholder": "například alekswilc",
|
||||||
|
"placeholder_with_station": "například alekswilc,Katowice",
|
||||||
|
"none": "Žádné"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"stats": {
|
||||||
|
"trains": "vlaků",
|
||||||
|
"dispatchers": "výpravčích",
|
||||||
|
"profiles": "profilů"
|
||||||
|
},
|
||||||
|
"title": "Simrail Stats",
|
||||||
|
"description": "Nejlepší stránka se záznamy a statistikami hráčů SimRail!",
|
||||||
|
"buttons": {
|
||||||
|
"project": "Stránka projektu",
|
||||||
|
"forum": "Stránka na fóru SimRail",
|
||||||
|
"discord": "Discord server"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"author": "Pro komunitu SimRail vytvořeno s ❤️ uživatelem <anchor>{{author}}</anchor> "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notfound": {
|
||||||
|
"title": "Stránka nenalezena",
|
||||||
|
"description": "Vypadá to, že ses ztratil...",
|
||||||
|
"button": "Vrátit se na hlavní stránku"
|
||||||
|
},
|
||||||
|
"leaderboard": {
|
||||||
|
"train": {
|
||||||
|
"distance": "Ujetá vzdálenost: {{distance}} km",
|
||||||
|
"points": "Získané body: {{points}}"
|
||||||
|
},
|
||||||
|
"station": {
|
||||||
|
"time": "Čas strávený v režimu výpravčího: {{time}}"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"trainDistance": "Největší ujetá vzdálenost",
|
||||||
|
"trainPoints": "Nejvyšší počet body",
|
||||||
|
"dispatcherTime": "Nejvíce hodin nahráno v režimu výpravčího"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content_loader": {
|
||||||
|
"error": {
|
||||||
|
"header": "Chyba načtení stránky",
|
||||||
|
"description": "Zkontrolujte vaše internetové připojení a načtěte stránku znovu",
|
||||||
|
"report": "Nahlásit chybu",
|
||||||
|
"refresh": "Načíst znovu"
|
||||||
|
},
|
||||||
|
"notfound": {
|
||||||
|
"header": "Nebyla nalezena žádná data",
|
||||||
|
"description": "Vaše vyhledávání nevrátilo žádné výsledky."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"stats": {
|
||||||
|
"distance": "Strojvedoucí",
|
||||||
|
"time": "Výpravčí"
|
||||||
|
},
|
||||||
|
"trains": {
|
||||||
|
"header": "Statistiky vlaků",
|
||||||
|
"train": "Vlak",
|
||||||
|
"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}}",
|
||||||
|
"sortby": {
|
||||||
|
"title": "Řadit podle: ",
|
||||||
|
"min": "Nejnižší",
|
||||||
|
"max": "Nejvyšší"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"notfound": {
|
||||||
|
"title": "Profil nebyl nalezen",
|
||||||
|
"description": "Profil tohoto hráče nemohl být nalezen, nebo si hráč nastavil soukromý Steam účet."
|
||||||
|
},
|
||||||
|
"blacklist": {
|
||||||
|
"title": "Profil není možné zobrazit",
|
||||||
|
"description": "Profil hráče nemohl být zobrazen kvůli aktivním činnostem moderátorů."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info": "Tyto statistiky jsou shromažďovány od {{date}}.",
|
||||||
|
"active": {
|
||||||
|
"train": "Řídí vlak {{train}} na serveru {{server}}",
|
||||||
|
"station": "Výpravčí ve stanici {{station}} na serveru {{server}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"errors": {
|
||||||
|
"notfound": {
|
||||||
|
"title": "Podrobnosti nebyly nalezeny",
|
||||||
|
"description": "Podrobnosti tohoto uživatele nebyly nalezeny."
|
||||||
|
},
|
||||||
|
"blacklist": {
|
||||||
|
"title": "Tyto podrobnosti není možné zobrazit",
|
||||||
|
"description": "Podrobnosti hráče nemohly být zobrazeny kvůli aktivním činnostem moderátorů."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"station": {
|
||||||
|
"header": "Opouští stanici",
|
||||||
|
"server": "Server: {{server}}",
|
||||||
|
"station": "Stanice: {{name}} - {{short}}",
|
||||||
|
"joined": "Čas připojení do stanice: {{date}}",
|
||||||
|
"left": "Čas odpojení ze stanice: {{date}}",
|
||||||
|
"spent": "Čas strávený ve stanici: {{date}}"
|
||||||
|
},
|
||||||
|
"train": {
|
||||||
|
"header": "Opouští vlak",
|
||||||
|
"server": "Server: {{server}}",
|
||||||
|
"train": "Vlak: {{name}} {{number}}",
|
||||||
|
"joined": "Čas připojení do vlaku: {{date}}",
|
||||||
|
"left": "Čas odpojení z vlaku: {{date}}",
|
||||||
|
"spent": "Čas strávený ve vlaku: {{date}}",
|
||||||
|
"distance": "Vzdálenost: {{distance}} km",
|
||||||
|
"points": "Body: {{points}}"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"copied": "Odkaz byl zkopírován!",
|
||||||
|
"report": "Údaje byly zkopírovány do schránky. Můžete je použít v kanálu #multiplayer-help-requests na oficiálním Discord serveru SimRailu. Nezapomeňte připsat důvod nahlášení!"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"report": "Nahlásit",
|
||||||
|
"copy": "Zkopírovat odkaz",
|
||||||
|
"profile": "Profil",
|
||||||
|
"record": "Zobrazit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"home": "Hlavní stránka",
|
||||||
|
"logs": "Záznamy",
|
||||||
|
"stations": "Stanice",
|
||||||
|
"trains": "Vlaky",
|
||||||
|
"leaderboard": "Žebříčky hráčů",
|
||||||
|
"active_players": "Aktivní hráči",
|
||||||
|
"info": "INFO",
|
||||||
|
"admin": "ADMIN",
|
||||||
|
"logged": "Přihlášen jako {{username}}",
|
||||||
|
"logout": "Odhlásit se",
|
||||||
|
"profiles": "Profily"
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"admin": "Administrátor",
|
||||||
|
"leaderboard_hidden": "Žebříček je skrytý",
|
||||||
|
"hidden": "Profil je skrytý"
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"header": "Akce moderátora",
|
||||||
|
"hideLeaderboard": {
|
||||||
|
"modal": {
|
||||||
|
"title": "Jste si jisti?",
|
||||||
|
"description": "Tato akce skryje profil uživatele z žebříčků."
|
||||||
|
},
|
||||||
|
"button": "Skrýt profil v žebříčku",
|
||||||
|
"button2": "Zobrazit profil v žebříčku",
|
||||||
|
"alert": "Profil hráče je skrytý."
|
||||||
|
},
|
||||||
|
"hide": {
|
||||||
|
"modal": {
|
||||||
|
"title": "Jste si jisti?",
|
||||||
|
"description": "Tato akce skryje profil uživatele ze zobrazování v žebříčku."
|
||||||
|
},
|
||||||
|
"button": "Skrýt profil",
|
||||||
|
"alert": "Profil hráče je skrytý."
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"button": "Vynutit aktualizaci",
|
||||||
|
"alert": "Profil je aktualizován!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"license": "License:",
|
"license": "License:",
|
||||||
"powered": "Based on:",
|
"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"
|
"author": "Created by <anchor>{{author}}</anchor> with ❤️ for the Simrail community"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -63,14 +63,27 @@
|
|||||||
"trains": {
|
"trains": {
|
||||||
"header": "Train Statistics",
|
"header": "Train Statistics",
|
||||||
"train": "Train",
|
"train": "Train",
|
||||||
|
"distance": "Distance: {{distance}}km",
|
||||||
|
"score": "Points: {{score}}",
|
||||||
|
"time": "Time: {{time}}",
|
||||||
|
"sortby": {
|
||||||
|
"title": "Sort: ",
|
||||||
|
"min": "Lowest",
|
||||||
|
"max": "Highest",
|
||||||
|
"time": "Time",
|
||||||
"distance": "Distance",
|
"distance": "Distance",
|
||||||
"points": "Points",
|
"score": "Points"
|
||||||
"time": "Time"
|
}
|
||||||
},
|
},
|
||||||
"stations": {
|
"stations": {
|
||||||
"header": "Station Statistics",
|
"header": "Station Statistics",
|
||||||
"station": "Station",
|
"station": "Station",
|
||||||
"time": "Time"
|
"time": "Time: {{time}}",
|
||||||
|
"sortby": {
|
||||||
|
"title": "Sort: ",
|
||||||
|
"min": "Lowest",
|
||||||
|
"max": "Highest"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"notfound": {
|
"notfound": {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"license": "Licencja:",
|
"license": "Licencja:",
|
||||||
"powered": "Oparte na:",
|
"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"
|
"author": "Stworzone przez <anchor>{{author}}</anchor> z ❤️ dla społeczności Simrail"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -63,14 +63,27 @@
|
|||||||
"trains": {
|
"trains": {
|
||||||
"header": "Statystyki pociągów",
|
"header": "Statystyki pociągów",
|
||||||
"train": "Pociąg",
|
"train": "Pociąg",
|
||||||
|
"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",
|
"distance": "Dystans",
|
||||||
"points": "Punkty",
|
"score": "Punkty"
|
||||||
"time": "Czas"
|
}
|
||||||
},
|
},
|
||||||
"stations": {
|
"stations": {
|
||||||
"header": "Statystyki stacji",
|
"header": "Statystyki stacji",
|
||||||
"station": "Stacja",
|
"station": "Stacja",
|
||||||
"time": "Czas"
|
"time": "Czas: {{time}}",
|
||||||
|
"sortby": {
|
||||||
|
"title": "Sortowanie: ",
|
||||||
|
"min": "Od najniższej",
|
||||||
|
"max": "Od najwyższej"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"notfound": {
|
"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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
@ -27,36 +27,36 @@ import useSWR from "swr";
|
|||||||
import { get } from "../../util/fetcher.ts";
|
import { get } from "../../util/fetcher.ts";
|
||||||
|
|
||||||
|
|
||||||
export const Profile = () =>
|
export const Profile = () => {
|
||||||
{
|
|
||||||
const { id } = useParams();
|
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();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* LOADING */ }
|
{/* LOADING */}
|
||||||
{ isLoading && <ContentLoader/> }
|
{(isLoading || images.isLoading) && <ContentLoader />}
|
||||||
{/* ERROR */ }
|
{/* ERROR */}
|
||||||
{ error && <LoadError/> }
|
{(error || images.error) && <LoadError />}
|
||||||
{/* BLACKLISTED */ }
|
{/* BLACKLISTED */}
|
||||||
{ data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
|
{data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
|
||||||
description="The player's profile could not be displayed due to active moderator actions."/> }
|
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") }
|
{data && data.code === 403 && <WarningAlert title={t("profile.errors.blacklist.title")}
|
||||||
description={ t("profile.errors.blacklist.description") }/> }
|
description={t("profile.errors.blacklist.description")} />}
|
||||||
{/* NOT FOUND */ }
|
{/* NOT FOUND */}
|
||||||
{ data && data.code === 404 && <PageMeta title="simrail.pro | Profile 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."/> }
|
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") }
|
{data && data.code === 404 && <WarningAlert title={t("profile.errors.notfound.title")}
|
||||||
description={ t("profile.errors.notfound.description") }/> }
|
description={t("profile.errors.notfound.description")} />}
|
||||||
|
|
||||||
{/* SUCCESS */ }
|
{/* SUCCESS */}
|
||||||
{ data && data.code === 200 && <PageMeta image={ data.data.player.username }
|
{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` }
|
title={`simrail.pro | ${data.data.player.username}'s profile`}
|
||||||
description={ `${ data.data.player.trainDistance ? 0 : ((data.data.player.trainDistance / 1000).toFixed(2)) } driving experience |
|
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.player.dispatcherTime ? 0 : formatTime(data.data.player.dispatcherTime)} dispatcher experience`} />}
|
||||||
{ data && data.code === 200 && <ProfileCard data={ data.data }/> }
|
{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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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