Files
gartenmanager/frontend/src/views/LoginView.vue
Faultier314 834a3bf4d5 feat: Phase 1 complete – full working application
Backend (FastAPI):
- REST API: auth, plants, beds, plantings
- CRUD layer with CRUDBase
- Pydantic v2 schemas for all entities
- Alembic migration: complete schema + all enums
- Seed data: 28 global plants + 15 compatibilities

Frontend (Vue 3 + PrimeVue):
- Axios client with JWT interceptor + auto-refresh
- Pinia stores: auth, beds, plants
- Views: Login, Beds, BedDetail, PlantLibrary
- Components: AppLayout, BedForm, PlantingForm, PlantForm

Docker:
- docker-compose.yml (production)
- docker-compose.dev.yml (development with hot-reload)
- Nginx config with SPA fallback + API proxy
- Multi-stage frontend Dockerfile
- .env.example, .gitignore

Version: 1.0.0-alpha
2026-04-06 07:45:00 +02:00

105 lines
2.9 KiB
Vue

<template>
<div class="login-wrapper">
<div class="login-card">
<div class="login-header">
<i class="pi pi-leaf" style="font-size: 2.5rem; color: var(--green-500)" />
<h1>Gartenmanager</h1>
<p>Bitte melden Sie sich an</p>
</div>
<form @submit.prevent="handleLogin">
<div class="field">
<label for="email">E-Mail</label>
<InputText
id="email"
v-model="form.email"
type="email"
autocomplete="email"
:disabled="loading"
required
class="w-full"
/>
</div>
<div class="field">
<label for="password">Passwort</label>
<Password
id="password"
v-model="form.password"
:feedback="false"
toggle-mask
:disabled="loading"
required
class="w-full"
input-class="w-full"
/>
</div>
<Message v-if="errorMsg" severity="error" :closable="false">{{ errorMsg }}</Message>
<Button
type="submit"
label="Anmelden"
icon="pi pi-sign-in"
:loading="loading"
class="w-full mt-2"
/>
</form>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import Button from 'primevue/button'
import Message from 'primevue/message'
const auth = useAuthStore()
const router = useRouter()
const route = useRoute()
const form = ref({ email: '', password: '' })
const loading = ref(false)
const errorMsg = ref('')
async function handleLogin() {
loading.value = true
errorMsg.value = ''
try {
await auth.login(form.value.email, form.value.password)
const redirect = route.query.redirect || '/beete'
router.push(redirect)
} catch (err) {
errorMsg.value = err.response?.data?.detail || 'Anmeldung fehlgeschlagen.'
} finally {
loading.value = false
}
}
</script>
<style scoped>
.login-wrapper {
min-height: 100vh; display: flex;
align-items: center; justify-content: center;
background: var(--surface-ground);
}
.login-card {
background: var(--surface-card);
border-radius: 12px;
padding: 2.5rem;
width: 100%; max-width: 420px;
box-shadow: 0 4px 24px rgba(0,0,0,.1);
}
.login-header { text-align: center; margin-bottom: 2rem; }
.login-header h1 { font-size: 1.6rem; font-weight: 700; color: var(--green-700); margin: 0.5rem 0 0.25rem; }
.login-header p { color: var(--text-color-secondary); font-size: 0.9rem; }
.field { margin-bottom: 1.25rem; display: flex; flex-direction: column; gap: 0.4rem; }
label { font-size: 0.875rem; font-weight: 600; color: var(--text-color); }
.w-full { width: 100%; }
.mt-2 { margin-top: 0.5rem; }
</style>