Refactor environment configuration for local development; update SMTP settings and add JWT secret, encryption key, and debug options. Enhance Nuxt configuration for development server and runtime settings. Introduce new membership application form with validation and PDF generation functionality. Update footer and navigation components to include new membership links. Revise user and session data in JSON files.
This commit is contained in:
317
pages/cms/mitgliedschaftsantraege.vue
Normal file
317
pages/cms/mitgliedschaftsantraege.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Fixed Header -->
|
||||
<div class="fixed top-16 left-0 right-0 bg-white shadow-sm border-b border-gray-200 z-40">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between py-3 sm:py-4">
|
||||
<h1 class="text-xl sm:text-3xl font-bold text-gray-900">
|
||||
Mitgliedschaftsanträge
|
||||
</h1>
|
||||
<button
|
||||
@click="refreshApplications"
|
||||
:disabled="loading"
|
||||
class="px-3 py-1.5 sm:px-4 sm:py-2 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 text-white text-sm sm:text-base font-medium rounded-lg transition-colors"
|
||||
>
|
||||
{{ loading ? 'Lädt...' : 'Aktualisieren' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="pt-20 sm:pt-24">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="text-center py-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
|
||||
<p class="mt-4 text-gray-600">Lade Anträge...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="applications.length === 0" class="text-center py-12">
|
||||
<div class="text-gray-400 text-6xl mb-4">📋</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Keine Anträge vorhanden</h3>
|
||||
<p class="text-gray-600">Es wurden noch keine Mitgliedschaftsanträge eingereicht.</p>
|
||||
</div>
|
||||
|
||||
<!-- Applications List -->
|
||||
<div v-else class="space-y-6">
|
||||
<div
|
||||
v-for="application in applications"
|
||||
:key="application.id"
|
||||
class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden"
|
||||
>
|
||||
<!-- Application Header -->
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
{{ application.personalData.vorname }} {{ application.personalData.nachname }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600">
|
||||
Eingereicht: {{ formatDate(application.timestamp) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Status Badge -->
|
||||
<span
|
||||
:class="[
|
||||
'px-3 py-1 rounded-full text-sm font-medium',
|
||||
getStatusClass(application.status)
|
||||
]"
|
||||
>
|
||||
{{ getStatusText(application.status) }}
|
||||
</span>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex space-x-2">
|
||||
<button
|
||||
@click="viewApplication(application)"
|
||||
class="px-3 py-1 text-sm bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
Anzeigen
|
||||
</button>
|
||||
<button
|
||||
v-if="application.status === 'pending'"
|
||||
@click="approveApplication(application.id)"
|
||||
class="px-3 py-1 text-sm bg-green-100 hover:bg-green-200 text-green-700 rounded-lg transition-colors"
|
||||
>
|
||||
Genehmigen
|
||||
</button>
|
||||
<button
|
||||
v-if="application.status === 'pending'"
|
||||
@click="rejectApplication(application.id)"
|
||||
class="px-3 py-1 text-sm bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Details -->
|
||||
<div class="px-6 py-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-900 mb-2">Kontaktdaten</h4>
|
||||
<div class="space-y-1 text-sm text-gray-600">
|
||||
<p><strong>E-Mail:</strong> {{ application.personalData.email }}</p>
|
||||
<p v-if="application.personalData.telefon_privat">
|
||||
<strong>Telefon:</strong> {{ application.personalData.telefon_privat }}
|
||||
</p>
|
||||
<p v-if="application.personalData.telefon_mobil">
|
||||
<strong>Mobil:</strong> {{ application.personalData.telefon_mobil }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-gray-900 mb-2">Antragsdetails</h4>
|
||||
<div class="space-y-1 text-sm text-gray-600">
|
||||
<p><strong>Art:</strong> {{ application.metadata.mitgliedschaftsart === 'aktiv' ? 'Aktives Mitglied' : 'Passives Mitglied' }}</p>
|
||||
<p><strong>Volljährig:</strong> {{ application.metadata.isVolljaehrig ? 'Ja' : 'Nein' }}</p>
|
||||
<p><strong>PDF:</strong> {{ application.metadata.pdfGenerated ? 'Generiert' : 'Nicht verfügbar' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Detail Modal -->
|
||||
<div
|
||||
v-if="selectedApplication"
|
||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
|
||||
@click.self="closeModal"
|
||||
>
|
||||
<div class="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto">
|
||||
<div class="px-6 py-4 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
Antrag: {{ selectedApplication.personalData.vorname }} {{ selectedApplication.personalData.nachname }}
|
||||
</h2>
|
||||
<button
|
||||
@click="closeModal"
|
||||
class="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 py-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Personal Data -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Persönliche Daten</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p><strong>Name:</strong> {{ selectedApplication.personalData.vorname }} {{ selectedApplication.personalData.nachname }}</p>
|
||||
<p><strong>E-Mail:</strong> {{ selectedApplication.personalData.email }}</p>
|
||||
<p v-if="selectedApplication.personalData.telefon_privat">
|
||||
<strong>Telefon:</strong> {{ selectedApplication.personalData.telefon_privat }}
|
||||
</p>
|
||||
<p v-if="selectedApplication.personalData.telefon_mobil">
|
||||
<strong>Mobil:</strong> {{ selectedApplication.personalData.telefon_mobil }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Application Details -->
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Antragsdetails</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p><strong>Status:</strong> {{ getStatusText(selectedApplication.status) }}</p>
|
||||
<p><strong>Art:</strong> {{ selectedApplication.metadata.mitgliedschaftsart === 'aktiv' ? 'Aktives Mitglied' : 'Passives Mitglied' }}</p>
|
||||
<p><strong>Volljährig:</strong> {{ selectedApplication.metadata.isVolljaehrig ? 'Ja' : 'Nein' }}</p>
|
||||
<p><strong>Eingereicht:</strong> {{ formatDate(selectedApplication.timestamp) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
@click="closeModal"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<button
|
||||
v-if="selectedApplication.status === 'pending'"
|
||||
@click="approveApplication(selectedApplication.id)"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Genehmigen
|
||||
</button>
|
||||
<button
|
||||
v-if="selectedApplication.status === 'pending'"
|
||||
@click="rejectApplication(selectedApplication.id)"
|
||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const applications = ref([])
|
||||
const loading = ref(false)
|
||||
const selectedApplication = ref(null)
|
||||
|
||||
const loadApplications = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await $fetch('/api/membership/applications')
|
||||
applications.value = response
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Anträge:', error)
|
||||
alert('Fehler beim Laden der Anträge')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const refreshApplications = () => {
|
||||
loadApplications()
|
||||
}
|
||||
|
||||
const viewApplication = (application) => {
|
||||
selectedApplication.value = application
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
selectedApplication.value = null
|
||||
}
|
||||
|
||||
const approveApplication = async (id) => {
|
||||
if (confirm('Antrag genehmigen?')) {
|
||||
try {
|
||||
await $fetch('/api/membership/update-status', {
|
||||
method: 'PUT',
|
||||
body: { id, status: 'approved' }
|
||||
})
|
||||
await loadApplications()
|
||||
alert('Antrag wurde genehmigt')
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Genehmigen:', error)
|
||||
alert('Fehler beim Genehmigen des Antrags')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rejectApplication = async (id) => {
|
||||
if (confirm('Antrag ablehnen?')) {
|
||||
try {
|
||||
await $fetch('/api/membership/update-status', {
|
||||
method: 'PUT',
|
||||
body: { id, status: 'rejected' }
|
||||
})
|
||||
await loadApplications()
|
||||
alert('Antrag wurde abgelehnt')
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Ablehnen:', error)
|
||||
alert('Fehler beim Ablehnen des Antrags')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const getStatusClass = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'bg-yellow-100 text-yellow-800'
|
||||
case 'approved':
|
||||
return 'bg-green-100 text-green-800'
|
||||
case 'rejected':
|
||||
return 'bg-red-100 text-red-800'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
return 'Ausstehend'
|
||||
case 'approved':
|
||||
return 'Genehmigt'
|
||||
case 'rejected':
|
||||
return 'Abgelehnt'
|
||||
default:
|
||||
return 'Unbekannt'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadApplications()
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: 'Mitgliedschaftsanträge - CMS - Harheimer TC',
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,562 @@
|
||||
<template>
|
||||
<div class="min-h-screen">
|
||||
<Membership />
|
||||
<div class="min-h-full py-16 bg-gray-50">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<h1 class="text-4xl sm:text-5xl font-display font-bold text-gray-900 mb-6">
|
||||
Mitgliedschaft
|
||||
</h1>
|
||||
<div class="w-24 h-1 bg-primary-600 mb-8" />
|
||||
|
||||
<!-- Mitgliedschaftspläne (ohne "Noch Fragen" Box) -->
|
||||
<div class="mb-12">
|
||||
<MembershipNoQuestions />
|
||||
</div>
|
||||
|
||||
<!-- Aufnahmeantrag Formular -->
|
||||
<div class="bg-white rounded-xl shadow-lg p-8">
|
||||
<h2 class="text-3xl font-display font-bold text-gray-900 mb-6">
|
||||
Beitrittserklärung
|
||||
</h2>
|
||||
|
||||
<form @submit.prevent="submitForm" class="space-y-8">
|
||||
<!-- Persönliche Daten -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Persönliche Daten
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="nachname" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Nachname
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="nachname"
|
||||
v-model="form.nachname"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="vorname" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Vorname
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="vorname"
|
||||
v-model="form.vorname"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="strasse" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Straße und Hausnummer
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="strasse"
|
||||
v-model="form.strasse"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="plz" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
PLZ
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="plz"
|
||||
v-model="form.plz"
|
||||
type="text"
|
||||
required
|
||||
pattern="[0-9]{5}"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="ort" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Wohnort
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="ort"
|
||||
v-model="form.ort"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="geburtsdatum" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Geburtsdatum
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="geburtsdatum"
|
||||
v-model="form.geburtsdatum"
|
||||
type="date"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="telefon_privat" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Telefon (privat)
|
||||
</label>
|
||||
<input
|
||||
id="telefon_privat"
|
||||
v-model="form.telefon_privat"
|
||||
type="tel"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
E-Mail
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="email"
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="telefon_mobil" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Telefon (Mobil)
|
||||
</label>
|
||||
<input
|
||||
id="telefon_mobil"
|
||||
v-model="form.telefon_mobil"
|
||||
type="tel"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mitgliedschaftsart -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Mitgliedschaftsart
|
||||
</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
v-model="form.mitgliedschaftsart"
|
||||
type="radio"
|
||||
value="aktiv"
|
||||
class="mr-3 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<span class="text-gray-700">Aktives Mitglied</span>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
v-model="form.mitgliedschaftsart"
|
||||
type="radio"
|
||||
value="passiv"
|
||||
class="mr-3 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<span class="text-gray-700">Passives Mitglied</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Beitragszahlung -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Beitragszahlung
|
||||
</h3>
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<p class="text-gray-700 mb-4">
|
||||
Den derzeitigen jährlichen Mitgliedsbeitrag in Höhe von:
|
||||
</p>
|
||||
<ul class="space-y-2 text-gray-700">
|
||||
<li>• € 120,-- (Erwachsene)</li>
|
||||
<li>• € 72,-- (Jugendliche bis zum vollendeten 18. Lebensjahr)</li>
|
||||
<li>• € 30,-- (passive Mitglieder)</li>
|
||||
</ul>
|
||||
<p class="text-gray-700 mt-4">
|
||||
bitte ich per Lastschrift jährlich von meinem Konto einzuziehen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input
|
||||
v-model="form.lastschrift_erlaubt"
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<div>
|
||||
<span class="text-gray-700">
|
||||
Hierzu erteile ich das beigefügte SEPA-Lastschriftmandat.
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 italic mt-1">Pflichtfeld</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Bankdaten -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Bankdaten für SEPA-Lastschrift
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<label for="kontoinhaber" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Kontoinhaber (Vorname und Name)
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="kontoinhaber"
|
||||
v-model="form.kontoinhaber"
|
||||
type="text"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="iban" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
IBAN
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 italic mb-2">Pflichtfeld</p>
|
||||
<input
|
||||
id="iban"
|
||||
v-model="form.iban"
|
||||
type="text"
|
||||
required
|
||||
placeholder="DE89 3704 0044 0532 0130 00"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="bic" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
BIC
|
||||
</label>
|
||||
<input
|
||||
id="bic"
|
||||
v-model="form.bic"
|
||||
type="text"
|
||||
placeholder="COBADEFFXXX"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="bank" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Kreditinstitut
|
||||
</label>
|
||||
<input
|
||||
id="bank"
|
||||
v-model="form.bank"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datenschutz -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Datenschutz und Einverständniserklärung
|
||||
</h3>
|
||||
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<p class="text-sm text-gray-700 mb-4">
|
||||
Der Vereinsvorstand weist darauf hin, dass ausreichende technische Maßnahmen zur Gewährleistung des Datenschutzes getroffen wurden. Dennoch kann bei einer Veröffentlichung von personenbezogenen Mitgliederdaten im Internet ein umfassender Datenschutz nicht garantiert werden.
|
||||
</p>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input
|
||||
v-model="form.datenschutz_einverstanden"
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm text-gray-700">
|
||||
Ich bestätige das Vorstehende zur Kenntnis genommen zu haben und willige ein, dass der Harheimer Tischtennis-Club 1954 e.V. allgemeine Daten zu meiner Person (Name, Fotografien, Mannschaft, Leistungsergebnisse, Turnierteilnahmen, Lizenzen u.ä.) auf der Homepage des Vereins veröffentlichen darf.
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 italic mt-1">Pflichtfeld</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vereinssatzung -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-xl font-semibold text-gray-900 border-b border-gray-200 pb-2">
|
||||
Vereinssatzung
|
||||
</h3>
|
||||
|
||||
<label class="flex items-start">
|
||||
<input
|
||||
v-model="form.satzung_anerkannt"
|
||||
type="checkbox"
|
||||
required
|
||||
class="mr-3 mt-1 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<div>
|
||||
<span class="text-gray-700">
|
||||
Ich erkenne die Vereinssatzung (erhältlich beim Vorstand bzw. auf der Vereinshomepage) an.
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 italic mt-1">Pflichtfeld</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Hinweise -->
|
||||
<div class="bg-yellow-50 p-4 rounded-lg border border-yellow-200">
|
||||
<h4 class="font-semibold text-gray-900 mb-2">Wichtige Hinweise:</h4>
|
||||
<ul class="text-sm text-gray-700 space-y-1">
|
||||
<li>• Die Mitgliedschaft im Harheimer Tischtennis-Club erlangt erst nach Bestätigung durch den Vorstand Wirksamkeit.</li>
|
||||
<li>• Die Beitragspflicht beginnt mit dem darauf folgenden Monat.</li>
|
||||
<li>• Sie können Ihre Einwilligung zur Datenveröffentlichung jederzeit widerrufen.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Submit Buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center pt-6">
|
||||
<button
|
||||
type="button"
|
||||
@click="fillWithDummyData"
|
||||
:disabled="isGenerating"
|
||||
class="px-6 py-3 bg-gray-600 hover:bg-gray-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
Mit Testdaten füllen
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!isFormValid || isGenerating"
|
||||
class="px-8 py-3 bg-primary-600 hover:bg-primary-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-semibold rounded-lg transition-colors flex items-center justify-center"
|
||||
>
|
||||
<svg v-if="isGenerating" class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
{{ isGenerating ? 'Formular wird erstellt...' : 'Beitrittsformular erstellen' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Noch Fragen Box -->
|
||||
<div class="mt-12 bg-gradient-to-r from-primary-600 to-primary-700 rounded-2xl p-8 sm:p-12 text-center">
|
||||
<h3 class="text-3xl font-display font-bold text-white mb-4">
|
||||
Noch Fragen zur Mitgliedschaft?
|
||||
</h3>
|
||||
<p class="text-xl text-primary-100 mb-6">
|
||||
Kontaktieren Sie uns - wir beraten Sie gerne persönlich
|
||||
</p>
|
||||
<NuxtLink
|
||||
to="/kontakt"
|
||||
class="inline-flex items-center px-8 py-4 bg-white text-primary-600 font-semibold rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Jetzt Kontakt aufnehmen
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Membership from '~/components/Membership.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const form = ref({
|
||||
// Persönliche Daten
|
||||
nachname: '',
|
||||
vorname: '',
|
||||
strasse: '',
|
||||
plz: '',
|
||||
ort: '',
|
||||
geburtsdatum: '',
|
||||
telefon_privat: '',
|
||||
email: '',
|
||||
telefon_mobil: '',
|
||||
|
||||
// Mitgliedschaftsart
|
||||
mitgliedschaftsart: 'aktiv',
|
||||
|
||||
// Bankdaten
|
||||
kontoinhaber: '',
|
||||
iban: '',
|
||||
bic: '',
|
||||
bank: '',
|
||||
|
||||
// Einverständnisse
|
||||
lastschrift_erlaubt: false,
|
||||
datenschutz_einverstanden: false,
|
||||
satzung_anerkannt: false
|
||||
})
|
||||
|
||||
const isGenerating = ref(false)
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return form.value.nachname &&
|
||||
form.value.vorname &&
|
||||
form.value.strasse &&
|
||||
form.value.plz &&
|
||||
form.value.ort &&
|
||||
form.value.geburtsdatum &&
|
||||
form.value.email &&
|
||||
form.value.mitgliedschaftsart &&
|
||||
form.value.kontoinhaber &&
|
||||
form.value.iban &&
|
||||
form.value.lastschrift_erlaubt &&
|
||||
form.value.datenschutz_einverstanden &&
|
||||
form.value.satzung_anerkannt
|
||||
})
|
||||
|
||||
const isVolljaehrig = computed(() => {
|
||||
if (!form.value.geburtsdatum) return true // Default zu volljährig
|
||||
|
||||
const heute = new Date()
|
||||
const geburtsdatum = new Date(form.value.geburtsdatum)
|
||||
const alter = heute.getFullYear() - geburtsdatum.getFullYear()
|
||||
const monatDiff = heute.getMonth() - geburtsdatum.getMonth()
|
||||
|
||||
if (monatDiff < 0 || (monatDiff === 0 && heute.getDate() < geburtsdatum.getDate())) {
|
||||
return alter - 1 >= 18
|
||||
}
|
||||
|
||||
return alter >= 18
|
||||
})
|
||||
|
||||
const fillWithDummyData = () => {
|
||||
// Dummy-Daten für Testzwecke
|
||||
form.value = {
|
||||
// Persönliche Daten
|
||||
nachname: 'Mustermann',
|
||||
vorname: 'Max',
|
||||
strasse: 'Musterstraße 123',
|
||||
plz: '60437',
|
||||
ort: 'Frankfurt am Main',
|
||||
geburtsdatum: '1990-05-15', // Volljährig
|
||||
telefon_privat: '069 12345678',
|
||||
email: 'max.mustermann@example.com',
|
||||
telefon_mobil: '0171 1234567',
|
||||
|
||||
// Mitgliedschaftsart
|
||||
mitgliedschaftsart: 'aktiv',
|
||||
|
||||
// Bankdaten
|
||||
kontoinhaber: 'Max Mustermann',
|
||||
iban: 'DE89 3704 0044 0532 0130 00',
|
||||
bic: 'COBADEFFXXX',
|
||||
bank: 'Commerzbank AG',
|
||||
|
||||
// Einverständnisse
|
||||
lastschrift_erlaubt: true,
|
||||
datenschutz_einverstanden: true,
|
||||
satzung_anerkannt: true
|
||||
}
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (!isFormValid.value) return
|
||||
|
||||
isGenerating.value = true
|
||||
|
||||
try {
|
||||
const response = await $fetch('/api/membership/generate-pdf', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
...form.value,
|
||||
isVolljaehrig: isVolljaehrig.value
|
||||
}
|
||||
})
|
||||
|
||||
if (response.success) {
|
||||
// PDF herunterladen über geschützten Endpoint
|
||||
try {
|
||||
const downloadResponse = await $fetch(response.downloadUrl, {
|
||||
method: 'GET',
|
||||
responseType: 'blob'
|
||||
})
|
||||
|
||||
// Blob zu Download-Link konvertieren
|
||||
const blob = new Blob([downloadResponse], {
|
||||
type: 'application/pdf'
|
||||
})
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `Beitrittserklärung_${form.value.nachname}_${form.value.vorname}.pdf`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
// Erfolgsmeldung
|
||||
alert('Beitrittsformular wurde erfolgreich erstellt und per E-Mail an den Vorstand/Trainer gesendet!')
|
||||
} catch (downloadError) {
|
||||
console.error('Download-Fehler:', downloadError)
|
||||
alert('Formular wurde erfolgreich erstellt und per E-Mail an den Vorstand/Trainer gesendet!\n\nFalls der Download fehlschlägt, können Sie das Formular auch später über den Link in der E-Mail herunterladen.')
|
||||
}
|
||||
|
||||
// Formular zurücksetzen
|
||||
form.value = {
|
||||
nachname: '',
|
||||
vorname: '',
|
||||
strasse: '',
|
||||
plz: '',
|
||||
ort: '',
|
||||
geburtsdatum: '',
|
||||
telefon_privat: '',
|
||||
email: '',
|
||||
telefon_mobil: '',
|
||||
mitgliedschaftsart: 'aktiv',
|
||||
kontoinhaber: '',
|
||||
iban: '',
|
||||
bic: '',
|
||||
bank: '',
|
||||
lastschrift_erlaubt: false,
|
||||
datenschutz_einverstanden: false,
|
||||
satzung_anerkannt: false
|
||||
}
|
||||
} else {
|
||||
alert('Fehler beim Erstellen des Formulars: ' + (response.error || 'Unbekannter Fehler'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler:', error)
|
||||
alert('Fehler beim Erstellen des Formulars. Bitte versuchen Sie es erneut.')
|
||||
} finally {
|
||||
isGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: 'Mitgliedschaft - Harheimer TC',
|
||||
})
|
||||
</script>
|
||||
|
||||
</script>
|
||||
Reference in New Issue
Block a user