Compare commits

..

101 Commits
v3.3.1 ... main

Author SHA1 Message Date
6d752c99d7
Merge pull request 'new-build' (#133) from new-build into main
All checks were successful
continuous-integration/drone/tag Build is passing
Reviewed-on: #133
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-27 15:55:55 +02:00
d516d22411
build(): new build system 2025-04-27 15:55:32 +02:00
9833bcc09a
build(): new build system 2025-04-27 15:54:53 +02:00
0f2d0bcf44
Merge pull request 'new-build' (#132) from new-build into main
All checks were successful
continuous-integration/drone/tag Build is passing
Reviewed-on: #132
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-27 15:52:44 +02:00
35bbbd902c
build(): new build system, fix(): remove gf 2025-04-27 15:52:16 +02:00
4b301c9644
build(): test new build sys, add script
All checks were successful
continuous-integration/drone/tag Build is passing
2025-04-27 15:44:55 +02:00
69d174e5c4
build(): test new build sys, add script
All checks were successful
continuous-integration/drone/tag Build is passing
2025-04-27 15:41:40 +02:00
8d2b870d54
build(): test new build sys, add script
All checks were successful
continuous-integration/drone/tag Build is passing
2025-04-27 15:25:42 +02:00
4b6f41a6c7
build(): test new build sys, add script
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-04-27 15:24:18 +02:00
b41a0a9127
build(): test new build sys 2025-04-27 15:24:09 +02:00
418d5122bc
Merge pull request 'fix(frontend): profile sorting' (#131) from fix-sort into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #131
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-27 00:29:48 +02:00
94903146c4
fix(frontend): profile sorting 2025-04-27 00:28:31 +02:00
fb806b0991
Merge pull request 'fix: fix profile' (#128) from fix-profile into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #128
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 21:16:25 +02:00
361fc9fb97
fix: fix profile 2025-04-21 21:15:33 +02:00
e14399d04b
Merge pull request 'fix: fix profile' (#127) from fix-profile into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #127
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 21:12:53 +02:00
b19b18da5b
fix: fix profile 2025-04-21 21:12:18 +02:00
837e3de296
Merge pull request 'fix: fix build' (#126) from fix-build into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #126
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 21:06:22 +02:00
3cfd4752a1
fix: fix build 2025-04-21 21:05:57 +02:00
b6956bfcfc
Merge pull request 'fix: fix build' (#125) from fix-build into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #125
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 21:03:35 +02:00
1947859e0d
fix: fix build 2025-04-21 21:03:08 +02:00
a628ad5d56
Merge pull request 'fix: fix build' (#124) from fix-build into main
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Reviewed-on: #124
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 21:02:30 +02:00
9deb009d82
fix: fix build
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2025-04-21 21:02:15 +02:00
2ead40d6ae
Merge pull request 'fix: fix build' (#123) from fix-build into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #123
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 20:59:56 +02:00
9b63300a0a
fix: fix build 2025-04-21 20:59:34 +02:00
d51d7d36a8
Merge pull request 'fix: fix build' (#122) from fix-build into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #122
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 20:52:59 +02:00
f8afea20df
fix: fix build 2025-04-21 20:52:33 +02:00
68436020c1
Merge pull request 'fix: add required files' (#121) from new-profiles into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #121
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 20:48:39 +02:00
f4561e41c3
fix: add required files 2025-04-21 20:48:21 +02:00
1e01260e82
Merge pull request 'feat: rewrite profile section, some minor chaanges' (#120) from new-profiles into main
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #120
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-21 20:47:11 +02:00
82397be39c
fix: use optionalstyle in imgproxy#generateUrl 2025-04-21 20:45:29 +02:00
99dc69ee8f
feat: rewrite profile section, some minor chaanges 2025-04-21 20:41:45 +02:00
996f16a313
Merge pull request 'update license year.' (#119) from license-year into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #119
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-04-11 16:52:41 +02:00
0f8b1a4729
update license year. 2025-04-11 16:48:47 +02:00
badefb0f7f
Merge pull request 'build(): drone CI build' (#118) from drone-ci into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #118
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-03-27 16:57:27 +01:00
2ba037fab5
build(): drone CI build 2025-03-27 16:56:14 +01:00
637134081a
Merge pull request 'build(): drone CI' (#117) from drone-ci into main
All checks were successful
Build simrail.pro / build (push) Successful in 16s
continuous-integration/drone/push Build is passing
Reviewed-on: #117
2025-03-27 16:54:39 +01:00
96c17ffe85
build(): drone CI
All checks were successful
Build simrail.pro / build (push) Successful in 14s
2025-03-27 16:54:21 +01:00
8db158afe9
Merge pull request 'build(): drone CI' (#116) from drone-ci into main
Some checks failed
continuous-integration/drone/push Build is failing
Build simrail.pro / build (push) Successful in 14s
Reviewed-on: #116
2025-03-27 16:51:58 +01:00
6015349c6f
build(): drone CI
Some checks failed
Build simrail.pro / build (push) Has been cancelled
2025-03-27 16:51:18 +01:00
cd191a6f61
Merge pull request 'build(): drone CI' (#115) from drone-ci into main
Some checks failed
continuous-integration/drone/push Build is failing
Build simrail.pro / build (push) Successful in 14s
Reviewed-on: #115
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-03-27 16:50:37 +01:00
08bfe767bb
build(): drone CI
All checks were successful
Build simrail.pro / build (push) Successful in 41s
2025-03-27 16:49:36 +01:00
6a4ebf56c4
Merge pull request 'fix(): refactor frontend build process to optimize site performance' (#114) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 29s
Reviewed-on: #114
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-03-05 14:40:36 +01:00
1e4cafc07f
fix(): refactor frontend build process to optimize site performance
All checks were successful
Build simrail.pro / build (push) Successful in 1m6s
2025-03-05 14:37:07 +01:00
623bdd8d42
Merge pull request 'fix(): rollbacl' (#113) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #113
2025-03-04 16:02:11 +01:00
51b3ff2e22
fix(): rollbacl
All checks were successful
Build simrail.pro / build (push) Successful in 42s
2025-03-04 16:01:56 +01:00
816ee5c454
Merge pull request 'fix(): rollbacl' (#112) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Has been cancelled
Reviewed-on: #112
2025-03-04 16:01:23 +01:00
9a3f004c72
fix(): rollbacl
Some checks failed
Build simrail.pro / build (push) Failing after 32s
2025-03-04 16:01:07 +01:00
f528da171b
Merge pull request 'fix(): rollbacl' (#111) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 25s
Reviewed-on: #111
2025-03-04 16:00:00 +01:00
b23921a28c
fix(): rollbacl
All checks were successful
Build simrail.pro / build (push) Successful in 54s
2025-03-04 15:59:42 +01:00
e045ded046
Merge pull request 'fix(): rollbacl' (#110) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 23s
Reviewed-on: #110
2025-03-04 15:59:19 +01:00
859b7ab3cc
fix(): rollbacl
Some checks failed
Build simrail.pro / build (push) Failing after 26s
2025-03-04 15:58:58 +01:00
34432e9622
Merge pull request 'fix(): rollbacl' (#109) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #109
2025-03-04 15:54:47 +01:00
3af371703b
fix(): rollbacl
All checks were successful
Build simrail.pro / build (push) Successful in 43s
2025-03-04 15:54:28 +01:00
fcef4e428e
Merge pull request 'fix(): rollbacl' (#108) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 22s
Reviewed-on: #108
2025-03-04 15:49:46 +01:00
ca8e270c5e
fix(): rollbacl
All checks were successful
Build simrail.pro / build (push) Successful in 43s
2025-03-04 15:49:30 +01:00
a9d35c604e
Merge pull request 'fix(): rollbacl' (#107) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 23s
Reviewed-on: #107
2025-03-04 15:48:40 +01:00
8c24a29de2
fix(): rollbacl
Some checks failed
Build simrail.pro / build (push) Failing after 43s
2025-03-04 15:48:17 +01:00
e079a1177e
Merge pull request 'fix(): fix nginx' (#106) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 23s
Reviewed-on: #106
2025-03-04 15:46:47 +01:00
7bf053e452
fix(): fix nginx
Some checks failed
Build simrail.pro / build (push) Failing after 23s
2025-03-04 15:46:09 +01:00
328f665c5c
Merge pull request 'fix(): fix nginx' (#105) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 23s
Reviewed-on: #105
2025-03-04 15:45:21 +01:00
05fe82eff1
fix(): fix nginx
Some checks failed
Build simrail.pro / build (push) Failing after 33s
2025-03-04 15:45:05 +01:00
3143ce1058
Merge pull request 'fix(): fix nginx' (#104) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 23s
Reviewed-on: #104
2025-03-04 15:44:06 +01:00
85d18190a7
fix(): fix nginx
Some checks failed
Build simrail.pro / build (push) Failing after 44s
2025-03-04 15:43:51 +01:00
45434f5a2d
Merge pull request 'fix(): fix nginx' (#103) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #103
2025-03-04 15:41:16 +01:00
922b7ea633
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 39s
2025-03-04 15:40:57 +01:00
f0f01ccda1
Merge pull request 'fix(): fix nginx' (#102) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #102
2025-03-04 15:38:43 +01:00
947bd5dedc
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 56s
2025-03-04 15:38:21 +01:00
a2a9fd25b5
Merge pull request 'fix(): fix nginx' (#101) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #101
2025-03-04 15:36:10 +01:00
afdc700a64
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 39s
2025-03-04 15:35:54 +01:00
f5ba173b24
Merge pull request 'fix(): fix nginx' (#100) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 22s
Reviewed-on: #100
2025-03-04 15:34:53 +01:00
7459649829
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 39s
2025-03-04 15:34:37 +01:00
31c1f3fc40
Merge pull request 'fix(): fix nginx' (#99) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 22s
Reviewed-on: #99
2025-03-04 15:31:35 +01:00
df1d9df212
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 44s
2025-03-04 15:31:03 +01:00
680a18d15a
Merge pull request 'fix(): fix nginx' (#98) from feat-1 into main
Some checks failed
Build simrail.pro / build (push) Failing after 22s
Reviewed-on: #98
2025-03-04 15:30:06 +01:00
40f31ba723
fix(): fix nginx
Some checks failed
Build simrail.pro / build (push) Failing after 31s
2025-03-04 15:29:41 +01:00
f9974a3430
Merge pull request 'fix(): rollbacl' (#97) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #97
2025-03-04 15:20:36 +01:00
c5249da57e
fix(): rollbacl
All checks were successful
Build simrail.pro / build (push) Successful in 34s
2025-03-04 15:20:19 +01:00
85e00e8d52
Merge pull request 'fix(): fix nginx' (#96) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 23s
Reviewed-on: #96
2025-03-04 15:16:27 +01:00
5841c77913
fix(): fix nginx
All checks were successful
Build simrail.pro / build (push) Successful in 35s
2025-03-04 15:16:03 +01:00
84d2972869
Merge pull request 'fix(): nginx config for spa' (#95) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 22s
Reviewed-on: #95
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-03-04 15:09:30 +01:00
94407bc7c4
Merge branch 'main' of https://git.alekswilc.dev/simrail/simrail.pro
All checks were successful
Build simrail.pro / build (push) Successful in 37s
2025-03-04 15:06:46 +01:00
47695d7e4f
fix(): nginx config for SPA 2025-03-04 15:06:23 +01:00
529d8b8020
Merge pull request 'feat(): docker, fix darkmode' (#93) from feat-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 25s
Reviewed-on: #93
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-02-20 01:39:15 +01:00
3db6ced4d2
feat(): docker, fix darkmode
All checks were successful
Build simrail.pro / build (push) Successful in 1m29s
2025-02-20 01:36:13 +01:00
e5614da846
Merge pull request 'Update packages/backend/src/util/imgproxy.ts' (#92) from alekswilc-patch-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 20s
Reviewed-on: #92
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-02-11 03:15:58 +01:00
71a5b77235
Update packages/backend/src/util/imgproxy.ts
All checks were successful
Build simrail.pro / build (push) Successful in 1m2s
2025-02-11 03:14:44 +01:00
7617738ab2
Merge pull request 'fix(): active trains search' (#90) from v3.0.1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 18s
Reviewed-on: #90
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2025-01-13 02:19:08 +01:00
9c3d3e0767
fix(): fix workflows
All checks were successful
Build simrail.pro / build (push) Successful in 18s
2025-01-13 02:18:21 +01:00
294068ee97
fix(): fix workflows
All checks were successful
Build simrail.pro / build (push) Successful in 1m6s
Build simrail.pro / build (pull_request) Successful in 19s
2025-01-13 02:16:42 +01:00
29a7ab3a6b
fix(): fix runner 2025-01-13 02:12:38 +01:00
b4cee2447d
fix(): active trains search 2025-01-13 02:08:56 +01:00
a4091c92e4
Merge pull request 'Update .gitea/workflows/build.yaml' (#89) from alekswilc-patch-1 into main
All checks were successful
Build simrail.pro / build (push) Successful in 19s
Reviewed-on: #89
Reviewed-by: Aleksander Wilczyński <aleks@alekswilc.dev>
2024-12-31 02:29:47 +01:00
77a9540be4
Update .gitea/workflows/build.yaml
All checks were successful
Build simrail.pro / build (push) Successful in 18s
2024-12-31 02:29:04 +01:00
f8f5a38add
Update .gitea/workflows/build.yaml
All checks were successful
Build simrail.pro / build (push) Successful in 18s
2024-12-31 02:26:37 +01:00
4c7482b919
Update .gitea/workflows/build.yaml
All checks were successful
Build project / build (push) Successful in 18s
2024-12-31 02:25:37 +01:00
f10c623aa8
Update .gitea/workflows/build.yaml
All checks were successful
Build project / build (push) Successful in 18s
Build project / build (pull_request) Successful in 18s
2024-12-31 02:23:29 +01:00
4e090ed281
Update .gitea/workflows/build.yaml
Some checks failed
Build project / build (22.x) (push) Failing after 9s
Build project / build (22.x) (pull_request) Failing after 3s
2024-12-31 02:12:33 +01:00
1b9c616e16
Update .gitea/workflows/build.yaml
All checks were successful
Build project / build (push) Successful in 19s
2024-12-31 02:07:55 +01:00
7fe575a651
Update .gitea/workflows/build.yaml
Some checks failed
Build project / build (push) Failing after 17s
2024-12-31 02:07:05 +01:00
f33c54ba26
Update .gitea/workflows/build.yaml
Some checks failed
Build project / build (push) Failing after 36s
2024-12-31 02:05:54 +01:00
a9094fd1ed
Add .gitea/workflows/build.yaml 2024-12-31 02:04:04 +01:00
108 changed files with 1099 additions and 692 deletions

49
.drone.yml Normal file
View 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

View File

@ -4,7 +4,6 @@
"main": "dist/index.js",
"scripts": {
"build": "yarn workspace backend build && yarn workspace frontend build",
"postbuild": "copyfiles --error ./LICENSE.txt ./dist && copyfiles --error ./LICENSE.txt ./dist/frontend/",
"start": "concurrently --kill-others-on-fail \"yarn workspace backend start\" \"yarn workspace frontend dev\""
},
"workspaces": [

View 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"]

View File

@ -0,0 +1,4 @@
{
"tag": "",
"commit": ""
}

View File

@ -4,8 +4,10 @@
"main": "../../dist/backend/index.js",
"version": "3.0.0",
"scripts": {
"build": "tsc",
"start": "yarn build && doppler run node ../../dist/backend/index.js"
"build": "docker build --progress=plain -t simrailpro:backend .",
"rawbuild": "yarn tsc",
"start": "yarn rawbuild && node --env-file=.env dist/index.js",
"make-git-info": "node scripts/make-git.js"
},
"author": "Aleksander <alekswilc> Wilczyński",
"license": "AGPL-3.0-only",
@ -15,6 +17,7 @@
"@types/node": "^22.10.1",
"@types/uuid": "^10.0.0",
"copyfiles": "^2.4.1",
"tsc": "^2.0.4",
"typescript": "^5.5.4"
},
"type": "module",

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View 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) ?? "",
}))
})();

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -66,10 +66,9 @@ export class ActivePlayersRoute
app.get("/train", async (req, res) =>
{
const s = req.query.q?.toString().split(",").map(x => new RegExp(escapeRegexString(x), "i"));
const s = req.query.query?.toString().split(",").map(x => new RegExp(escapeRegexString(x), "i"));
const sserver = req.query.server?.toString();
let a: ActiveTrain[] = [];
for (const data of sserver ? [ client.trains[ sserver as Server["ServerCode"] ] ] : Object.values(client.trains))

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -67,7 +67,7 @@ export class LogRoute
log.player.avatar = generateUrl(log.player.avatar, "rs:auto:256:256:1/f:png");
}
res.status(200).json(new SuccessResponseBuilder().setCode(200).setData(log.toJSON()));
res.status(200).json(new SuccessResponseBuilder().setCode(200).setData(log.toJSON()).toJSON());
});
return app;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -24,6 +24,7 @@ import { StatsRoute } from "./routes/stats.js";
import { LogRoute } from "./routes/log.js";
import { ActivePlayersRoute } from "./routes/activePlayer.js";
import { AdminRoute } from "./routes/admin.js";
import { ImagesRoute } from './routes/images.js';
export class ApiModule
{
@ -39,6 +40,8 @@ export class ApiModule
router.use("/leaderboard/", LeaderboardRoute.load());
router.use("/active/", ActivePlayersRoute.load());
router.use("/admin/", AdminRoute.load());
router.use("/images/", ImagesRoute.load());
router.use("/stats/", StatsRoute.load());
router.use("/log/", LogRoute.load());

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* See LICENSE for more.
*/
import wcmatch from "wildcard-match";
/*
@ -120,10 +137,80 @@ export const trainsList = [
"4E/EU07-*",
],
},
{
train: 'Ty2',
pattern: [
'Ty2/*'
]
}
];
export const stationsMap: Record<string, string> = {
"Grodzisk Mazowiecki": "https://api.simrail.eu:8083/Thumbnails/Stations/gr1m.jpg",
"Korytów": "https://api.simrail.eu:8083/Thumbnails/Stations/kr1m.jpg",
"Szeligi": "https://api.simrail.eu:8083/Thumbnails/Stations/sz1m.jpg",
"Włoszczowa Północ": "https://api.simrail.eu:8083/Thumbnails/Stations/wp1m.jpg",
"Knapówka": "https://api.simrail.eu:8083/Thumbnails/Stations/kn1m.jpg",
"Psary": "https://api.simrail.eu:8083/Thumbnails/Stations/ps1m.jpg",
"Góra Włodowska": "https://api.simrail.eu:8083/Thumbnails/Stations/gw1m.jpg",
"Idzikowice": "https://api.simrail.eu:8083/Thumbnails/Stations/id1m.jpg",
"Katowice Zawodzie": "https://api.simrail.eu:8083/Thumbnails/Stations/kz1m.jpg",
"Sosnowiec Główny": "https://api.simrail.eu:8083/Thumbnails/Stations/sg1m.jpg",
"Dąbrowa Górnicza": "https://api.simrail.eu:8083/Thumbnails/Stations/dg1m.jpg",
"Zawiercie": "https://api.simrail.eu:8083/Thumbnails/Stations/zw1m.jpg",
"Będzin": "https://api.simrail.eu:8083/Thumbnails/Stations/b1m.jpg",
"Sosnowiec Południowy": "https://api.simrail.eu:8083/Thumbnails/Stations/spl1m.jpg",
"Opoczno Południe": "https://api.simrail.eu:8083/Thumbnails/Stations/op1m.jpg",
"Dąbrowa Górnicza Wschodnia": "https://api.simrail.eu:8083/Thumbnails/Stations/dws1m.jpg",
"Dorota": "https://api.simrail.eu:8083/Thumbnails/Stations/dra1m.jpg",
"Łazy Ła": "https://api.simrail.eu:8083/Thumbnails/Stations/la1m.jpg",
"Łazy": "https://api.simrail.eu:8083/Thumbnails/Stations/lb1m.jpg",
"Juliusz": "https://api.simrail.eu:8083/Thumbnails/Stations/ju1m.jpg",
"Łazy Łc": "https://api.simrail.eu:8083/Thumbnails/Stations/lc1m.jpg",
"Katowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ko1m.jpg",
"Dąbrowa Górnicza Ząbkowice": "https://api.simrail.eu:8083/Thumbnails/Stations/dz1m.jpg",
"Sławków": "https://api.simrail.eu:8083/Thumbnails/Stations/sl1m.jpg",
"Starzyny": "https://api.simrail.eu:8083/Thumbnails/Stations/str1m.jpg",
"Bukowno": "https://api.simrail.eu:8083/Thumbnails/Stations/bo1m.jpg",
"Tunel": "https://api.simrail.eu:8083/Thumbnails/Stations/tl1m.jpg",
"Dąbrowa Górnicza Huta Katowice": "https://api.simrail.eu:8083/Thumbnails/Stations/dghk1m.jpg",
"Sosnowiec Kazimierz": "https://api.simrail.eu:8083/Thumbnails/Stations/skz1m.jpg",
"Pruszków": "https://api.simrail.eu:8083/Thumbnails/Stations/pr1m.jpg",
"Strzałki": "https://api.simrail.eu:8083/Thumbnails/Stations/st1m.jpg",
"Olszamowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ol1m.jpg",
"Miechów": "https://api.simrail.eu:8083/Thumbnails/Stations/mi1m.jpg",
"Kraków Przedmieście": "https://api.simrail.eu:8083/Thumbnails/Stations/kpm1m.jpg",
"Kraków Batowice": "https://api.simrail.eu:8083/Thumbnails/Stations/kb1m.jpg",
"Raciborowice": "https://api.simrail.eu:8083/Thumbnails/Stations/ra1m.jpg",
"Zastów": "https://api.simrail.eu:8083/Thumbnails/Stations/zs1m.jpg",
"Niedźwiedź": "https://api.simrail.eu:8083/Thumbnails/Stations/nd1m.jpg",
"Słomniki": "https://api.simrail.eu:8083/Thumbnails/Stations/sm1m.jpg",
"Kozłów": "https://api.simrail.eu:8083/Thumbnails/Stations/koz1m.jpg",
"N/A": 'https://shared.steamstatic.com/store_item_assets/steam/apps/1422130/header.jpg'
};
export const trainsMap: Record<string, string> = {
"Traxx (E186)": "https://wiki.simrail.eu/vehicle/poland/trains/elec-loco/traxx/20241029163359_1.jpg",
"Dragon2 (E6ACTa, E6ACTadb)": "https://wiki.simrail.eu/vehicle/e6acta-016.jpg",
"Dragon2 (ET25)": "https://wiki.simrail.eu/vehicle/et25-002.jpg",
"Pendolino (ED250)": "https://wiki.simrail.eu/vehicle/ed250-001.png",
"EN57": "https://wiki.simrail.eu/vehicle/en57-009.png",
"EN71": "https://wiki.simrail.eu/vehicle/en71-002.png",
"EN76": "https://wiki.simrail.eu/vehicle/en76-006.jpg",
"EN96": "https://wiki.simrail.eu/vehicle/en96-001.jpg",
"EP07": "https://wiki.simrail.eu/vehicle/ep07-174.jpg",
"EP08": "https://wiki.simrail.eu/vehicle/poland/trains/elec-loco/ep08/20241106002003_1.jpg",
"ET22": "https://wiki.simrail.eu/vehicle/et22-243.png",
"EU07": "https://wiki.simrail.eu/vehicle/eu07-005.jpg",
"Ty2": "https://wiki.simrail.eu/vehicle/ty2-70.png",
"N/A": 'https://shared.steamstatic.com/store_item_assets/steam/apps/1422130/header.jpg'
};
export const getVehicle = (name: string) =>
{
return trainsList.find(x => wcmatch(x.pattern)(name))?.train;
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -14,7 +14,7 @@
* See LICENSE for more.
*/
import { execSync } from "child_process";
import gitInfo from '../../git.json' with { type: "json" };
export class GitUtil
{
@ -22,26 +22,12 @@ export class GitUtil
private static getLatestVersion()
{
try
{
const data = execSync("git describe --tags --exact-match").toString();
return data.replace("\n", "");
} catch
{
return undefined;
}
return gitInfo.tag;
}
private static getLatestCommit()
{
try
{
const data = execSync("git rev-parse --short HEAD").toString();
return data.replace("\n", "");
} catch
{
return undefined;
}
return gitInfo.commit;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -28,7 +28,7 @@ export const imgProxySign = (target: string) =>
export const generateUrl = (url: string, options: string = "rs:auto:128:128:1/f:png") =>
{
if (url.includes('https://imgproxy.alekswilc.dev/')) return url;
if (url.includes('https://proxy.cdn.alekswilc.dev/')) return url;
if (process.env.NODE_ENV === "development")
{
@ -38,5 +38,5 @@ export const generateUrl = (url: string, options: string = "rs:auto:128:128:1/f:
}
const signature = imgProxySign(`/${ options }/plain/${ url }`);
return `https://imgproxy.alekswilc.dev/${ signature }/${ options }/plain/${ url }`;
return `https://proxy.cdn.alekswilc.dev/${ signature }/${ options }/plain/${ url }`;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -39,7 +39,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
@ -55,7 +55,7 @@
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "../../dist/backend", /* Specify an output folder for all emitted files. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */

View 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" ]

View File

@ -1,5 +1,5 @@
<!--
~ Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
~ Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published

View File

@ -5,8 +5,9 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"rawbuild": "vite build",
"preview": "vite preview",
"build": "docker build --progress=plain -t simrailpro:frontend ."
},
"dependencies": {
"dayjs": "^1.11.13",
@ -35,7 +36,7 @@
"prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.4.1",
"tailwindcss": "^3.4.1",
"vite": "^4.4.7",
"vite": "^6.2.0",
"webpack": "^5.88.2"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* See LICENSE for more.
*/
import { Dispatch, SetStateAction } from "react";
export const ConfirmModal = ({ showModal, setShowModal, onConfirm, title, description }: {

View File

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

View 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>
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -16,34 +16,38 @@
import { Dispatch, SetStateAction } from "react";
const getPaginationNums = (page: number, pages: number) => {
if (pages <= 5)
return Array.from({ length: pages }, (_, i) => i + 1);
const numbers = [1];
if (page <= 3) {
numbers.push(2, 3, 4);
} else if (page >= pages - 2) {
numbers.push(pages - 3, pages - 2, pages - 1);
} else {
numbers.push(page - 1, page, page + 1);
}
numbers.push(pages);
return [...new Set(numbers)].sort((a, b) => a - b);
}
export const Paginator = ({ page, setPage, pages }: {
page: number,
pages: number,
setPage: Dispatch<SetStateAction<number>>
}) =>
{
let numbers = [ 1, page - 2, page - 1, page, page + 1, page + 2 ];
page === 1 && (numbers = [ page, page + 1, page + 2, page + 3, page + 4 ]);
page === 2 && (numbers = [ page - 1, page, page + 1, page + 2, page + 3 ]);
page === 3 && (numbers = [ page - 2, page - 1, page, page + 1, page + 2 ]);
(page === pages) && (numbers = [ 1, page - 4, page - 3, page - 2, page - 1 ]);
(page === (pages - 1)) && (numbers = [ 1, page - 3, page - 2, page - 1, page ]);
(page === (pages - 2)) && (numbers = [ 1, page - 2, page - 1, page, page + 1 ]);
numbers = numbers.filter(x => (pages + 1) >= x && x > 0);
}) => {
// todo: rewrite this shit XDDDDDDDD
const numbers = getPaginationNums(page, pages);
return <div
className="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark flex flex-row align-center justify-center p-2">
className="rounded-sm flex flex-row align-center justify-center p-2">
<ul className="flex flex-wrap items-center">
<li>
<a className="cursor-pointer flex h-9 w-9 items-center justify-center rounded-l-md border border-stroke hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark"
onClick={ () => setPage(page => (page - 1) < 1 ? 1 : page - 1) }>
onClick={() => setPage(page => (page - 1) < 1 ? 1 : page - 1)}>
<svg className="fill-current" width="8" height="16" viewBox="0 0 8 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<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>
</a></li>
{ numbers.map(num =>
{
{numbers.map(num => {
return <li>
<a onClick={ () => setPage(num) }
className={ `cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${ page === num && "text-primary border-primary dark:border-primary" }` }>{ num }</a>
<a onClick={() => setPage(num)}
className={`cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${page === num && "text-primary border-primary dark:border-primary"}`}>{num}</a>
</li>;
}) }
{ !!pages && <li>
<a className={ `cursor-pointer flex items-center justify-center border border-stroke border-l-transparent py-[5px] px-4 font-medium hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none ${ page === pages && "text-primary border-primary dark:border-primary" }` }
onClick={ () => setPage(pages) }>{ pages }</a></li> }
})}
<li>
<a className="cursor-pointer flex h-9 w-9 items-center justify-center rounded-r-md border border-stroke border-l-transparent hover:border-primary hover:bg-gray hover:text-primary dark:border-strokedark dark:hover:border-primary dark:hover:bg-graydark select-none"
onClick={ () => setPage(page => (page + 1) > pages ? pages : page + 1) }>
onClick={() => setPage(page => (page + 1) > pages ? pages : page + 1)}>
<svg className="fill-current" width="8" height="16" viewBox="0 0 8 16" fill="none"
xmlns="http://www.w3.org/2000/svg">
<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"

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -15,9 +15,8 @@
*/
import { useState } from "react";
import { TProfileData } from "../../../types/profile.ts";
import { TProfileData, TProfilePlayer } from "../../../types/profile.ts";
import { useTranslation } from "react-i18next";
import { ArrowIcon, FlexArrowIcon } from "../../mini/icons/ArrowIcon.tsx";
import { formatTime } from "../../../util/time.ts";
import { useAuth } from "../../../hooks/useAuth.tsx";
import { ConfirmModal } from "../../mini/modal/ConfirmModal.tsx";
@ -25,78 +24,108 @@ import { post } from "../../../util/fetcher.ts";
import { toast } from "react-toastify";
import dayjs from "dayjs";
import { UserIcons } from "../../mini/icons/UserIcons.tsx";
import { StationStat } from '../../mini/profile/StationStat.tsx';
import { chunk } from '../../../util/chunk.ts';
import { Paginator } from '../../mini/util/Paginator.tsx';
import { TrainStat } from '../../mini/profile/TrainStat.tsx';
import { TImagesData } from '../../../types/images.ts';
export const ProfileCard = ({ data }: { data: TProfileData }) =>
{
const sortTrainsByList: Record<number, string> = {
[0]: 'time',
[1]: 'score',
[2]: 'distance',
}
const [ showTrains, setShowTrains ] = useState(false);
const [ showStations, setShowStations ] = useState(false);
const [ sortTrainsBy, setSortTrainsBy ] = useState<"time" | "score" | "distance">("distance");
const [ hideLeaderboardStatsModal, setHideLeaderboardStatsModal ] = useState(false);
const [ hideProfileModal, setHideProfileModal ] = useState(false);
export const ProfileCard = ({ data, images }: { data: TProfileData, images: TImagesData }) => {
const [sortTrainsBy, setSortTrainsBy] = useState(2);
const [sortTrainsBy2, setSortTrainsBy2] = useState(0);
const [sortStationsBy, setSortStationsBy] = useState(0);
const [hideLeaderboardStatsModal, setHideLeaderboardStatsModal] = useState(false);
const [hideProfileModal, setHideProfileModal] = useState(false);
const { isAdmin, token } = useAuth();
const adminToggleHideLeaderboardPlayerProfile = () =>
{
post(`/admin/profile/${ data.player.id }/${ data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard" }`, {}, { "X-Auth-Token": token })
.then((response) =>
{
if (response.code === 200)
{
// #region ADMIN
const adminToggleHideLeaderboardPlayerProfile = () => {
post(`/admin/profile/${data.player.id}/${data.player.flags.includes("leaderboard_hidden") ? "showLeaderboard" : "hideLeaderboard"}`, {}, { "X-Auth-Token": token })
.then((response) => {
if (response.code === 200) {
toast.success(t("admin.hideLeaderboard.alert"));
}
});
};
const adminHidePlayerProfile = () =>
{
post(`/admin/profile/${ data.player.id }/hide`, {}, { "X-Auth-Token": token })
.then((response) =>
{
if (response.code === 200)
{
const adminHidePlayerProfile = () => {
post(`/admin/profile/${data.player.id}/hide`, {}, { "X-Auth-Token": token })
.then((response) => {
if (response.code === 200) {
toast.success(t("admin.hide.alert"));
}
});
};
const adminForceUpdate = () =>
{
post(`/admin/profile/${ data.player.id }/forceUpdate`, {}, { "X-Auth-Token": token })
.then((response) =>
{
if (response.code === 200)
{
const adminForceUpdate = () => {
post(`/admin/profile/${data.player.id}/forceUpdate`, {}, { "X-Auth-Token": token })
.then((response) => {
if (response.code === 200) {
toast.success(t("admin.update.alert"));
}
});
};
// #endregion
const sortStations = (a: keyof TProfilePlayer['dispatcherStats'], b: keyof TProfilePlayer['dispatcherStats']) => {
if (sortStationsBy) {
const _a = a;
a = b;
b = _a;
}
return data.player.dispatcherStats[b].time - data.player.dispatcherStats[a].time
}
const sortTrains = (a: keyof TProfilePlayer['trainStats'], b: keyof TProfilePlayer['trainStats']) => {
if (sortTrainsBy2) {
const _a = a;
a = b;
b = _a;
}
return data.player.trainStats[b][(sortTrainsByList[sortTrainsBy] ?? 'distance') as 'distance'] - data.player.trainStats[a][(sortTrainsByList[sortTrainsBy] ?? 'distance') as 'distance'];
}
const dispatcherStats = [...chunk(Object.keys(data.player.dispatcherStats).sort(sortStations), 8)];
const [dispatcherPage, setDispatcherPage] = useState(1);
const trainStats = [...chunk(Object.keys(data.player.trainStats).sort(sortTrains), 8)];
const [trainPage, setTrainPage] = useState(1);
const { t } = useTranslation();
return <>
<ConfirmModal showModal={ hideLeaderboardStatsModal } setShowModal={ setHideLeaderboardStatsModal }
onConfirm={ adminToggleHideLeaderboardPlayerProfile }
title={ t("admin.hideLeaderboard.modal.title") }
description={ t("admin.hideLeaderboard.modal.description") }/>
<ConfirmModal showModal={ hideProfileModal } setShowModal={ setHideProfileModal }
onConfirm={ adminHidePlayerProfile } title={ t("admin.hide.modal.title") }
description={ t("admin.hide.modal.description") }/>
<ConfirmModal showModal={hideLeaderboardStatsModal} setShowModal={setHideLeaderboardStatsModal}
onConfirm={adminToggleHideLeaderboardPlayerProfile}
title={t("admin.hideLeaderboard.modal.title")}
description={t("admin.hideLeaderboard.modal.description")} />
<ConfirmModal showModal={hideProfileModal} setShowModal={setHideProfileModal}
onConfirm={adminHidePlayerProfile} title={t("admin.hide.modal.title")}
description={t("admin.hide.modal.description")} />
<div
className="overflow-hidden rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
className="overflow-hidden ">
<div className="px-4 pt-6 text-center lg:pb-8 xl:pb-11.5">
<div
className="mx-auto max-w-44 rounded-full">
<div className="relative rounded-full">
<img className="rounded-full" src={ data.player.avatar } alt="profile"/>
{ data.active &&
<span className="absolute w-full rounded-full border-white bg-[#219653] dark:border-black max-w-5.5 right-0 top-0 h-5.5 border-[3px]"></span> }
<img className="rounded-full" src={data.player.avatar} alt="profile" />
{data.active &&
<span className="absolute w-full rounded-full border-white bg-[#219653] dark:border-black max-w-5.5 right-0 top-0 h-5.5 border-[3px]"></span>}
</div>
</div>
<div className="mt-4">
<h3 className="text-2xl font-semibold text-black dark:text-white">
{ data.player.username } <UserIcons flags={ data.player.flags }/>
{data.player.username} <UserIcons flags={data.player.flags} />
</h3>
<div
@ -104,179 +133,144 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
<div
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
<span className="font-semibold text-black dark:text-white">
{ Math.floor(data.player.trainDistance / 1000) }km
{Math.floor(data.player.trainDistance / 1000)}km
</span>
<span className="text-sm text-wrap">{ t("profile.stats.distance") }</span>
<span className="text-sm text-wrap">{t("profile.stats.distance")}</span>
</div>
<div
className="flex flex-col items-center justify-center gap-1 border-r border-stroke px-4 dark:border-strokedark xsm:flex-row">
<span className="font-semibold text-black dark:text-white">
{ formatTime(data.player.dispatcherTime) }
{formatTime(data.player.dispatcherTime)}
</span>
<span className="text-sm text-wrap">{ t("profile.stats.time") }</span>
<span className="text-sm text-wrap">{t("profile.stats.time")}</span>
</div>
</div>
</div>
{ data.active && data.active.type === "train" &&
{data.active && data.active.type === "train" &&
<div className="mx-auto text-center">
<h4 className="font-semibold text-black dark:text-white">{ t("profile.active.train", { train: `${ data.active.trainName } - ${ data.active.trainNumber }`, server: data.active.server.toUpperCase() }) }</h4>
</div> }
<h4 className="font-semibold text-black dark:text-white">{t("profile.active.train", { train: `${data.active.trainName} - ${data.active.trainNumber}`, server: data.active.server.toUpperCase() })}</h4>
</div>}
{ data.active && data.active.type === "station" &&
{data.active && data.active.type === "station" &&
<div className="mx-auto text-center">
<h4 className="font-semibold text-black dark:text-white">{ t("profile.active.station", { station: `${ data.active.stationName } - ${ data.active.stationShort }`, server: data.active.server.toUpperCase() }) }</h4>
</div> }
<h4 className="font-semibold text-black dark:text-white">{t("profile.active.station", { station: `${data.active.stationName} - ${data.active.stationShort}`, server: data.active.server.toUpperCase() })}</h4>
</div>}
</div>
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.stations.header")}</h1>
<div className="flex flex-row gap-4">
<a className='cursor-pointer' onClick={() => setSortStationsBy((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
<strong>{t('profile.stations.sortby.title')}</strong> {sortStationsBy === 0 ? t('profile.stations.sortby.max') : t('profile.stations.sortby.min')}
</p></a>
</div>
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
{dispatcherStats[dispatcherPage - 1].map(stationName => {
const station = data.player.dispatcherStats[stationName];
{ Object.keys(data.player.trainStats || {}).length > 0 &&
<div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
<div className="group relative cursor-pointer" onClick={ () => setShowTrains(val => !val) }>
<h1 className="text-xl text-black dark:text-white pb-5">{ t("profile.trains.header") }</h1>
<ArrowIcon rotated={ showTrains }/>
return <StationStat stationName={stationName} time={station.time} image={images.stations[stationName] ?? images.stations['N/A']} />
})}
</div>
<div className="flex flex-col pt-4">
<Paginator setPage={setDispatcherPage} page={dispatcherPage} pages={dispatcherStats.length} />
</div>
</div>
{ showTrains &&
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
<div className="grid grid-cols-3 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-3">
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.train") }
</h5>
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.trains.header")}</h1>
<div className="flex flex-col">
<a className='cursor-pointer' onClick={() => setSortTrainsBy2((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
<strong>{t('profile.trains.sortby.title')}</strong> {sortTrainsBy2 === 0 ? t('profile.trains.sortby.max') : t('profile.trains.sortby.min')}
</p></a>
<a className='cursor-pointer' onClick={() => setSortTrainsBy((prev) => {
prev++;
if (prev > 2) prev = 0;
return prev;
})}><p className='text-base'>
<strong>{t('profile.trains.sortby.title')}</strong> {t('profile.trains.sortby.' + sortTrainsByList[sortTrainsBy])}
</p></a>
</div>
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("distance") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.distance") }
</h5>
<FlexArrowIcon rotated={ sortTrainsBy === "distance" || !sortTrainsBy }/>
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
{trainStats[trainPage - 1].map(trainName => {
const train = data.player.trainStats[trainName];
return <TrainStat trainName={trainName} time={train.time} distance={train.distance} score={train.score} image={images.trains[trainName] ?? images.trains['N/A']} />
})}
</div>
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("score") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.points") }
</h5>
<FlexArrowIcon rotated={ sortTrainsBy === "score" }/>
<div className="flex flex-col pt-4">
<Paginator setPage={setTrainPage} page={trainPage} pages={trainStats.length} />
</div>
{/*<div className="hidden sm:flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"*/ }
{/* onClick={ () => setSortTrainsBy("time") }>*/ }
{/* <h5 className="text-sm font-medium uppercase xsm:text-base">*/ }
{/* { t("profile.trains.time") }*/ }
{/* </h5>*/ }
{/* <FlexArrowIcon rotated={ sortTrainsBy === "time" }/>*/ }
{/*</div>*/ }
</div>
{ Object.keys(data.player.trainStats).sort((a, b) => data.player.trainStats[ b ][ sortTrainsBy ] - data.player.trainStats[ a ][ sortTrainsBy ]).map(trainName =>
{
const train = data.player.trainStats[ trainName ];
{/* <div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
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="grid grid-cols-2 rounded-sm bg-gray-2 dark:bg-meta-4">
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.stations.station") }
{t("profile.stations.station")}
</h5>
</div>
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.stations.time") }
{t("profile.stations.time")}
</h5>
</div>
</div>
{ Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[ b ].time - data.player.dispatcherStats[ a ].time).map(stationName =>
{
const station = data.player.dispatcherStats[ stationName ];
{Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[b].time - data.player.dispatcherStats[a].time).map(stationName => {
const station = data.player.dispatcherStats[stationName];
return <div
className={ `grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark` }
key={ stationName }
className={`grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark`}
key={stationName}
>
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
<p className="text-black dark:text-white sm:block break-all">
{ stationName }
{stationName}
</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-3">{ formatTime(station.time) }</p>
<p className="text-meta-3">{formatTime(station.time)}</p>
</div>
</div>;
}) }
})}
</div> }
</div>
</div> }
{ isAdmin && <>
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
<h1 className="text-xl text-black dark:text-white">{ t("admin.header") }</h1>
</div> */}
{isAdmin && <>
<div className="shadow-default items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
<h1 className="text-xl text-black dark:text-white">{t("admin.header")}</h1>
<div className="items-center justify-center p-2.5 flex xl:p-5 gap-2 flex-wrap sm:flex-nowrap">
{ data.player.flags.includes("leaderboard_hidden") ?
<button className={ "inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-success" }
onClick={ () => adminToggleHideLeaderboardPlayerProfile() }>
{ t("admin.hideLeaderboard.button2") }
{data.player.flags.includes("leaderboard_hidden") ?
<button className={"inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-success"}
onClick={() => adminToggleHideLeaderboardPlayerProfile()}>
{t("admin.hideLeaderboard.button2")}
</button> :
<button className={ "inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-danger" }
onClick={ () => setHideLeaderboardStatsModal(true) }>
{ t("admin.hideLeaderboard.button") }
</button> }
<button className={"inline-flex items-center justify-center rounded-md py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5 bg-danger"}
onClick={() => setHideLeaderboardStatsModal(true)}>
{t("admin.hideLeaderboard.button")}
</button>}
<button className="inline-flex items-center justify-center rounded-md bg-danger py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
onClick={ () => setHideProfileModal(true) }>
{ t("admin.hide.button") }
onClick={() => setHideProfileModal(true)}>
{t("admin.hide.button")}
</button>
<button className="inline-flex items-center justify-center rounded-md bg-primary py-2 px-5 text-center font-medium text-white hover:bg-opacity-50 lg:px-4 xl:px-5"
onClick={ () => adminForceUpdate() }>
{ t("admin.update.button") }
onClick={() => adminForceUpdate()}>
{t("admin.update.button")}
</button>
</div>
</div>
</> }
</>}
<div className="shadow-default dark:bg-boxdark items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
<div className="items-center justify-center p-2.5 flex flex-col xl:p-5 gap-2">
<h1 className="text-sm text-black dark:text-white">
{ t("profile.info", { date: dayjs(data.player.createdAt).format("DD/MM/YYYY") }) }
{t("profile.info", { date: dayjs(data.player.createdAt).format("DD/MM/YYYY") })}
</h1>
</div>

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,3 +1,20 @@
/*
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* See LICENSE for more.
*/
import { useContext, createContext, ReactNode } from "react";
import useSWR from "swr";
import { get } from "../util/fetcher.ts";

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -35,7 +35,7 @@ function useLocalStorage<T>(
return item ? JSON.parse(item) : initialValue;
} catch
{
return item ? item : initialValue;
return (item && item !== "undefined") ? item : initialValue;
}
}
} catch (error)

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -20,7 +20,7 @@
"footer": {
"license": "Licence:",
"powered": "Založeno na:",
"thanks": "Speciální poděkování: <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, komunita SimRail a moje přítelkyně",
"thanks": "Speciální poděkování: <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, komunita SimRail",
"author": "Pro komunitu SimRail vytvořeno s ❤️ uživatelem <anchor>{{author}}</anchor> "
}
},
@ -63,14 +63,27 @@
"trains": {
"header": "Statistiky vlaků",
"train": "Vlak",
"distance": "Vzdálenost: {{distance}}km",
"score": "Body: {{score}",
"time": "Čas: {{time}}",
"sortby": {
"title": "Řadit podle: ",
"min": "Nejnižší",
"max": "Nejvyšší",
"time": "Čas",
"distance": "Vzdálenost",
"points": "Body",
"time": "Čas"
"score": "Body"
}
},
"stations": {
"header": "Statistiky stanic",
"station": "Stanice",
"time": "Čas"
"time": "Čas: {{time}}",
"sortby": {
"title": "Řadit podle: ",
"min": "Nejnižší",
"max": "Nejvyšší"
}
},
"errors": {
"notfound": {

View File

@ -20,7 +20,7 @@
"footer": {
"license": "License:",
"powered": "Based on:",
"thanks": "Special thanks to <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite>, Simrail community and my girlfriend",
"thanks": "Special thanks to <bahu>BAHU.PRO hosting</bahu>, <simrailelite>Simrail ELITE discord</simrailelite> and Simrail community",
"author": "Created by <anchor>{{author}}</anchor> with ❤️ for the Simrail community"
}
},
@ -63,14 +63,27 @@
"trains": {
"header": "Train Statistics",
"train": "Train",
"distance": "Distance: {{distance}}km",
"score": "Points: {{score}}",
"time": "Time: {{time}}",
"sortby": {
"title": "Sort: ",
"min": "Lowest",
"max": "Highest",
"time": "Time",
"distance": "Distance",
"points": "Points",
"time": "Time"
"score": "Points"
}
},
"stations": {
"header": "Station Statistics",
"station": "Station",
"time": "Time"
"time": "Time: {{time}}",
"sortby": {
"title": "Sort: ",
"min": "Lowest",
"max": "Highest"
}
},
"errors": {
"notfound": {

View File

@ -20,7 +20,7 @@
"footer": {
"license": "Licencja:",
"powered": "Oparte na:",
"thanks": "Specjalne podziękowania dla <bahu>serwerowni BAHU.PRO</bahu>, <simrailelite>discorda Simrail ELITE</simrailelite>, społeczności Simrail i mojej dziewczyny",
"thanks": "Specjalne podziękowania dla <bahu>serwerowni BAHU.PRO</bahu>, <simrailelite>discorda Simrail ELITE</simrailelite> i społeczności Simrail",
"author": "Stworzone przez <anchor>{{author}}</anchor> z ❤️ dla społeczności Simrail"
}
},
@ -63,14 +63,27 @@
"trains": {
"header": "Statystyki pociągów",
"train": "Pociąg",
"distance": "Dystans: {{distance}}km",
"score": "Punkty: {{score}}",
"time": "Czas: {{time}}",
"sortby": {
"title": "Sortowanie: ",
"min": "Od najniższej",
"max": "Od najwyższej",
"time": "Czas",
"distance": "Dystans",
"points": "Punkty",
"time": "Czas"
"score": "Punkty"
}
},
"stations": {
"header": "Statystyki stacji",
"station": "Stacja",
"time": "Czas"
"time": "Czas: {{time}}",
"sortby": {
"title": "Sortowanie: ",
"min": "Od najniższej",
"max": "Od najwyższej"
}
},
"errors": {
"notfound": {

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
@ -27,36 +27,36 @@ import useSWR from "swr";
import { get } from "../../util/fetcher.ts";
export const Profile = () =>
{
export const Profile = () => {
const { id } = useParams();
const { data, error, isLoading } = useSWR(`/profiles/${ id }`, get, { refreshInterval: 5_000, errorRetryCount: 5 });
const { data, error, isLoading } = useSWR(`/profiles/${id}`, get, { refreshInterval: 5_000, errorRetryCount: 5 });
const images = useSWR(`/images/`, get);
const { t } = useTranslation();
return (
<>
{/* LOADING */ }
{ isLoading && <ContentLoader/> }
{/* ERROR */ }
{ error && <LoadError/> }
{/* BLACKLISTED */ }
{ data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
description="The player's profile could not be displayed due to active moderator actions."/> }
{ data && data.code === 403 && <WarningAlert title={ t("profile.errors.blacklist.title") }
description={ t("profile.errors.blacklist.description") }/> }
{/* NOT FOUND */ }
{ data && data.code === 404 && <PageMeta title="simrail.pro | Profile not found"
description="Player's profile could not be found or the player has a private Steam profile."/> }
{ data && data.code === 404 && <WarningAlert title={ t("profile.errors.notfound.title") }
description={ t("profile.errors.notfound.description") }/> }
{/* LOADING */}
{(isLoading || images.isLoading) && <ContentLoader />}
{/* ERROR */}
{(error || images.error) && <LoadError />}
{/* BLACKLISTED */}
{data && data.code === 403 && <PageMeta title="simrail.pro | Profile hidden"
description="The player's profile could not be displayed due to active moderator actions." />}
{data && data.code === 403 && <WarningAlert title={t("profile.errors.blacklist.title")}
description={t("profile.errors.blacklist.description")} />}
{/* NOT FOUND */}
{data && data.code === 404 && <PageMeta title="simrail.pro | Profile not found"
description="Player's profile could not be found or the player has a private Steam profile." />}
{data && data.code === 404 && <WarningAlert title={t("profile.errors.notfound.title")}
description={t("profile.errors.notfound.description")} />}
{/* SUCCESS */ }
{ data && data.code === 200 && <PageMeta image={ data.data.player.username }
title={ `simrail.pro | ${ data.data.player.username }'s profile` }
description={ `${ data.data.player.trainDistance ? 0 : ((data.data.player.trainDistance / 1000).toFixed(2)) } driving experience |
${ data.data.player.dispatcherTime ? 0 : formatTime(data.data.player.dispatcherTime) } dispatcher experience` }/> }
{ data && data.code === 200 && <ProfileCard data={ data.data }/> }
{/* SUCCESS */}
{data && data.code === 200 && images.data && images.data.code === 200 && <PageMeta image={data.data.player.username}
title={`simrail.pro | ${data.data.player.username}'s profile`}
description={`${data.data.player.trainDistance ? 0 : ((data.data.player.trainDistance / 1000).toFixed(2))} driving experience |
${data.data.player.dispatcherTime ? 0 : formatTime(data.data.player.dispatcherTime)} dispatcher experience`} />}
{data && data.code === 200 && images.data && images.data.code === 200 && <ProfileCard data={data.data} images={images.data.data} />}
</>
);
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View 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>
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
* Copyright (C) 2025 Aleksander <alekswilc> Wilczyński (aleks@alekswilc.dev)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published

Some files were not shown because too many files have changed in this diff Show More