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,30 +16,34 @@
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"
@ -51,17 +55,12 @@ 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>
</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)}>

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,56 +24,86 @@ 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");
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 = () =>
{
// #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)
{
.then((response) => {
if (response.code === 200) {
toast.success(t("admin.hideLeaderboard.alert"));
}
});
};
const adminHidePlayerProfile = () =>
{
const adminHidePlayerProfile = () => {
post(`/admin/profile/${data.player.id}/hide`, {}, { "X-Auth-Token": token })
.then((response) =>
{
if (response.code === 200)
{
.then((response) => {
if (response.code === 200) {
toast.success(t("admin.hide.alert"));
}
});
};
const adminForceUpdate = () =>
{
const adminForceUpdate = () => {
post(`/admin/profile/${data.player.id}/forceUpdate`, {}, { "X-Auth-Token": token })
.then((response) =>
{
if (response.code === 200)
{
.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}
@ -84,7 +113,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
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">
@ -129,84 +158,52 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
</div>}
</div>
{ 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 }/>
</div>
{ showTrains &&
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
<div className="grid grid-cols-3 rounded-sm bg-gray-2 dark:bg-meta-4 sm:grid-cols-3">
<div className="p-2.5 text-center xl:p-5">
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.train") }
</h5>
</div>
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("distance") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.distance") }
</h5>
<FlexArrowIcon rotated={ sortTrainsBy === "distance" || !sortTrainsBy }/>
</div>
<div className="flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"
onClick={ () => setSortTrainsBy("score") }>
<h5 className="text-sm font-medium uppercase xsm:text-base">
{ t("profile.trains.points") }
</h5>
<FlexArrowIcon rotated={ sortTrainsBy === "score" }/>
</div>
{/*<div className="hidden sm:flex flex-row align-center justify-center gap-2 p-2.5 text-center xl:p-5 cursor-pointer"*/ }
{/* onClick={ () => setSortTrainsBy("time") }>*/ }
{/* <h5 className="text-sm font-medium uppercase xsm:text-base">*/ }
{/* { t("profile.trains.time") }*/ }
{/* </h5>*/ }
{/* <FlexArrowIcon rotated={ sortTrainsBy === "time" }/>*/ }
{/*</div>*/ }
</div>
{ Object.keys(data.player.trainStats).sort((a, b) => data.player.trainStats[ b ][ sortTrainsBy ] - data.player.trainStats[ a ][ sortTrainsBy ]).map(trainName =>
{
const train = data.player.trainStats[ trainName ];
return <div
className={ `grid grid-cols-3 sm:grid-cols-3 border-t border-t-stroke dark:border-t-strokedark` }
key={ trainName }
>
<div className="flex items-center justify-center gap-3 p-2.5 lg:p-5">
<p className="text-black dark:text-white sm:block break-all">
{ trainName }
</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-6 sm:block break-all">{ Math.floor(train.distance / 1000) }km</p>
</div>
<div className="flex items-center justify-center p-2.5 lg:p-5">
<p className="text-meta-3">{ train.score }</p>
</div>
{/*<div className="hidden sm:flex items-center justify-center p-2.5 lg:p-5">*/ }
{/* <p className="text-meta-3">{ formatTime(train.time) }</p>*/ }
{/*</div>*/ }
</div>;
}) }
</div> }
</div> }
{ Object.keys(data.player.dispatcherStats || {}).length > 0 &&
<div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
<div className="group relative cursor-pointer" onClick={ () => setShowStations(val => !val) }>
<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>
<ArrowIcon rotated={ showStations }/>
<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>
{ showStations &&
<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];
return <StationStat stationName={stationName} time={station.time} image={images.stations[stationName] ?? images.stations['N/A']} />
})}
</div>
<div className="flex flex-col pt-4">
<Paginator setPage={setDispatcherPage} page={dispatcherPage} pages={dispatcherStats.length} />
</div>
</div>
<div className="px-5 pt-6 pb-5 sm:px-7.5 rounded-md">
<h1 className="text-xl text-black dark:text-white pb-5">{t("profile.trains.header")}</h1>
<div className="flex flex-col">
<a className='cursor-pointer' onClick={() => setSortTrainsBy2((prev) => prev === 0 ? 1 : 0)}><p className='text-base'>
<strong>{t('profile.trains.sortby.title')}</strong> {sortTrainsBy2 === 0 ? t('profile.trains.sortby.max') : t('profile.trains.sortby.min')}
</p></a>
<a className='cursor-pointer' onClick={() => setSortTrainsBy((prev) => {
prev++;
if (prev > 2) prev = 0;
return prev;
})}><p className='text-base'>
<strong>{t('profile.trains.sortby.title')}</strong> {t('profile.trains.sortby.' + sortTrainsByList[sortTrainsBy])}
</p></a>
</div>
<div className="grid grid-cols-1 gap-7.5 sm:grid-cols-3 xl:grid-cols-4 pt-4">
{trainStats[trainPage - 1].map(trainName => {
const train = data.player.trainStats[trainName];
return <TrainStat trainName={trainName} time={train.time} distance={train.distance} score={train.score} image={images.trains[trainName] ?? images.trains['N/A']} />
})}
</div>
<div className="flex flex-col pt-4">
<Paginator setPage={setTrainPage} page={trainPage} pages={trainStats.length} />
</div>
</div>
{/* <div className="bg-white px-5 pt-6 pb-5 shadow-default dark:bg-boxdark sm:px-7.5">
<div className="flex flex-col rounded-sm border border-stroke dark:border-strokedark">
<div className="grid grid-cols-2 rounded-sm bg-gray-2 dark:bg-meta-4">
<div className="p-2.5 text-center xl:p-5">
@ -221,8 +218,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
</h5>
</div>
</div>
{ Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[ b ].time - data.player.dispatcherStats[ a ].time).map(stationName =>
{
{Object.keys(data.player.dispatcherStats).sort((a, b) => data.player.dispatcherStats[b].time - data.player.dispatcherStats[a].time).map(stationName => {
const station = data.player.dispatcherStats[stationName];
return <div
className={`grid grid-cols-2 border-t border-t-stroke dark:border-t-strokedark`}
@ -240,13 +236,11 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
</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">
<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">
@ -274,7 +268,7 @@ export const ProfileCard = ({ data }: { data: TProfileData }) =>
</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") })}
</h1>

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,19 +27,19 @@ 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 images = useSWR(`/images/`, get);
const { t } = useTranslation();
return (
<>
{/* LOADING */}
{ isLoading && <ContentLoader/> }
{(isLoading || images.isLoading) && <ContentLoader />}
{/* ERROR */}
{ error && <LoadError/> }
{(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." />}
@ -52,11 +52,11 @@ export const Profile = () =>
description={t("profile.errors.notfound.description")} />}
{/* SUCCESS */}
{ data && data.code === 200 && <PageMeta image={ data.data.player.username }
{data && data.code === 200 && images.data && images.data.code === 200 && <PageMeta image={data.data.player.username}
title={`simrail.pro | ${data.data.player.username}'s profile`}
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 }/> }
{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