Add team roles and content management updates
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import PasswordField from '../components/ui/PasswordField.vue'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { ApiRequestError } from '../lib/http'
|
||||
|
||||
@@ -14,7 +15,17 @@ const form = reactive({
|
||||
password: '',
|
||||
})
|
||||
|
||||
const passwordForm = reactive({
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const loginMode = ref<'password' | 'twitch'>('password')
|
||||
const errorMessage = ref('')
|
||||
const successMessage = ref('')
|
||||
|
||||
const passwordChangeRequired = computed(() => Boolean(authStore.session?.mustChangePassword))
|
||||
|
||||
const redirectTarget = computed(() => {
|
||||
const redirect = Array.isArray(route.query.redirect)
|
||||
@@ -30,26 +41,86 @@ const redirectTarget = computed(() => {
|
||||
|
||||
async function submitLogin() {
|
||||
errorMessage.value = ''
|
||||
successMessage.value = ''
|
||||
|
||||
try {
|
||||
await authStore.demoLogin({
|
||||
login: form.login,
|
||||
password: form.password,
|
||||
})
|
||||
} catch (error) {
|
||||
try {
|
||||
await authStore.teamLogin({
|
||||
login: form.login,
|
||||
password: form.password,
|
||||
})
|
||||
} catch (teamError) {
|
||||
if (error instanceof ApiRequestError && error.status === 503) {
|
||||
errorMessage.value = 'Der Demo-Login ist im Backend noch nicht vollständig konfiguriert.'
|
||||
return
|
||||
}
|
||||
|
||||
if (error instanceof ApiRequestError && error.status === 404 && teamError instanceof ApiRequestError && teamError.status === 404) {
|
||||
errorMessage.value = 'Der Team-Login ist auf diesem Deployment nicht aktiviert.'
|
||||
return
|
||||
}
|
||||
|
||||
errorMessage.value = 'Login oder Passwort stimmt nicht.'
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (authStore.session?.mustChangePassword) {
|
||||
passwordForm.currentPassword = form.password
|
||||
form.password = ''
|
||||
successMessage.value = 'Bitte setze jetzt ein eigenes Passwort für diesen Team-Login.'
|
||||
return
|
||||
}
|
||||
|
||||
await router.replace(redirectTarget.value)
|
||||
}
|
||||
|
||||
async function startTwitchLogin() {
|
||||
errorMessage.value = ''
|
||||
successMessage.value = ''
|
||||
|
||||
try {
|
||||
await authStore.startTwitchAuthorization({
|
||||
purpose: 'team-login',
|
||||
returnUrl: redirectTarget.value,
|
||||
})
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error
|
||||
? error.message
|
||||
: 'Twitch-Login konnte nicht gestartet werden.'
|
||||
}
|
||||
}
|
||||
|
||||
async function submitPasswordChange() {
|
||||
errorMessage.value = ''
|
||||
successMessage.value = ''
|
||||
|
||||
if (passwordForm.newPassword.length < 10) {
|
||||
errorMessage.value = 'Das neue Passwort muss mindestens 10 Zeichen lang sein.'
|
||||
return
|
||||
}
|
||||
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
errorMessage.value = 'Die neuen Passwörter stimmen nicht überein.'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await authStore.changePassword({
|
||||
currentPassword: passwordForm.currentPassword,
|
||||
newPassword: passwordForm.newPassword,
|
||||
})
|
||||
|
||||
await router.replace(redirectTarget.value)
|
||||
} catch (error) {
|
||||
if (error instanceof ApiRequestError && error.status === 404) {
|
||||
errorMessage.value = 'Der Demo-Login ist auf diesem Deployment nicht aktiviert.'
|
||||
return
|
||||
}
|
||||
|
||||
if (error instanceof ApiRequestError && error.status === 503) {
|
||||
errorMessage.value = 'Der Demo-Login ist im Backend noch nicht vollständig konfiguriert.'
|
||||
return
|
||||
}
|
||||
|
||||
errorMessage.value = 'Login oder Passwort stimmt nicht.'
|
||||
errorMessage.value = error instanceof ApiRequestError
|
||||
? error.message
|
||||
: 'Das Passwort konnte nicht geändert werden.'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -69,13 +140,35 @@ async function submitLogin() {
|
||||
</RouterLink>
|
||||
|
||||
<section class="login-card" aria-labelledby="login-title">
|
||||
<div class="login-card__eyebrow">Demo Zugang</div>
|
||||
<h1 id="login-title">Admin Login</h1>
|
||||
<div class="login-card__eyebrow">{{ passwordChangeRequired ? 'Erster Login' : 'Team Zugang' }}</div>
|
||||
<h1 id="login-title">{{ passwordChangeRequired ? 'Passwort ändern' : 'Admin Login' }}</h1>
|
||||
<p>
|
||||
Melde dich mit den Demo-Credentials an, um das Admin-Panel für die Präsentation zu öffnen.
|
||||
<template v-if="passwordChangeRequired">
|
||||
Dieser Account wurde mit einem temporären Passwort erstellt. Bitte vergib jetzt ein eigenes Passwort.
|
||||
</template>
|
||||
<template v-else>
|
||||
Melde dich mit Team-Login an oder nutze deinen gebundenen Twitch-Account.
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<form class="login-form" @submit.prevent="submitLogin">
|
||||
<div v-if="!passwordChangeRequired" class="login-mode-toggle" role="tablist" aria-label="Login-Art">
|
||||
<button
|
||||
type="button"
|
||||
:class="loginMode === 'password' ? 'login-mode-toggle__button login-mode-toggle__button--active' : 'login-mode-toggle__button'"
|
||||
@click="loginMode = 'password'"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="loginMode === 'twitch' ? 'login-mode-toggle__button login-mode-toggle__button--active' : 'login-mode-toggle__button'"
|
||||
@click="loginMode = 'twitch'"
|
||||
>
|
||||
Twitch
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form v-if="!passwordChangeRequired && loginMode === 'password'" class="login-form" @submit.prevent="submitLogin">
|
||||
<label>
|
||||
<span>Login</span>
|
||||
<input
|
||||
@@ -89,25 +182,99 @@ async function submitLogin() {
|
||||
|
||||
<label>
|
||||
<span>Passwort</span>
|
||||
<input
|
||||
<PasswordField
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
variant="login"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
placeholder="Demo-Passwort eingeben"
|
||||
>
|
||||
/>
|
||||
</label>
|
||||
|
||||
<p v-if="errorMessage" class="login-form__error" role="alert">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<p v-if="successMessage" class="login-form__success" role="status">
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
|
||||
<button class="login-form__submit" type="submit" :disabled="authStore.loading">
|
||||
<span v-if="authStore.loading">Sternentor öffnet...</span>
|
||||
<span v-else>Admin Panel öffnen</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form v-else-if="!passwordChangeRequired" class="login-form" @submit.prevent="startTwitchLogin">
|
||||
<div class="login-form__oauth-note">
|
||||
<strong>Offizieller Twitch Login</strong>
|
||||
<span>Du wirst zu Twitch weitergeleitet und danach automatisch zurück ins Admin Panel gebracht.</span>
|
||||
</div>
|
||||
|
||||
<p v-if="errorMessage" class="login-form__error" role="alert">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<p v-if="successMessage" class="login-form__success" role="status">
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
|
||||
<button class="login-form__submit" type="submit" :disabled="authStore.loading">
|
||||
<span v-if="authStore.loading">Twitch wird geöffnet...</span>
|
||||
<span v-else>Mit Twitch offiziell anmelden</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form v-else class="login-form" @submit.prevent="submitPasswordChange">
|
||||
<label>
|
||||
<span>Aktuelles Passwort</span>
|
||||
<PasswordField
|
||||
v-model="passwordForm.currentPassword"
|
||||
variant="login"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
placeholder="Temporäres Passwort"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Neues Passwort</span>
|
||||
<PasswordField
|
||||
v-model="passwordForm.newPassword"
|
||||
variant="login"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
minlength="10"
|
||||
placeholder="Mindestens 10 Zeichen"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Passwort wiederholen</span>
|
||||
<PasswordField
|
||||
v-model="passwordForm.confirmPassword"
|
||||
variant="login"
|
||||
autocomplete="new-password"
|
||||
required
|
||||
minlength="10"
|
||||
placeholder="Neues Passwort bestätigen"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<p v-if="errorMessage" class="login-form__error" role="alert">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<p v-if="successMessage" class="login-form__success" role="status">
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
|
||||
<button class="login-form__submit" type="submit" :disabled="authStore.loading">
|
||||
<span v-if="authStore.loading">Passwort wird gespeichert...</span>
|
||||
<span v-else>Passwort speichern</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="login-card__footer">
|
||||
<RouterLink to="/">Zurück zur Landingpage</RouterLink>
|
||||
<span>Demo-Modus</span>
|
||||
@@ -253,6 +420,35 @@ async function submitLogin() {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.login-mode-toggle {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 6px;
|
||||
margin: 24px 0 18px;
|
||||
padding: 6px;
|
||||
border: 1px solid rgba(124, 86, 196, 0.16);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.login-mode-toggle__button {
|
||||
min-height: 42px;
|
||||
border: 0;
|
||||
border-radius: 11px;
|
||||
background: transparent;
|
||||
color: #7c7192;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.login-mode-toggle__button--active {
|
||||
background: #6f4fd1;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 22px rgba(111, 79, 209, 0.24);
|
||||
}
|
||||
|
||||
.login-form label {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
@@ -283,6 +479,23 @@ async function submitLogin() {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.login-form__oauth-note {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(145, 70, 255, 0.16);
|
||||
border-radius: 18px;
|
||||
background: rgba(248, 245, 255, 0.88);
|
||||
color: #6f6685;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.login-form__oauth-note strong {
|
||||
color: #3f3556;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.login-form__error {
|
||||
padding: 13px 15px;
|
||||
border: 1px solid rgba(225, 29, 72, 0.18);
|
||||
@@ -293,6 +506,16 @@ async function submitLogin() {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.login-form__success {
|
||||
padding: 13px 15px;
|
||||
border: 1px solid rgba(22, 163, 74, 0.18);
|
||||
border-radius: 18px;
|
||||
background: rgba(240, 253, 244, 0.9);
|
||||
color: #15803d !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.login-form__submit {
|
||||
min-height: 58px;
|
||||
border: 0;
|
||||
|
||||
Reference in New Issue
Block a user