Refactor app architecture and clean local artifacts

This commit is contained in:
AzuTear
2026-06-24 23:43:14 +02:00
parent 17134b3b82
commit fef1d36fe8
274 changed files with 37724 additions and 6065 deletions
+363
View File
@@ -0,0 +1,363 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { ApiRequestError } from '../lib/http'
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const form = reactive({
login: 'jayuhime_admin',
password: '',
})
const errorMessage = ref('')
const redirectTarget = computed(() => {
const redirect = Array.isArray(route.query.redirect)
? route.query.redirect[0]
: route.query.redirect
if (!redirect || !redirect.startsWith('/') || redirect.startsWith('/login')) {
return '/admin'
}
return redirect
})
async function submitLogin() {
errorMessage.value = ''
try {
await authStore.demoLogin({
login: form.login,
password: form.password,
})
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.'
}
}
</script>
<template>
<main class="login-page">
<div class="login-page__stars" aria-hidden="true">
<span class="login-page__star login-page__star--one"></span>
<span class="login-page__star login-page__star--two"></span>
<span class="login-page__star login-page__star--three"></span>
<span class="login-page__star login-page__star--four"></span>
</div>
<RouterLink to="/" class="login-page__brand" aria-label="Zur Landingpage">
<span class="login-page__brand-icon"></span>
<span>VTuber Star Award</span>
</RouterLink>
<section class="login-card" aria-labelledby="login-title">
<div class="login-card__eyebrow">Demo Zugang</div>
<h1 id="login-title">Admin Login</h1>
<p>
Melde dich mit den Demo-Credentials an, um das Admin-Panel für die Präsentation zu öffnen.
</p>
<form class="login-form" @submit.prevent="submitLogin">
<label>
<span>Login</span>
<input
v-model.trim="form.login"
type="text"
autocomplete="username"
required
placeholder="E-Mail oder Username"
>
</label>
<label>
<span>Passwort</span>
<input
v-model="form.password"
type="password"
autocomplete="current-password"
required
placeholder="Demo-Passwort eingeben"
>
</label>
<p v-if="errorMessage" class="login-form__error" role="alert">
{{ errorMessage }}
</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>
<div class="login-card__footer">
<RouterLink to="/">Zurück zur Landingpage</RouterLink>
<span>Demo-Modus</span>
</div>
</section>
</main>
</template>
<style scoped>
.login-page {
position: relative;
min-height: 100vh;
overflow: hidden;
display: grid;
place-items: center;
padding: 32px 18px;
background:
radial-gradient(circle at 18% 24%, rgba(255, 221, 238, 0.9), transparent 26%),
radial-gradient(circle at 82% 20%, rgba(210, 190, 255, 0.75), transparent 28%),
radial-gradient(circle at 50% 100%, rgba(255, 220, 151, 0.38), transparent 32%),
linear-gradient(145deg, #fbf6ff 0%, #f2e9ff 48%, #fff8f1 100%);
color: #3f3556;
font-family: 'Outfit', sans-serif;
}
.login-page::before {
content: "";
position: absolute;
inset: 11% 8% auto;
height: 330px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(139, 108, 219, 0.14), rgba(231, 177, 62, 0.16), rgba(247, 108, 173, 0.12));
filter: blur(42px);
transform: rotate(-6deg);
}
.login-page__brand {
position: absolute;
top: 28px;
left: 32px;
z-index: 2;
display: inline-flex;
align-items: center;
gap: 10px;
color: #3f3556;
text-decoration: none;
font-family: 'Fredoka', sans-serif;
font-weight: 700;
font-size: 18px;
}
.login-page__brand-icon {
display: inline-grid;
place-items: center;
width: 36px;
height: 36px;
border-radius: 13px;
color: #fff;
background: linear-gradient(135deg, #8b6cdb, #e7b13e);
box-shadow: 0 14px 30px rgba(139, 108, 219, 0.26);
}
.login-page__stars {
position: absolute;
inset: 0;
pointer-events: none;
}
.login-page__star {
position: absolute;
color: rgba(139, 108, 219, 0.7);
text-shadow: 0 0 22px rgba(139, 108, 219, 0.4);
animation: loginTwinkle 3s ease-in-out infinite;
}
.login-page__star--one {
top: 18%;
left: 22%;
font-size: 34px;
}
.login-page__star--two {
top: 21%;
right: 20%;
font-size: 28px;
animation-delay: 0.8s;
}
.login-page__star--three {
bottom: 20%;
left: 18%;
font-size: 22px;
color: rgba(231, 177, 62, 0.72);
animation-delay: 1.3s;
}
.login-page__star--four {
right: 17%;
bottom: 24%;
font-size: 24px;
color: rgba(247, 108, 173, 0.62);
animation-delay: 1.9s;
}
.login-card {
position: relative;
z-index: 1;
width: min(100%, 520px);
padding: 42px;
border: 1px solid rgba(139, 108, 219, 0.18);
border-radius: 38px;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 28px 80px rgba(76, 48, 119, 0.16);
backdrop-filter: blur(22px);
}
.login-card__eyebrow {
color: #8b6cdb;
font-size: 13px;
font-weight: 900;
letter-spacing: 0.22em;
text-transform: uppercase;
}
.login-card h1 {
margin: 10px 0 12px;
font-family: 'Playfair Display', serif;
font-size: clamp(42px, 8vw, 70px);
line-height: 0.95;
color: #3f3556;
}
.login-card p {
margin: 0;
color: #736987;
font-size: 17px;
line-height: 1.65;
}
.login-form {
display: grid;
gap: 18px;
margin-top: 30px;
}
.login-form label {
display: grid;
gap: 9px;
color: #65748b;
font-size: 12px;
font-weight: 900;
letter-spacing: 0.18em;
text-transform: uppercase;
}
.login-form input {
width: 100%;
box-sizing: border-box;
border: 1.5px solid #ded4ff;
border-radius: 22px;
padding: 17px 18px;
background: rgba(255, 255, 255, 0.82);
color: #3f3556;
font: 700 17px 'Outfit', sans-serif;
outline: none;
box-shadow: 0 12px 32px rgba(139, 108, 219, 0.06);
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.login-form input:focus {
border-color: #8b6cdb;
box-shadow: 0 0 0 5px rgba(139, 108, 219, 0.14);
transform: translateY(-1px);
}
.login-form__error {
padding: 13px 15px;
border: 1px solid rgba(225, 29, 72, 0.18);
border-radius: 18px;
background: rgba(255, 241, 242, 0.9);
color: #be123c !important;
font-size: 14px !important;
font-weight: 800;
}
.login-form__submit {
min-height: 58px;
border: 0;
border-radius: 999px;
color: #fff;
cursor: pointer;
font: 900 17px 'Fredoka', sans-serif;
background: linear-gradient(135deg, #7c52dc 0%, #ad42e8 48%, #f76cad 100%);
box-shadow: 0 18px 40px rgba(139, 108, 219, 0.32);
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}
.login-form__submit:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 22px 48px rgba(139, 108, 219, 0.38);
}
.login-form__submit:disabled {
cursor: wait;
opacity: 0.72;
}
.login-card__footer {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 24px;
color: #9286a7;
font-size: 14px;
font-weight: 700;
}
.login-card__footer a {
color: #7355c8;
text-decoration: none;
}
@keyframes loginTwinkle {
0%, 100% {
opacity: 0.45;
transform: scale(0.92) rotate(0deg);
}
50% {
opacity: 1;
transform: scale(1.08) rotate(8deg);
}
}
@media (max-width: 640px) {
.login-page {
place-items: start center;
padding-top: 104px;
}
.login-page__brand {
left: 18px;
top: 20px;
}
.login-card {
padding: 30px 22px;
border-radius: 30px;
}
}
</style>