Files
skrift-app/src/components/OfferForm.tsx
s4luorth bad4abc945
All checks were successful
Build & Deploy / deploy (push) Successful in 21s
Muster & Angebot: Vor-/Nachname getrennt, „Ort" statt „Stadt", Angebot als Seite
- Muster bestellen + individuelles Angebot: getrennte Felder Vorname/Nachname
  (statt einem Namensfeld).
- Muster-Formular: Feld „Stadt" → „Ort".
- „Individuelles Angebot" öffnet jetzt eine eigene Seite (/angebot) statt Popup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 21:15:38 +02:00

142 lines
6.1 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 { useEffect, useState } from 'react';
import { ApiError, fetchMyCustomer, loadSession, sendOfferRequest } from '../lib/api';
const istEmail = (e: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.trim());
/**
* Anfrageformular „Individuelles Angebot".
*
* Wird an zwei Stellen genutzt: im Konfigurator als eigene Seite (nach der Wahl
* eines Anfrageprodukts) und in der Seitenleiste als Dialog. Deshalb rendert es
* nur den Inhalt – die Umrahmung (Karte bzw. Overlay) kommt von außen.
*
* `onClose` blendet im Dialog den Schließen-Button ein; inline (im Konfigurator)
* bleibt er weg.
*/
export default function OfferForm({ produkt, onClose }: { produkt?: string; onClose?: () => void }) {
const gewaehltesProdukt = produkt?.trim() || '';
const [form, setForm] = useState({ first_name: '', last_name: '', email: '', phone: '', quantity: '', message: '' });
const [datenschutz, setDatenschutz] = useState(false);
const [status, setStatus] = useState<'formular' | 'sendet' | 'fertig'>('formular');
const [fehler, setFehler] = useState<string[]>([]);
// Bei angemeldeten Kunden Name und E-Mail vorausfüllen.
useEffect(() => {
if (!loadSession()) return;
fetchMyCustomer()
.then((c) => {
if (!c) return;
setForm((f) => ({
...f,
first_name: c.first_name || f.first_name,
last_name: c.last_name || f.last_name,
email: c.email || f.email,
phone: c.phone || f.phone,
}));
})
.catch(() => { /* Vorausfüllen ist optional */ });
}, []);
const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const roh = e.target.value;
setForm({ ...form, [k]: k === 'phone' ? roh.replace(/[^0-9+ ]/g, '') : roh });
};
async function absenden(e: React.FormEvent) {
e.preventDefault();
if (!istEmail(form.email)) { setFehler(['Bitte geben Sie eine gültige E-Mail-Adresse an.']); return; }
if (!form.phone.trim()) { setFehler(['Bitte geben Sie eine Telefonnummer an.']); return; }
setFehler([]);
setStatus('sendet');
try {
await sendOfferRequest({
first_name: form.first_name.trim(),
last_name: form.last_name.trim(),
email: form.email.trim(),
phone: form.phone.trim() || undefined,
quantity: form.quantity ? Number(form.quantity) : null,
message: form.message.trim(),
product: gewaehltesProdukt || undefined,
});
setStatus('fertig');
} catch (err) {
setFehler(err instanceof ApiError ? (err.details.length ? err.details : [err.message]) : ['Anfrage konnte nicht gesendet werden.']);
setStatus('formular');
}
}
if (status === 'fertig') {
return (
<>
<p>Vielen Dank – wir melden uns werktags innerhalb eines Tages mit einem Angebot bei Ihnen.</p>
{onClose && (
<button className="sk-btn sk-btn--primary" style={{ marginTop: 'var(--sk-space-4)' }} onClick={onClose}>
Schließen
</button>
)}
</>
);
}
return (
<>
<p className="sk-note">
Größere Mengen, Sonderformate, Unterschriftenservice oder automatisierte Follow-ups – schildern Sie
kurz Ihr Vorhaben.
</p>
{gewaehltesProdukt && (
<div className="sk-menu-head" style={{ borderRadius: 'var(--sk-radius)', border: '1px solid var(--sk-border)', marginTop: 'var(--sk-space-3)' }}>
<div className="sk-menu-sub">Ihre Anfrage betrifft</div>
<div className="sk-menu-name">{gewaehltesProdukt}</div>
</div>
)}
<form style={{ marginTop: 'var(--sk-space-4)' }} onSubmit={absenden}>
<div className="sk-field">
<label className="sk-label" htmlFor="a-vorname">Vorname</label>
<input id="a-vorname" className="sk-input" value={form.first_name} onChange={set('first_name')} required />
</div>
<div className="sk-field">
<label className="sk-label" htmlFor="a-nachname">Nachname</label>
<input id="a-nachname" className="sk-input" value={form.last_name} onChange={set('last_name')} required />
</div>
<div className="sk-field">
<label className="sk-label" htmlFor="a-mail">E-Mail</label>
<input id="a-mail" className="sk-input" type="email" value={form.email} onChange={set('email')} required />
</div>
<div className="sk-field">
<label className="sk-label sk-label--required" htmlFor="a-tel">Telefon</label>
<input id="a-tel" className="sk-input" type="tel" value={form.phone} onChange={set('phone')} required />
</div>
<div className="sk-field">
<label className="sk-label" htmlFor="a-menge">Ungefähre Menge <span className="sk-note">(optional)</span></label>
<input id="a-menge" className="sk-input" type="number" min={1} value={form.quantity} onChange={set('quantity')} />
</div>
<div className="sk-field">
<label className="sk-label" htmlFor="a-text">Ihr Vorhaben</label>
<textarea id="a-text" className="sk-input" rows={4} value={form.message} onChange={set('message')} required />
</div>
<label className="sk-check" style={{ marginBottom: 'var(--sk-space-4)' }}>
<input type="checkbox" checked={datenschutz} onChange={(e) => setDatenschutz(e.target.checked)} required />
<span>
Ich habe die{' '}
<a href="https://skrift.de/datenschutz" target="_blank" rel="noreferrer">Datenschutzerklärung</a>{' '}
zur Kenntnis genommen und bin mit der Verarbeitung meiner Daten zur Bearbeitung meiner
Anfrage einverstanden.
</span>
</label>
{fehler.length > 0 && (
<ul className="sk-error" style={{ margin: '0 0 var(--sk-space-3)', paddingLeft: '1.1em' }}>
{fehler.map((f) => <li key={f}>{f}</li>)}
</ul>
)}
<button className="sk-btn sk-btn--primary sk-btn--block" disabled={status === 'sendet' || !datenschutz || !form.phone.trim()}>
{status === 'sendet' ? 'Wird gesendet …' : 'Anfrage senden'}
</button>
</form>
</>
);
}