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>
210 lines
7.2 KiB
TypeScript
210 lines
7.2 KiB
TypeScript
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 15 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;
|