Compare commits

..

No commits in common. "main" and "feature/30-profile-forgot-link" have entirely different histories.

37 changed files with 1382 additions and 6185 deletions

View File

@ -1,187 +0,0 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# ---> Windows
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# ---> macOS
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# TODO: where does this rule come from?
docs/_book
# TODO: where does this rule come from?
test/
/package-lock.json
/yarn.lock
/public/env.js

109
.drone.yml Normal file
View File

@ -0,0 +1,109 @@
---
kind: secret
name: docker_username
get:
path: odit-registry-builder
name: username
---
kind: secret
name: docker_password
get:
path: odit-registry-builder
name: password
---
kind: secret
name: git_ssh
get:
path: odit-git-bot
name: sshkey
---
kind: pipeline
type: kubernetes
name: build:dev
steps:
# - name: run full license export
# depends_on: ["clone"]
# image: node:alpine
# commands:
# - yarn
# - yarn licenses:export
# - name: push new licenses file to repo
# depends_on: ["run full license export"]
# image: appleboy/drone-git-push
# settings:
# branch: dev
# commit: true
# commit_message: new license file version [CI SKIP]
# author_email: bot@odit.services
# remote: git@git.odit.services:lfk/selfservice.git
# ssh_key:
# from_secret: git_ssh
- name: build dev
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/selfservice
tags:
- dev
registry: registry.odit.services
mtu: 1000
trigger:
branch:
- dev
event:
- push
---
kind: pipeline
type: kubernetes
name: build:latest
steps:
- name: build latest
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/selfservice
tags:
- latest
registry: registry.odit.services
mtu: 1000
trigger:
branch:
- main
event:
- push
---
kind: pipeline
type: kubernetes
name: build:tags
steps:
- name: build $DRONE_TAG
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: registry.odit.services/lfk/selfservice
tags:
- '${DRONE_TAG}'
registry: registry.odit.services
mtu: 1000
trigger:
event:
- tag

View File

@ -2,9 +2,5 @@
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"src/locales" "src/locales"
], ],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested"
"i18n-ally.extract.keygenStyle": "snake_case",
"i18n-ally.enabledFrameworks": [
"vue"
]
} }

View File

@ -1,33 +0,0 @@
steps:
- name: build latest
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/selfservice
tags:
- latest
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/selfservice:dev
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
branch: main
- name: build dev
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/selfservice
tags:
- dev
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/selfservice:dev
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
branch: dev
when:
event: push

View File

@ -1,17 +0,0 @@
steps:
- name: build tag
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/selfservice
tags:
- "${CI_COMMIT_TAG}"
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/selfservice:dev
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
event:
- tag

View File

