10bf4b94ad
Large frontend modernization: add route fade transition and hoard-soft-enter keyframes with prefers-reduced-motion support; introduce smoother motion tokens and stronger shadow tokens. Update global CSS to use subtle surface gradients, unified transitions, hover lift effects, focus rings and improved button/card/table/overlay styles. Wrap <router-view> in a transition and adjust brand/logo sizing and interactions. Revamp several pages/components (Home, Login, Impressum, 404, Forbidden) with adjusted typography, animated entry for sections and improved card hover states. Admin/Dashboard pages enhanced: AdminUsers gains stats, mobile card list & computed counts; AdminUserDetail and Dashboard show compact summary cards and updated styles. Documentation updated (style.md, codexInfo.md) to reflect the new modernisation rules. No API or backend changes.
215 lines
5.6 KiB
Vue
215 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { AuthRequestError, changePassword } from '@/services/authSession'
|
|
import { useAppBannersStore } from '@/stores/appBanners'
|
|
|
|
const router = useRouter()
|
|
const appBannersStore = useAppBannersStore()
|
|
|
|
const oldPassword = ref('')
|
|
const newPassword = ref('')
|
|
const newPasswordConfirm = ref('')
|
|
|
|
const showOldPassword = ref(false)
|
|
const showNewPassword = ref(false)
|
|
const showNewPasswordConfirm = ref(false)
|
|
|
|
const isSubmitting = ref(false)
|
|
const errorMessage = ref('')
|
|
|
|
const submitDisabled = computed(() => {
|
|
return (
|
|
isSubmitting.value ||
|
|
oldPassword.value.length === 0 ||
|
|
newPassword.value.length === 0 ||
|
|
newPasswordConfirm.value.length === 0
|
|
)
|
|
})
|
|
|
|
async function handleSubmit() {
|
|
if (submitDisabled.value) {
|
|
errorMessage.value = 'Bitte alle Passwortfelder ausfüllen.'
|
|
return
|
|
}
|
|
|
|
if (newPassword.value !== newPasswordConfirm.value) {
|
|
errorMessage.value = 'Die neuen Passwörter stimmen nicht überein.'
|
|
return
|
|
}
|
|
|
|
isSubmitting.value = true
|
|
errorMessage.value = ''
|
|
|
|
try {
|
|
await changePassword({
|
|
oldPassword: oldPassword.value,
|
|
newPassword: newPassword.value,
|
|
newPasswordConfirm: newPasswordConfirm.value,
|
|
})
|
|
|
|
await router.replace({
|
|
name: 'Login',
|
|
query: { passwordChanged: '1' },
|
|
})
|
|
} catch (error) {
|
|
if (error instanceof AuthRequestError) {
|
|
errorMessage.value = error.message
|
|
|
|
if (error.status === 401 || error.status === 403) {
|
|
appBannersStore.pushError('Session abgelaufen. Bitte erneut anmelden.', 'Passwort ändern')
|
|
await router.replace({ name: 'Login' })
|
|
return
|
|
}
|
|
} else {
|
|
errorMessage.value = 'Passwortänderung fehlgeschlagen. Bitte versuche es erneut.'
|
|
}
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<v-container fluid class="change-password-page hoard-page hoard-page--centered">
|
|
<section class="change-password-shell hoard-panel hoard-shell-grid">
|
|
<header class="change-password-head">
|
|
<p class="hoard-kicker">Sicherheitsvorgabe</p>
|
|
<h1>Passwort ändern</h1>
|
|
<p>Hier kannst du ganz bequem dein Passwort aktualisieren.</p>
|
|
</header>
|
|
|
|
<v-alert
|
|
v-if="errorMessage"
|
|
type="error"
|
|
variant="tonal"
|
|
border="start"
|
|
>
|
|
{{ errorMessage }}
|
|
</v-alert>
|
|
|
|
<v-form class="change-password-form" @submit.prevent="handleSubmit">
|
|
<v-text-field
|
|
v-model="oldPassword"
|
|
label="Altes Passwort"
|
|
:type="showOldPassword ? 'text' : 'password'"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-lock-outline"
|
|
:append-inner-icon="showOldPassword ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
autocomplete="current-password"
|
|
required
|
|
:disabled="isSubmitting"
|
|
@click:append-inner="showOldPassword = !showOldPassword"
|
|
/>
|
|
|
|
<v-divider class="change-password-divider" />
|
|
<p class="change-password-section-label">Neues Passwort</p>
|
|
|
|
<v-text-field
|
|
v-model="newPassword"
|
|
label="Neues Passwort"
|
|
:type="showNewPassword ? 'text' : 'password'"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-lock-reset"
|
|
:append-inner-icon="showNewPassword ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
autocomplete="new-password"
|
|
required
|
|
:disabled="isSubmitting"
|
|
@click:append-inner="showNewPassword = !showNewPassword"
|
|
/>
|
|
|
|
<v-text-field
|
|
v-model="newPasswordConfirm"
|
|
label="Neues Passwort bestätigen"
|
|
:type="showNewPasswordConfirm ? 'text' : 'password'"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-lock-check-outline"
|
|
:append-inner-icon="showNewPasswordConfirm ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
|
|
autocomplete="new-password"
|
|
required
|
|
:disabled="isSubmitting"
|
|
@click:append-inner="showNewPasswordConfirm = !showNewPasswordConfirm"
|
|
/>
|
|
|
|
<p class="change-password-hint">
|
|
Nach erfolgreicher Änderung wirst du automatisch abgemeldet und meldest dich mit dem neuen Passwort wieder an.
|
|
</p>
|
|
|
|
<v-btn
|
|
type="submit"
|
|
color="primary"
|
|
size="large"
|
|
prepend-icon="mdi-content-save-outline"
|
|
:loading="isSubmitting"
|
|
:disabled="submitDisabled"
|
|
block
|
|
>
|
|
Passwort speichern
|
|
</v-btn>
|
|
</v-form>
|
|
</section>
|
|
</v-container>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.change-password-page {
|
|
--hoard-shell-width: min(720px, 100%);
|
|
}
|
|
|
|
.change-password-shell {
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.change-password-head h1,
|
|
.change-password-head p {
|
|
margin: 0;
|
|
}
|
|
|
|
.change-password-head h1 {
|
|
margin-top: var(--space-2);
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.change-password-head p {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.change-password-form {
|
|
display: grid;
|
|
gap: var(--space-3);
|
|
}
|
|
|
|
.change-password-divider {
|
|
margin-top: var(--space-1);
|
|
margin-bottom: 0;
|
|
border-color: color-mix(in srgb, var(--color-border) 82%, var(--color-surface) 18%);
|
|
}
|
|
|
|
.change-password-section-label {
|
|
margin: 0;
|
|
color: var(--color-primary-700);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.change-password-hint {
|
|
margin: 0;
|
|
color: var(--color-text-muted);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.change-password-shell {
|
|
animation: hoard-soft-enter 260ms both;
|
|
}
|
|
}
|
|
|
|
@media (width <= 600px) {
|
|
.change-password-form {
|
|
gap: var(--space-2);
|
|
}
|
|
}
|
|
</style>
|