Files
Webklar/src/pages/Contact.tsx
T
rootandClaude Opus 5.5 cbc149f9f8 Kontaktformular: Anfragen über die Kontakt-API, damit WEBklar per Mail davon erfährt
Bisher schrieb das Formular als Gast direkt in Appwrite, und niemand las die
Einträge. Jetzt geht es an POST /api/contact (webklar-webclaw-contact-api):
die API speichert in Appwrite und meldet jede Anfrage sofort an
support@webklar.com, Antworten gehen an die Absenderadresse.

- src/lib/contact.ts ersetzt src/lib/appwrite.ts, kein Appwrite-SDK mehr im Bundle
- Honeypot-Feld "website" (unsichtbar, nicht per Tab erreichbar)
- maxLength passend zu den Grenzen der API (120/200/200/4000)
- eigener Hinweis bei 429 (zu viele Anfragen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:53:02 +00:00

210 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useRef, useState } from "react";
import { usePageMeta } from "@/hooks/use-page-meta";
import { CONTACT_LIMITS, ContactSendError, sendContactMessage } from "@/lib/contact";
import "./unterseiten.css";
type Rueckmeldung = { art: "erfolg" | "fehler"; titel: string; text: string };
const Contact = () => {
usePageMeta(
"Projekt besprechen",
"Beschreiben Sie Ihr Vorhaben in zwei Sätzen. WEBklar meldet sich spätestens am nächsten Werktag."
);
const [isSubmitting, setIsSubmitting] = useState(false);
const [rueckmeldung, setRueckmeldung] = useState<Rueckmeldung | null>(null);
const statusRef = useRef<HTMLDivElement>(null);
const [formData, setFormData] = useState({
name: "",
email: "",
company: "",
message: "",
website: "",
});
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isSubmitting) return;
setIsSubmitting(true);
setRueckmeldung(null);
try {
await sendContactMessage(formData);
setRueckmeldung({
art: "erfolg",
titel: "Nachricht gesendet.",
text: "Wir melden uns spätestens am nächsten Werktag bei Ihnen.",
});
setFormData({ name: "", email: "", company: "", message: "", website: "" });
} catch (fehler) {
setRueckmeldung({
art: "fehler",
titel: "Die Nachricht konnte nicht gesendet werden.",
text:
fehler instanceof ContactSendError && fehler.tooMany
? "Es kamen gerade zu viele Anfragen. Bitte versuchen Sie es in ein paar Minuten noch einmal oder schreiben Sie uns direkt per E-Mail."
: "Bitte versuchen Sie es noch einmal oder schreiben Sie uns direkt per E-Mail.",
});
} finally {
setIsSubmitting(false);
// Rückmeldung ins Bild holen, falls sie unter dem Bildschirmrand erscheint
requestAnimationFrame(() => statusRef.current?.scrollIntoView?.({ block: "nearest" }));
}
};
return (
<div className="wk-seite">
<div className="wk-container">
<div className="wk-seite__kopf">
<h1 className="wk-seite__titel wk-seite__titel--gross">Projekt besprechen</h1>
<p className="wk-seite__lead">
Zwei Sätze genügen. Wir melden uns spätestens am nächsten Werktag. Im ersten Gespräch klären wir
in&nbsp;15&nbsp;Minuten, ob und wie wir helfen können – kostenlos und unverbindlich.
</p>
</div>
<div className="wk-kontakt-seite__raster">
<form onSubmit={handleSubmit} className="wk-form" aria-labelledby="kontakt-formular-titel">
<h2 id="kontakt-formular-titel" className="wk-visually-hidden">
Kontaktformular
</h2>
<div className="wk-form__paar">
<div className="wk-feld">
<label htmlFor="name">Name</label>
<input
id="name"
name="name"
type="text"
required
autoComplete="name"
maxLength={CONTACT_LIMITS.name}
value={formData.name}
onChange={handleChange}
placeholder="Ihr Name"
/>
</div>
<div className="wk-feld">
<label htmlFor="email">E-Mail</label>
<input
id="email"
name="email"
type="email"
required
autoComplete="email"
maxLength={CONTACT_LIMITS.email}
value={formData.email}
onChange={handleChange}
placeholder="ihre@email.de"
/>
</div>
</div>
<div className="wk-feld">
<label htmlFor="company">
Unternehmen <span className="wk-feld__optional">(optional)</span>
</label>
<input
id="company"
name="company"
type="text"
autoComplete="organization"
maxLength={CONTACT_LIMITS.company}
value={formData.company}
onChange={handleChange}
placeholder="Name Ihres Betriebs"
/>
</div>
<div className="wk-feld">
<label htmlFor="message">Nachricht</label>
<textarea
id="message"
name="message"
required
maxLength={CONTACT_LIMITS.message}
value={formData.message}
onChange={handleChange}
placeholder="z. B. neue Website, Online-Shop, Web-App oder Automatisierung …"
/>
</div>
{/* Honeypot gegen Bots: für Menschen unsichtbar und nicht erreichbar, die Kontakt-API verwirft
Anfragen, in denen es ausgefüllt ist */}
<div className="wk-visually-hidden" aria-hidden="true">
<label htmlFor="website">Website (bitte leer lassen)</label>
<input
id="website"
name="website"
type="text"
tabIndex={-1}
autoComplete="off"
value={formData.website}
onChange={handleChange}
/>
</div>
<div className="wk-form__senden">
<button type="submit" className="wk-btn wk-btn--primary" aria-disabled={isSubmitting}>
{isSubmitting ? "Wird gesendet …" : "Nachricht senden"}
</button>
</div>
{/* Rückmeldung für Screenreader: der Bereich steht immer im Dokument, nur der Inhalt wechselt */}
<div ref={statusRef} className="wk-form__status" aria-live="polite" aria-atomic="true" data-art={rueckmeldung?.art}>
{rueckmeldung && (
<>
<strong>{rueckmeldung.titel}</strong>
<span>{rueckmeldung.text}</span>
</>
)}
</div>
<p className="wk-form__datenschutz wk-muted wk-small">
Wir verwenden Ihre Angaben nur, um Ihre Anfrage zu beantworten. Mehr dazu in unserer{" "}
<a
href="https://project.webklar.com/datenschutz.html"
target="_blank"
rel="noopener noreferrer"
>
Datenschutzerklärung
</a>
.
</p>
</form>
<div className="wk-direkt">
<h2>Direkt erreichen</h2>
<dl>
<div>
<dt>E-Mail</dt>
<dd>
<a href="mailto:support@webklar.com">support@webklar.com</a>
</dd>
</div>
<div>
<dt>Telefon</dt>
<dd>
<a href="tel:+4917623726355" className="wk-num">
0176 23726355
</a>
</dd>
</div>
</dl>
</div>
</div>
</div>
</div>
);
};
export default Contact;