@ -2,273 +2,8 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.0.1](https://git.odit.services/lfk/selfservice/compare/1.0.0...1.0.1)
- fix(container): Add dockeringore [`7fcb6a9`](https://git.odit.services/lfk/selfservice/commit/7fcb6a9fc3f98772990790f6385200732f8bce7c)
### [1.0.0](https://git.odit.services/lfk/selfservice/compare/0.11.3...1.0.0)
> 2 December 2024
- chore(deps): update all [`4a11fef`](https://git.odit.services/lfk/selfservice/commit/4a11fef0e0ad0940535fd1d6a1a57a829dc2b50d)
- chore: pnpm v9 [`b85c395`](https://git.odit.services/lfk/selfservice/commit/b85c3958c288293bb98df2326f73dfad1684e3cb)
- wip: general updates [`1eab535`](https://git.odit.services/lfk/selfservice/commit/1eab5353810a0a351cfc72b493c5156f55a4c9d2)
- refactor [`4af5c45`](https://git.odit.services/lfk/selfservice/commit/4af5c4545bfaad21d71bcbf91d2cd8ea53847b62)
- fix: translations [`548148e`](https://git.odit.services/lfk/selfservice/commit/548148e8982d7d146182af9f2a69de87f0b3529e)
- refactor: translations [`196b386`](https://git.odit.services/lfk/selfservice/commit/196b386d6d57e91fbb4f46091a59ba04bb6e6984)
- feat(ci)!: Switch to woodpecker [`9a3d307`](https://git.odit.services/lfk/selfservice/commit/9a3d307dfae0851eb842ad3f1c34460bca13bff1)
- i18n [`048edfb`](https://git.odit.services/lfk/selfservice/commit/048edfba89a69efd3362dd8df8b89f8780f5f3fe)
- registration brand font + more translations [`afffde8`](https://git.odit.services/lfk/selfservice/commit/afffde8fa04df88d0a8bff05d0a28550414ce385)
- feat: lfk font [`af9a168`](https://git.odit.services/lfk/selfservice/commit/af9a168f587768d8b1af8990729e743481ba687d)
- updated profile page [`4fcc38c`](https://git.odit.services/lfk/selfservice/commit/4fcc38c374553b22f92a734903df1b7a032348fa)
- 🚀Bumped version to v1.0.0 [`2285ea5`](https://git.odit.services/lfk/selfservice/commit/2285ea507069cf362baa950e7337aec72a37d4e3)
- i18n [`f296cbc`](https://git.odit.services/lfk/selfservice/commit/f296cbc1a0c9035db6546c85781be71b152264d7)
- feat(Footer): ref lfk [`6376dcb`](https://git.odit.services/lfk/selfservice/commit/6376dcb5fe7e3b1e1e639e41bc1b682036d838a0)
- feat(Home): improve ui [`75b61d9`](https://git.odit.services/lfk/selfservice/commit/75b61d991bd483aaf00871c12c9268fc6e21435b)
- refactor(ci): Make ready for new woodpecker [`780bc54`](https://git.odit.services/lfk/selfservice/commit/780bc54604998abd275f3b7d50a3849002eb243d)
- refactor: code cleanup [`ec6d252`](https://git.odit.services/lfk/selfservice/commit/ec6d252928fb84248041a3675590b85293c96133)
- chore: 2025 [`cb2f8ee`](https://git.odit.services/lfk/selfservice/commit/cb2f8eeab1a7e260df938b8aa94fa31f8e1b5efe)
- footer update [`90c085d`](https://git.odit.services/lfk/selfservice/commit/90c085db28798bbcac24eb9700285221a9bcad92)
- improved style in registered view [`1a3ab98`](https://git.odit.services/lfk/selfservice/commit/1a3ab98412b82eeb1f0dea8ea642f5fc6435c09d)
- add i18n ally snake case config [`0acda07`](https://git.odit.services/lfk/selfservice/commit/0acda07eb53a85f7b2e87a3e26d90b7cdf1fe012)
- feat(Home): improve background.jpg import [`3e289d8`](https://git.odit.services/lfk/selfservice/commit/3e289d83f9db98641f0f7f732b622dab9955f3a1)
- chore(deps): node@23.3.0 [`f71b01c`](https://git.odit.services/lfk/selfservice/commit/f71b01c39a62ef4402004e2fbcfacb37bc92ea39)
- Pin pnpm [`c36db2c`](https://git.odit.services/lfk/selfservice/commit/c36db2c18fc5f7cfd7bf5d295f3faf3931df736b)
#### [0.11.3](https://git.odit.services/lfk/selfservice/compare/0.11.2...0.11.3)
> 7 April 2023
- fix: registration code white bg [`#50`](https://git.odit.services/lfk/selfservice/issues/50)
- Pinned pnpm and lockfile [`6aca13f`](https://git.odit.services/lfk/selfservice/commit/6aca13f0cb1bc8fa43f0f09c554cec0758acd7a1)
- switched drone to kaniko with cache [`cac5103`](https://git.odit.services/lfk/selfservice/commit/cac5103d52427e777e4c3624ebb253ad7bd393d6)
- Switched dockerfile to pnpm with cache [`78fea1e`](https://git.odit.services/lfk/selfservice/commit/78fea1ea045a7281fe0a2841fc4d5f1505dcb3cf)
- 🚀Bumped version to v0.11.3 [`240f44d`](https://git.odit.services/lfk/selfservice/commit/240f44df60184c492f6cff3d864c1f9f2563ff80)
- chore(deps): pnpm@8.1.1 [`677e65f`](https://git.odit.services/lfk/selfservice/commit/677e65f44ae4bcc6f9af4732d813f337da0c51d2)
- fix(Footer): darkmode colors in lightmode [`772eca6`](https://git.odit.services/lfk/selfservice/commit/772eca64bb38ce1cf1ea1b58859dd744588a88e1)
#### [0.11.2](https://git.odit.services/lfk/selfservice/compare/0.11.1...0.11.2)
> 15 March 2023
- Added footer to every page [`b9624aa`](https://git.odit.services/lfk/selfservice/commit/b9624aa5ad57002852c84081fc710c2e977251bd)
- 🚀Bumped version to v0.11.2 [`fa51b31`](https://git.odit.services/lfk/selfservice/commit/fa51b31fe41d41b3ec9becad07ddab4cf73be051)
- Accept tos to accept privacy_policy [`09e48e7`](https://git.odit.services/lfk/selfservice/commit/09e48e771f4c0db4a7dbb898b05b347f1aad2292)
#### [0.11.1](https://git.odit.services/lfk/selfservice/compare/0.11.0...0.11.1)
> 18 February 2023
- Profile: add "---" if no data set [`659fa77`](https://git.odit.services/lfk/selfservice/commit/659fa77dca3b527d896d752386e91a167a1b8f4f)
- 🚀Bumped version to v0.11.1 [`8364321`](https://git.odit.services/lfk/selfservice/commit/8364321a7cb6a0037ee0c5055b028c4cf883fb10)
#### [0.11.0](https://git.odit.services/lfk/selfservice/compare/0.10.0...0.11.0)
> 3 February 2023
- pnpm migration [`a3a1e89`](https://git.odit.services/lfk/selfservice/commit/a3a1e89aa14798e1d180697f91809e282a229f23)
- 🚀Bumped version to v0.11.0 [`10c9127`](https://git.odit.services/lfk/selfservice/commit/10c9127256edf2cbad1dc6690d5f4e87c32f6396)
#### [0.10.0](https://git.odit.services/lfk/selfservice/compare/0.9.0...0.10.0)
> 3 February 2023
- text cleanups + footer [`eccce07`](https://git.odit.services/lfk/selfservice/commit/eccce0795b6960aa3a2e9368de5ae4bdf80997d7)
- updated nginx config for cache busting [`8e2b355`](https://git.odit.services/lfk/selfservice/commit/8e2b355466e9a37510be8bf03311d6b28e1a0d9f)
- improved profile page padding + full width registration code for easier scanning [`54540be`](https://git.odit.services/lfk/selfservice/commit/54540be6e12ed85afc96061b8548cd4ec6700eec)
- 🚀Bumped version to v0.10.0 [`5587175`](https://git.odit.services/lfk/selfservice/commit/5587175534188e05ab43d0eed9745484e01edb63)
#### [0.9.0](https://git.odit.services/lfk/selfservice/compare/0.8.3...0.9.0)
> 3 February 2023
- wip: error registration ui feedback [`9ac14e8`](https://git.odit.services/lfk/selfservice/commit/9ac14e8a5d740fb4cd87645e69b2cfde73e4acc7)
- wip: registration confirmation ui [`8928f84`](https://git.odit.services/lfk/selfservice/commit/8928f841dcd370daf28416adbf1adbe16fa4d76f)
- 🚀Bumped version to v0.9.0 [`3392a2e`](https://git.odit.services/lfk/selfservice/commit/3392a2e68edb8fcf2aa6dd4b962abbf6ac27a414)
#### [0.8.3](https://git.odit.services/lfk/selfservice/compare/0.8.2...0.8.3)
> 3 February 2023
- fix: registration code (broke in merge) [`123509d`](https://git.odit.services/lfk/selfservice/commit/123509d0a6d15fe254e28f7a316bd6c260fe7bd2)
- 🚀Bumped version to v0.8.3 [`4a5b9d2`](https://git.odit.services/lfk/selfservice/commit/4a5b9d25698931158a602b2159b4e6c4fa129e8e)
- wip: fix registration code [`51f8d0f`](https://git.odit.services/lfk/selfservice/commit/51f8d0fb42d0dbba268799eb9385686d22fd2284)
#### [0.8.2](https://git.odit.services/lfk/selfservice/compare/0.8.1...0.8.2)
> 3 February 2023
- move /registered/ to props [`c35f943`](https://git.odit.services/lfk/selfservice/commit/c35f943957e5ba84361a437c1d945331248746d1)
- 🚀Bumped version to v0.8.2 [`f4d1c7b`](https://git.odit.services/lfk/selfservice/commit/f4d1c7b053d1e7210911772bde1b2d80a30ab225)
- /profile/ text cleanups [`64e6ef8`](https://git.odit.services/lfk/selfservice/commit/64e6ef8cec38a8193c4fb28c5f1b26ee0e4d5063)
- /profile/ move from anchor tag to button [`ac75828`](https://git.odit.services/lfk/selfservice/commit/ac75828309043532c6ab8aad63e0c40edf450459)
- /profile/ toast styles [`c7f3a89`](https://git.odit.services/lfk/selfservice/commit/c7f3a893af8705af12f2e7ae7e40197ca4c28666)
- /profile/ dont instantly show mail format error [`ab9b400`](https://git.odit.services/lfk/selfservice/commit/ab9b400fff1b421a41dd27479a81fb5e3740b9ef)
- /profile/ autocomplete mail [`30fd7ea`](https://git.odit.services/lfk/selfservice/commit/30fd7ead0833b0b3ab4e5509608aa92112151380)
#### [0.8.1](https://git.odit.services/lfk/selfservice/compare/0.8.0...0.8.1)
> 2 February 2023
- 🚀Bumped version to v0.8.1 [`292e440`](https://git.odit.services/lfk/selfservice/commit/292e44057aee9ef57a51aa9fa0372c3678b81de0)
- Adjusted button text [`20fca67`](https://git.odit.services/lfk/selfservice/commit/20fca6794dd7e0c714cd09c80a68b1d3592ab09c)
- Switched request login link api path to login (backend v13.0.0) [`8139d63`](https://git.odit.services/lfk/selfservice/commit/8139d637151c8c0184e4a98f151991b429d0a70c)
#### [0.8.0](https://git.odit.services/lfk/selfservice/compare/0.7.11...0.8.0)
> 2 February 2023
- 🚀Bumped version to v0.8.0 [`7051909`](https://git.odit.services/lfk/selfservice/commit/7051909bf960fb44b43e979ac4d304dff9ef2ec4)
- Merge pull request 'dont autologin on register -> require mail link' (#47) from feature/46-dont-autologin-on-register-require-mail-link into dev [`f7a0682`](https://git.odit.services/lfk/selfservice/commit/f7a0682c3392b8882be4a676882c8a49d55bd5fe)
- add /registered/?mail route [`eb20b54`](https://git.odit.services/lfk/selfservice/commit/eb20b547e79d352f3b7cd1b5ce7b7dbfcf8c19f7)
#### [0.7.11](https://git.odit.services/lfk/selfservice/compare/0.7.10...0.7.11)
> 2 February 2023
- Profile: improved mobile responsiveness/ design + toast clear [`077b33f`](https://git.odit.services/lfk/selfservice/commit/077b33f03180d0bd6c45becaaa63d3408c645deb)
- 🚀Bumped version to v0.7.11 [`c63adf5`](https://git.odit.services/lfk/selfservice/commit/c63adf557bcb29c8eccc05d5a83d476c75380d95)
- fix: registration code download button style [`52a6b3d`](https://git.odit.services/lfk/selfservice/commit/52a6b3dc776b806eaa8fee058a1c381ab63a8ea5)
#### [0.7.10](https://git.odit.services/lfk/selfservice/compare/0.7.9...0.7.10)
> 1 February 2023
- Configureable barcode format via bwp-js [`9e06c46`](https://git.odit.services/lfk/selfservice/commit/9e06c464118c5b5d0cd78c8b8379523bf3bfdbd4)
- Added download for registration code [`53800b4`](https://git.odit.services/lfk/selfservice/commit/53800b4fa355bb972e51e71b5b1f98772deed114)
- 🚀Bumped version to v0.7.10 [`8b5e1ca`](https://git.odit.services/lfk/selfservice/commit/8b5e1cac1353373b13cf9d570deb7cadcd437247)
- Pinned version [`6438288`](https://git.odit.services/lfk/selfservice/commit/64382880c40ba5c1e3c9004ce7fc65099849cd44)
#### [0.7.9](https://git.odit.services/lfk/selfservice/compare/0.7.8...0.7.9)
> 1 February 2023
- migrate to bwip-js [`8cfddb5`](https://git.odit.services/lfk/selfservice/commit/8cfddb502964be7edf45cdc524344ea2f7f20142)
- fix codeconfig.height [`9b261bf`](https://git.odit.services/lfk/selfservice/commit/9b261bf20023561a7c9691dff33c9a6d2b5c0cac)
- 🚀Bumped version to v0.7.9 [`debbd92`](https://git.odit.services/lfk/selfservice/commit/debbd9219cb53dbd48cf0cb7bee329b765ce4647)
- drop jsbarcode [`713dd15`](https://git.odit.services/lfk/selfservice/commit/713dd153126851e8cf1045bf5ba3ca702a39c738)
#### [0.7.8](https://git.odit.services/lfk/selfservice/compare/0.7.7...0.7.8)
> 1 February 2023
- add barcode to profile [`851190e`](https://git.odit.services/lfk/selfservice/commit/851190e6a7f8b9cccbf05e60f9b50b96c196959c)
- 🚀Bumped version to v0.7.8 [`e5a01bc`](https://git.odit.services/lfk/selfservice/commit/e5a01bcd7629164655cacd10dd1f014260c67c4b)
- certificate generation: success toast styling [`1603a09`](https://git.odit.services/lfk/selfservice/commit/1603a097f71ed85c901baf8da04cb06b86474649)
#### [0.7.7](https://git.odit.services/lfk/selfservice/compare/0.7.6...0.7.7)
> 31 January 2023
- 🚀Bumped version to v0.7.7 [`c2b6152`](https://git.odit.services/lfk/selfservice/commit/c2b615294e605db37695b13cec1158f535986911)
- fix: registration w/o phone [`c647628`](https://git.odit.services/lfk/selfservice/commit/c64762831f1e6dffc9cbc3f531e23435b455a5a9)
#### [0.7.6](https://git.odit.services/lfk/selfservice/compare/0.7.5...0.7.6)
> 31 January 2023
- update texts of pdf generation status toasts [`#44`](https://git.odit.services/lfk/selfservice/issues/44)
- 🚀Bumped version to v0.7.6 [`9b446ab`](https://git.odit.services/lfk/selfservice/commit/9b446abc1fa231bb1f5a78c545400c617eaa4af5)
- update release script [`5d974e5`](https://git.odit.services/lfk/selfservice/commit/5d974e562ed1ed5aeac579afe000c2dca945ff71)
#### [0.7.5](https://git.odit.services/lfk/selfservice/compare/0.7.4...0.7.5)
> 30 January 2023
- fix: relativ links [`917cb6b`](https://git.odit.services/lfk/selfservice/commit/917cb6be340844bcc2318bf73cec37c3c831fd5d)
- 🚀Bumped version to v0.7.5 [`1249248`](https://git.odit.services/lfk/selfservice/commit/1249248a9d3e0d72665bca6871a651f2491a4039)
- 2023 [`9812d79`](https://git.odit.services/lfk/selfservice/commit/9812d79d4de820ce791f69634c5861f4f04ad7f1)
- update nginx base [`0bd6d54`](https://git.odit.services/lfk/selfservice/commit/0bd6d543bf60b7a333b96d5d319269d4bf50db96)
- updated base node image [`92d7bfd`](https://git.odit.services/lfk/selfservice/commit/92d7bfd59407273f86809b53ffc9f67fb8ba0ec7)
- AGB link [`d159cb5`](https://git.odit.services/lfk/selfservice/commit/d159cb59be35f963abc89c42732ba6110d27830c)
#### [0.7.4](https://git.odit.services/lfk/selfservice/compare/0.7.3...0.7.4)
> 21 April 2021
- Updated dronefile [`9c9ceaa`](https://git.odit.services/lfk/selfservice/commit/9c9ceaa6664f6ea21a536a12f04e552156e81da4)
- 🚀Bumped version to v0.7.4 [`4663214`](https://git.odit.services/lfk/selfservice/commit/4663214ede3880386b5389885e32c953571290e6)
#### [0.7.3](https://git.odit.services/lfk/selfservice/compare/0.7.2...0.7.3)
> 21 April 2021
- 🚀Bumped version to v0.7.3 [`602d80b`](https://git.odit.services/lfk/selfservice/commit/602d80bd14c4bd00a24b746be1e74d9e41af0445)
- Merge pull request 'Button fixes bugfix/42-button_links' (#43) from bugfix/42-button_links into dev [`45ee4ab`](https://git.odit.services/lfk/selfservice/commit/45ee4ab81260adf5b938d9f5359d256cce879acb)
- Removed useless register now button [`1102d29`](https://git.odit.services/lfk/selfservice/commit/1102d29c0e174b7a34fc4d3e6fe32d2dfb276765)
- Fixed register button link [`020c310`](https://git.odit.services/lfk/selfservice/commit/020c310865912b8f0752069e1c7e2adf71ab9835)
#### [0.7.2](https://git.odit.services/lfk/selfservice/compare/0.7.1...0.7.2)
> 14 April 2021
- 🚀Bumped version to v0.7.2 [`1a3af20`](https://git.odit.services/lfk/selfservice/commit/1a3af200dd41c8cc4271690ed72bef911901ce54)
- Document generation hotfix 🐞 [`b74bea0`](https://git.odit.services/lfk/selfservice/commit/b74bea03401c672ae774aaddc6da5beb67e2890e)
#### [0.7.1](https://git.odit.services/lfk/selfservice/compare/0.7.0...0.7.1)
> 13 April 2021
- Merge pull request 'bugfix/31-env-js-linking-ci' (#41) from bugfix/31-env-js-linking-ci into dev [`#31`](https://git.odit.services/lfk/selfservice/issues/31)
- ⏫ dependency bump [`ad13bae`](https://git.odit.services/lfk/selfservice/commit/ad13bae068416bed10d00e6887a05d580a836482)
- 🚀Bumped version to v0.7.1 [`b0172c5`](https://git.odit.services/lfk/selfservice/commit/b0172c500b0613209ac44e61023043065b3854b0)
- added 'yarn postbuild' script for fixing env.js in dist/index.html [`51d058b`](https://git.odit.services/lfk/selfservice/commit/51d058bf966c3dcb064562e6bf696a748d0cd148)
#### [0.7.0](https://git.odit.services/lfk/selfservice/compare/0.6.1...0.7.0)
> 6 April 2021
- 🚀Bumped version to v0.7.0 [`152e741`](https://git.odit.services/lfk/selfservice/commit/152e74190d13d30110d494a9062f868390ca19b3)
- Merge pull request 'Donation list feature/39-donation_list' (#40) from feature/39-donation_list into dev [`4c83e2e`](https://git.odit.services/lfk/selfservice/commit/4c83e2e738a075354383dca4d500808f761247d2)
- Added total to bottom of page [`89820d4`](https://git.odit.services/lfk/selfservice/commit/89820d44501793365248b8e778522cdc188afa70)
- Sorted translations 🌍 [`cb1b9d3`](https://git.odit.services/lfk/selfservice/commit/cb1b9d330b28f11cceed9691aee4ccee5246d346)
- Added basic sponsoring table [`f141130`](https://git.odit.services/lfk/selfservice/commit/f141130db5ede60a623747f4e324f66259e5bc75)
- Added translations 🌍 [`d713fbe`](https://git.odit.services/lfk/selfservice/commit/d713fbef94e75fbbc62254ea8f0ca50dc4e93d79)
- Now w/ formatted currency amount [`815a36f`](https://git.odit.services/lfk/selfservice/commit/815a36f20271f47f49c3814b001c3404d43113cd)
- Fixed spaces in name [`baa6da3`](https://git.odit.services/lfk/selfservice/commit/baa6da3dd06c493fccc61945b9bcbd9b2e79d910)
- Updated env description [`5a123b0`](https://git.odit.services/lfk/selfservice/commit/5a123b0cf89f49d450becbbc03b28c5bb6416b7c)
- added distance formatting [`121022c`](https://git.odit.services/lfk/selfservice/commit/121022c8434484a363a3f2ea68aba4d8ef7cad2d)
#### [0.6.1](https://git.odit.services/lfk/selfservice/compare/0.6.0...0.6.1)
> 6 April 2021
- 🚀Bumped version to v0.6.1 [`f5ae214`](https://git.odit.services/lfk/selfservice/commit/f5ae2145df07413329f60c229d9571fd1de2ca79)
- Fixed imprint/privacy default links [`96c0e56`](https://git.odit.services/lfk/selfservice/commit/96c0e5698697361e65c5aa80c9a0aada5c3d5f30)
#### [0.6.0](https://git.odit.services/lfk/selfservice/compare/0.5.1...0.6.0)
> 3 April 2021
- 🚀Bumped version to v0.6.0 [`bc1de2a`](https://git.odit.services/lfk/selfservice/commit/bc1de2acdcbf8f3319026887cb6b3d594fcd63dc)
- first part of certificate generation with manual data [`f633439`](https://git.odit.services/lfk/selfservice/commit/f6334397dc99cd38d31ef524bc26ba7d386b33dd)
- Renoved fixed data [`f0a7f35`](https://git.odit.services/lfk/selfservice/commit/f0a7f35dec07508a07d4c11abda297ad0bf91187)
- Revert "🚀Bumped version to v0.6.0" [`145b499`](https://git.odit.services/lfk/selfservice/commit/145b49906bdf2d75abf93e1779f188a691a3a909)
- 🚀Bumped version to v0.6.0 [`0a62e8f`](https://git.odit.services/lfk/selfservice/commit/0a62e8f5d1929bfe9a53a9709a3031f06a2487c3)
- Merge pull request 'Certificate download feature/19-runner_certficates' (#38) from feature/19-runner_certficates into dev [`53eeb6b`](https://git.odit.services/lfk/selfservice/commit/53eeb6be3ddf4735a11b6c4636c28b4d16aa58ee)
- Fixed blob handling [`b938cfc`](https://git.odit.services/lfk/selfservice/commit/b938cfc49e269e76761a752f8257ebbab88f6959)
- Reverted relative linking fix [`c3b2b93`](https://git.odit.services/lfk/selfservice/commit/c3b2b93d90102b4e31cfce15220acfc8fe48a7c5)
#### [0.5.1](https://git.odit.services/lfk/selfservice/compare/0.5.0...0.5.1)
> 3 April 2021
- Merge pull request 'feature/30-profile-forgot-link' (#35) from feature/30-profile-forgot-link into dev [`#30`](https://git.odit.services/lfk/selfservice/issues/30)
- 🚀Bumped version to v0.5.1 [`1a3c9ed`](https://git.odit.services/lfk/selfservice/commit/1a3c9edeb3987907ffe2223da8f3be079a1c80d2)
- Merge pull request 'Env linking bugfix bugfix/31-env_linking' (#37) from bugfix/31-env_linking into dev [`fd6bd88`](https://git.odit.services/lfk/selfservice/commit/fd6bd88d4238cca2a2755ab1bc51ff7870bef947)
- basic ProfileNone layout [`863568d`](https://git.odit.services/lfk/selfservice/commit/863568d7d0e132dd1b7d13a2cb1afe8db02c472c)
- add forgot view + action [`30f3a51`](https://git.odit.services/lfk/selfservice/commit/30f3a51ef82ad966225abc6fd95508af56106924)
- Added building pipeline for current branch [`544542a`](https://git.odit.services/lfk/selfservice/commit/544542ac1e9717506676c1495c2d530eca833d31)
- Removed useless code [`713db5e`](https://git.odit.services/lfk/selfservice/commit/713db5e1e8fed5f38b3a5d19cfe870eff16eeda1)
- Now manually linking env and main [`67c0dae`](https://git.odit.services/lfk/selfservice/commit/67c0dae537df5842e89c0ed040c0fef02df01b69)
- Fixed post url and body [`0795ea3`](https://git.odit.services/lfk/selfservice/commit/0795ea318d80b764eee89462ad87da86aa7ce0ae)
- 🐞 fix home routes [`60dea51`](https://git.odit.services/lfk/selfservice/commit/60dea511b9fbb6d206ee531618e788e686fa6b5a)
#### [0.5.0](https://git.odit.services/lfk/selfservice/compare/0.4.5...0.5.0) #### [0.5.0](https://git.odit.services/lfk/selfservice/compare/0.4.5...0.5.0)
> 1 April 2021
- 🚀Bumped version to v0.5.0 [`676f0e8`](https://git.odit.services/lfk/selfservice/commit/676f0e83393be823ccb612846650a9a98bf7f0b3)
- Revert "🚀Bumped version to v0.5.0" [`27fd8f9`](https://git.odit.services/lfk/selfservice/commit/27fd8f9a9106294ae6522f2fff42dfbf84f06f82) - Revert "🚀Bumped version to v0.5.0" [`27fd8f9`](https://git.odit.services/lfk/selfservice/commit/27fd8f9a9106294ae6522f2fff42dfbf84f06f82)
- 🚀Bumped version to v0.5.0 [`b8c1b8c`](https://git.odit.services/lfk/selfservice/commit/b8c1b8c43b6d050d239d836bbb6ace2504fdcd21) - 🚀Bumped version to v0.5.0 [`b8c1b8c`](https://git.odit.services/lfk/selfservice/commit/b8c1b8c43b6d050d239d836bbb6ace2504fdcd21)
- Merge pull request 'Runner self deletion feature/21-user_deletion' (#36) from feature/21-user_deletion into dev [`bd512e0`](https://git.odit.services/lfk/selfservice/commit/bd512e0651363f1f2543136d354d9ada9990d83d) - Merge pull request 'Runner self deletion feature/21-user_deletion' (#36) from feature/21-user_deletion into dev [`bd512e0`](https://git.odit.services/lfk/selfservice/commit/bd512e0651363f1f2543136d354d9ada9990d83d)

View File

@ -1,15 +1,9 @@
FROM node:23.3.0-alpine3.20 AS build FROM node:15.9.0-alpine3.13
# FROM registry.odit.services/hub/library/node:23.3.0-alpine3.20 AS build
# ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
COPY . . COPY . .
# RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@9 RUN yarn
RUN npm i -g pnpm@9 RUN yarn build
RUN pnpm i --frozen-lockfile
RUN pnpm build
RUN pnpm postbuild
# final image # final image
FROM registry.odit.services/library/nginx-brotli:3.15 AS final FROM fholzer/nginx-brotli:v1.19.1
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=0 /app/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

View File

@ -2,8 +2,29 @@
runner selfservice portal runner selfservice portal
## ⚡ Development
### Requirements
- Node.js v14.16.0 or newer
- yarn package manager >= v1.22.10 < 2
### Recommended Extensions
- will be automatically recommended via `./vscode/extensions.json`
- we also provide a config for i18n-ally in the `./vscode/` folder
### Fastest Dev Environment
- You can install the [Remote - Containers](https://github.com/Microsoft/vscode-remote-release) extension and use all recommended extensions and editor settings via the provided `./devcontainer/` config
### Manual Dev Environment
```
yarn && yarn dev --open
```
## 🔨 Environment config ## 🔨 Environment config
- copy the `/public/env.sample.js` file to `/public/env.js` - copy the `/public/env.sample.js` file to `/public/env.js`
- set the required environment variables - set the required environment variables
- `documentserver_key`: url to the [document server](https://git.odit.services/lfk/document-server) instance - `documentserver_key`: url to the [document server](https://git.odit.services/lfk/document-server) instance
- `baseurl`: url to the main lfk instance - WITH TRAILING SLASH - `baseurl`: url to the main lfk instance - WITH TRAILING SLASH
- see [@lfk/deployment](https://git.odit.services/lfk/deployment) for a complete deployment guide
- `baseurl_selfservice`: location of the selfservice instance - WITH TRAILING SLASH
- e.g. path: `/selfservice/`
- e.g. url: `https://example.com/selfservice/`

View File

@ -1,6 +0,0 @@
import { existsSync, writeFileSync, readFileSync } from "node:fs";
if (existsSync("./dist/index.html")) {
const content = readFileSync("./dist/index.html", { encoding: "utf8" });
const newcontent = content.replace(`"/env.js"`, `"./env.js"`);
writeFileSync("./dist/index.html", newcontent);
}

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon-lfk.png" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lauf für Kaya! - Selfservice</title> <title>Lauf für Kaya! - Selfservice</title>
</head> </head>

View File

@ -11,12 +11,6 @@ http {
rewrite /profile/(.*) /$1 break; rewrite /profile/(.*) /$1 break;
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
add_header Last-Modified $date_gmt;
add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
} }
location ~* \.(?:ico|css|gif|jpe?g|png)$ { location ~* \.(?:ico|css|gif|jpe?g|png)$ {

View File

@ -1,18 +1,16 @@
import { readdirSync, readFileSync, writeFileSync } from "node:fs"; const fs = require('fs');
// get all language files // get all language files
const files = readdirSync("./src/locales/"); const files = fs.readdirSync('./src/locales/');
files.forEach((f) => { files.forEach((f) => {
// read file as object // read file as object
const unordered = JSON.parse(readFileSync(`src/locales/${f}`)); const unordered = JSON.parse(fs.readFileSync(`src/locales/${f}`));
// order object by keys alpabetically A-Z // order object by keys alpabetically A-Z
const ordered = Object.keys(unordered) const ordered = Object.keys(unordered).sort().reduce((obj, key) => {
.sort()
.reduce((obj, key) => {
obj[key] = unordered[key]; obj[key] = unordered[key];
return obj; return obj;
}, {}); }, {});
// format output as json for commit diff compatibility // format output as json for commit diff compatibility
const out = JSON.stringify(ordered, 0, 4); const out = JSON.stringify(ordered, 0, 4);
// write output file // write output file
writeFileSync(`src/locales/${f}`, out); fs.writeFileSync(`src/locales/${f}`, out);
}); });

View File

@ -1,33 +1,33 @@
{ {
"name": "@odit/lfk-selfservice", "name": "@odit/lfk-selfservice",
"version": "1.0.1", "version": "0.5.0",
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"release": "release-it", "release": "release-it --only-version"
"postbuild": "node env_fix.js"
}, },
"dependencies": { "dependencies": {
"@fontsource/athiti": "^5.1.0", "marked": "2.0.1",
"@tailwindcss/vite": "4.0.0-beta.4", "redaxios": "0.4.1",
"bwip-js": "4.5.1", "toastify-js": "1.10.0",
"marked": "15.0.3", "validator": "13.5.2",
"redaxios": "0.5.1", "vue-i18n": "9.0.0",
"tailwindcss": "4.0.0-beta.4", "vue-toastification": "2.0.0-rc.1",
"toastify-js": "1.12.0", "vue": "3.0.9",
"validator": "13.12.0", "vue-router": "4.0.5"
"vue": "3.5.13",
"vue-i18n": "10.0.5",
"vue-router": "4.5.0",
"vue-toastification": "2.0.0-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.1", "@tailwindcss/jit": "0.1.18",
"autoprefixer": "10.4.20", "@tailwindcss/aspect-ratio": "0.2.0",
"release-it": "17.10.0", "@tailwindcss/forms": "0.3.2",
"vite": "6.0.2", "@tailwindcss/line-clamp": "0.2.0",
"vite-plugin-vue-devtools": "^7.6.7" "@tailwindcss/typography": "0.4.0",
"@vitejs/plugin-vue": "1.2.1",
"@vue/compiler-sfc": "3.0.10",
"autoprefixer": "10.2.5",
"tailwindcss": "2.0.4",
"release-it": "14.5.0",
"vite": "2.1.5"
}, },
"release-it": { "release-it": {
"git": { "git": {
@ -35,7 +35,7 @@
"requireCleanWorkingDir": false, "requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}", "commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev", "requireBranch": "dev",
"push": true, "push": false,
"tag": true, "tag": true,
"tagName": null, "tagName": null,
"tagAnnotation": "v${version}" "tagAnnotation": "v${version}"

4228
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
'@tailwindcss/jit': {},
autoprefixer: {},
},
}

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -3,12 +3,10 @@ const config = {
documentserver_key: '', documentserver_key: '',
// required, with trailing slash // required, with trailing slash
baseurl: '', baseurl: '',
// full url (including fqdn) // optional, will fallback to /selfservice/
baseurl_documentserver: 'http://localhost:4010/documents', baseurl_selfservice: '/selfservice/',
// optional, will fallback to code128
code_format: 'ean13',
// optional, will fallback to /imprint // optional, will fallback to /imprint
url_imprint: '', url_imprint: '',
// optional, will fallback to /privacy // optional, will fallback to /privacy
url_privacy: '', url_privacy: ''
}; };

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,29 @@
<template>
<section class="container px-4 py-32 mx-auto">
<div class="w-full mx-auto lg:w-1/3">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="h-20 feather feather-alert-triangle"
viewBox="0 0 24 24"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0zM12 9v4M12 17h.01"
/>
</svg>
<p
class="mt-5 mb-3 text-xl font-bold text-black dark:text-gray-50 md:text-2xl"
>{{ $t('configuration_error') }}</p>
<p class="mb-3 text-base font-medium text-gray-700 dark:text-gray-400">
{{ $t('the_system_is_not_properly_configured_please_contact_the_system_administrator_for_help') }}
<br />
<br />
{{ $t('if_you_are_the_system_administrator_please_refer_to_the_official_product_documentation_readme_for_configuration_guidance') }}
</p>
</div>
</section>
</template>

View File

@ -1,16 +1,28 @@
<template> <template>
<footer> <footer class="text-gray-400 bg-gray-900 body-font">
<div class="container px-5 py-8 mx-auto flex items-center sm:flex-row flex-col"> <div class="container px-5 py-8 mx-auto flex items-center sm:flex-row flex-col">
<p class="text-sm text-gray-400 sm:ml-4 sm:pl-4 sm:py-2 sm:mt-0 mt-4 text-center md:text-left"> <p class="text-sm text-gray-400 sm:ml-4 sm:pl-4 sm:py-2 sm:mt-0 mt-4">
Lauf für Kaya! Selfservice<br>Copyright © 2024<br>proudly powered by Lauf für Kaya! Selfservice - Copyright © 2021 + proudly powered by
<a class="underline" target="_blank" rel="noopener,noreferrer" <a
href="https://odit.services?ref=lfk">ODIT.Services</a> class="underline"
target="_blank"
rel="noopener,noreferrer"
href="https://odit.services"
>ODIT.Services</a>
</p> </p>
<span class="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start"> <span class="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start">
<a target="_blank" rel="noopener,noreferrer" :href="[[imprint_url]]" class="ml-3 text-gray-400 underline">{{ <a
$t('imprint') }}</a> target="_blank"
<a target="_blank" rel="noopener,noreferrer" :href="[[privacy_url]]" class="ml-3 text-gray-400 underline">{{ rel="noopener,noreferrer"
$t('privacy_policy') }}</a> :href="[[imprint_url]]"
class="ml-3 text-gray-400 underline"
>Impressum</a>
<a
target="_blank"
rel="noopener,noreferrer"
:href="[[privacy_url]]"
class="ml-3 text-gray-400 underline"
>Datenschutzerklärung</a>
</span> </span>
</div> </div>
</footer> </footer>
@ -19,8 +31,8 @@
export default { export default {
data() { data() {
return { return {
imprint_url: config.url_imprint || "/imprint" imprint_url: config.url_imprint || "/imprint/"
, privacy_url: config.url_privacy || "/privacy" , privacy_url: config.url_privacy || "/privacy/"
} }
}, },
} }

View File

@ -0,0 +1,30 @@
<template>
<h1>{{ msg }}</h1>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite Documentation</a> |
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
</p>
<button @click="state.count++">count is: {{ state.count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<script setup>
import { defineProps, reactive } from 'vue'
defineProps({
msg: String
})
const state = reactive({ count: 0 })
</script>
<style scoped>
a {
color: #42b983;
}
</style>

109
src/components/Login.vue Normal file
View File

@ -0,0 +1,109 @@
<template>
<div class="min-h-screen flex items-center justify-center">
<div class="max-w-md w-full py-12 px-6">
<img class="mx-auto h-24 w-auto" src="/favicon.png" alt="" />
<h1 class="sm:text-3xl text-2xl font-medium title-font mb-4 text-center">Lauf für Kaya! - Registrieren</h1>
<p
class="mx-auto leading-relaxed text-base text-center"
>Bitte anmelden...</p>
<div class="mt-5">
<div class="rounded-md shadow-sm">
<div>
<input
aria-label="E-Mail Adresse"
name="email"
type="email"
required
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder="E-Mail Adresse"
v-model="mail"
/>
</div>
<div class="-mt-px relative">
<input
aria-label="Passwort"
name="password"
type="password"
required
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder="Passwort"
/>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm">
<a href="./reset" class="text-gray-900 underline">Passwort vergessen?</a>
</div>
</div>
</div>
<div class="mt-5">
<button
v-if="loading === false"
@click="login"
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm text-white"
>
<span class="absolute left-0 inset-y pl-3">
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
</svg>
</span>
Log in
</button>
<button
v-if="loading === true"
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md font-semibold bg-yellow-500 sm:text-sm text-black"
>
<span class="absolute left-0 inset-y pl-3">
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
</svg>
</span>
Logging in...
</button>
</div>
</div>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white dark:bg-gray-900">Sie haben noch keinen Account?</span>
</div>
</div>
<div class="mt-6">
<a
href="./register"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>Account erstellen</a>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import axios from "redaxios";
import Toastify from "toastify-js";
let mail = ref("");
let loading = ref(false);
function login() {
loading.value = true;
axios.get("").then((res) => {
loading.value = false;
Toastify({
text: "This is a toast",
duration: 3000,
}).showToast();
});
}
</script>

View File

@ -0,0 +1,109 @@
<template>
<div class="min-h-screen flex items-center justify-center">
<div class="max-w-md w-full py-12 px-6">
<!-- <img class="mx-auto h-8 w-auto" src="/img/tailwindui-logo.svg" alt="" /> -->
<h1 class="sm:text-3xl text-2xl font-medium title-font mb-4 text-center">Login</h1>
<p
class="mx-auto leading-relaxed text-base text-center"
>Bitte anmelden...</p>
<div class="mt-5">
<div class="rounded-md shadow-sm">
<div>
<input
aria-label="E-Mail Adresse"
name="email"
type="email"
required
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-t-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder="E-Mail Adresse"
v-model="mail"
/>
</div>
<div class="-mt-px relative">
<input
aria-label="Passwort"
name="password"
type="password"
required
class="border-gray-300 placeholder-gray-500 appearance-none rounded-none relative block w-full px-3 py-2 border text-gray-900 rounded-b-md focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm"
placeholder="Passwort"
/>
<div class="absolute inset-y-0 right-0 pr-3 flex items-center text-sm">
<a href="./reset" class="text-gray-900 underline">Passwort vergessen?</a>
</div>
</div>
</div>
<div class="mt-5">
<button
v-if="loading === false"
@click="login"
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md font-semibold bg-gray-800 hover:bg-gray-700 focus:bg-gray-900 focus:outline-none focus:shadow-outline sm:text-sm text-white"
>
<span class="absolute left-0 inset-y pl-3">
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
</svg>
</span>
Log in
</button>
<button
v-if="loading === true"
type="submit"
class="relative block w-full py-2 px-3 border border-transparent rounded-md font-semibold bg-yellow-500 sm:text-sm text-black"
>
<span class="absolute left-0 inset-y pl-3">
<svg class="h-5 w-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clip-rule="evenodd"
/>
</svg>
</span>
Logging in...
</button>
</div>
</div>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white dark:bg-gray-900">Sie haben noch keinen Account?</span>
</div>
</div>
<div class="mt-6">
<a
href="./register"
class="block w-full text-center py-2 px-3 border border-gray-300 rounded-md font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>Account erstellen</a>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import axios from "redaxios";
import Toastify from "toastify-js";
let mail = ref("");
let loading = ref(false);
function login() {
loading.value = true;
axios.get("").then((res) => {
loading.value = false;
Toastify({
text: "This is a toast",
duration: 3000,
}).showToast();
});
}
</script>

View File

@ -1,71 +1,54 @@
{ {
"access_is_only_provided_via_your_email_link": "Der Zugang erfolgt über den Link, den Sie bei der Registrierung erhalten haben.", "access_is_only_provided_via_your_email_link": "Der Zugang erfolgt nur über den Link, den Sie bei der Registrierung erhalten haben.",
"alle_daten_geloescht": "Alle Daten gelöscht!", "already_have_an_account": "Sie haben bereits einen Account?",
"already_registered": "bereits registriert...",
"amount_per_kilometer_in_eur": "Betrag pro Kilometer (in €)",
"apartment_suite_etc": "Addresszeile 2", "apartment_suite_etc": "Addresszeile 2",
"buergerlauf": "Bürgerlauf",
"cancel_keep_my_data": "Abbrechen, meine Daten behalten", "cancel_keep_my_data": "Abbrechen, meine Daten behalten",
"configuration_error": "Konfigurationsfehler", "configuration_error": "Konfigurationsfehler",
"confirm_delete_all_of_my_data": "Bestätigen, meine Daten löschen", "confirm_delete_all_of_my_data": "Bestätigung, meine gesamten Daten löschen",
"confirm_personal_data": "Hiermit bestätige ich die Vollständigkeit und Richtigkeit der oben genannten Angaben", "confirm_personal_data": "Hiermit bestätige ich die Vollständigkeit und Richtigkeit der oben genannten Angaben",
"current_total_amount_in_eur": "Aktueller Gesamtbetrag (in €)", "delete-all-of-my-data": "Meine gesamten Daten löschen",
"delete_my_data": "Meine Daten löschen", "delete_my_data": "Meine Daten löschen",
"deletion_in_progress": "Daten werden gelöscht...",
"distance": "Distanz", "distance": "Distanz",
"download_certificate": "Urkunde herunterladen", "download_certificate": "Urkunde herunterladen",
"download_registrationcode": "Registrierungscode herunterladen",
"e_mail_adress": "E-Mail Adresse", "e_mail_adress": "E-Mail Adresse",
"error-loading-privacy-policy": "Fehler beim Laden der Datenschutzerklärung", "go_to_login": "Zum Login",
"error_loading_imprint": "Fehler beim Laden des Impressums",
"error_requesting_the_login_link": "Fehler beim Anfordern des Login-Links...",
"i_accept": "Ich habe die ", "i_accept": "Ich habe die ",
"i_accept_end": "gelesen und akzeptiert.", "i_accept_end": "gelesen und akzeptiert.",
"if_you_are_the_system_administrator_please_refer_to_the_official_product_documentation_readme_for_configuration_guidance": "Wenn Sie der Systemadministrator sind, finden Sie Konfigurationsanweisungen in der offiziellen Produktdokumentation / README.", "if_you_are_the_system_administrator_please_refer_to_the_official_product_documentation_readme_for_configuration_guidance": "Wenn Sie der Systemadministrator sind, finden Sie Konfigurationsanweisungen in der offiziellen Produktdokumentation / README.",
"imprint": "Impressum", "imprint": "Impressum",
"invalid_input_phone_number_should_be_international_format": "ungültige Eingabe... Die Telefonnummer sollte ein internationales Format haben",
"lap_time": "Rundenzeit", "lap_time": "Rundenzeit",
"lap_times": "Rundenzeiten", "lap_times": "Rundenzeiten",
"login_link_gesendet_an_user_email_value": "Login-Link gesendet an ", "lost_your_registration_mail": "Haben Sie Ihre Registrierungsmail verloren?",
"login_link_is_requested": "Login-Link wird angefordert...",
"lost_your_registration_mail": "Brauchen Sie einen neuen Login-Link?",
"main_page_text": "Hier können Sie sich für den Lauf Für Kaya! registrieren oder ihr Läuferprofil verwalten.", "main_page_text": "Hier können Sie sich für den Lauf Für Kaya! registrieren oder ihr Läuferprofil verwalten.",
"mittelname": "Mittelname", "mittelname": "Mittelname",
"nachname": "Nachname", "nachname": "Nachname",
"no_laps_scans_were_recorded_yet": "Es wurden noch keine Runden / Scans aufgezeichnet ...", "no_laps_scans_were_recorded_yet": "Es wurden noch keine Runden / Scans aufgezeichnet ...",
"no_sponsorings_for_you_were_recorded_yet": "Es gibt noch keine Sponsorings für dich",
"not_registered_yet": "Noch nicht registriert?", "not_registered_yet": "Noch nicht registriert?",
"organization": "Organisation", "organization": "Organisation",
"ort": "Ort", "ort": "Ort",
"phone_number": "Telefonnummer (international formatiert)", "phone_number": "Telefonnummer",
"please_provide_a_valid_zipcode": "Bitte geben Sie eine gültige Postleitzahl an...", "please_provide_a_valid_zipcode": "Bitte geben Sie eine gültige Postleitzahl an...",
"please_provide_valid_mail": "Bitte geben Sie eine gültige E-Mail Adresse an", "please_provide_valid_mail": "Bitte geben Sie eine gültige E-Mail Adresse an",
"plz": "PLZ", "plz": "PLZ",
"privacy_policy": "Datenschutzerklärung", "privacy_policy": "Datenschutzerklärung",
"profil_konnte_nicht_geladen_werden": "Profil konnte nicht geladen werden...",
"profil_konnte_nicht_geloescht_werden": "Profil konnte nicht gelöscht werden...",
"profil_wird_geloescht": "Profil wird gelöscht...",
"profile": "Profil", "profile": "Profil",
"provide_address": "Adresse angeben?", "provide_address": "Adresse angeben?",
"register": { "register": {
"register_now": "Jetzt für den Lauf für Kaya! 2025 registrieren." "register_now": "Jetzt für den Lauf für Kaya! 2021 registrieren."
}, },
"register_now": "Jetzt registrieren!", "register_now": "Jetzt registrieren!",
"register_now_small": "Jetzt registrieren", "register_now_small": "Jetzt registrieren",
"registration_running": "Registrierung läuft...",
"registrationcode": "Registrierungscode",
"registrieren": "Registrieren", "registrieren": "Registrieren",
"registriert": "Registriert", "resend_the_registration_mail": "Registrierungsmail erneut versenden",
"registrierungscode_generiert": "Registrierungscode generiert!", "save_changes": "Änderungen speichern",
"registrierungscode_wird_generiert": "Registrierungscode wird generiert...",
"resend_the_registration_mail": "Login-Link anfordern",
"sponsoring": "Sponsoring", "sponsoring": "Sponsoring",
"strasse": "Straße", "strasse": "Straße",
"the_system_is_not_properly_configured_please_contact_the_system_administrator_for_help": "Das System ist nicht richtig konfiguriert. Bitte wenden Sie sich an den Systemadministrator, um Hilfe zu erhalten.", "the_system_is_not_properly_configured_please_contact_the_system_administrator_for_help": "Das System ist nicht richtig konfiguriert. Bitte wenden Sie sich an den Systemadministrator, um Hilfe zu erhalten.",
"this_is_not_a_valid_international_phone_number": "Dies ist keine gültige internationale Telefonnummer", "this_is_not_a_valid_international_phone_number": "Dies ist keine gültige internationale Telefonnummer",
"total": "Gesamt", "tos": "AGBs",
"urkunde_generiert": "Urkunde generiert!",
"urkunde_konnte_nicht_generiert_werden": "Urkunde konnte nicht generiert werden...",
"urkunde_wird_generiert": "Urkunde wird generiert...",
"view_my_data": "Meine Läuferdaten einsehen", "view_my_data": "Meine Läuferdaten einsehen",
"vorname": "Vorname" "vorname": "Vorname",
"you_have_been_registered": "Sie wurden registriert!",
"you_have_not_provided_a_valid_access_key": "Sie haben keinen gültigen Zugangsschlüssel angegeben..."
} }

View File

@ -1,71 +1,54 @@
{ {
"access_is_only_provided_via_your_email_link": "Access is only provided via the link you received upon registration.", "access_is_only_provided_via_your_email_link": "Access is only provided via the link you received upon registration.",
"alle_daten_geloescht": "all data deleted!", "all_data_deleted": "All Data deleted!",
"already_registered": "already registered...", "already_have_an_account": "Already have an account?",
"amount_per_kilometer_in_eur": "Amount per kilometer (in €)",
"apartment_suite_etc": "Apartment, suite, etc.", "apartment_suite_etc": "Apartment, suite, etc.",
"buergerlauf": "Public Run",
"cancel_keep_my_data": "Cancel, keep my data", "cancel_keep_my_data": "Cancel, keep my data",
"configuration_error": "Configuration error", "configuration_error": "Configuration error",
"confirm_delete_all_of_my_data": "Confirm, delete all of my data", "confirm_delete_all_of_my_data": "Confirm, delete all of my data",
"confirm_personal_data": "I hereby confirm that the above information is complete and correct", "confirm_personal_data": "I hereby confirm that the above information is complete and correct",
"current_total_amount_in_eur": "Current total amount (in €)",
"delete_my_data": "Delete my data", "delete_my_data": "Delete my data",
"deletion_in_progress": "Deletion in progress...",
"distance": "Distance", "distance": "Distance",
"download_certificate": "Download certificate", "download_certificate": "Download certificate",
"download_registrationcode": "Download registrationcode",
"e_mail_adress": "mail address", "e_mail_adress": "mail address",
"error-loading-privacy-policy": "Error loading Privacy Policy", "go_to_login": "Go To Login",
"error_loading_imprint": "Error loading Imprint",
"error_requesting_the_login_link": "Error requesting the login link...",
"i_accept": "I have read and accepted the ", "i_accept": "I have read and accepted the ",
"i_accept_end": "", "i_accept_end": "",
"if_you_are_the_system_administrator_please_refer_to_the_official_product_documentation_readme_for_configuration_guidance": "If you are the system administrator, please refer to the official product documentation/ README for configuration guidance.", "if_you_are_the_system_administrator_please_refer_to_the_official_product_documentation_readme_for_configuration_guidance": "If you are the system administrator, please refer to the official product documentation/ README for configuration guidance.",
"imprint": "Imprint", "imprint": "Imprint",
"invalid_input_phone_number_should_be_international_format": "invalid input... phone number should be international format",
"lap_time": "Lap time", "lap_time": "Lap time",
"lap_times": "Lap times", "lap_times": "Lap times",
"login_link_gesendet_an_user_email_value": "Login-Link sent to ",
"login_link_is_requested": "Login link is requested...",
"lost_your_registration_mail": "Lost your registration mail?", "lost_your_registration_mail": "Lost your registration mail?",
"main_page_text": "Here you can register for the Lauf Für Kaya! or manage your runner profile.", "main_page_text": "Here you can register for the Lauf Für Kaya! or manage your runner profile.",
"mittelname": "Middlename", "mittelname": "Middlename",
"nachname": "Lastname", "nachname": "Lastname",
"no_laps_scans_were_recorded_yet": "No laps/ scans were recorded yet...", "no_laps_scans_were_recorded_yet": "No laps/ scans were recorded yet...",
"no_sponsorings_for_you_were_recorded_yet": "No sponsorings for you were recorded yet...",
"not_registered_yet": "Not registered yet?", "not_registered_yet": "Not registered yet?",
"organization": "Organization", "organization": "Organization",
"ort": "City", "ort": "City",
"phone_number": "Phone Number (international format)", "phone_number": "Phone Number",
"please_provide_a_valid_zipcode": "Please provide a valid zipcode...", "please_provide_a_valid_zipcode": "Please provide a valid zipcode...",
"please_provide_valid_mail": "Please provide a valid mail address.", "please_provide_valid_mail": "Please provide a valid mail address.",
"plz": "zipcode", "plz": "zipcode",
"privacy_policy": "Privacy Policy", "privacy_policy": "Privacy Policy",
"profil_konnte_nicht_geladen_werden": "could not load profile...",
"profil_konnte_nicht_geloescht_werden": "couold not delete profile...",
"profil_wird_geloescht": "deleting profile...",
"profile": "Profile", "profile": "Profile",
"provide_address": "Provide a postal address?", "provide_address": "Provide a postal address?",
"register": { "register": {
"register_now": "Register now for Lauf für Kaya! 2025." "register_now": "Register now for Lauf für Kaya! 2021."
}, },
"register_now": "Register now!", "register_now": "Register now!",
"register_now_small": "Register now", "register_now_small": "Register now",
"registration_running": "registration is running...",
"registrationcode": "Registration Code",
"registrieren": "Register Now", "registrieren": "Register Now",
"registriert": "Registered", "resend_the_registration_mail": "Resend the registration mail",
"registrierungscode_generiert": "created registration code!", "save_changes": "Save changes",
"registrierungscode_wird_generiert": "creating registration code...",
"resend_the_registration_mail": "Send me a login link",
"sponsoring": "Sponsoring", "sponsoring": "Sponsoring",
"strasse": "Street/ Block", "strasse": "Street/ Block",
"the_system_is_not_properly_configured_please_contact_the_system_administrator_for_help": "The system is not properly configured. Please contact the system administrator for help.", "the_system_is_not_properly_configured_please_contact_the_system_administrator_for_help": "The system is not properly configured. Please contact the system administrator for help.",
"this_is_not_a_valid_international_phone_number": "This is not a valid international phone number", "this_is_not_a_valid_international_phone_number": "This is not a valid international phone number",
"total": "Total", "tos": "Terms of Service",
"urkunde_generiert": "created certificate",
"urkunde_konnte_nicht_generiert_werden": "could not create your certificate...",
"urkunde_wird_generiert": "creating certificate...",
"view_my_data": "View my data", "view_my_data": "View my data",
"vorname": "Firstname" "vorname": "Firstname",
"you_have_been_registered": "You have been registered!",
"you_have_not_provided_a_valid_access_key": "You have not provided a valid access key..."
} }

View File

@ -2,14 +2,6 @@ import { createApp } from 'vue';
import './tailwind.css'; import './tailwind.css';
import 'toastify-js/src/toastify.css'; import 'toastify-js/src/toastify.css';
import 'vue-toastification/dist/index.css'; import 'vue-toastification/dist/index.css';
// import '@fontsource/athiti';
import '@fontsource/athiti/200.css';
import '@fontsource/athiti/300.css';
import '@fontsource/athiti/400.css';
import '@fontsource/athiti/500.css';
import '@fontsource/athiti/600.css';
import '@fontsource/athiti/700.css';
//
import App from './App.vue'; import App from './App.vue';
import { routes } from './routes.js'; import { routes } from './routes.js';
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';

View File

@ -1,23 +1,24 @@
import Home from "./views/Home.vue"; // import EnvError from './components/EnvError.vue';
import Imprint from "./views/Imprint.vue"; import Home from './views/Home.vue';
import Privacy from "./views/Privacy.vue"; import Imprint from './views/Imprint.vue';
import Register from "./views/Register.vue"; import Privacy from './views/Privacy.vue';
import Profile from "./views/Profile.vue"; import Register from './views/Register.vue';
import ProfileNone from "./views/ProfileNone.vue"; import Profile from './views/Profile.vue';
import ProfileNone from './views/ProfileNone.vue';
console.log(config); console.log(config);
/** @type {import('vue-router').RouterOptions['routes']} */ /** @type {import('vue-router').RouterOptions['routes']} */
export const routes = [ export const routes = [
{ path: "/", component: Home }, { path: config.baseurl_selfservice + '', component: Home },
{ path: "/imprint", component: Imprint }, { path: config.baseurl_selfservice + 'imprint', component: Imprint },
{ path: "/imprint/", component: Imprint }, { path: config.baseurl_selfservice + 'imprint/', component: Imprint },
{ path: "/privacy", component: Privacy }, { path: config.baseurl_selfservice + 'privacy', component: Privacy },
{ path: "/privacy/", component: Privacy }, { path: config.baseurl_selfservice + 'privacy/', component: Privacy },
{ path: "/register", component: Register }, { path: config.baseurl_selfservice + 'register', component: Register },
{ path: "/register/", component: Register }, { path: config.baseurl_selfservice + 'register/', component: Register },
{ path: "/register/:token", component: Register, props: true }, { path: config.baseurl_selfservice + 'register/:token', component: Register, props: true },
{ path: "/profile", component: Profile }, { path: config.baseurl_selfservice + 'profile', component: Profile },
{ path: "/profile/", component: ProfileNone }, { path: config.baseurl_selfservice + 'profile/', component: ProfileNone },
{ path: "/profile/:token", component: Profile, props: true }, { path: config.baseurl_selfservice + 'profile/:token', component: Profile, props: true }
]; ];

View File

@ -1 +1,3 @@
@import "tailwindcss"; @import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View File

@ -1,20 +1,23 @@
<template> <template>
<div class="bg-cover bg-fixed m-0 h-screen text-white" <div class="bg-cover bg-fixed m-0 h-screen" style="background-image: url('./background.jpg');">
v-bind:style='{ backgroundImage: "url(" + background_base64 + ")", }'>
<section class="container px-4 py-24 mx-auto"> <section class="container px-4 py-24 mx-auto">
<div class="w-full mx-auto text-center"> <div class="w-full mx-auto text-center">
<img src="/favicon-lfk.png" class="h-32 mx-auto" /> <img src="/favicon.png" class="h-32 mx-auto" />
<h1 <h1
class="mb-6 text-4xl font-extrabold leading-none tracking-normal md:text-6xl md:tracking-tight font-[Athiti]"> class="mb-6 text-4xl font-extrabold leading-none tracking-normal text-white md:text-6xl md:tracking-tight"
Lauf Für Kaya!<br>2025</h1> >Lauf Für Kaya!</h1>
<h2 class="mb-6 text-xl font-bold leading-none tracking-normal md:text-3xl md:tracking-tight font-[Athiti]"> <p class="px-0 mb-6 text-lg text-gray-100 md:text-xl lg:px-24">Selfservice Portal</p>
Selfservice Portal</h2> <p class="px-0 mb-6 text-md text-gray-100 lg:px-24">{{ $t('main_page_text') }}</p>
<p class="px-0 mb-6 text-md lg:px-24 font-medium">{{ $t('main_page_text') }}</p> <div class="mt-6 sm:flex place-content-center">
<a class="w-full block mx-auto md:w-3/4 px-6 py-3 border border-transparent text-base font-semibold rounded-md text-gray-900 bg-white shadow-sm hover:text-gray-600 focus:outline-none focus:text-gray-600 xl:text-lg xl:py-4" <a
href="/register/">{{ $t('register_now') }}</a> class="w-full sm:w-auto inline-flex px-6 py-3 border border-transparent text-base font-semibold rounded-md text-gray-900 bg-white shadow-sm hover:text-gray-600 focus:outline-none focus:text-gray-600 transition ease-in-out duration-150 xl:text-lg xl:py-4"
<a href="/profile/" href="./register/"
class="md:mt-4 mt-2 w-full block mx-auto md:w-3/4 px-6 py-3 text-base font-semibold rounded-md bg-gray-800 shadow-sm hover:bg-gray-700 focus:outline-none focus:bg-gray-700 xl:text-lg xl:py-4 border border-gray-400">{{ >{{ $t('register_now') }}</a>
$t('view_my_data') }}</a> <a
href="./profile/"
class="mt-4 sm:ml-4 sm:mt-0 w-full sm:w-auto inline-flex px-6 py-3 border border-transparent text-base font-semibold rounded-md text-white bg-gray-800 shadow-sm hover:bg-gray-700 focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150 xl:text-lg xl:py-4"
>{{ $t('view_my_data') }}</a>
</div>
</div> </div>
</section> </section>
</div> </div>
@ -22,6 +25,6 @@
</template> </template>
<script setup> <script setup>
import background_base64 from "../assets/background.jpg?inline";
import Footer from "@/components/Footer.vue"; import Footer from "@/components/Footer.vue";
console.log(config);
</script> </script>

View File

@ -2,24 +2,22 @@
<section class="container px-4 py-24 mx-auto"> <section class="container px-4 py-24 mx-auto">
<div class="simplecontent"> <div class="simplecontent">
<div class="mb-24 text-left md:text-center"> <div class="mb-24 text-left md:text-center">
<h1 class="mb-4 text-4xl font-bold leading-tight text-gray-900 dark:text-gray-50 md:text-5xl">{{ $t('imprint') <h1
}} class="mb-4 text-4xl font-bold leading-tight text-gray-900 dark:text-gray-50 md:text-5xl"
</h1> >{{$t('imprint')}}</h1>
</div> </div>
<div class="mx-auto prose" v-html="content"></div> <div class="mx-auto prose" v-html="content"></div>
</div> </div>
</section> </section>
<Footer></Footer>
</template> </template>
<style src="../simple.css"></style> <style src="../simple.css">
</style>
<script> <script>
import { marked } from "marked"; import marked from "marked";
import Footer from "@/components/Footer.vue";
export default { export default {
components: { Footer },
data() { data() {
return { return {
content: "", content: ""
} }
}, },
async beforeMount() { async beforeMount() {
@ -31,7 +29,7 @@ export default {
try { try {
md = await fetch(`/imprint_en.md`); md = await fetch(`/imprint_en.md`);
} catch (error) { } catch (error) {
md = t('error_loading_imprint'); md = "Error loading Imprint";
} }
} }
this.content = marked(await md.text()); this.content = marked(await md.text());

View File

@ -2,20 +2,19 @@
<section class="container px-4 py-24 mx-auto"> <section class="container px-4 py-24 mx-auto">
<div class="simplecontent"> <div class="simplecontent">
<div class="mb-24 text-left md:text-center"> <div class="mb-24 text-left md:text-center">
<h1 class="mb-4 text-4xl font-bold leading-tight text-gray-900 dark:text-gray-50 md:text-5xl">{{ <h1
$t('privacy_policy') }}</h1> class="mb-4 text-4xl font-bold leading-tight text-gray-900 dark:text-gray-50 md:text-5xl"
>{{ $t('privacy_policy') }}</h1>
</div> </div>
<div class="mx-auto prose" v-html="content"></div> <div class="mx-auto prose" v-html="content"></div>
</div> </div>
</section> </section>
<Footer></Footer>
</template> </template>
<style src="../simple.css"></style> <style src="../simple.css">
</style>
<script> <script>
import { marked } from "marked"; import marked from "marked";
import Footer from "@/components/Footer.vue";
export default { export default {
components: { Footer },
data() { data() {
return { return {
content: "" content: ""
@ -30,7 +29,7 @@ export default {
try { try {
md = await fetch(`/privacy_en.md`); md = await fetch(`/privacy_en.md`);
} catch (error) { } catch (error) {
md = t('error-loading-privacy-policy'); md = "Error loading Privacy Policy";
} }
} }
this.content = marked(await md.text()); this.content = marked(await md.text());

View File

@ -2,534 +2,269 @@
<div class="min-h-screen w-full p-4"> <div class="min-h-screen w-full p-4">
<section class="text-white body-font"> <section class="text-white body-font">
<div class="container mx-auto flex items-center md:flex-row flex-col"> <div class="container mx-auto flex items-center md:flex-row flex-col">
<div class=" <div
flex flex-col class="flex flex-col md:pr-10 md:mb-0 mb-6 pr-0 w-full md:w-auto md:text-left text-center text-black dark:text-gray-200"
md:pr-10 md:mb-0 >
mb-6 <p
pr-0 class="text-3xl font-bold whitespace-nowrap"
w-full v-text="(state.firstname || '') + ' ' + (state.middlename || '') + ' ' + (state.lastname || '')"
md:w-auto md:text-left ></p>
text-center text-black
dark:text-gray-200
">
<p class="text-4xl md:text-3xl font-bold whitespace-nowrap font-[Athiti]" v-text="(state.firstname || '') +
' ' +
(state.middlename || '') +
' ' +
(state.lastname || '')
"></p>
<p class="text-md whitespace-nowrap">{{ state.group }}</p> <p class="text-md whitespace-nowrap">{{ state.group }}</p>
</div> </div>
<div class="inline-flex md:ml-auto md:mr-0 mx-auto items-center"> <div class="inline-flex md:ml-auto md:mr-0 mx-auto items-center">
<div v-if="state.delete_active === false"> <div v-if="(state.delete_active === false)">
<button type="button" class=" <button
focus:border-black focus:ring-2 focus:ring-black type="button"
text-white text-base md:text-sm class="focus:border-black focus:ring-2 focus:ring-black text-white text-sm py-2.5 px-5 rounded-md bg-blue-500 hover:bg-blue-600 hover:shadow-lg"
py-3.5 >
px-5 <svg
md:py-2.5 xmlns="http://www.w3.org/2000/svg"
md:px-5 width="24"
rounded-md height="24"
bg-blue-500 viewBox="0 0 24 24"
hover:bg-blue-600 hover:shadow-lg fill="none"
w-full stroke="currentColor"
md:w-auto stroke-width="2"
mb-1 stroke-linecap="round"
md:mr-1 stroke-linejoin="round"
" @click="get_registration"> class="feather feather-download"
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" style="display: inline;height: 1rem;vertical-align: sub;"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" >
class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" /> <polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" /> <line x1="12" y1="15" x2="12" y2="3" />
</svg> </svg>
{{ $t("download_registrationcode") }} {{ $t('download_certificate') }}
</button> </button>
<button type="button" class=" <button
focus:border-black focus:ring-2 focus:ring-black type="button"
text-white text-base md:text-sm class="focus:border-black focus:ring-2 focus:ring-black text-white text-sm py-2.5 px-5 rounded-md bg-red-600 hover:bg-red-700 hover:shadow-lg ml-1"
py-3.5 @click="() => { state.delete_active = true }"
px-5 >
md:py-2.5 <svg
md:px-5 xmlns="http://www.w3.org/2000/svg"
rounded-md width="24"
bg-blue-500 height="24"
hover:bg-blue-600 hover:shadow-lg viewBox="0 0 24 24"
w-full fill="none"
md:w-auto stroke="none"
mb-1 stroke-width="2"
md:mr-1 stroke-linecap="round"
" @click="get_certificate"> stroke-linejoin="round"
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" class="feather feather-download"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline;height: 1rem;vertical-align: sub;"
class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub"> >
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
{{ $t("download_certificate") }}
</button>
<button type="button" class="
focus:border-black focus:ring-2 focus:ring-black
text-white text-base md:text-sm
py-3.5
px-5
md:py-2.5
md:px-5
rounded-md
bg-red-600
hover:bg-red-700 hover:shadow-lg
w-full
md:w-auto
" @click="() => {
state.delete_active = true;
}
">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub">
<path d="M0 0h24v24H0z" /> <path d="M0 0h24v24H0z" />
<path fill="currentColor" <path
d="M17 6h5v2h-2v13a1 1 0 01-1 1H5a1 1 0 01-1-1V8H2V6h5V3a1 1 0 011-1h8a1 1 0 011 1v3zm1 2H6v12h12V8zm-5 6l2 2-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2 1 1-2 2zM9 4v2h6V4H9z" /> fill="currentColor"
d="M17 6h5v2h-2v13a1 1 0 01-1 1H5a1 1 0 01-1-1V8H2V6h5V3a1 1 0 011-1h8a1 1 0 011 1v3zm1 2H6v12h12V8zm-5 6l2 2-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2 1 1-2 2zM9 4v2h6V4H9z"
/>
</svg> </svg>
{{ $t("delete_my_data") }} {{ $t('delete_my_data') }}
</button> </button>
</div> </div>
<div v-if="state.delete_active === true"> <div v-if="(state.delete_active === true)">
<button type="button" class=" <button
focus:border-black focus:ring-2 focus:ring-black type="button"
text-white text-base md:text-sm class="focus:border-black focus:ring-2 focus:ring-black text-white text-sm py-2.5 px-5 rounded-md bg-blue-500 hover:bg-blue-600 hover:shadow-lg"
py-3.5 @click="() => { state.delete_active = false }"
px-5 >
md:py-2.5 <svg
md:px-5 xmlns="http://www.w3.org/2000/svg"
rounded-md width="24"
mb-1 height="24"
md:mb-auto viewBox="0 0 24 24"
w-full fill="none"
md:w-auto stroke="none"
bg-blue-500 stroke-width="2"
hover:bg-blue-600 hover:shadow-lg stroke-linecap="round"
" @click="() => { stroke-linejoin="round"
state.delete_active = false; class="feather feather-download"
} style="display: inline;height: 1rem;vertical-align: sub;"
"> >
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub">
<path fill="none" d="M0 0h24v24H0z" /> <path fill="none" d="M0 0h24v24H0z" />
<path fill="currentColor" d="M12 11l5-5 1 1-5 5 5 5-1 1-5-5-5 5-1-1 5-5-5-5 1-1z" /> <path fill="currentColor" d="M12 11l5-5 1 1-5 5 5 5-1 1-5-5-5 5-1-1 5-5-5-5 1-1z" />
</svg> </svg>
{{ $t("cancel_keep_my_data") }} {{ $t('cancel_keep_my_data') }}
</button> </button>
<button type="button" class=" <button
focus:border-black focus:ring-2 focus:ring-black type="button"
text-white text-base md:text-sm class="focus:border-black focus:ring-2 focus:ring-black text-white text-sm py-2.5 px-5 rounded-md bg-red-600 hover:bg-red-700 hover:shadow-lg ml-1"
py-3.5 @click="delete_me"
px-5 >
md:py-2.5 <svg
md:px-5 xmlns="http://www.w3.org/2000/svg"
rounded-md width="24"
w-full height="24"
md:w-auto viewBox="0 0 24 24"
bg-red-600 fill="none"
hover:bg-red-700 hover:shadow-lg stroke="none"
md:ml-1 stroke-width="2"
" @click="delete_me"> stroke-linecap="round"
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-linejoin="round"
stroke="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"
class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub"> style="display: inline;height: 1rem;vertical-align: sub;"
>
<path d="M0 0h24v24H0z" /> <path d="M0 0h24v24H0z" />
<path fill="currentColor" <path
d="M17 6h5v2h-2v13a1 1 0 01-1 1H5a1 1 0 01-1-1V8H2V6h5V3a1 1 0 011-1h8a1 1 0 011 1v3zm1 2H6v12h12V8zm-5 6l2 2-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2 1 1-2 2zM9 4v2h6V4H9z" /> fill="currentColor"
d="M17 6h5v2h-2v13a1 1 0 01-1 1H5a1 1 0 01-1-1V8H2V6h5V3a1 1 0 011-1h8a1 1 0 011 1v3zm1 2H6v12h12V8zm-5 6l2 2-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2 1 1-2 2zM9 4v2h6V4H9z"
/>
</svg> </svg>
{{ $t("confirm_delete_all_of_my_data") }} {{ $t('confirm_delete_all_of_my_data') }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="w-full"> <div class="w-full p-4">
<div class="flex flex-wrap flex-col w-full tabs"> <div class="flex flex-wrap flex-col w-full tabs">
<div class="flex lg:flex-wrap flex-row lg:space-x-2"> <div class="flex lg:flex-wrap flex-row lg:space-x-2">
<div class="flex-none"> <div class="flex-none">
<button @click="() => { <button
state.activetab = 'profile'; @click="() => { state.activetab = 'profile' }"
} :class="{ 'tab-active border-b-2 font-medium border-blue-500': (state.activetab === 'profile') }"
" :class="{ class="tab tab-underline py-4 px-6 block"
'tab-active border-b-2 font-medium border-blue-500': type="button"
state.activetab === 'profile', >{{ $t('profile') }}</button>
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("profile") }}
</button>
</div> </div>
<div class="flex-none"> <div class="flex-none">
<button @click="() => { <button
state.activetab = 'laptimes'; @click="() => { state.activetab = 'laptimes' }"
} :class="{ 'tab-active border-b-2 font-medium border-blue-500': (state.activetab === 'laptimes') }"
" :class="{ class="tab tab-underline py-4 px-6 block"
'tab-active border-b-2 font-medium border-blue-500': type="button"
state.activetab === 'laptimes', >{{ $t('lap_times') }}</button>
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("lap_times") }}
</button>
</div> </div>
<div class="flex-none"> <div class="flex-none">
<button @click="() => { <button
state.activetab = 'sponsorings'; @click="() => { state.activetab = 'sponsorings' }"
} :class="{ 'tab-active border-b-2 font-medium border-blue-500': (state.activetab === 'sponsorings') }"
" :class="{ class="tab tab-underline py-4 px-6 block"
'tab-active border-b-2 font-medium border-blue-500': type="button"
state.activetab === 'sponsorings', >{{ $t('sponsoring') }}</button>
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("sponsoring") }}
</button>
</div> </div>
</div> </div>
<div v-if="state.activetab === 'profile'" class="tab-content block"> <div v-if="(state.activetab === 'profile')" class="tab-content block">
<div class="py-4 w-full"> <div class="py-4 w-full">
<div class="flex flex-col"> <div class="flex flex-col">
<form class="form flex flex-wrap w-full"> <form class="form flex flex-wrap w-full">
<div class="w-full"> <div class="w-full">
<div class="form-element"> <div class="form-element">
<div class="text-lg">{{ $t("registrationcode") }}</div> <div class="text-lg">{{ $t('vorname') }}</div>
<img class="w-full md:w-auto mb-2 bg-white p-2" alt="Registrierungscode" :src="state.barcode" /> <p
<div class="text-lg">{{ $t("vorname") }}</div> class="h-10 w-full dark:bg-gray-800 rounded text-base outline-none dark:text-gray-100 text-gray-600 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
<p class=" v-text="state.firstname"
h-10 />
w-full
dark:bg-gray-800
rounded
text-base
outline-none
dark:text-gray-100
text-gray-600
py-1
px-3
leading-8
transition-colors
duration-200
ease-in-out
" v-text="state.firstname || '---'" />
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="text-lg">{{ $t("mittelname") }}</div> <div class="text-lg">{{ $t('mittelname') }}</div>
<p class=" <p
h-10 class="h-10 w-full dark:bg-gray-800 rounded text-base outline-none dark:text-gray-100 text-gray-600 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
w-full v-text="state.middlename"
dark:bg-gray-800 />
rounded
text-base
outline-none
dark:text-gray-100
text-gray-600
py-1
px-3
leading-8
transition-colors
duration-200
ease-in-out
" v-text="state.middlename || '---'" />
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="text-lg">{{ $t("nachname") }}</div> <div class="text-lg">{{ $t('nachname') }}</div>
<p class=" <p
h-10 class="h-10 w-full dark:bg-gray-800 rounded text-base outline-none dark:text-gray-100 text-gray-600 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
w-full v-text="state.lastname"
dark:bg-gray-800 />
rounded
text-base
outline-none
dark:text-gray-100
text-gray-600
py-1
px-3
leading-8
transition-colors
duration-200
ease-in-out
" v-text="state.lastname || '---'" />
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="text-lg">{{ $t("e_mail_adress") }}</div> <div class="text-lg">{{ $t('e_mail_adress') }}</div>
<p class=" <p
h-10 class="h-10 w-full dark:bg-gray-800 rounded text-base outline-none dark:text-gray-100 text-gray-600 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
w-full v-text="state.email"
dark:bg-gray-800 />
rounded
text-base
outline-none
dark:text-gray-100
text-gray-600
py-1
px-3
leading-8
transition-colors
duration-200
ease-in-out
" v-text="state.email || '---'" />
</div> </div>
<div class="form-element"> <div class="form-element">
<div class="text-lg">{{ $t("phone_number") }}</div> <div class="text-lg">{{ $t('phone_number') }}</div>
<p class=" <p
h-10 class="h-10 w-full dark:bg-gray-800 rounded text-base outline-none dark:text-gray-100 text-gray-600 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
w-full v-text="state.phone"
dark:bg-gray-800 />
rounded
text-base
outline-none
dark:text-gray-100
text-gray-600
py-1
px-3
leading-8
transition-colors
duration-200
ease-in-out
" v-text="state.phone || '---'" />
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div v-if="state.activetab === 'laptimes'" class="tab-content block"> <div v-if="(state.activetab === 'laptimes')" class="tab-content block">
<div class="py-4 w-full"> <div class="py-4 w-full">
<section class="text-gray-400 dark:bg-gray-900 body-font"> <section class="text-gray-400 dark:bg-gray-900 body-font">
<div class="container mx-auto"> <div class="container mx-auto">
<div class="lg:w-2/3 w-full mx-auto overflow-auto"> <div class="lg:w-2/3 w-full mx-auto overflow-auto">
<table v-if="state.scans.length > 0" class="table-auto w-full text-left whitespace-no-wrap"> <table
<thead class=" v-if="state.scans.length > 0"
text-black class="table-auto w-full text-left whitespace-no-wrap"
bg-gray-300 >
dark:text-white <thead
text-sm class="text-black bg-gray-300 dark:text-white text-sm dark:bg-gray-800"
dark:bg-gray-800 >
">
<tr> <tr>
<th class=" <th
px-4 class="px-4 py-3 title-font tracking-wider font-medium"
py-3 >{{ $t('distance') }}</th>
title-font <th
tracking-wider class="px-4 py-3 title-font tracking-wider font-medium"
font-medium >{{ $t('lap_time') }}</th>
">
{{ $t("distance") }}
</th>
<th class="
px-4
py-3
title-font
tracking-wider
font-medium
">
{{ $t("lap_time") }}
</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-gray-900 dark:text-gray-50"> <tbody class="text-gray-900 dark:text-gray-50">
<tr v-for="s in state.scans" :key="s.id"> <tr v-for="s in state.scans" :key="s.id">
<td class="px-4 py-3"> <td class="px-4 py-3">
<span v-text="s.distance"></span> <span v-text="s.distance"></span>m
</td> </td>
<td class="px-4 py-3" v-text="s.lapTime"></td> <td class="px-4 py-3" v-text="s.lapTime"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div v-else class=" <div v-else class="text-center font-bold text-black dark:text-white text-2xl">
text-center <img
font-bold src="../assets/empty_laps.svg"
text-black style="height:25rem; margin:0 auto;"
dark:text-white :alt="[[$t('no_laps_scans_were_recorded_yet')]]"
text-2xl />
"> {{ $t('no_laps_scans_were_recorded_yet') }}
<img src="../assets/empty_laps.svg" style="height: 25rem; margin: 0 auto"
:alt="[[$t('no_laps_scans_were_recorded_yet')]]" />
{{ $t("no_laps_scans_were_recorded_yet") }}
</div> </div>
</div> </div>
</div> </div>
</section> </section>
</div> </div>
</div> </div>
<div v-if="state.activetab === 'sponsorings'" class="tab-content block"> <div v-if="(state.activetab === 'sponsorings')" class="tab-content block">
<div class="py-4 w-full"> <div class="py-4 w-full">coming soon...</div>
<section class="text-gray-400 dark:bg-gray-900 body-font">
<div class="container mx-auto">
<div class="lg:w-2/3 w-full mx-auto overflow-auto">
<table v-if="state.sponsorings.length > 0" class="table-auto w-full text-left whitespace-no-wrap">
<thead class="
text-black
bg-gray-300
dark:text-white
text-sm
dark:bg-gray-800
">
<tr>
<th class="
px-4
py-3
title-font
tracking-wider
font-medium
">
Name
</th>
<th class="
px-4
py-3
title-font
tracking-wider
font-medium
">
{{ $t("amount_per_kilometer_in_eur") }}
</th>
<th class="
px-4
py-3
title-font
tracking-wider
font-medium
">
{{ $t("current_total_amount_in_eur") }}
</th>
</tr>
</thead>
<tbody class="text-gray-900 dark:text-gray-50">
<tr v-for="s in state.sponsorings" :key="s.id">
<td class="px-4 py-3">
<span v-text="s.donor.firstname"></span>
<span v-if="s.donor.middlename">
<span v-text="s.donor.middlename"></span>
</span>
<span v-text="s.donor.lastname"></span>
</td>
<td class="px-4 py-3">
<span v-text="(s.amountPerDistance / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })
"></span>
</td>
<td class="px-4 py-3">
<span v-text="(s.amount / 100)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })
"></span>
</td>
</tr>
</tbody>
<tfoot class="text-gray-900 dark:text-gray-50">
<tr>
<td class="px-4 py-3">{{ $t("total") }}</td>
<td class="px-4 py-3">
<span v-text="(
state.sponsorings.reduce(function (
sum,
current
) {
return sum + current.amountPerDistance;
},
0) / 100
)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })
"></span>
</td>
<td class="px-4 py-3">
<span v-text="(
state.sponsorings.reduce(function (
sum,
current
) {
return sum + current.amount;
},
0) / 100
)
.toFixed(2)
.toLocaleString('de-DE', { valute: 'EUR' })
"></span>
</td>
</tr>
</tfoot>
</table>
<div v-else class="
text-center
font-bold
text-black
dark:text-white
text-2xl
">
<img src="../assets/empty_laps.svg" style="height: 25rem; margin: 0 auto" :alt="[
[$t('no_sponsorings_for_you_were_recorded_yet')],
]" />
{{ $t("no_sponsorings_for_you_were_recorded_yet") }}
</div>
</div>
</div>
</section>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- -->
<Footer></Footer>
</div>
</template> </template>
<script setup> <script setup>
import { reactive } from "vue"; import { reactive } from "vue";
import { TYPE, useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import axios from "redaxios"; import axios from "redaxios";
import { toCanvas } from "bwip-js";
import Footer from "@/components/Footer.vue";
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
function textToBase64Barcode(text) {
const canvas = document.createElement("canvas");
let codeconfig = {
bcid: config.code_format || "code39",
text: `${text}`,
scale: 3,
includetext: false,
textxalign: "center",
backgroundcolor: "ffffff",
};
if (
codeconfig.bcid === "code39" ||
codeconfig.bcid === "code128" ||
codeconfig.bcid === "ean13"
) {
codeconfig.height = 10;
}
toCanvas(canvas, codeconfig);
return canvas.toDataURL("image/png");
}
const state = reactive({ const state = reactive({
barcode: "",
phone: "", phone: "",
email: "", email: "",
firstname: "", firstname: "",
middlename: "", middlename: "",
lastname: "", lastname: "",
scans: [], scans: [],
sponsorings: [],
group: "", group: "",
activetab: "profile", activetab: "profile",
delete_active: false, delete_active: false,
fullobject: {}, })
});
const toast = useToast(); const toast = useToast();
const props = defineProps({ const props = defineProps({
token: String, token: String
}); })
const accesstoken = atob(props.token); const accesstoken = atob(props.token);
axios axios.get(`${config.baseurl}api/runners/me/${accesstoken}`)
.get(`${config.baseurl}api/runners/me/${accesstoken}`)
.then(({ data }) => { .then(({ data }) => {
state.phone = data.phone; state.phone = data.phone;
state.email = data.email; state.email = data.email;
@ -537,102 +272,30 @@ axios
state.middlename = data.middlename; state.middlename = data.middlename;
state.lastname = data.lastname; state.lastname = data.lastname;
state.group = data.group; state.group = data.group;
state.sponsorings = data.distanceDonations; }).catch((error) => {
state.fullobject = data; toast.error("An error occured while loading your profile data");
state.barcode = textToBase64Barcode(data.id ?? "???");
}) })
.catch((error) => { axios.get(`${config.baseurl}api/runners/me/${accesstoken}/scans`)
toast.clear();
toast.error(t('profil_konnte_nicht_geladen_werden'));
});
axios
.get(`${config.baseurl}api/runners/me/${accesstoken}/scans`)
.then(({ data }) => { .then(({ data }) => {
data.map(function (s) { data.map(function(s) {
s.lapTime = s.lapTime = Math.floor(s.lapTime / 60) + 'min ' + (Math.floor(s.lapTime % 60) + "").padStart(2, "0") + "s"
Math.floor(s.lapTime / 60) +
"min " +
(Math.floor(s.lapTime % 60) + "").padStart(2, "0") +
"s";
s.distance =
Math.floor(s.distance / 1000) +
"km " +
(Math.floor(s.distance % 1000) + "").padStart(3, "0") +
"m";
return s; return s;
}); })
data.filter((s) => s.valid === true); data.filter(s => s.valid === true);
state.scans = data; state.scans = data;
}).catch((error) => {
toast.error("An error occured while loading your profile data");
}) })
.catch((error) => {
toast.error(t('profil_konnte_nicht_geladen_werden'));
});
function delete_me() { function delete_me() {
toast.clear(); toast("Deletion in progress...");
toast(t('profil_wird_geloescht')); let url = `${config.baseurl}api/runners/me/${accesstoken}?force=true`
let url = `${config.baseurl}api/runners/me/${accesstoken}?force=true`; axios.delete(url)
axios
.delete(url)
.then(() => { .then(() => {
toast.clear(); toast("All Data deleted!");
toast(t('alle_daten_geloescht')); location.replace(`${config.baseurl_selfservice}`);
location.replace(`/`);
}) })
.catch((error) => { .catch((error) => {
toast.clear(); toast.error("An error occured while deleting your profile data");
toast.error(t('profil_konnte_nicht_geloescht_werden'));
}); });
} }
function get_certificate() {
toast(t('urkunde_wird_generiert'));
const browserlocale = (
(navigator.languages && navigator.languages[0]) ||
""
).substr(0, 2);
let url = `${config.baseurl_documentserver}certificates?locale=${browserlocale}&download=true&key=${config.documentserver_key}`;
let postdata = Object.assign({}, state.fullobject);
postdata.group = {
name: postdata.group,
};
postdata = [postdata];
axios
.post(url, postdata, {
responseType: "blob",
})
.then((response) => {
console.log(response);
if (response.status !== 200) {
toast.error(t('urkunde_konnte_nicht_generiert_werden'));
} else {
var fileURL = window.URL.createObjectURL(
new Blob([response.data], { type: "application/pdf" })
);
var fileLink = document.createElement("a");
fileLink.href = fileURL;
fileLink.setAttribute("download", "Certificate.pdf");
document.body.appendChild(fileLink);
fileLink.click();
fileLink.remove();
toast.clear();
toast(t('urkunde_generiert'), { type: TYPE.SUCCESS });
}
})
.catch((err) => {
console.error(err);
toast.clear();
toast.error(t('urkunde_konnte_nicht_generiert_werden'));
});
}
function get_registration() {
toast.clear();
toast(t('registrierungscode_wird_generiert'));
var a = document.createElement("a");
a.href = state.barcode;
a.download = "LfK25_Registrierungscode.png";
a.click();
toast.clear();
toast(t('registrierungscode_generiert'), { type: TYPE.SUCCESS });
}
</script> </script>

View File

@ -1,11 +1,13 @@
<template> <template>
<div class="min-h-screen flex items-center justify-center"> <div class="min-h-screen flex items-center justify-center">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6">
<img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt /> <img class="mx-auto h-24 w-auto" src="/favicon.png" alt />
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center font-[Athiti]">Lauf für Kaya! - {{ <h1
$t('profile') class="sm:text-3xl text-2xl font-medium title-font mb-4 text-center"
}}</h1> >Lauf für Kaya! - {{ $t('profile') }}</h1>
<p class="mx-auto leading-relaxed text-base text-center"> <p class="mx-auto leading-relaxed text-base text-center">
{{ $t('you_have_not_provided_a_valid_access_key') }}
<br />
{{ $t('access_is_only_provided_via_your_email_link') }} {{ $t('access_is_only_provided_via_your_email_link') }}
</p> </p>
<div class="mt-6"> <div class="mt-6">
@ -14,7 +16,9 @@
<div class="w-full border-t border-gray-300"></div> <div class="w-full border-t border-gray-300"></div>
</div> </div>
<div class="relative flex justify-center text-sm"> <div class="relative flex justify-center text-sm">
<span class="px-2 bg-white dark:bg-gray-900">{{ $t('lost_your_registration_mail') }}</span> <span
class="px-2 bg-white dark:bg-gray-900"
>{{ $t('lost_your_registration_mail') }}</span>
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
@ -22,20 +26,28 @@
{{ $t('e_mail_adress') }} {{ $t('e_mail_adress') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="user_email" name="email_address" id="email_address" autocomplete="email" <input
:placeholder="[[$t('e_mail_adress')]]" type="email" v-model="user_email"
name="email_address"
id="email_address"
autocomplete="off"
:placeholder="[[$t('e_mail_adress')]]"
type="email"
:class="{ 'border-red-500': (!isEmail(user_email)), 'border-green-300': (isEmail(user_email)) }" :class="{ 'border-red-500': (!isEmail(user_email)), 'border-green-300': (isEmail(user_email)) }"
class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-2 bg-gray-50 text-gray-500 rounded-md p-2" /> class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-2 bg-gray-50 text-gray-500 rounded-md p-2"
<p v-if="!isEmail(user_email) && user_email !== ''" class="text-sm">{{ />
$t('please_provide_valid_mail') <p
}}</p> v-if="!isEmail(user_email)"
class="text-sm"
>{{ $t('please_provide_valid_mail') }}</p>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<button :disabled="(!state.submit_enabled)" <a
:disabled="(!state.submit_enabled)"
:class="{ 'opacity-50': (!state.submit_enabled), 'cursor-not-allowed': (!state.submit_enabled) }" :class="{ 'opacity-50': (!state.submit_enabled), 'cursor-not-allowed': (!state.submit_enabled) }"
@click="resendMail" @click="resendMail"
class="block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 dark:bg-gray-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">{{ class="block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 dark:bg-gray-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
$t('resend_the_registration_mail') }}</button> >{{ $t('resend_the_registration_mail') }}</a>
</div> </div>
</div> </div>
<div class="mt-12"> <div class="mt-12">
@ -48,24 +60,23 @@
</div> </div>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<a href="/register/" <a
class="text-white block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 bg-blue-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm">{{ href="./register/"
$t('register_now_small') }}</a> class="text-white block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 bg-blue-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
>{{ $t('register_now_small') }}</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<Footer></Footer>
</template> </template>
<script setup> <script setup>
import { computed, ref, reactive } from "vue"; import { computed, ref, reactive, defineProps } from "vue";
import axios from "redaxios"; import axios from "redaxios";
import isEmail from 'validator/es/lib/isEmail'; import isEmail from 'validator/es/lib/isEmail';
import { TYPE, useToast } from "vue-toastification"; import isMobilePhone from 'validator/es/lib/isMobilePhone';
import Footer from "@/components/Footer.vue"; import isPostalCode from 'validator/es/lib/isPostalCode';
import { useI18n } from 'vue-i18n' import { useToast } from "vue-toastification";
const { t } = useI18n()
let user_email = ref(""); let user_email = ref("");
// //
@ -77,16 +88,16 @@ const state = reactive({
const toast = useToast(); const toast = useToast();
function resendMail() { function resendMail() {
if (isEmail(user_email.value)) { if (isEmail(user_email.value)) {
toast(t('login_link_is_requested')); toast("sending password reset mail...");
const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2); const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2);
axios.post(`${config.baseurl}api/runners/login?mail=${user_email.value}&locale=${browserlocale}`) axios.post(`${config.baseurl}api/runners/forgot?mail=${user_email.value}&locale=${browserlocale}`)
.then(({ data }) => { .then(({ data }) => {
console.log(data); console.log(data);
toast(t('login_link_gesendet_an_user_email_value') + user_email.value); toast("sent password reset mail to " + user_email.value + "!");
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
toast(t('error_requesting_the_login_link'), { type: TYPE.ERROR }); toast("user does not exist...");
}); });
} }
} }

View File

@ -1,356 +1,272 @@
<template> <template>
<div class="min-h-screen flex items-center justify-center" v-if="registrationState === 'registered'"> <div class="min-h-screen flex items-center justify-center">
<div class="max-w-md w-full py-12 px-6 font-[Athiti]"> <div class="max-w-md w-full py-12 px-6">
<img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt /> <img class="mx-auto h-24 w-auto" src="/favicon.png" alt />
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center"> <h1
Lauf für Kaya! - {{ $t('registriert') }} class="sm:text-3xl text-2xl font-medium title-font mb-4 text-center"
</h1> >Lauf für Kaya! - {{ $t('registrieren') }}</h1>
<p class="mx-auto leading-relaxed text-base text-center"> <p class="mx-auto leading-relaxed text-base text-center">{{ $t('register.register_now') }}</p>
Bitte klicken Sie zum Fortfahren auf den Link, den wir an <p
<b class="font-bold">{{ userdetails.mail }}</b> geschickt haben. v-if="state.org_name !== ''"
</p> class="mx-auto leading-relaxed text-base text-center"
</div> >{{ $t('organization') }}: {{ state.org_name }}</p>
</div> <p
<div class="min-h-screen flex items-center justify-center" v-else> v-if="state.org_name !== '' && state.org_teams.length > 0"
<div class="max-w-md w-full py-12 px-6 font-[Athiti]"> class="mx-auto leading-relaxed text-base text-center"
<img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt /> >Team:</p>
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center"> <select
Lauf für Kaya! - {{ $t("registrieren") }} v-model="org_team"
</h1> v-if="state.org_name !== '' && state.org_teams.length > 0"
<p class="mx-auto leading-relaxed text-base text-center font-medium"> class="w-full border bg-white rounded px-3 py-2 outline-none block mt-1 text-sm dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700 form-select focus:border-purple-400 focus:outline-none focus:shadow-outline-purple dark:focus:shadow-outline-gray"
{{ $t("register.register_now") }} >
</p> <option v-for="t in state.org_teams" :key="t.id" :value="t.id">{{ t.name }}</option>
<p v-if="state.org_name !== ''" class="mx-auto leading-relaxed text-base text-center font-medium">
{{ $t("organization") }}: {{ state.org_name }}
</p>
<p v-if="state.org_name !== '' && state.org_teams.length > 0"
class="mx-auto leading-relaxed text-base text-center">
Team:
</p>
<select v-model="org_team" v-if="state.org_name !== '' && state.org_teams.length > 0" class="
w-full
border
bg-white
rounded
px-3
py-2
outline-none
block
mt-1
text-sm
dark:text-gray-300 dark:border-gray-600 dark:bg-gray-700
form-select
focus:border-purple-400 focus:outline-none focus:shadow-outline-purple
dark:focus:shadow-outline-gray
">
<option v-for="t in state.org_teams" :key="t.id" :value="t.id">
{{ t.name }}
</option>
</select> </select>
<p v-if="state.org_name === ''" class="mx-auto leading-relaxed text-base text-center"> <p
{{ $t('buergerlauf') }} v-if="state.org_name === ''"
</p> class="mx-auto leading-relaxed text-base text-center"
>Bürgerlauf</p>
<div class="mt-4"> <div class="mt-4">
<label for="first_name" class="block font-medium"> <label for="first_name" class="block font-medium">
{{ $t("vorname") }} {{ $t('vorname') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.firstname" name="firstname" id="first_name" autocomplete="off" <input
:placeholder="[[$t('vorname')]]" type="text" :class="{ v-model="userdetails.firstname"
'border-red-500': !userdetails.firstname.trim(), name="firstname"
'border-green-300': userdetails.firstname.trim(), id="first_name"
}" class=" autocomplete="off"
dark:bg-gray-800 :placeholder="[[$t('vorname')]]"
mt-1 type="text"
block :class="{ 'border-red-500': (!userdetails.firstname.trim()), 'border-green-300': (userdetails.firstname.trim()) }"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
<!-- --> <!-- -->
<label for="middle_name" class="block font-medium">{{ <label for="middle_name" class="block font-medium">{{ $t('mittelname') }}</label>
$t("mittelname") <input
}}</label> v-model="userdetails.middlename"
<input v-model="userdetails.middlename" name="middlename" id="middle_name" autocomplete="off" name="middlename"
:placeholder="[[$t('mittelname')]]" type="text" class=" id="middle_name"
dark:bg-gray-800 autocomplete="off"
mt-1 :placeholder="[[$t('mittelname')]]"
block type="text"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
<!-- --> <!-- -->
<label for="last_name" class="block font-medium"> <label for="last_name" class="block font-medium">
{{ $t("nachname") }} {{ $t('nachname') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.lastname" name="lastname" id="last_name" autocomplete="off" <input
:placeholder="[[$t('nachname')]]" type="text" :class="{ v-model="userdetails.lastname"
'border-red-500': !userdetails.lastname.trim(), name="lastname"
'border-green-300': userdetails.lastname.trim(), id="last_name"
}" class=" autocomplete="off"
dark:bg-gray-800 :placeholder="[[$t('nachname')]]"
mt-1 type="text"
block :class="{ 'border-red-500': (!userdetails.lastname.trim()), 'border-green-300': (userdetails.lastname.trim()) }"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
<!-- --> <!-- -->
<label for="email_address" class="block font-medium"> <label for="email_address" class="block font-medium">
{{ $t("e_mail_adress") }} {{ $t('e_mail_adress') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.mail" name="email_address" id="email_address" autocomplete="off" <input
:placeholder="[[$t('e_mail_adress')]]" type="email" :class="{ v-model="userdetails.mail"
'border-red-500': !isEmail(userdetails.mail), name="email_address"
'border-green-300': isEmail(userdetails.mail), id="email_address"
}" class=" autocomplete="off"
dark:bg-gray-800 :placeholder="[[$t('e_mail_adress')]]"
mt-1 type="email"
block :class="{ 'border-red-500': (!isEmail(userdetails.mail)), 'border-green-300': (isEmail(userdetails.mail)) }"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm <p v-if="!isEmail(userdetails.mail)" class="text-sm">{{ $t('please_provide_valid_mail') }}</p>
border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
<p v-if="!isEmail(userdetails.mail)" class="text-sm">
{{ $t("please_provide_valid_mail") }}
</p>
<!-- --> <!-- -->
<label for="phone" class="select-none block font-medium">{{ <label for="phone" class="select-none block font-medium">{{ $t('phone_number') }}</label>
$t("phone_number") <input
}}</label> v-model="userdetails.phone"
<input v-model="userdetails.phone" name="phone" id="phone" autocomplete="off" name="phone"
:placeholder="[[$t('phone_number')]]" type="text" :class="{ id="phone"
'border-red-500': autocomplete="off"
!isMobilePhone(userdetails.phone) && userdetails.phone.trim(), :placeholder="[[$t('phone_number')]]"
'border-green-300': type="text"
isMobilePhone(userdetails.phone) && userdetails.phone.trim(), :class="{ 'border-red-500': (!isMobilePhone(userdetails.phone) && userdetails.phone.trim()), 'border-green-300': (isMobilePhone(userdetails.phone) && userdetails.phone.trim()) }"
}" class=" class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-2 bg-gray-50 text-gray-500 rounded-md p-2"
dark:bg-gray-800 />
mt-1 <p
block v-if="(!isMobilePhone(userdetails.phone) && userdetails.phone.trim())"
w-full class="text-sm"
shadow-sm >{{ $t('this_is_not_a_valid_international_phone_number') }}</p>
sm:text-sm
border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
<p v-if="!isMobilePhone(userdetails.phone) && userdetails.phone.trim()" class="text-sm">
{{ $t("this_is_not_a_valid_international_phone_number") }}
</p>
<!-- --> <!-- -->
<div class="grid grid-cols-6 mt-6"> <div class="grid grid-cols-6 mt-6">
<div class="col-span-6"></div> <div class="col-span-6"></div>
<div class="flex items-start col-span-6"> <div class="flex items-start col-span-6">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input v-model="provide_address" id="address_activated" name="address_activated" type="checkbox" <input
class="h-4 w-4 text-indigo-600 border-gray-300 rounded" /> v-model="provide_address"
id="address_activated"
name="address_activated"
type="checkbox"
class="h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="address_activated" class="font-medium text-gray-600 select-none">{{ $t("provide_address") <label
}}</label> for="address_activated"
class="font-medium text-gray-400 select-none"
>{{ $t('provide_address') }}</label>
</div> </div>
</div> </div>
<div v-if="provide_address === true" class="col-span-6"> <div v-if="provide_address === true" class="col-span-6">
<div class="col-span-6"> <div class="col-span-6">
<label for="street" class="block font-medium"> <label for="street" class="block font-medium">
{{ $t("strasse") }} {{ $t('strasse') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.address.street" type="text" name="street" :placeholder="[[$t('strasse')]]" <input
id="street" autocomplete="street-address" :class="{ v-model="userdetails.address.street"
'border-red-500': !userdetails.address.street.trim(), type="text"
'border-green-300': userdetails.address.street.trim(), name="street"
}" class=" :placeholder="[[$t('strasse')]]"
dark:bg-gray-800 id="street"
mt-1 autocomplete="street-address"
block :class="{ 'border-red-500': (!userdetails.address.street.trim()), 'border-green-300': (userdetails.address.street.trim()) }"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
</div> </div>
<div class="col-span-6"> <div class="col-span-6">
<label for="address2" class="block font-medium">{{ <label for="address2" class="block font-medium">{{ $t('apartment_suite_etc') }}</label>
$t("apartment_suite_etc") <input
}}</label> v-model="userdetails.address.address2"
<input v-model="userdetails.address.address2" type="text" name="address2" type="text"
:placeholder="[[$t('apartment_suite_etc')]]" id="address2" autocomplete="street-address" class=" name="address2"
dark:bg-gray-800 :placeholder="[[$t('apartment_suite_etc')]]"
mt-1 id="address2"
block autocomplete="street-address"
w-full class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
shadow-sm />
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
</div> </div>
<div class="col-span-6 sm:col-span-6 lg:col-span-2"> <div class="col-span-6 sm:col-span-6 lg:col-span-2">
<label for="city" class="block font-medium"> <label for="city" class="block font-medium">
{{ $t("ort") }} {{ $t('ort') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.address.city" type="text" name="city" :placeholder="[[$t('ort')]]" id="city" <input
:class="{ v-model="userdetails.address.city"
'border-red-500': !userdetails.address.city.trim(), type="text"
'border-green-300': userdetails.address.city.trim(), name="city"
}" class=" :placeholder="[[$t('ort')]]"
dark:bg-gray-800 id="city"
mt-1 :class="{ 'border-red-500': (!userdetails.address.city.trim()), 'border-green-300': (userdetails.address.city.trim()) }"
block class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
w-full />
shadow-sm
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
</div> </div>
<div class="col-span-6 sm:col-span-3 lg:col-span-2"> <div class="col-span-6 sm:col-span-3 lg:col-span-2">
<label for="postal_code" class="block font-medium"> <label for="postal_code" class="block font-medium">
{{ $t("plz") }} {{ $t('plz') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
<input v-model="userdetails.address.zipcode" type="text" name="postal_code" :placeholder="[[$t('plz')]]" <input
id="postal_code" autocomplete="postal-code" :class="{ v-model="userdetails.address.zipcode"
'border-red-500': !isPostalCode( type="text"
userdetails.address.zipcode, name="postal_code"
'DE' :placeholder="[[$t('plz')]]"
), id="postal_code"
'border-green-300': isPostalCode( autocomplete="postal-code"
userdetails.address.zipcode, :class="{ 'border-red-500': (!isPostalCode(userdetails.address.zipcode, 'DE')), 'border-green-300': (isPostalCode(userdetails.address.zipcode, 'DE')) }"
'DE' class="dark:bg-gray-800 mt-1 block w-full shadow-sm sm:text-sm border-gray-300 border-2 bg-gray-50 text-gray-500 rounded-md p-2"
), />
}" class="
dark:bg-gray-800
mt-1
block
w-full
shadow-sm
sm:text-sm
border-gray-300 border-2
bg-gray-50
text-gray-500
rounded-md
p-2
" />
</div> </div>
<p v-if="!isPostalCode(userdetails.address.zipcode, 'DE')" class="text-sm"> <p
{{ $t("please_provide_a_valid_zipcode") }} v-if="!isPostalCode(userdetails.address.zipcode, 'DE')"
</p> class="text-sm"
>{{ $t('please_provide_a_valid_zipcode') }}</p>
</div> </div>
</div> </div>
<div class="flex items-start mt-6"> <div class="flex items-start mt-6">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input v-model="agb_accepted" id="agb_accepted" name="agb_accepted" type="checkbox" <input
class="h-4 w-4 text-indigo-600 border-gray-300 rounded" /> v-model="agb_accepted"
id="agb_accepted"
name="agb_accepted"
type="checkbox"
class="h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="agb_accepted" class="font-medium text-gray-600 select-none"> <label for="agb_accepted" class="font-medium text-gray-400 select-none">
{{ $t("i_accept", { tos: $t("privacy_policy") }) }} {{ $t('i_accept', { tos: $t('tos') }) }}
<a target="_blank" rel="noreferrer,noopener" href="https://lauf-fuer-kaya.de/datenschutz/" <a
class="underline">{{ $t("privacy_policy") }}</a> target="_blank"
{{ $t("i_accept_end") }} rel="noreferrer,noopener"
href
class="underline"
>{{ $t('tos') }}</a>
{{ $t('i_accept_end') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
</div> </div>
</div> </div>
<div class="flex items-start mt-6"> <div class="flex items-start mt-6">
<div class="flex items-center h-5"> <div class="flex items-center h-5">
<input v-model="data_confirmed" id="data_confirmed" name="data_confirmed" type="checkbox" <input
class="h-4 w-4 text-indigo-600 border-gray-300 rounded" /> v-model="data_confirmed"
id="data_confirmed"
name="data_confirmed"
type="checkbox"
class="h-4 w-4 text-indigo-600 border-gray-300 rounded"
/>
</div> </div>
<div class="ml-3 text-sm"> <div class="ml-3 text-sm">
<label for="data_confirmed" class="font-medium text-gray-600 select-none"> <label for="data_confirmed" class="font-medium text-gray-400 select-none">
{{ $t("confirm_personal_data") }} {{ $t('confirm_personal_data') }}
<span class="font-bold">*</span> <span class="font-bold">*</span>
</label> </label>
</div> </div>
</div> </div>
<div class="mt-6"> <div class="mt-6">
<button @click="login" :disabled="!state.submit_enabled" :class="{ <button
'opacity-50': !state.submit_enabled, @click="login"
'cursor-not-allowed': !state.submit_enabled, :disabled="(!state.submit_enabled)"
}" class=" :class="{ 'opacity-50': (!state.submit_enabled), 'cursor-not-allowed': (!state.submit_enabled) }"
text-white class="text-white block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 bg-blue-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
block >{{ $t('registrieren') }}</button>
w-full </div>
text-center </div>
py-2 <div class="mt-6">
px-3 <div class="relative">
border-2 border-gray-300 <div class="absolute inset-0 flex items-center">
rounded-md <div class="w-full border-t border-gray-300"></div>
p-1 </div>
bg-blue-800 <div class="relative flex justify-center text-sm">
font-medium <span class="px-2 bg-white dark:bg-gray-900">{{ $t('already_have_an_account') }}</span>
not-disabled:hover:border-gray-400 </div>
not-disabled:hover:bg-blue-600 </div>
not-disabled:cursor-pointer <div class="mt-6">
not-disabled:focus:outline-none focus:border-gray-400 <a
sm:text-sm href="./login"
"> class="block w-full text-center py-2 px-3 border-2 border-gray-300 rounded-md p-1 dark:bg-gray-800 font-medium hover:border-gray-400 focus:outline-none focus:border-gray-400 sm:text-sm"
{{ $t("registrieren") }} >{{ $t('go_to_login') }}</a>
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<Footer></Footer>
</template> </template>
<script setup> <script setup>
import { computed, ref, reactive } from "vue"; import { computed, ref, reactive, defineProps } from "vue";
import axios from "redaxios"; import axios from "redaxios";
import isEmail from "validator/es/lib/isEmail"; import isEmail from 'validator/es/lib/isEmail';
import isMobilePhone from "validator/es/lib/isMobilePhone"; import isMobilePhone from 'validator/es/lib/isMobilePhone';
import isPostalCode from "validator/es/lib/isPostalCode"; import isPostalCode from 'validator/es/lib/isPostalCode';
import { TYPE, useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import Footer from "@/components/Footer.vue";
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({ const props = defineProps({
token: String, token: String
}); })
if (props.token) { if (props.token) {
axios axios.get(`${config.baseurl}api/organizations/selfservice/${props.token}`)
.get(`${config.baseurl}api/organizations/selfservice/${props.token}`)
.then(({ data }) => { .then(({ data }) => {
state.org_name = data.name; state.org_name = data.name;
state.org_teams = data.teams; state.org_teams = data.teams;
@ -361,51 +277,31 @@ if (props.token) {
}); });
} }
let userdetails = ref({ let userdetails = ref({ firstname: "", lastname: "", middlename: "", mail: "", phone: "", address: { street: "", address2: "", city: "", zipcode: "" } });
firstname: "",
lastname: "",
middlename: "",
mail: "",
phone: "",
address: { street: "", address2: "", city: "", zipcode: "" },
});
let provide_address = ref(false); let provide_address = ref(false);
let agb_accepted = ref(false); let agb_accepted = ref(false);
let data_confirmed = ref(false); let data_confirmed = ref(false);
let org_team = ref(""); let org_team = ref("");
let registrationState = ref("pending");
// //
const state = reactive({ const state = reactive({
org_name: "", org_name: "",
org_teams: [], org_teams: [],
submit_enabled: computed( submit_enabled: computed(() => agb_accepted.value === true && data_confirmed.value === true && (isMobilePhone(userdetails.value.phone) || !userdetails.value.phone.trim()) && isEmail(userdetails.value.mail)
() => && userdetails.value.firstname
agb_accepted.value === true && && userdetails.value.lastname && (provide_address.value === false || provide_address.value === true && (userdetails.value.address.street.trim() && userdetails.value.address.city.trim() && isPostalCode(userdetails.value.address.zipcode, "DE"))))
data_confirmed.value === true && })
(isMobilePhone(userdetails.value.phone) ||
!userdetails.value.phone.trim()) &&
isEmail(userdetails.value.mail) &&
userdetails.value.firstname &&
userdetails.value.lastname &&
(provide_address.value === false ||
(provide_address.value === true &&
userdetails.value.address.street.trim() &&
userdetails.value.address.city.trim() &&
isPostalCode(userdetails.value.address.zipcode, "DE")))
),
});
const toast = useToast(); const toast = useToast();
function login() { function login() {
userdetails = userdetails.value; userdetails = userdetails.value;
if (userdetails?.phone === "" || isMobilePhone(userdetails.phone)) { if (userdetails.phone === "" || isMobilePhone(userdetails.phone)) {
if (isEmail(userdetails.mail)) { if (isEmail(userdetails.mail)) {
let postdata = { let postdata = {
email: userdetails.mail, "email": userdetails.mail,
firstname: userdetails.firstname, "firstname": userdetails.firstname,
middlename: userdetails.middlename, "middlename": userdetails.middlename,
lastname: userdetails.lastname, "lastname": userdetails.lastname,
address: {}, "address": {}
}; }
if (isMobilePhone(userdetails.phone)) { if (isMobilePhone(userdetails.phone)) {
postdata.phone = userdetails.phone; postdata.phone = userdetails.phone;
} }
@ -416,37 +312,25 @@ function login() {
city: userdetails.address.city, city: userdetails.address.city,
postalcode: userdetails.address.zipcode, postalcode: userdetails.address.zipcode,
country: "DE", country: "DE",
};
} }
if (state.org_name !== "" && state.org_teams.length > 0) { }
if (state.org_name !== '' && state.org_teams.length > 0) {
postdata.team = org_team.value; postdata.team = org_team.value;
} }
toast(t('registration_running')); toast("registration in progress...");
const browserlocale = ( const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2);
(navigator.languages && navigator.languages[0]) ||
""
).substr(0, 2);
let url = `${config.baseurl}api/runners/register/?locale=${browserlocale}`; let url = `${config.baseurl}api/runners/register/?locale=${browserlocale}`;
if (props.token) { if (props.token) {
url = `${config.baseurl}api/runners/register/${props.token}/?locale=${browserlocale}`; url = `${config.baseurl}api/runners/register/${props.token}/?locale=${browserlocale}`
} }
registrationState.value = "loading"; axios.post(url, postdata)
axios .then(({ data }) => {
.post(url, postdata) const token = btoa(data.token);
.then(() => { // alert(token);
registrationState.value = "registered"; location.replace(`${config.baseurl_selfservice}profile/${token}`);
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
if (error.data.message === "E-Mail already registered") {
toast(t('already_registered'), { type: TYPE.ERROR });
} else if (error.data.message === "Invalid body, check 'errors' property for more info.") {
error.data.errors.forEach(e => {
if (e.property === "phone") {
toast(t('invalid_input_phone_number_should_be_international_format'), { type: TYPE.ERROR });
}
});
}
}); });
} }
} }

11
tailwind.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
purge: [ './index.html', './src/**/*.{vue,js,ts,jsx,tsx}' ],
darkMode: 'media', // or 'media' or 'class'
theme: {
extend: {}
},
variants: {
extend: {}
},
plugins: []
};

View File

@ -1,15 +1,13 @@
import { fileURLToPath, URL } from "node:url"; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from "vite"; import path from 'path';
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";
import vueDevTools from "vite-plugin-vue-devtools";
export default defineConfig({ export default defineConfig({
plugins: [vue(), vueDevTools(), tailwindcss()], plugins: [ vue() ],
base: './',
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), '@': path.resolve(__dirname, '/src')
}, }
}, }
}); });