Import aus Monorepo (frischer Start)
All checks were successful
Build & Deploy / deploy (push) Successful in 26s
All checks were successful
Build & Deploy / deploy (push) Successful in 26s
This commit is contained in:
135
src/components/OfferForm.tsx
Normal file
135
src/components/OfferForm.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
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({ 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,
|
||||
name: [c.first_name, c.last_name].filter(Boolean).join(' ') || f.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({
|
||||
name: form.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-name">Name</label>
|
||||
<input id="a-name" className="sk-input" value={form.name} onChange={set('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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user