SEO: - Landing page converted to Server Component (SSR) - Extracted NavAuthButtons + ContactForm as client islands - Removed fake aggregateRating from JSON-LD - Added FAQPage JSON-LD schema (7 questions) - Extended sitemap: /datenschutz, /spenden, /demo Map fixes: - WebGL context lost recovery (black tiles after inactivity) - Page visibility handler for tile reload on tab switch - Arrow direction: geographic bearing instead of screen angle - All markers rotationAlignment viewport->map (geographic orientation) - DEL key now deletes selected lines/polygons/arrows (not just symbols) - Default drawing color: black
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Loader2, Send, Check } from 'lucide-react'
|
|
|
|
export function ContactForm() {
|
|
const [name, setName] = useState('')
|
|
const [email, setEmail] = useState('')
|
|
const [message, setMessage] = useState('')
|
|
const [sending, setSending] = useState(false)
|
|
const [sent, setSent] = useState(false)
|
|
const [error, setError] = useState('')
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setSending(true)
|
|
setError('')
|
|
try {
|
|
const res = await fetch('/api/contact', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, email, message }),
|
|
})
|
|
if (res.ok) {
|
|
setSent(true)
|
|
setName('')
|
|
setEmail('')
|
|
setMessage('')
|
|
} else {
|
|
const data = await res.json()
|
|
setError(data.error || 'Senden fehlgeschlagen')
|
|
}
|
|
} catch {
|
|
setError('Verbindung fehlgeschlagen')
|
|
} finally {
|
|
setSending(false)
|
|
}
|
|
}
|
|
|
|
if (sent) {
|
|
return (
|
|
<div className="text-center bg-green-50 border border-green-200 rounded-xl p-8">
|
|
<Check className="w-10 h-10 text-green-600 mx-auto mb-3" />
|
|
<h3 className="font-semibold text-green-900 text-lg">Nachricht gesendet!</h3>
|
|
<p className="text-green-700 mt-2">Vielen Dank! Ich melde mich so schnell wie möglich.</p>
|
|
<Button variant="outline" className="mt-4" onClick={() => setSent(false)}>
|
|
Weitere Nachricht senden
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="grid sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
|
<input
|
|
type="text"
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
required
|
|
placeholder="Dein Name"
|
|
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">E-Mail</label>
|
|
<input
|
|
type="email"
|
|
value={email}
|
|
onChange={e => setEmail(e.target.value)}
|
|
required
|
|
placeholder="name@feuerwehr.ch"
|
|
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Nachricht</label>
|
|
<textarea
|
|
value={message}
|
|
onChange={e => setMessage(e.target.value)}
|
|
required
|
|
rows={5}
|
|
placeholder="Deine Nachricht..."
|
|
className="w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent resize-none"
|
|
/>
|
|
</div>
|
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
<Button
|
|
type="submit"
|
|
className="bg-red-600 hover:bg-red-700"
|
|
disabled={sending || !name || !email || !message}
|
|
>
|
|
{sending ? (
|
|
<><Loader2 className="w-4 h-4 mr-2 animate-spin" /> Wird gesendet...</>
|
|
) : (
|
|
<><Send className="w-4 h-4 mr-2" /> Nachricht senden</>
|
|
)}
|
|
</Button>
|
|
</form>
|
|
)
|
|
}
|