feat(myTischtennis): integrate Playwright for CAPTCHA handling and enhance login form functionality
- Added Playwright as a dependency to handle CAPTCHA challenges during login attempts. - Implemented a new endpoint to retrieve the login form from myTischtennis, parsing necessary fields for user input. - Enhanced the login process to utilize Playwright for browser automation when CAPTCHA is required. - Updated the MyTischtennisDialog component to support local login form submission instead of using an iframe. - Refactored the MyTischtennisController to include proxy functionality for serving resources and handling login submissions. - Improved error handling and user feedback during login attempts, ensuring a smoother user experience.
This commit is contained in:
@@ -126,7 +126,11 @@
|
||||
</div>
|
||||
|
||||
<main class="main-content">
|
||||
<router-view class="content fade-in"></router-view>
|
||||
<router-view v-slot="{ Component }">
|
||||
<div class="content fade-in">
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</router-view>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,18 +6,29 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<!-- Im Login-Modus: Zeige MyTischtennis-Login-Formular in iframe -->
|
||||
<div v-if="loginMode" class="login-iframe-container">
|
||||
<iframe
|
||||
ref="loginIframe"
|
||||
:src="loginUrl"
|
||||
class="login-iframe"
|
||||
@load="onIframeLoad"
|
||||
></iframe>
|
||||
<div v-if="loading" class="iframe-loading">
|
||||
{{ $t('myTischtennisDialog.loadingLoginForm') }}
|
||||
<!-- Im Login-Modus: lokales Formular, Login serverseitig via Playwright-Fallback -->
|
||||
<template v-if="loginMode">
|
||||
<div class="form-group">
|
||||
<label for="mtt-login-email">{{ $t('myTischtennisDialog.email') }}:</label>
|
||||
<input
|
||||
type="email"
|
||||
id="mtt-login-email"
|
||||
v-model="formData.email"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="mtt-login-password">{{ $t('myTischtennisDialog.password') }}:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="mtt-login-password"
|
||||
v-model="formData.password"
|
||||
:placeholder="$t('myTischtennisDialog.passwordPlaceholder')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Im Bearbeiten-Modus: Zeige normales Formular -->
|
||||
<template v-else>
|
||||
@@ -55,7 +66,22 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Update-Checkbox entfernt - automatische Abrufe wurden deaktiviert -->
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="formData.autoUpdateRatings"
|
||||
:disabled="!formData.savePassword"
|
||||
/>
|
||||
<span>{{ $t('myTischtennisDialog.autoUpdateRatings') }}</span>
|
||||
</label>
|
||||
<p class="hint">
|
||||
{{ $t('myTischtennisDialog.autoUpdateRatingsHint') }}
|
||||
</p>
|
||||
<p v-if="formData.autoUpdateRatings && !formData.savePassword" class="warning">
|
||||
{{ $t('myTischtennisDialog.autoUpdateWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" v-if="formData.password">
|
||||
<label for="app-password">{{ $t('myTischtennisDialog.appPassword') }}:</label>
|
||||
@@ -81,6 +107,9 @@
|
||||
<button class="btn-secondary" @click="$emit('close')" :disabled="saving">
|
||||
{{ $t('myTischtennisDialog.cancel') }}
|
||||
</button>
|
||||
<button v-if="loginMode" class="btn-primary" @click="performLogin" :disabled="!canLogin || saving">
|
||||
{{ saving ? $t('myTischtennisDialog.saving') : $t('myTischtennisDialog.login') }}
|
||||
</button>
|
||||
<button v-if="!loginMode" class="btn-primary" @click="saveAccount()" :disabled="!canSave || saving">
|
||||
{{ saving ? $t('myTischtennisDialog.saving') : $t('myTischtennisDialog.save') }}
|
||||
</button>
|
||||
@@ -110,24 +139,16 @@ export default {
|
||||
email: this.account?.email || '',
|
||||
password: '',
|
||||
savePassword: this.account?.savePassword || false,
|
||||
autoUpdateRatings: false, // Automatische Updates deaktiviert
|
||||
autoUpdateRatings: this.account?.autoUpdateRatings || false,
|
||||
userPassword: ''
|
||||
},
|
||||
saving: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
urlCheckInterval: null
|
||||
error: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loginUrl() {
|
||||
// Verwende Backend-Proxy für Login-Seite, damit Cookies im Backend-Kontext bleiben
|
||||
// Verwende absolute URL für iframe
|
||||
const baseUrl = import.meta.env.VITE_BACKEND || window.location.origin;
|
||||
// Füge Token als Query-Parameter hinzu, damit Backend userId extrahieren kann
|
||||
const token = this.$store.state.token;
|
||||
const tokenParam = token ? `?token=${encodeURIComponent(token)}` : '';
|
||||
return `${baseUrl}/api/mytischtennis/login-page${tokenParam}`;
|
||||
canLogin() {
|
||||
return !!this.formData.password;
|
||||
},
|
||||
canSave() {
|
||||
// E-Mail ist erforderlich
|
||||
@@ -135,11 +156,6 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Im Login-Modus: Passwort ist erforderlich
|
||||
if (this.loginMode) {
|
||||
return !!this.formData.password;
|
||||
}
|
||||
|
||||
// Wenn ein Passwort eingegeben wurde, muss auch das App-Passwort eingegeben werden
|
||||
if (this.formData.password && !this.formData.userPassword) {
|
||||
return false;
|
||||
@@ -148,66 +164,26 @@ export default {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.loginMode) {
|
||||
this.loading = true;
|
||||
// URL-Überwachung wird erst gestartet, nachdem das iframe geladen wurde
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
// URL-Überwachung stoppen
|
||||
if (this.urlCheckInterval) {
|
||||
clearInterval(this.urlCheckInterval);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onIframeLoad() {
|
||||
this.loading = false;
|
||||
console.log('[MyTischtennisDialog] Iframe geladen');
|
||||
|
||||
// Starte URL-Überwachung erst NACH dem Laden des iframes
|
||||
// Warte 3 Sekunden, damit der Benutzer Zeit hat, sich einzuloggen
|
||||
setTimeout(() => {
|
||||
this.startUrlMonitoring();
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
checkIframeUrl() {
|
||||
async performLogin() {
|
||||
if (!this.canLogin) return;
|
||||
this.error = null;
|
||||
this.saving = true;
|
||||
try {
|
||||
const iframe = this.$refs.loginIframe;
|
||||
if (!iframe || !iframe.contentWindow) return;
|
||||
|
||||
// Versuche, die URL zu lesen (funktioniert nur bei gleicher Origin)
|
||||
// Da mytischtennis.de eine andere Origin ist, können wir die URL nicht direkt lesen
|
||||
// Stattdessen überwachen wir über PostMessage oder Polling
|
||||
await apiClient.post('/mytischtennis/verify', {
|
||||
password: this.formData.password
|
||||
});
|
||||
this.$emit('logged-in');
|
||||
} catch (error) {
|
||||
// Cross-Origin-Zugriff nicht möglich - das ist normal
|
||||
console.log('[MyTischtennisDialog] Cross-Origin-Zugriff nicht möglich (erwartet)');
|
||||
console.error('Fehler beim Login:', error);
|
||||
this.error = error.response?.data?.error
|
||||
|| error.response?.data?.message
|
||||
|| this.$t('myTischtennisDialog.errorSaving');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
startUrlMonitoring() {
|
||||
// Überwache, ob der Login erfolgreich war
|
||||
// Prüfe, ob bereits eine gültige Session existiert
|
||||
this.urlCheckInterval = setInterval(async () => {
|
||||
try {
|
||||
// Prüfe, ob bereits eine gültige Session existiert
|
||||
// Nach erfolgreichem Login im iframe sollte submitLogin die Session gespeichert haben
|
||||
const sessionResponse = await apiClient.get('/mytischtennis/session');
|
||||
if (sessionResponse.data && sessionResponse.data.session && sessionResponse.data.session.accessToken) {
|
||||
// Session vorhanden - Login erfolgreich!
|
||||
clearInterval(this.urlCheckInterval);
|
||||
this.urlCheckInterval = null;
|
||||
this.$emit('logged-in');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// Noch nicht eingeloggt oder Fehler - ignorieren
|
||||
// (wird alle 3 Sekunden wieder versucht)
|
||||
}
|
||||
}, 3000); // Alle 3 Sekunden prüfen
|
||||
},
|
||||
|
||||
async saveAccount() {
|
||||
if (!this.canSave) return;
|
||||
|
||||
@@ -218,7 +194,7 @@ export default {
|
||||
const payload = {
|
||||
email: this.formData.email,
|
||||
savePassword: this.formData.savePassword,
|
||||
autoUpdateRatings: false // Automatische Updates immer deaktiviert
|
||||
autoUpdateRatings: this.formData.savePassword ? this.formData.autoUpdateRatings : false
|
||||
};
|
||||
|
||||
// Nur password und userPassword hinzufügen, wenn ein Passwort eingegeben wurde
|
||||
@@ -231,7 +207,9 @@ export default {
|
||||
this.$emit('saved');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
this.error = error.response?.data?.message || this.$t('myTischtennisDialog.errorSaving');
|
||||
this.error = error.response?.data?.error
|
||||
|| error.response?.data?.message
|
||||
|| this.$t('myTischtennisDialog.errorSaving');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
@@ -266,33 +244,6 @@ export default {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-iframe-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
min-height: 600px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.iframe-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
|
||||
@@ -155,11 +155,6 @@ if (typeof window !== 'undefined' && (process.env.NODE_ENV === 'development' ||
|
||||
window.setLanguage = setLanguage;
|
||||
window.getCurrentLanguage = getCurrentLanguage;
|
||||
window.getAvailableLanguages = getAvailableLanguages;
|
||||
console.log('🌐 Sprache-Test-Funktionen verfügbar:');
|
||||
console.log(' - setLanguage("de") - Sprache ändern');
|
||||
console.log(' - getCurrentLanguage() - Aktuelle Sprache abrufen');
|
||||
console.log(' - getAvailableLanguages() - Verfügbare Sprachen anzeigen');
|
||||
console.log(' - Oder URL-Parameter verwenden: ?lang=de');
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ $t('auth.login') }}</h2>
|
||||
<form @submit.prevent="executeLogin">
|
||||
<input v-model="email" type="email" :placeholder="$t('auth.email')" required />
|
||||
<input v-model="password" type="password" :placeholder="$t('auth.password')" required />
|
||||
<button type="submit">{{ $t('auth.login') }}</button>
|
||||
</form>
|
||||
<div class="forgot-password-link">
|
||||
<router-link to="/forgot-password">{{ $t('auth.forgotPassword') }}</router-link>
|
||||
<div class="login-page">
|
||||
<div>
|
||||
<h2>{{ $t('auth.login') }}</h2>
|
||||
<form @submit.prevent="executeLogin">
|
||||
<input v-model="email" type="email" :placeholder="$t('auth.email')" required />
|
||||
<input v-model="password" type="password" :placeholder="$t('auth.password')" required />
|
||||
<button type="submit">{{ $t('auth.login') }}</button>
|
||||
</form>
|
||||
<div class="forgot-password-link">
|
||||
<router-link to="/forgot-password">{{ $t('auth.forgotPassword') }}</router-link>
|
||||
</div>
|
||||
<div class="register-link">
|
||||
<p>{{ $t('auth.noAccount') }} <router-link to="/register">{{ $t('auth.register') }}</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="register-link">
|
||||
<p>{{ $t('auth.noAccount') }} <router-link to="/register">{{ $t('auth.register') }}</router-link></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
@@ -34,6 +34,7 @@
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -43,6 +44,11 @@ import InfoDialog from '../components/InfoDialog.vue';
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: {
|
||||
InfoDialog,
|
||||
ConfirmDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dialog States
|
||||
|
||||
@@ -1,77 +1,81 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<h1>{{ $t('myTischtennisAccount.title') }}</h1>
|
||||
|
||||
<div class="account-container">
|
||||
<div v-if="loading" class="loading">{{ $t('myTischtennisAccount.loading') }}</div>
|
||||
<div class="mytt-account-page">
|
||||
<div class="page-container">
|
||||
<h1>{{ $t('myTischtennisAccount.title') }}</h1>
|
||||
|
||||
<div v-else-if="account" class="account-info">
|
||||
<div class="info-section">
|
||||
<h2>{{ $t('myTischtennisAccount.linkedAccount') }}</h2>
|
||||
|
||||
<div class="info-row">
|
||||
<label>{{ $t('myTischtennisAccount.email') }}</label>
|
||||
<span>{{ account.email }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<label>{{ $t('myTischtennisAccount.passwordSaved') }}</label>
|
||||
<span>{{ accountStatus && accountStatus.hasPassword ? $t('myTischtennisAccount.yes') : $t('myTischtennisAccount.no') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.clubId">
|
||||
<label>{{ $t('myTischtennisAccount.club') }}</label>
|
||||
<span>{{ account.clubName }} ({{ account.clubId }}{{ account.fedNickname ? ' - ' + account.fedNickname : '' }})</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginSuccess">
|
||||
<label>{{ $t('myTischtennisAccount.lastSuccessfulLogin') }}</label>
|
||||
<span>{{ formatDate(account.lastLoginSuccess) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginAttempt">
|
||||
<label>{{ $t('myTischtennisAccount.lastLoginAttempt') }}</label>
|
||||
<span>{{ formatDate(account.lastLoginAttempt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn-primary" @click="openEditDialog">{{ $t('myTischtennisAccount.editAccount') }}</button>
|
||||
<button type="button" class="btn-secondary" @click="testConnection">{{ $t('myTischtennisAccount.loginAgain') }}</button>
|
||||
<button class="btn-danger" @click="deleteAccount">{{ $t('myTischtennisAccount.unlinkAccount') }}</button>
|
||||
<div class="account-container">
|
||||
<div v-if="loading" class="loading">{{ $t('myTischtennisAccount.loading') }}</div>
|
||||
|
||||
<div v-else-if="account" class="account-info">
|
||||
<div class="info-section">
|
||||
<h2>{{ $t('myTischtennisAccount.linkedAccount') }}</h2>
|
||||
|
||||
<div class="info-row">
|
||||
<label>{{ $t('myTischtennisAccount.email') }}</label>
|
||||
<span>{{ account.email }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<label>{{ $t('myTischtennisAccount.passwordSaved') }}</label>
|
||||
<span>{{ accountStatus && accountStatus.hasPassword ? $t('myTischtennisAccount.yes') : $t('myTischtennisAccount.no') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.clubId">
|
||||
<label>{{ $t('myTischtennisAccount.club') }}</label>
|
||||
<span>{{ account.clubName }} ({{ account.clubId }}{{ account.fedNickname ? ' - ' + account.fedNickname : '' }})</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginSuccess">
|
||||
<label>{{ $t('myTischtennisAccount.lastSuccessfulLogin') }}</label>
|
||||
<span>{{ formatDate(account.lastLoginSuccess) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginAttempt">
|
||||
<label>{{ $t('myTischtennisAccount.lastLoginAttempt') }}</label>
|
||||
<span>{{ formatDate(account.lastLoginAttempt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn-primary" @click="openEditDialog">{{ $t('myTischtennisAccount.editAccount') }}</button>
|
||||
<button type="button" class="btn-secondary" @click="testConnection" :disabled="verifyingLogin">
|
||||
{{ verifyingLogin ? 'Login wird durchgeführt…' : $t('myTischtennisAccount.loginAgain') }}
|
||||
</button>
|
||||
<button class="btn-danger" @click="deleteAccount">{{ $t('myTischtennisAccount.unlinkAccount') }}</button>
|
||||
</div>
|
||||
<p v-if="loginFeedback.message" class="login-feedback" :class="`login-feedback--${loginFeedback.type}`">
|
||||
{{ loginFeedback.message }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-account">
|
||||
<p>{{ $t('myTischtennisAccount.noAccountLinked') }}</p>
|
||||
<button class="btn-primary" @click="openEditDialog">{{ $t('myTischtennisAccount.linkAccount') }}</button>
|
||||
|
||||
<div v-else class="no-account">
|
||||
<p>{{ $t('myTischtennisAccount.noAccountLinked') }}</p>
|
||||
<button class="btn-primary" @click="openEditDialog">{{ $t('myTischtennisAccount.linkAccount') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>{{ $t('myTischtennisAccount.aboutMyTischtennis') }}</h3>
|
||||
<p>{{ $t('myTischtennisAccount.aboutDescription') }}</p>
|
||||
<ul>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature1') }}</li>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature2') }}</li>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature3') }}</li>
|
||||
</ul>
|
||||
<p><strong>{{ $t('messages.info') }}:</strong> {{ $t('myTischtennisAccount.aboutHint') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>{{ $t('myTischtennisAccount.aboutMyTischtennis') }}</h3>
|
||||
<p>{{ $t('myTischtennisAccount.aboutDescription') }}</p>
|
||||
<ul>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature1') }}</li>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature2') }}</li>
|
||||
<li>{{ $t('myTischtennisAccount.aboutFeature3') }}</li>
|
||||
</ul>
|
||||
<p><strong>{{ $t('messages.info') }}:</strong> {{ $t('myTischtennisAccount.aboutHint') }}</p>
|
||||
</div>
|
||||
<!-- Edit Dialog -->
|
||||
<MyTischtennisDialog
|
||||
v-if="showDialog"
|
||||
:account="account"
|
||||
:login-mode="loginMode"
|
||||
@close="closeDialog"
|
||||
@saved="onAccountSaved"
|
||||
@logged-in="onLoggedIn"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<MyTischtennisDialog
|
||||
v-if="showDialog"
|
||||
:account="account"
|
||||
:login-mode="loginMode"
|
||||
@close="closeDialog"
|
||||
@saved="onAccountSaved"
|
||||
@logged-in="onLoggedIn"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Info Dialog -->
|
||||
<InfoDialog
|
||||
v-model="infoDialog.isOpen"
|
||||
@@ -91,6 +95,7 @@
|
||||
@confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -129,7 +134,12 @@ export default {
|
||||
account: null,
|
||||
accountStatus: null,
|
||||
showDialog: false,
|
||||
loginMode: false
|
||||
loginMode: false,
|
||||
verifyingLogin: false,
|
||||
loginFeedback: {
|
||||
type: '',
|
||||
message: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -181,10 +191,12 @@ export default {
|
||||
console.error('Fehler beim Laden des Accounts:', error);
|
||||
this.account = null;
|
||||
this.accountStatus = null;
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: this.$t('myTischtennisAccount.errorLoadingAccount'),
|
||||
type: 'error'
|
||||
});
|
||||
await this.showInfo(
|
||||
this.$t('messages.error'),
|
||||
this.$t('myTischtennisAccount.errorLoadingAccount'),
|
||||
'',
|
||||
'error'
|
||||
);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -203,25 +215,55 @@ export default {
|
||||
async onAccountSaved() {
|
||||
this.closeDialog();
|
||||
await this.loadAccount();
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: this.$t('myTischtennisAccount.accountSaved'),
|
||||
type: 'success'
|
||||
});
|
||||
await this.showInfo(this.$t('messages.success'), this.$t('myTischtennisAccount.accountSaved'), '', 'success');
|
||||
},
|
||||
|
||||
async onLoggedIn() {
|
||||
this.closeDialog();
|
||||
await this.loadAccount();
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: this.$t('myTischtennisAccount.loginSuccessful'),
|
||||
type: 'success'
|
||||
});
|
||||
await this.showInfo(this.$t('messages.success'), this.$t('myTischtennisAccount.loginSuccessful'), '', 'success');
|
||||
},
|
||||
|
||||
async testConnection() {
|
||||
// Öffne das Login-Dialog mit vorausgefüllter E-Mail
|
||||
this.showDialog = true;
|
||||
this.loginMode = true;
|
||||
if (this.verifyingLogin) return;
|
||||
this.verifyingLogin = true;
|
||||
this.loginFeedback = {
|
||||
type: 'info',
|
||||
message: 'Login wird durchgeführt...'
|
||||
};
|
||||
try {
|
||||
// 1-Klick-Re-Login: zuerst gespeicherte Session/Passwort serverseitig verwenden
|
||||
await apiClient.post('/mytischtennis/verify', {});
|
||||
await this.loadAccount();
|
||||
this.loginFeedback = {
|
||||
type: 'success',
|
||||
message: 'Login erfolgreich.'
|
||||
};
|
||||
await this.showInfo(this.$t('messages.success'), this.$t('myTischtennisAccount.loginSuccessful'), '', 'success');
|
||||
} catch (error) {
|
||||
// Falls gespeicherte Daten nicht ausreichen, Passwort-Dialog öffnen
|
||||
const needsPassword = error?.response?.status === 400;
|
||||
|
||||
if (needsPassword) {
|
||||
this.loginFeedback = {
|
||||
type: 'error',
|
||||
message: 'Bitte Passwort eingeben, um den Login erneut durchzuführen.'
|
||||
};
|
||||
this.showDialog = true;
|
||||
this.loginMode = true;
|
||||
this.verifyingLogin = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const message = getSafeErrorMessage(error, this.$t('myTischtennisAccount.errorLoadingAccount'));
|
||||
this.loginFeedback = {
|
||||
type: 'error',
|
||||
message
|
||||
};
|
||||
await this.showInfo(this.$t('messages.error'), message, '', 'error');
|
||||
} finally {
|
||||
this.verifyingLogin = false;
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAccount() {
|
||||
@@ -406,6 +448,28 @@ h1 {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-secondary:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.login-feedback {
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.login-feedback--info {
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.login-feedback--success {
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.login-feedback--error {
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Fetch Statistics */
|
||||
.fetch-stats-section {
|
||||
margin-top: 2rem;
|
||||
|
||||
@@ -608,13 +608,13 @@ export default {
|
||||
},
|
||||
|
||||
activeAssignmentClassLabel() {
|
||||
if (this.activeAssignmentClassId === undefined) {
|
||||
return this.$t('tournaments.selectClassPrompt');
|
||||
}
|
||||
let label = this.$t('tournaments.selectClassPrompt');
|
||||
if (this.activeAssignmentClassId === null) {
|
||||
return this.$t('tournaments.withoutClass');
|
||||
label = this.$t('tournaments.withoutClass');
|
||||
} else if (this.activeAssignmentClassId !== undefined) {
|
||||
label = this.getClassName(this.activeAssignmentClassId) || this.$t('tournaments.unknown');
|
||||
}
|
||||
return this.getClassName(this.activeAssignmentClassId) || this.$t('tournaments.unknown');
|
||||
return label;
|
||||
},
|
||||
|
||||
canAssignClass() {
|
||||
|
||||
Reference in New Issue
Block a user