Refactor app architecture and clean local artifacts
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user