Phase 1: API + Frontend Skelett
This commit is contained in:
206
www/fahrschuldesk.de/src/views/auth/Register.vue
Normal file
206
www/fahrschuldesk.de/src/views/auth/Register.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-100 to-slate-200 px-4 py-12">
|
||||
<div class="w-full max-w-lg">
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary mb-4">
|
||||
<svg class="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-slate-800">Fahrschule registrieren</h1>
|
||||
<p class="text-slate-500 mt-1">Starten Sie Ihre 14-tägige kostenlose Testphase</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white rounded-2xl shadow-xl p-8">
|
||||
<form @submit.prevent="handleRegister" class="space-y-5">
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">Name der Fahrschule *</label>
|
||||
<input
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="Muster Fahrschule"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
|
||||
:class="{ 'border-red-400': errors.name }"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<p v-if="errors.name" class="mt-1 text-sm text-red-500">{{ errors.name[0] }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">E-Mail *</label>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
placeholder="info@fahrschule.de"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
|
||||
:class="{ 'border-red-400': errors.email }"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<p v-if="errors.email" class="mt-1 text-sm text-red-500">{{ errors.email[0] }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">Telefon</label>
|
||||
<input
|
||||
v-model="form.phone"
|
||||
type="tel"
|
||||
placeholder="+49 123 456789"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">Adresse</label>
|
||||
<textarea
|
||||
v-model="form.address"
|
||||
rows="2"
|
||||
placeholder="Straße, PLZ Ort"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all resize-none"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">Passwort *</label>
|
||||
<input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="Mindestens 8 Zeichen"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
|
||||
:class="{ 'border-red-400': errors.password }"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<p v-if="errors.password" class="mt-1 text-sm text-red-500">{{ errors.password[0] }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Password Confirm -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700 mb-1.5">Passwort bestätigen *</label>
|
||||
<input
|
||||
v-model="form.password_confirmation"
|
||||
type="password"
|
||||
placeholder="Passwort wiederholen"
|
||||
class="w-full px-4 py-2.5 rounded-lg border border-slate-300 focus:ring-2 focus:ring-primary focus:border-primary outline-none transition-all"
|
||||
:disabled="loading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Terms -->
|
||||
<div class="flex items-start gap-3">
|
||||
<input
|
||||
v-model="form.accept_terms"
|
||||
type="checkbox"
|
||||
id="terms"
|
||||
class="mt-1 rounded border-slate-300 text-primary focus:ring-primary"
|
||||
:disabled="loading"
|
||||
/>
|
||||
<label for="terms" class="text-sm text-slate-600">
|
||||
Ich akzeptiere die
|
||||
<a href="#" class="text-primary hover:underline">AGB</a> und
|
||||
<a href="#" class="text-primary hover:underline">Datenschutzerklärung</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if="errorMessage" class="p-3 rounded-lg bg-red-50 text-red-600 text-sm">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<!-- Success -->
|
||||
<div v-if="successMessage" class="p-3 rounded-lg bg-green-50 text-green-600 text-sm">
|
||||
{{ successMessage }}
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading || !form.accept_terms"
|
||||
class="w-full py-3 px-4 bg-primary text-white font-medium rounded-lg hover:bg-primary-600 focus:ring-4 focus:ring-primary-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span v-if="loading" class="flex items-center justify-center gap-2">
|
||||
<svg class="animate-spin h-5 w-5" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
Registrieren...
|
||||
</span>
|
||||
<span v-else>Jetzt kostenlos testen</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="text-center text-sm text-slate-500 mt-6">
|
||||
Bereits registriert?
|
||||
<router-link to="/login" class="text-primary hover:underline">Jetzt anmelden</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
accept_terms: false
|
||||
})
|
||||
|
||||
const errors = reactive({})
|
||||
const errorMessage = ref('')
|
||||
const successMessage = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
async function handleRegister() {
|
||||
errorMessage.value = ''
|
||||
successMessage.value = ''
|
||||
Object.keys(errors).forEach(k => delete errors[k])
|
||||
|
||||
if (form.password !== form.password_confirmation) {
|
||||
errors.password_confirmation = ['Passwörter stimmen nicht überein']
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
const result = await auth.register({
|
||||
name: form.name,
|
||||
email: form.email,
|
||||
phone: form.phone,
|
||||
address: form.address,
|
||||
password: form.password
|
||||
})
|
||||
|
||||
loading.value = false
|
||||
|
||||
if (result.success) {
|
||||
successMessage.value = 'Registrierung erfolgreich! Nach der Freigabe durch einen Admin erhalten Sie eine E-Mail.'
|
||||
setTimeout(() => {
|
||||
router.push('/login')
|
||||
}, 3000)
|
||||
} else {
|
||||
errorMessage.value = result.message
|
||||
if (result.errors) {
|
||||
Object.assign(errors, result.errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user