Compare commits

..

5 Commits
1.2.7 ... main

Author SHA1 Message Date
00a0c99f70
Merge branch 'dev'
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-02 16:49:36 +01:00
8fb39276af
chore: 2025 2024-11-21 10:48:22 +01:00
a1b600df1c
Merge branch 'dev'
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-03 13:54:34 +01:00
fe1bbc7816
Merge branch 'dev'
All checks were successful
continuous-integration/drone/push Build is passing
2023-02-02 00:21:22 +01:00
7ae0081d69 Merge pull request 'sync - 0.4.5' (#34) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #34
2021-04-01 16:46:05 +00:00
26 changed files with 1927 additions and 1925 deletions

View File

@ -85,6 +85,8 @@ out
.nuxt .nuxt
dist dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js # 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 # https://nextjs.org/blog/next-9-1#public-directory-support
# public # public

View File

@ -1,27 +0,0 @@
name: Build Latest image
on:
push:
branches:
- dev
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/selfservice:dev
platforms: linux/amd64,linux/arm64

View File

@ -1,27 +0,0 @@
name: Build Latest image
on:
push:
branches:
- main
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/selfservice:latest
platforms: linux/amd64,linux/arm64

View File

@ -1,27 +0,0 @@
name: Build release images
on:
push:
tags:
- "*.*.*"
jobs:
build-container:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to registry
uses: docker/login-action@v3
with:
registry: registry.odit.services
username: ${{ vars.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
${{ vars.REGISTRY }}/lfk/selfservice:${{ github.ref_name }}
platforms: linux/amd64,linux/arm64

33
.woodpecker/build.yml Normal file
View File

@ -0,0 +1,33 @@
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

17
.woodpecker/release.yml Normal file
View File

@ -0,0 +1,17 @@
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,116 +2,9 @@
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.2.7](https://git.odit.services/lfk/selfservice/compare/1.2.6...1.2.7)
- refactor(ci): Switch to actions [`a862593`](https://git.odit.services/lfk/selfservice/commit/a862593c5315043577699d1a6fd50854dd1bca00)
- footer: cleanup imprint & privacy url [`0e557ef`](https://git.odit.services/lfk/selfservice/commit/0e557ef4080e997b06adcbbadf3e82f12152281b)
- fix: footer [`422df7c`](https://git.odit.services/lfk/selfservice/commit/422df7c3f832dc29721e783dc4a86ee55e9d8ccc)
- footer padding [`0af7352`](https://git.odit.services/lfk/selfservice/commit/0af73525bc154ba730351d7a4970e9737edaa4db)
- chore: clean up .dockerignore by removing Gatsby references [`bab145d`](https://git.odit.services/lfk/selfservice/commit/bab145d78c16dd7c56136a274d051cf408935e3e)
#### [1.2.6](https://git.odit.services/lfk/selfservice/compare/1.2.5...1.2.6)
> 18 March 2025
- 🚀Bumped version to v1.2.6 [`63fc5ec`](https://git.odit.services/lfk/selfservice/commit/63fc5ec7474f65c743db9c281829fef31b623af6)
- fix(profile): font sizes [`c98a65d`](https://git.odit.services/lfk/selfservice/commit/c98a65d918e5d652ee98044b4d5333c7000e1c87)
#### [1.2.5](https://git.odit.services/lfk/selfservice/compare/1.2.4...1.2.5)
> 18 March 2025
- refactor: move to new lfk ts client [`865058c`](https://git.odit.services/lfk/selfservice/commit/865058c8bb7eec03278bf1f4a7b708429d0b5b20)
- feat: cleanup [`b3197dd`](https://git.odit.services/lfk/selfservice/commit/b3197dd3f95cd7d222f1ea168e9826f7ad7ef903)
- refactor: simplify imprint + privacy [`50fbfe0`](https://git.odit.services/lfk/selfservice/commit/50fbfe05f1ba830ea19f9e86b7a2fdce588f1a31)
- feat: improved tabs [`51b66eb`](https://git.odit.services/lfk/selfservice/commit/51b66eb85b3003996ac2414757ae62ee7ba80ce5)
- fix(register): phone number verification [`d5eefbb`](https://git.odit.services/lfk/selfservice/commit/d5eefbb5e22f4cc7b50e1f0c469779d3b7e310f5)
- feat: improved icons [`9af9c89`](https://git.odit.services/lfk/selfservice/commit/9af9c897f17b8a1be12f47dc271382629fc298ff)
- feat: profile cleanup [`d50719c`](https://git.odit.services/lfk/selfservice/commit/d50719c0dad4e3fbf008fb240edff80c4ea6ab4c)
- 🚀Bumped version to v1.2.5 [`b1ab04f`](https://git.odit.services/lfk/selfservice/commit/b1ab04fa53817178e016d7c2c387db12c0f6a987)
- register: drop middlename [`d503061`](https://git.odit.services/lfk/selfservice/commit/d5030616043fb9fa4eccc7894ee3ada92928d102)
- feat: profile cleanup [`03532cc`](https://git.odit.services/lfk/selfservice/commit/03532cc365e38d7313ff2e8571ae15975d8a53e5)
- feat: cleanup [`0ff6df6`](https://git.odit.services/lfk/selfservice/commit/0ff6df68d61404c7be7a1e9b88a354fb12ce0907)
#### [1.2.4](https://git.odit.services/lfk/selfservice/compare/1.2.3...1.2.4)
> 17 March 2025
- feat: loading screen [`2939911`](https://git.odit.services/lfk/selfservice/commit/2939911c993c3817d841d4cb4660aa940e478cc0)
- 🚀Bumped version to v1.2.4 [`f1d552c`](https://git.odit.services/lfk/selfservice/commit/f1d552ce64557b5da0dea91e114d3ebf2f8f0199)
#### [1.2.3](https://git.odit.services/lfk/selfservice/compare/1.2.2...1.2.3)
> 17 March 2025
- chore(deps): bump [`64bb2d1`](https://git.odit.services/lfk/selfservice/commit/64bb2d157daab257b6e0e7c4e6ed04f4b3772740)
- feat: cleanup profile [`86ec22a`](https://git.odit.services/lfk/selfservice/commit/86ec22aa435d9138ae3cde6387ce7ead14f3c964)
- feat: improve profile [`846d10f`](https://git.odit.services/lfk/selfservice/commit/846d10f0b95dad460a068bdaf3ca489d96c0b723)
- feat: profile cleanup [`1202f2e`](https://git.odit.services/lfk/selfservice/commit/1202f2ebca5fbc0baea145dda6f99668d8c47e92)
- feat: improve profile [`34e63cf`](https://git.odit.services/lfk/selfservice/commit/34e63cf690431da973a969376b493d8b34f5c7c0)
- i18n [`c94f9e5`](https://git.odit.services/lfk/selfservice/commit/c94f9e550e1cbe4626242423deb6d9ab994eea63)
- feat: wip: sponsoring add [`382757a`](https://git.odit.services/lfk/selfservice/commit/382757aa66cd79a6a8081ff4b21f6efe46a3ccfd)
- feat: cleanup profile [`0366f95`](https://git.odit.services/lfk/selfservice/commit/0366f95951d1415b300b174699d93e4bf17f3e18)
- 🚀Bumped version to v1.2.3 [`e7b9c6e`](https://git.odit.services/lfk/selfservice/commit/e7b9c6e2036addd18e109e3ab040e69dee2f658d)
- shareSponsorLink function [`ccea9d6`](https://git.odit.services/lfk/selfservice/commit/ccea9d61975bfa54928d557735cd3ce79d671435)
- no selfservice sponsor add for now [`3641d2a`](https://git.odit.services/lfk/selfservice/commit/3641d2a78341b91a26a9d4cc31c40707096768b1)
- feat: disable darkmode for now, also is better for visibility on day of run... [`0848209`](https://git.odit.services/lfk/selfservice/commit/0848209d49e4445881bf9536d87fe18ea2a6c924)
- wip [`4714b81`](https://git.odit.services/lfk/selfservice/commit/4714b814650d4138d8522dd57b5ee59a8c96a0ac)
- feat(footer): 2024 [`f6f46f4`](https://git.odit.services/lfk/selfservice/commit/f6f46f41bf2c6fcf75dbd79a28f6dd14114445e3)
- cleanup [`7f2e6b9`](https://git.odit.services/lfk/selfservice/commit/7f2e6b916076874cfb2e787ae174320b50d2d7e0)
#### [1.2.2](https://git.odit.services/lfk/selfservice/compare/1.2.1...1.2.2)
> 16 December 2024
- feat(profile): add cursor pointer [`1d7cd60`](https://git.odit.services/lfk/selfservice/commit/1d7cd601ee027dd7df0405079e208d03078210bb)
- 🚀Bumped version to v1.2.2 [`c8ceae5`](https://git.odit.services/lfk/selfservice/commit/c8ceae5cf016341af1bc903fb219e544bb2f0d58)
#### [1.2.1](https://git.odit.services/lfk/selfservice/compare/1.2.0...1.2.1)
> 16 December 2024
- feat(profile): show total distance [`55abb9e`](https://git.odit.services/lfk/selfservice/commit/55abb9ed22e4c66c05536897ba33b12915eea226)
- refactor(profile): replace styles with tailwindcss [`c34a8a7`](https://git.odit.services/lfk/selfservice/commit/c34a8a7fcc77a0fa27280365ebf2382fbffc1e61)
- 🚀Bumped version to v1.2.1 [`9ddb188`](https://git.odit.services/lfk/selfservice/commit/9ddb188ef659742018f00d786e030f80a0d9bbc5)
- feat(profile): updated tab alignment [`d564131`](https://git.odit.services/lfk/selfservice/commit/d5641312ca0b35a5c5ab9b7b19ed3a40971ac4fd)
#### [1.2.0](https://git.odit.services/lfk/selfservice/compare/1.1.2...1.2.0)
> 16 December 2024
- refacor(documents): Switch to new document-server [`721892c`](https://git.odit.services/lfk/selfservice/commit/721892c315de9c2c1158d0f728dc2ef387a5d8c2)
- 🚀Bumped version to v1.2.0 [`7d2a29c`](https://git.odit.services/lfk/selfservice/commit/7d2a29c0d834fbe783e59308af89bb8fb46a8015)
#### [1.1.2](https://git.odit.services/lfk/selfservice/compare/1.1.1...1.1.2)
> 11 December 2024
- chore(deps): bump all [`25c2a17`](https://git.odit.services/lfk/selfservice/commit/25c2a170bc9cde66498ae3d7f966201f2b28b679)
- 🚀Bumped version to v1.1.2 [`1d55445`](https://git.odit.services/lfk/selfservice/commit/1d55445c1b67ec2e1be73172d8e451f038451f59)
- refactor: drop postbuild step [`b21ad63`](https://git.odit.services/lfk/selfservice/commit/b21ad636ad69886878d5bd0f441f4187e4f22a5c)
- fix(profile): migrate to code128 [`762454a`](https://git.odit.services/lfk/selfservice/commit/762454a08674303881063337ddf86da564b191f1)
#### [1.1.1](https://git.odit.services/lfk/selfservice/compare/1.1.0...1.1.1)
> 11 December 2024
- 🚀Bumped version to v1.1.1 [`4771bf1`](https://git.odit.services/lfk/selfservice/commit/4771bf135986f90f344757083236539b9d590e83)
- fix(profile): passed id is a jwt [`dbe707b`](https://git.odit.services/lfk/selfservice/commit/dbe707b062ced048428b8c1f62a0ab047ab0051b)
#### [1.1.0](https://git.odit.services/lfk/selfservice/compare/1.0.1...1.1.0)
> 2 December 2024
- refactor: drop sub-directory routing [`4bcbc67`](https://git.odit.services/lfk/selfservice/commit/4bcbc67436e6c0b0905e3ef2613894854d659091)
- 🚀Bumped version to v1.1.0 [`dee1b7a`](https://git.odit.services/lfk/selfservice/commit/dee1b7a6eab11689bae8914e74bea7cb364475e2)
#### [1.0.1](https://git.odit.services/lfk/selfservice/compare/1.0.0...1.0.1) #### [1.0.1](https://git.odit.services/lfk/selfservice/compare/1.0.0...1.0.1)
> 2 December 2024
- fix(container): Add dockeringore [`7fcb6a9`](https://git.odit.services/lfk/selfservice/commit/7fcb6a9fc3f98772990790f6385200732f8bce7c) - fix(container): Add dockeringore [`7fcb6a9`](https://git.odit.services/lfk/selfservice/commit/7fcb6a9fc3f98772990790f6385200732f8bce7c)
- 🚀Bumped version to v1.0.1 [`595735a`](https://git.odit.services/lfk/selfservice/commit/595735ad003b849521e6e5f2b24da4880f768dff)
### [1.0.0](https://git.odit.services/lfk/selfservice/compare/0.11.3...1.0.0) ### [1.0.0](https://git.odit.services/lfk/selfservice/compare/0.11.3...1.0.0)

View File

@ -1,4 +1,4 @@
FROM node:23.4.0-alpine3.20 AS build FROM node:23.3.0-alpine3.20 AS build
# FROM registry.odit.services/hub/library/node:23.3.0-alpine3.20 AS build # FROM registry.odit.services/hub/library/node:23.3.0-alpine3.20 AS build
# ARG NPM_REGISTRY_URL=https://registry.npmjs.org # ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
@ -7,6 +7,7 @@ COPY . .
RUN npm i -g pnpm@9 RUN npm i -g pnpm@9
RUN pnpm i --frozen-lockfile RUN pnpm i --frozen-lockfile
RUN pnpm build RUN pnpm build
RUN pnpm postbuild
# final image # final image
FROM registry.odit.services/library/nginx-brotli:3.15 AS final FROM registry.odit.services/library/nginx-brotli:3.15 AS final

6
env_fix.js Normal file
View File

@ -0,0 +1,6 @@
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

@ -1,20 +1,20 @@
{ {
"name": "@odit/lfk-selfservice", "name": "@odit/lfk-selfservice",
"version": "1.2.7", "version": "1.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"release": "release-it" "release": "release-it",
"postbuild": "node env_fix.js"
}, },
"dependencies": { "dependencies": {
"@fontsource/athiti": "5.2.5", "@fontsource/athiti": "^5.1.0",
"@odit/lfk-client": "^0.0.1", "@tailwindcss/vite": "4.0.0-beta.4",
"@tailwindcss/vite": "4.0.14", "bwip-js": "4.5.1",
"bwip-js": "4.5.2", "marked": "15.0.3",
"marked": "15.0.7",
"redaxios": "0.5.1", "redaxios": "0.5.1",
"tailwindcss": "4.0.14", "tailwindcss": "4.0.0-beta.4",
"toastify-js": "1.12.0", "toastify-js": "1.12.0",
"validator": "13.12.0", "validator": "13.12.0",
"vue": "3.5.13", "vue": "3.5.13",
@ -23,11 +23,11 @@
"vue-toastification": "2.0.0-rc.1" "vue-toastification": "2.0.0-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "5.2.3", "@vitejs/plugin-vue": "5.2.1",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.20",
"release-it": "18.1.2", "release-it": "17.10.0",
"vite": "6.2.2", "vite": "6.0.2",
"vite-plugin-vue-devtools": "7.7.2" "vite-plugin-vue-devtools": "^7.6.7"
}, },
"release-it": { "release-it": {
"git": { "git": {

2342
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
const config = { const config = {
// required // required
documentserver_key: "", documentserver_key: '',
// required, with trailing slash // required, with trailing slash
baseurl: "", baseurl: '',
// optional, full url, will fallback to https://lauf-fuer-kaya.de/impressum
url_imprint: "",
// optional, full url, will fallback to https://lauf-fuer-kaya.de/datenschutz
url_privacy: "",
// full url (including fqdn) // full url (including fqdn)
baseurl_documentserver: "http://localhost:3000", baseurl_documentserver: 'http://localhost:4010/documents',
// optional, will fallback to code128 // optional, will fallback to code128
code_format: "ean13", code_format: 'ean13',
// optional, will fallback to /imprint
url_imprint: '',
// optional, will fallback to /privacy
url_privacy: '',
}; };

1
public/imprint_en.md Normal file
View File

@ -0,0 +1 @@
TODO:

1
public/privacy_en.md Normal file
View File

@ -0,0 +1 @@
TODO:

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,21 +1,26 @@
<template> <template>
<footer class="p-8"> <footer>
<p class="text-sm sm:py-2 sm:mt-0 mt-4 text-center md:text-left"> <div class="container px-5 py-8 mx-auto flex items-center sm:flex-row flex-col">
Lauf für Kaya! Selfservice<br>Copyright © 2025<br>proudly powered by <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">
Lauf für Kaya! Selfservice<br>Copyright © 2024<br>proudly powered by
<a class="underline" target="_blank" rel="noopener,noreferrer" <a class="underline" target="_blank" rel="noopener,noreferrer"
href="https://odit.services?ref=lfk">ODIT.Services</a><br> href="https://odit.services?ref=lfk">ODIT.Services</a>
<a target="_blank" rel="noopener,noreferrer" :href="[[imprint_url]]" class="underline">{{
$t('imprint') }}</a> <a target="_blank" rel="noopener,noreferrer" :href="[[privacy_url]]" class="underline">{{
$t('privacy_policy') }}</a>
</p> </p>
<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">{{
$t('imprint') }}</a>
<a target="_blank" rel="noopener,noreferrer" :href="[[privacy_url]]" class="ml-3 text-gray-400 underline">{{
$t('privacy_policy') }}</a>
</span>
</div>
</footer> </footer>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
imprint_url: config.url_imprint || "https://lauf-fuer-kaya.de/impressum" imprint_url: config.url_imprint || "/imprint"
, privacy_url: config.url_privacy || "https://lauf-fuer-kaya.de/datenschutz" , privacy_url: config.url_privacy || "/privacy"
} }
}, },
} }

View File

@ -1,6 +1,5 @@
{ {
"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 über den Link, den Sie bei der Registrierung erhalten haben.",
"add_sponsoring": "Sponsoring hinzufügen",
"alle_daten_geloescht": "Alle Daten gelöscht!", "alle_daten_geloescht": "Alle Daten gelöscht!",
"already_registered": "bereits registriert...", "already_registered": "bereits registriert...",
"amount_per_kilometer_in_eur": "Betrag pro Kilometer (in €)", "amount_per_kilometer_in_eur": "Betrag pro Kilometer (in €)",
@ -16,9 +15,9 @@
"download_certificate": "Urkunde herunterladen", "download_certificate": "Urkunde herunterladen",
"download_registrationcode": "Registrierungscode herunterladen", "download_registrationcode": "Registrierungscode herunterladen",
"e_mail_adress": "E-Mail Adresse", "e_mail_adress": "E-Mail Adresse",
"e_mail_des_sponsors": "E-Mail des Sponsors", "error-loading-privacy-policy": "Fehler beim Laden der Datenschutzerklärung",
"error_loading_imprint": "Fehler beim Laden des Impressums",
"error_requesting_the_login_link": "Fehler beim Anfordern des Login-Links...", "error_requesting_the_login_link": "Fehler beim Anfordern des Login-Links...",
"first_lap": "👏 erste Runde",
"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.",
@ -32,8 +31,7 @@
"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",
"nachname_des_sponsors": "Nachname des Sponsors", "no_laps_scans_were_recorded_yet": "Es wurden noch keine Runden / Scans aufgezeichnet ...",
"no_laps_scans_were_recorded_yet": "Noch keine Runden aufgezeichnet ...",
"no_sponsorings_for_you_were_recorded_yet": "Es gibt noch keine Sponsorings für dich", "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",
@ -60,20 +58,14 @@
"registrierungscode_generiert": "Registrierungscode generiert!", "registrierungscode_generiert": "Registrierungscode generiert!",
"registrierungscode_wird_generiert": "Registrierungscode wird generiert...", "registrierungscode_wird_generiert": "Registrierungscode wird generiert...",
"resend_the_registration_mail": "Login-Link anfordern", "resend_the_registration_mail": "Login-Link anfordern",
"sponsor_add_agree": "Mit dem Absenden bestätige ich, dass der Sponsor mit der Übermittlung seiner Daten einverstanden ist und ich dessen Berechtigung habe",
"sponsoring": "Sponsoring", "sponsoring": "Sponsoring",
"sponsoring_pro_kilometer_in_eur": "Sponsoring pro Kilometer (in €)",
"strasse": "Straße", "strasse": "Straße",
"telefonnummer_des_sponsors": "Telefonnummer des Sponsors",
"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", "total": "Gesamt",
"total_distance": "Gesamt-Distanz",
"urkunde_generiert": "Urkunde generiert!", "urkunde_generiert": "Urkunde generiert!",
"urkunde_konnte_nicht_generiert_werden": "Urkunde konnte nicht generiert werden...", "urkunde_konnte_nicht_generiert_werden": "Urkunde konnte nicht generiert werden...",
"urkunde_wird_generiert": "Urkunde wird generiert...", "urkunde_wird_generiert": "Urkunde wird generiert...",
"view_my_data": "Meine Läuferdaten einsehen", "view_my_data": "Meine Läuferdaten einsehen",
"vorname": "Vorname", "vorname": "Vorname"
"vorname_des_sponsors": "Vorname des Sponsors",
"z_b_1eur_oder_0_50eur": "z.B. 1€ ODER 0,50€"
} }

View File

@ -1,6 +1,5 @@
{ {
"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.",
"add_sponsoring": "New Sponsoring",
"alle_daten_geloescht": "all data deleted!", "alle_daten_geloescht": "all data deleted!",
"already_registered": "already registered...", "already_registered": "already registered...",
"amount_per_kilometer_in_eur": "Amount per kilometer (in €)", "amount_per_kilometer_in_eur": "Amount per kilometer (in €)",
@ -16,9 +15,9 @@
"download_certificate": "Download certificate", "download_certificate": "Download certificate",
"download_registrationcode": "Download registrationcode", "download_registrationcode": "Download registrationcode",
"e_mail_adress": "mail address", "e_mail_adress": "mail address",
"e_mail_des_sponsors": "E-Mail of the Sponsor", "error-loading-privacy-policy": "Error loading Privacy Policy",
"error_loading_imprint": "Error loading Imprint",
"error_requesting_the_login_link": "Error requesting the login link...", "error_requesting_the_login_link": "Error requesting the login link...",
"first_lap": "👏 first lap",
"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.",
@ -32,7 +31,6 @@
"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",
"nachname_des_sponsors": "last name of the sponsor",
"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...", "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?",
@ -60,20 +58,14 @@
"registrierungscode_generiert": "created registration code!", "registrierungscode_generiert": "created registration code!",
"registrierungscode_wird_generiert": "creating registration code...", "registrierungscode_wird_generiert": "creating registration code...",
"resend_the_registration_mail": "Send me a login link", "resend_the_registration_mail": "Send me a login link",
"sponsor_add_agree": "By submitting, I confirm that the sponsor agrees to the transmission of his data and that I have his authorization",
"sponsoring": "Sponsoring", "sponsoring": "Sponsoring",
"sponsoring_pro_kilometer_in_eur": "Sponsoring per Kilometer (in €)",
"strasse": "Street/ Block", "strasse": "Street/ Block",
"telefonnummer_des_sponsors": "Sponsor's phone number",
"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", "total": "Total",
"total_distance": "total distance",
"urkunde_generiert": "created certificate", "urkunde_generiert": "created certificate",
"urkunde_konnte_nicht_generiert_werden": "could not create your certificate...", "urkunde_konnte_nicht_generiert_werden": "could not create your certificate...",
"urkunde_wird_generiert": "creating certificate...", "urkunde_wird_generiert": "creating certificate...",
"view_my_data": "View my data", "view_my_data": "View my data",
"vorname": "Firstname", "vorname": "Firstname"
"vorname_des_sponsors": "Sponsor's first name",
"z_b_1eur_oder_0_50eur": "e.g. €1 OR €0.50"
} }

View File

@ -1,13 +1,19 @@
import Home from "./views/Home.vue"; import Home from "./views/Home.vue";
import Imprint from "./views/Imprint.vue";
import Privacy from "./views/Privacy.vue";
import Register from "./views/Register.vue"; import Register from "./views/Register.vue";
import Profile from "./views/Profile.vue"; import Profile from "./views/Profile.vue";
import ProfileNone from "./views/ProfileNone.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: "/", component: Home },
{ path: "/imprint", component: Imprint },
{ path: "/imprint/", component: Imprint },
{ path: "/privacy", component: Privacy },
{ path: "/privacy/", component: Privacy },
{ path: "/register", component: Register }, { path: "/register", component: Register },
{ path: "/register/", component: Register }, { path: "/register/", component: Register },
{ path: "/register/:token", component: Register, props: true }, { path: "/register/:token", component: Register, props: true },

View File

@ -1,8 +1 @@
@import "tailwindcss"; @import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
* {
font-family: Athiti;
}

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="bg-cover bg-fixed m-0 h-screen text-white" <div class="bg-cover bg-fixed m-0 h-screen text-white"
v-bind:style='{ backgroundImage: "url(" + background_base64 + ")", }'> v-bind:style='{ backgroundImage: "url(" + background_base64 + ")", }'>
<section class="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-lfk.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"> class="mb-6 text-4xl font-extrabold leading-none tracking-normal md:text-6xl md:tracking-tight font-[Athiti]">
Lauf Für Kaya!<br>2025</h1> Lauf Für Kaya!<br>2025</h1>
<h2 class="mb-6 text-xl font-bold leading-none tracking-normal md:text-3xl md:tracking-tight"> <h2 class="mb-6 text-xl font-bold leading-none tracking-normal md:text-3xl md:tracking-tight font-[Athiti]">
Selfservice Portal</h2> Selfservice Portal</h2>
<p class="px-0 mb-6 text-md lg:px-24 font-medium">{{ $t('main_page_text') }}</p> <p class="px-0 mb-6 text-md lg:px-24 font-medium">{{ $t('main_page_text') }}</p>
<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 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"

40
src/views/Imprint.vue Normal file
View File

@ -0,0 +1,40 @@
<template>
<section class="container px-4 py-24 mx-auto">
<div class="simplecontent">
<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>
</div>
<div class="mx-auto prose" v-html="content"></div>
</div>
</section>
<Footer></Footer>
</template>
<style src="../simple.css"></style>
<script>
import { marked } from "marked";
import Footer from "@/components/Footer.vue";
export default {
components: { Footer },
data() {
return {
content: "",
}
},
async beforeMount() {
const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2);
let md = "";
try {
md = await fetch(`/imprint_${browserlocale}.md`);
} catch (error) {
try {
md = await fetch(`/imprint_en.md`);
} catch (error) {
md = t('error_loading_imprint');
}
}
this.content = marked(await md.text());
},
}
</script>

39
src/views/Privacy.vue Normal file
View File

@ -0,0 +1,39 @@
<template>
<section class="container px-4 py-24 mx-auto">
<div class="simplecontent">
<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('privacy_policy') }}</h1>
</div>
<div class="mx-auto prose" v-html="content"></div>
</div>
</section>
<Footer></Footer>
</template>
<style src="../simple.css"></style>
<script>
import { marked } from "marked";
import Footer from "@/components/Footer.vue";
export default {
components: { Footer },
data() {
return {
content: ""
}
},
async beforeMount() {
const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2);
let md = "";
try {
md = await fetch(`/privacy_${browserlocale}.md`);
} catch (error) {
try {
md = await fetch(`/privacy_en.md`);
} catch (error) {
md = t('error-loading-privacy-policy');
}
}
this.content = marked(await md.text());
},
}
</script>

View File

@ -1,164 +1,306 @@
<template> <template>
<div class="w-full p-4 lg:px-48 xl:w-2/3 xl:mx-auto"> <div class="min-h-screen w-full p-4">
<div class="md:pr-10 md:mb-0 mb-6 pr-0 w-full text-center text-black dark:text-gray-200"> <section class="text-white body-font">
<img src="/favicon-lfk.png" class="h-20 mx-auto" /> <div class="container mx-auto flex items-center md:flex-row flex-col">
<div v-if="loadstate === 'loaded'"> <div class="
<h1 class="text-3xl font-bold whitespace-nowrap" v-text="(state.firstname || '') + 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
">
<p class="text-4xl md:text-3xl font-bold whitespace-nowrap font-[Athiti]" v-text="(state.firstname || '') +
' ' + ' ' +
(state.middlename || '') + (state.middlename || '') +
' ' + ' ' +
(state.lastname || '') (state.lastname || '')
"></h1> "></p>
<p class="text-md whitespace-nowrap">Team: {{ state.group }}</p> <p class="text-md whitespace-nowrap">{{ state.group }}</p>
</div> </div>
<h1 v-else class="text-3xl font-bold whitespace-nowrap">Daten werden geladen...</h1> <div class="inline-flex md:ml-auto md:mr-0 mx-auto items-center">
</div>
<div v-if="loadstate === 'loaded'" class="flex flex-wrap">
<div class="w-full">
<div class="">
<div class="grid grid-cols-3 text-center gap-1">
<button @click="() => {
state.activetab = 'profile';
}
" :class="{
'tab-active font-medium bg-blue-600 hover:bg-blue-700 text-white':
state.activetab === 'profile',
'bg-neutral-200':
state.activetab !== 'profile',
}" class="cursor-pointer rounded-md p-2 py-3 md:py-4 md:px-6 block" type="button">
{{ $t("profile") }}
</button>
<button @click="() => {
state.activetab = 'laptimes';
}
" :class="{
'tab-active font-medium bg-blue-600 hover:bg-blue-700 text-white':
state.activetab === 'laptimes',
'bg-neutral-200':
state.activetab !== 'laptimes',
}" class="cursor-pointer rounded-md p-2 py-3 md:py-4 md:px-6 block" type="button">
{{ $t("lap_times") }}
</button>
<button @click="() => {
state.activetab = 'sponsorings';
}
" :class="{
'tab-active font-medium bg-blue-600 hover:bg-blue-700 text-white':
state.activetab === 'sponsorings',
'bg-neutral-200':
state.activetab !== 'sponsorings',
}" class="cursor-pointer rounded-md p-2 py-3 md:py-4 md:px-6 block" type="button">
{{ $t("sponsoring") }}
</button>
</div>
<div v-if="state.activetab === 'profile'" class="tab-content block">
<div class="w-full mx-auto">
<div class="flex flex-col">
<div class="flex flex-wrap w-full">
<div class="w-full">
<div v-if="state.delete_active === false"> <div v-if="state.delete_active === false">
<button type="button" <button type="button" class="
class="mt-2 focus:border-black focus:ring-2 focus:ring-black text-white text-base font-medium md:text-sm py-3.5 px-5 md:py-2.5 md:px-5 rounded-md bg-blue-600 hover:bg-blue-700 hover:shadow-lg w-full md:w-auto cursor-pointer mb-1 md:mr-1" focus:border-black focus:ring-2 focus:ring-black
@click="get_certificate"> text-white text-base md:text-sm
py-3.5
px-5
md:py-2.5
md:px-5
rounded-md
bg-blue-500
hover:bg-blue-600 hover:shadow-lg
w-full
md:w-auto
mb-1
md:mr-1
" @click="get_registration">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="inline-block"> 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" x2="12" y1="15" y2="3" /> <line x1="12" y1="15" x2="12" y2="3" />
</svg>
{{ $t("download_certificate") }}
</button>
</div>
<div>
<div class="text-lg">{{ $t("registrationcode") }}</div>
<img class="w-full md:w-auto mb-2 bg-white p-2" alt="Registrierungscode" :src="state.barcode" />
<button type="button"
class="focus:border-black focus:ring-2 focus:ring-black text-white text-base font-medium md:text-sm py-3.5 px-5 md:py-2.5 md:px-5 rounded-md bg-blue-600 hover:bg-blue-700 hover:shadow-lg w-full md:w-auto cursor-pointer mb-1 md:mr-1"
@click="get_registration">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="inline-block">
<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" x2="12" y1="15" y2="3" />
</svg> </svg>
{{ $t("download_registrationcode") }} {{ $t("download_registrationcode") }}
</button> </button>
</div> <button type="button" class="
<div class="mb-2"> focus:border-black focus:ring-2 focus:ring-black
<div class="text-lg">{{ $t("e_mail_adress") }}</div> text-white text-base md:text-sm
<p v-text="state.email || '---'" /> py-3.5
</div> px-5
<div class="mb-2"> md:py-2.5
<div class="text-lg">{{ $t("phone_number") }}</div> md:px-5
<p v-text="state.phone || '---'" /> rounded-md
</div> bg-blue-500
</div> hover:bg-blue-600 hover:shadow-lg
</div> w-full
</div> md:w-auto
<div v-if="state.delete_active === true"> mb-1
<button type="button" md:mr-1
class="focus:border-black focus:ring-2 focus:ring-black text-white text-base font-medium md:text-sm py-3.5 px-5 md:py-2.5 md:px-5 rounded-md mb-1 md:mb-auto w-full md:w-auto cursor-pointer bg-blue-600 hover:bg-blue-700 hover:shadow-lg" " @click="get_certificate">
@click="() => {
state.delete_active = false;
}
">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="inline-block"> class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub">
<circle cx="12" cy="12" r="10" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<path d="m4.9 4.9 14.2 14.2" /> <polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg> </svg>
{{ $t("cancel_keep_my_data") }} {{ $t("download_certificate") }}
</button> </button>
<button type="button" <button type="button" class="
class="focus:border-black focus:ring-2 focus:ring-black text-white text-base font-medium md:text-sm py-3.5 px-5 md:py-2.5 md:px-5 rounded-md w-full md:w-auto cursor-pointer bg-red-600 hover:bg-red-700 hover:shadow-lg md:ml-1" focus:border-black focus:ring-2 focus:ring-black
@click="delete_me"> text-white text-base md:text-sm
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" py-3.5
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" px-5
class="inline-block"> md:py-2.5
<path d="M3 6h18" /> md:px-5
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /> rounded-md
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /> bg-red-600
<line x1="10" x2="10" y1="11" y2="17" /> hover:bg-red-700 hover:shadow-lg
<line x1="14" x2="14" y1="11" y2="17" /> w-full
</svg> md:w-auto
{{ $t("confirm_delete_all_of_my_data") }} " @click="() => {
</button>
</div>
<button v-else type="button"
class="focus:border-black focus:ring-2 focus:ring-black text-white text-base font-medium 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 cursor-pointer"
@click="() => {
state.delete_active = true; state.delete_active = true;
} }
"> ">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
class="inline-block"> class="feather feather-download" style="display: inline; height: 1rem; vertical-align: sub">
<path d="M3 6h18" /> <path d="M0 0h24v24H0z" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /> <path fill="currentColor"
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /> 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" />
<line x1="10" x2="10" y1="11" y2="17" />
<line x1="14" x2="14" y1="11" y2="17" />
</svg> </svg>
{{ $t("delete_my_data") }} {{ $t("delete_my_data") }}
</button> </button>
</div> </div>
<div v-if="state.delete_active === true">
<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
mb-1
md:mb-auto
w-full
md:w-auto
bg-blue-500
hover:bg-blue-600 hover:shadow-lg
" @click="() => {
state.delete_active = false;
}
">
<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="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>
{{ $t("cancel_keep_my_data") }}
</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
w-full
md:w-auto
bg-red-600
hover:bg-red-700 hover:shadow-lg
md:ml-1
" @click="delete_me">
<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 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>
{{ $t("confirm_delete_all_of_my_data") }}
</button>
</div>
</div>
</div>
</section>
<div class="flex flex-wrap">
<div class="w-full">
<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-none">
<button @click="() => {
state.activetab = 'profile';
}
" :class="{
'tab-active border-b-2 font-medium border-blue-500':
state.activetab === 'profile',
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("profile") }}
</button>
</div>
<div class="flex-none">
<button @click="() => {
state.activetab = 'laptimes';
}
" :class="{
'tab-active border-b-2 font-medium border-blue-500':
state.activetab === 'laptimes',
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("lap_times") }}
</button>
</div>
<div class="flex-none">
<button @click="() => {
state.activetab = 'sponsorings';
}
" :class="{
'tab-active border-b-2 font-medium border-blue-500':
state.activetab === 'sponsorings',
}" class="tab tab-underline py-4 px-6 block" type="button">
{{ $t("sponsoring") }}
</button>
</div>
</div>
<div v-if="state.activetab === 'profile'" class="tab-content block">
<div class="py-4 w-full">
<div class="flex flex-col">
<form class="form flex flex-wrap w-full">
<div class="w-full">
<div class="form-element">
<div class="text-lg">{{ $t("registrationcode") }}</div>
<img class="w-full md:w-auto mb-2 bg-white p-2" alt="Registrierungscode" :src="state.barcode" />
<div class="text-lg">{{ $t("vorname") }}</div>
<p 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
" v-text="state.firstname || '---'" />
</div>
<div class="form-element">
<div class="text-lg">{{ $t("mittelname") }}</div>
<p 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
" v-text="state.middlename || '---'" />
</div>
<div class="form-element">
<div class="text-lg">{{ $t("nachname") }}</div>
<p 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
" v-text="state.lastname || '---'" />
</div>
<div class="form-element">
<div class="text-lg">{{ $t("e_mail_adress") }}</div>
<p 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
" v-text="state.email || '---'" />
</div>
<div class="form-element">
<div class="text-lg">{{ $t("phone_number") }}</div>
<p 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
" v-text="state.phone || '---'" />
</div>
</div>
</form>
</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="dark:bg-gray-900 body-font"> <section class="text-gray-400 dark:bg-gray-900 body-font">
<div class="mx-auto"> <div class="container mx-auto">
<div class="w-full mx-auto"> <div class="lg:w-2/3 w-full mx-auto overflow-auto">
<div v-if="state.scans.length > 0"> <table v-if="state.scans.length > 0" class="table-auto w-full text-left whitespace-no-wrap">
<p class="mb-2">
{{ $t('total_distance') }}: {{ getReadableDistanceForUI() }}
</p>
<table class="table-auto w-full text-left whitespace-no-wrap">
<thead class=" <thead class="
text-black text-black
bg-gray-300 bg-gray-300
@ -190,21 +332,20 @@
<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_readable"></span> <span v-text="s.distance"></span>
</td> </td>
<td class="px-4 py-3" v-text="s.lapTime_readable"></td> <td class="px-4 py-3" v-text="s.lapTime"></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<div v-else class=" <div v-else class="
text-center text-center
font-medium font-bold
text-black text-black
dark:text-white dark:text-white
text-xl text-2xl
"> ">
<img src="../assets/empty_laps.svg" class="mx-auto h-56" <img src="../assets/empty_laps.svg" style="height: 25rem; margin: 0 auto"
:alt="[[$t('no_laps_scans_were_recorded_yet')]]" /> :alt="[[$t('no_laps_scans_were_recorded_yet')]]" />
{{ $t("no_laps_scans_were_recorded_yet") }} {{ $t("no_laps_scans_were_recorded_yet") }}
</div> </div>
@ -214,82 +355,10 @@
</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 v-if="mode === 'add_sponsoring'">
<h1 class="text-3xl">{{ $t('add_sponsoring') }}</h1>
<form>
<div class="mt-6 grid gap-4 lg:gap-6">
<!-- Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 lg:gap-6">
<div>
<label for="sponsorvorname"
class="block mb-2 text-sm text-gray-700 font-medium dark:text-white">{{
$t('vorname_des_sponsors') }}</label>
<input v-bind="newsponsor_vorname" type="text" name="sponsorvorname" id="sponsorvorname"
placeholder="Vorname des Sponsors"
class="py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg border sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
</div>
<div>
<label for="sponsornachname"
class="block mb-2 text-sm text-gray-700 font-medium dark:text-white">{{
$t('nachname_des_sponsors') }}</label>
<input v-bind="newsponsor_nachname" type="text" name="sponsornachname" id="sponsornachname"
placeholder="Nachname des Sponsors"
class="py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg border sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 lg:gap-6">
<!-- End Grid -->
<div>
<label for="sponsortel" class="block mb-2 text-sm text-gray-700 font-medium dark:text-white">{{
$t('telefonnummer_des_sponsors') }}</label>
<input v-bind="newsponsor_tel" type="tel" name="sponsortel" id="sponsortel" autocomplete="tel"
class="py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg border sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
</div>
<div>
<label for="sponsormail" class="block mb-2 text-sm text-gray-700 font-medium dark:text-white">{{
$t('e_mail_des_sponsors') }}</label>
<input v-bind="newsponsor_mail" type="email" name="sponsormail" id="sponsormail"
class="py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg border sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
</div>
</div>
<div>
<label for="eurokilometer" class="block mb-2 text-sm text-gray-700 font-medium dark:text-white">{{
$t('sponsoring_pro_kilometer_in_eur') }}</label>
<input v-bind="newsponsor_value" type="number" name="eurokilometer" id="eurokilometer"
placeholder="z.B. 1€ ODER 0,50€"
class="py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg border sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600">
</div>
</div>
</form>
<!-- End Grid -->
<!-- Checkbox -->
<div class="mt-3 flex">
<div class="flex">
<input v-model="newsponsor_check" id="sponsor_agree" name="sponsor_agree" type="checkbox"
class="shrink-0 mt-1.5 border-gray-200 rounded-sm text-blue-600 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-800">
</div>
<div class="ms-3">
<label for="sponsor_agree" class="text-sm text-gray-600 dark:text-neutral-400">{{
$t('sponsor_add_agree') }}</label>
</div>
</div>
<!-- End Checkbox -->
<button :disabled="!newsponsor_check" @click="addSponsoring" type="button"
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-teal-100 text-teal-800 hover:bg-teal-200 focus:outline-hidden focus:bg-teal-200 disabled:opacity-50 disabled:pointer-events-none dark:text-teal-500 dark:bg-teal-800/30 dark:hover:bg-teal-800/20 dark:focus:bg-teal-800/20 mt-2 cursor-pointer">
{{ $t('add_sponsoring') }}
</button>
</div>
<div v-else>
<div class="py-4 w-full"> <div class="py-4 w-full">
<section class="dark:bg-gray-900 body-font"> <section class="text-gray-400 dark:bg-gray-900 body-font">
<div class="mx-auto"> <div class="container mx-auto">
<div class="w-full 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"> <table v-if="state.sponsorings.length > 0" class="table-auto w-full text-left whitespace-no-wrap">
<thead class=" <thead class="
text-black text-black
@ -329,10 +398,9 @@
</tr> </tr>
</thead> </thead>
<tbody class="text-gray-900 dark:text-gray-50"> <tbody class="text-gray-900 dark:text-gray-50">
<tr class="odd:bg-white even:bg-gray-100 dark:odd:bg-neutral-900 dark:even:bg-neutral-800" <tr v-for="s in state.sponsorings" :key="s.id">
v-for="s in state.sponsorings" :key="s.id">
<td class="px-4 py-3"> <td class="px-4 py-3">
<span v-text="s.donor.firstname + ' '"></span> <span v-text="s.donor.firstname"></span>
<span v-if="s.donor.middlename"> <span v-if="s.donor.middlename">
<span v-text="s.donor.middlename"></span> <span v-text="s.donor.middlename"></span>
</span> </span>
@ -352,13 +420,7 @@
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot class="text-black <tfoot class="text-gray-900 dark:text-gray-50">
bg-gray-300
border-t-2
border-t-current
dark:text-white
text-sm
dark:bg-gray-800">
<tr> <tr>
<td class="px-4 py-3">{{ $t("total") }}</td> <td class="px-4 py-3">{{ $t("total") }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
@ -394,19 +456,16 @@
</table> </table>
<div v-else class=" <div v-else class="
text-center text-center
font-medium font-bold
text-black text-black
dark:text-white dark:text-white
text-xl text-2xl
"> ">
<img src="../assets/empty_laps.svg" class="h-56 mx-auto" :alt="[ <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')],
]" /> ]" />
{{ $t("no_sponsorings_for_you_were_recorded_yet") }} {{ $t("no_sponsorings_for_you_were_recorded_yet") }}
</div> </div>
<!-- <button
class="py-3 px-4 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-teal-100 text-teal-800 hover:bg-teal-200 focus:outline-hidden focus:bg-teal-200 disabled:opacity-50 disabled:pointer-events-none dark:text-teal-500 dark:bg-teal-800/30 dark:hover:bg-teal-800/20 dark:focus:bg-teal-800/20 mt-2"
@click="mode = 'add_sponsoring'">{{ $t('add_sponsoring') }}</button> -->
</div> </div>
</div> </div>
</section> </section>
@ -421,36 +480,18 @@
</template> </template>
<script setup> <script setup>
import Footer from "@/components/Footer.vue"; import { reactive } from "vue";
import { runnerSelfServiceControllerGet, runnerSelfServiceControllerGetScans, runnerSelfServiceControllerRemove } from "@odit/lfk-client";
import { toCanvas } from "bwip-js";
import axios from "redaxios";
import { reactive, ref } from "vue";
import { useI18n } from 'vue-i18n';
import { TYPE, useToast } from "vue-toastification"; import { TYPE, useToast } from "vue-toastification";
import axios from "redaxios";
import { toCanvas } from "bwip-js";
import Footer from "@/components/Footer.vue";
import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const loadstate = ref("loading")
const mode = ref("")
//
const newsponsor_check = ref(false)
const newsponsor_value = ref("")
const newsponsor_mail = ref("")
const newsponsor_tel = ref("")
const newsponsor_vorname = ref("")
const newsponsor_nachname = ref("")
function shareSponsorLink() {
navigator.share({
title: state.firstname,
text: "Am 23.05.2025 findet der Lauf für Kaya! statt 🏃‍♂️🏃‍♀️\nWerde mein Sponsor beim Lauf für Kaya! 2025 und unterstütze mich pro gelaufenem Kilometer: https://lauf-fuer-kaya.de",
// url: "https://lauf-fuer-kaya.de",
})
}
function textToBase64Barcode(text) { function textToBase64Barcode(text) {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
let codeconfig = { let codeconfig = {
bcid: config.code_format || "code128", bcid: config.code_format || "code39",
text: `${text}`, text: `${text}`,
scale: 3, scale: 3,
includetext: false, includetext: false,
@ -486,24 +527,10 @@ const toast = useToast();
const props = defineProps({ const props = defineProps({
token: String, token: String,
}); });
const accesstoken = props.token; const accesstoken = atob(props.token);
axios
function getReadableDistanceForUI() { .get(`${config.baseurl}api/runners/me/${accesstoken}`)
return state.scans.reduce((accumulator, currentValue) => accumulator + currentValue.distance, 0) .then(({ data }) => {
}
function getReadableDistance(distance) {
const km = Math.floor(distance / 1000)
const m = Math.floor(distance % 1000)
console.log({ km, m });
if (km > 0) {
return `${km},${m} km`
}
return `${m} m`
}
runnerSelfServiceControllerGet({ path: { jwt: accesstoken } }).then(({ data }) => {
loadstate.value = "loaded"
state.phone = data.phone; state.phone = data.phone;
state.email = data.email; state.email = data.email;
state.firstname = data.firstname; state.firstname = data.firstname;
@ -515,24 +542,23 @@ runnerSelfServiceControllerGet({ path: { jwt: accesstoken } }).then(({ data }) =
state.barcode = textToBase64Barcode(data.id ?? "???"); state.barcode = textToBase64Barcode(data.id ?? "???");
}) })
.catch((error) => { .catch((error) => {
loadstate.value = "error"
toast.clear(); toast.clear();
toast.error(t('profil_konnte_nicht_geladen_werden')); toast.error(t('profil_konnte_nicht_geladen_werden'));
}); });
runnerSelfServiceControllerGetScans({ path: { jwt: accesstoken } }).then(({ data }) => { axios
let counter = 0 .get(`${config.baseurl}api/runners/me/${accesstoken}/scans`)
.then(({ data }) => {
data.map(function (s) { data.map(function (s) {
if (counter === 0) { s.lapTime =
s.lapTime_readable = t('first_lap')
} else {
s.lapTime_readable =
Math.floor(s.lapTime / 60) + Math.floor(s.lapTime / 60) +
"min " + "min " +
(Math.floor(s.lapTime % 60) + "").padStart(2, "0") + (Math.floor(s.lapTime % 60) + "").padStart(2, "0") +
"s"; "s";
} s.distance =
s.distance_readable = getReadableDistance(s.distance); Math.floor(s.distance / 1000) +
counter++; "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);
@ -541,27 +567,13 @@ runnerSelfServiceControllerGetScans({ path: { jwt: accesstoken } }).then(({ data
.catch((error) => { .catch((error) => {
toast.error(t('profil_konnte_nicht_geladen_werden')); toast.error(t('profil_konnte_nicht_geladen_werden'));
}); });
function addSponsoring() {
const postdata = {
"receiptNeeded": false,
"firstname": newsponsor_vorname.value,
"middlename": "",
"lastname": newsponsor_nachname.value,
"phone": newsponsor_tel.value,
"email": newsponsor_mail.value,
"address": {}
}
console.log(postdata);
// TODO: implement: donationControllerPostDistance({body:{}})
}
function delete_me() { function delete_me() {
toast.clear(); toast.clear();
toast(t('profil_wird_geloescht')); toast(t('profil_wird_geloescht'));
runnerSelfServiceControllerRemove({ let url = `${config.baseurl}api/runners/me/${accesstoken}?force=true`;
path: { axios
jwt: accesstoken .delete(url)
}, query: { force: true } .then(() => {
}).then(() => {
toast.clear(); toast.clear();
toast(t('alle_daten_geloescht')); toast(t('alle_daten_geloescht'));
location.replace(`/`); location.replace(`/`);
@ -577,36 +589,12 @@ function get_certificate() {
(navigator.languages && navigator.languages[0]) || (navigator.languages && navigator.languages[0]) ||
"" ""
).substr(0, 2); ).substr(0, 2);
let url = `${config.baseurl_documentserver}/v1/pdfs/certificates?key=${config.documentserver_key}`; let url = `${config.baseurl_documentserver}certificates?locale=${browserlocale}&download=true&key=${config.documentserver_key}`;
let postdata = { let postdata = Object.assign({}, state.fullobject);
locale: browserlocale, postdata.group = {
runners: [ name: postdata.group,
{
first_name: state.firstname,
middle_name: state.middlename,
last_name: state.lastname,
id: state.fullobject.id,
distance: state.fullobject.distance,
group: {
name: state.group,
id: state.fullobject.group.id || 0,
},
distance_donations: state.fullobject.distanceDonations.map((s) => {
return {
id: s.id || 0,
amount: s.amount,
amount_per_distance: s.amountPerDistance,
donor: {
id: s.donor.id || 0,
first_name: s.donor.firstname,
middle_name: s.donor.middlename,
last_name: s.donor.lastname,
},
};
}),
}
]
}; };
postdata = [postdata];
axios axios
.post(url, postdata, { .post(url, postdata, {
responseType: "blob", responseType: "blob",
@ -638,13 +626,13 @@ function get_certificate() {
}); });
} }
function get_registration() { function get_registration() {
// toast.clear(); toast.clear();
// toast(t('registrierungscode_wird_generiert')); toast(t('registrierungscode_wird_generiert'));
var a = document.createElement("a"); var a = document.createElement("a");
a.href = state.barcode; a.href = state.barcode;
a.download = `LfK25_Registrierungscode_${state.firstname}_${state.lastname}.png`; a.download = "LfK25_Registrierungscode.png";
a.click(); a.click();
// toast.clear(); toast.clear();
// toast(t('registrierungscode_generiert'), { type: TYPE.SUCCESS }); toast(t('registrierungscode_generiert'), { type: TYPE.SUCCESS });
} }
</script> </script>

View File

@ -2,7 +2,7 @@
<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-lfk.png" alt />
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center">Lauf für Kaya! - {{ <h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center font-[Athiti]">Lauf für Kaya! - {{
$t('profile') $t('profile')
}}</h1> }}</h1>
<p class="mx-auto leading-relaxed text-base text-center"> <p class="mx-auto leading-relaxed text-base text-center">
@ -59,12 +59,12 @@
</template> </template>
<script setup> <script setup>
import Footer from "@/components/Footer.vue"; import { computed, ref, reactive } from "vue";
import { runnerSelfServiceControllerRequestNewToken } from "@odit/lfk-client"; import axios from "redaxios";
import isEmail from 'validator/es/lib/isEmail'; import isEmail from 'validator/es/lib/isEmail';
import { computed, reactive, ref } from "vue";
import { useI18n } from 'vue-i18n';
import { TYPE, useToast } from "vue-toastification"; import { TYPE, useToast } from "vue-toastification";
import Footer from "@/components/Footer.vue";
import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
let user_email = ref(""); let user_email = ref("");
@ -79,7 +79,8 @@ function resendMail() {
if (isEmail(user_email.value)) { if (isEmail(user_email.value)) {
toast(t('login_link_is_requested')); toast(t('login_link_is_requested'));
const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2); const browserlocale = ((navigator.languages && navigator.languages[0]) || '').substr(0, 2);
runnerSelfServiceControllerRequestNewToken({ query: { locale: browserlocale, mail: user_email.value } }).then(({ data }) => { axios.post(`${config.baseurl}api/runners/login?mail=${user_email.value}&locale=${browserlocale}`)
.then(({ data }) => {
console.log(data); console.log(data);
toast(t('login_link_gesendet_an_user_email_value') + user_email.value); toast(t('login_link_gesendet_an_user_email_value') + user_email.value);
}) })

View File

@ -1,6 +1,6 @@
<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" v-if="registrationState === 'registered'">
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6 font-[Athiti]">
<img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt /> <img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt />
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center"> <h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center">
Lauf für Kaya! - {{ $t('registriert') }} Lauf für Kaya! - {{ $t('registriert') }}
@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="min-h-screen flex items-center justify-center" v-else> <div class="min-h-screen flex items-center justify-center" v-else>
<div class="max-w-md w-full py-12 px-6"> <div class="max-w-md w-full py-12 px-6 font-[Athiti]">
<img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt /> <img class="mx-auto h-24 w-auto" src="/favicon-lfk.png" alt />
<h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center"> <h1 class="sm:text-3xl text-2xl font-semibold title-font mb-4 text-center">
Lauf für Kaya! - {{ $t("registrieren") }} Lauf für Kaya! - {{ $t("registrieren") }}
@ -73,6 +73,24 @@
p-2 p-2
" /> " />
<!-- --> <!-- -->
<label for="middle_name" class="block font-medium">{{
$t("mittelname")
}}</label>
<input v-model="userdetails.middlename" name="middlename" id="middle_name" autocomplete="off"
:placeholder="[[$t('mittelname')]]" type="text" 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
" />
<!-- -->
<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>
@ -126,9 +144,9 @@
<input v-model="userdetails.phone" name="phone" id="phone" autocomplete="off" <input v-model="userdetails.phone" name="phone" id="phone" autocomplete="off"
:placeholder="[[$t('phone_number')]]" type="text" :class="{ :placeholder="[[$t('phone_number')]]" type="text" :class="{
'border-red-500': 'border-red-500':
!isPhoneOkay(userdetails.phone), !isMobilePhone(userdetails.phone) && userdetails.phone.trim(),
'border-green-300': 'border-green-300':
isPhoneOkay(userdetails.phone), isMobilePhone(userdetails.phone) && userdetails.phone.trim(),
}" class=" }" class="
dark:bg-gray-800 dark:bg-gray-800
mt-1 mt-1
@ -142,7 +160,7 @@
rounded-md rounded-md
p-2 p-2
" /> " />
<p v-if="!isPhoneOkay(userdetails.phone)" class="text-sm"> <p v-if="!isMobilePhone(userdetails.phone) && userdetails.phone.trim()" class="text-sm">
{{ $t("this_is_not_a_valid_international_phone_number") }} {{ $t("this_is_not_a_valid_international_phone_number") }}
</p> </p>
<!-- --> <!-- -->
@ -317,20 +335,23 @@
</template> </template>
<script setup> <script setup>
import Footer from "@/components/Footer.vue"; import { computed, ref, reactive } from "vue";
import { runnerSelfServiceControllerGetSelfserviceOrg, runnerSelfServiceControllerRegisterOrganizationRunner, runnerSelfServiceControllerRegisterRunner } from "@odit/lfk-client"; 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 { computed, reactive, ref } from "vue";
import { useI18n } from 'vue-i18n';
import { TYPE, useToast } from "vue-toastification"; import { TYPE, useToast } from "vue-toastification";
import Footer from "@/components/Footer.vue";
import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const props = defineProps({ const props = defineProps({
token: String, token: String,
}); });
if (props.token) { if (props.token) {
runnerSelfServiceControllerGetSelfserviceOrg({ path: { token: props.token } }).then(({ data }) => { axios
.get(`${config.baseurl}api/organizations/selfservice/${props.token}`)
.then(({ data }) => {
state.org_name = data.name; state.org_name = data.name;
state.org_teams = data.teams; state.org_teams = data.teams;
org_team.value = data.teams[0]?.id; org_team.value = data.teams[0]?.id;
@ -348,21 +369,6 @@ let userdetails = ref({
phone: "", phone: "",
address: { street: "", address2: "", city: "", zipcode: "" }, address: { street: "", address2: "", city: "", zipcode: "" },
}); });
function isPhoneOkay() {
if (userdetails.value.phone === "") {
return true
}
if (userdetails.value.phone.includes(" ")) {
return false
}
if (!userdetails.value.phone.includes("+")) {
return false
}
if (isMobilePhone(userdetails.value.phone)) {
return true
}
return false
}
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);
@ -376,7 +382,8 @@ const state = reactive({
() => () =>
agb_accepted.value === true && agb_accepted.value === true &&
data_confirmed.value === true && data_confirmed.value === true &&
isPhoneOkay() && (isMobilePhone(userdetails.value.phone) ||
!userdetails.value.phone.trim()) &&
isEmail(userdetails.value.mail) && isEmail(userdetails.value.mail) &&
userdetails.value.firstname && userdetails.value.firstname &&
userdetails.value.lastname && userdetails.value.lastname &&
@ -389,25 +396,25 @@ const state = reactive({
}); });
const toast = useToast(); const toast = useToast();
function login() { function login() {
// userdetails = userdetails.value; userdetails = userdetails.value;
if (isPhoneOkay()) { if (userdetails?.phone === "" || isMobilePhone(userdetails.phone)) {
if (isEmail(userdetails.value.mail)) { if (isEmail(userdetails.mail)) {
let postdata = { let postdata = {
email: userdetails.value.mail, email: userdetails.mail,
firstname: userdetails.value.firstname, firstname: userdetails.firstname,
middlename: userdetails.value.middlename, middlename: userdetails.middlename,
lastname: userdetails.value.lastname, lastname: userdetails.lastname,
address: {}, address: {},
}; };
if (userdetails.value.phone !== "") { if (isMobilePhone(userdetails.phone)) {
postdata.phone = userdetails.value.phone postdata.phone = userdetails.phone;
} }
if (provide_address.value === true) { if (provide_address.value === true) {
postdata.address = { postdata.address = {
address1: userdetails.value.address.street, address1: userdetails.address.street,
address2: userdetails.value.address.address2 || "", address2: userdetails.address.address2 || "",
city: userdetails.value.address.city, city: userdetails.address.city,
postalcode: userdetails.value.address.zipcode, postalcode: userdetails.address.zipcode,
country: "DE", country: "DE",
}; };
} }
@ -419,9 +426,13 @@ function login() {
(navigator.languages && navigator.languages[0]) || (navigator.languages && navigator.languages[0]) ||
"" ""
).substr(0, 2); ).substr(0, 2);
registrationState.value = "loading"; let url = `${config.baseurl}api/runners/register/?locale=${browserlocale}`;
if (props.token) { if (props.token) {
runnerSelfServiceControllerRegisterOrganizationRunner({ path: { token: props.token }, body: postdata, query: { locale: browserlocale } }) url = `${config.baseurl}api/runners/register/${props.token}/?locale=${browserlocale}`;
}
registrationState.value = "loading";
axios
.post(url, postdata)
.then(() => { .then(() => {
registrationState.value = "registered"; registrationState.value = "registered";
}) })
@ -437,24 +448,6 @@ function login() {
}); });
} }
}); });
} else {
runnerSelfServiceControllerRegisterRunner({ body: postdata, query: { locale: browserlocale } })
.then(() => {
registrationState.value = "registered";
})
.catch((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 });
}
});
}
});
}
} }
} }
} }