326 lines
13 KiB
TypeScript
326 lines
13 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import SignaturePad from './SignaturePad';
|
|
import { formatDate } from '@/lib/date-utils';
|
|
import { FiUpload, FiDownload } from 'react-icons/fi';
|
|
|
|
interface ContractSigningFormProps {
|
|
booking: any;
|
|
location: any;
|
|
photobox: any;
|
|
token: string;
|
|
}
|
|
|
|
export default function ContractSigningForm({ booking, location, photobox, token }: ContractSigningFormProps) {
|
|
const router = useRouter();
|
|
const [signatureData, setSignatureData] = useState<string | null>(null);
|
|
const [acceptTerms, setAcceptTerms] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [showSignature, setShowSignature] = useState(false);
|
|
const [showUpload, setShowUpload] = useState(false);
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
const [uploadProgress, setUploadProgress] = useState(false);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!signatureData || !acceptTerms) return;
|
|
|
|
setSubmitting(true);
|
|
|
|
try {
|
|
const res = await fetch('/api/contract/sign', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
token,
|
|
signatureData,
|
|
name: booking.customerName,
|
|
email: booking.customerEmail,
|
|
}),
|
|
});
|
|
|
|
if (res.ok) {
|
|
router.push(`/contract/success`);
|
|
} else {
|
|
alert('Fehler beim Unterschreiben des Vertrags');
|
|
setSubmitting(false);
|
|
}
|
|
} catch (error) {
|
|
alert('Fehler beim Unterschreiben des Vertrags');
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (e.target.files && e.target.files[0]) {
|
|
const file = e.target.files[0];
|
|
if (file.type === 'application/pdf') {
|
|
setSelectedFile(file);
|
|
} else {
|
|
alert('Bitte wählen Sie eine PDF-Datei aus');
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleUpload = async () => {
|
|
if (!selectedFile || !acceptTerms) return;
|
|
|
|
setUploadProgress(true);
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', selectedFile);
|
|
formData.append('token', token);
|
|
|
|
const res = await fetch('/api/contract/upload', {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
|
|
if (res.ok) {
|
|
router.push(`/contract/success`);
|
|
} else {
|
|
alert('Fehler beim Hochladen des Vertrags');
|
|
setUploadProgress(false);
|
|
}
|
|
} catch (error) {
|
|
alert('Fehler beim Hochladen des Vertrags');
|
|
setUploadProgress(false);
|
|
}
|
|
};
|
|
|
|
const handleDownloadContract = async () => {
|
|
try {
|
|
const decoded = Buffer.from(token, 'base64url').toString();
|
|
const bookingId = decoded.split('-')[0];
|
|
|
|
const res = await fetch(`/api/bookings/${bookingId}/contract`);
|
|
if (res.ok) {
|
|
const blob = await res.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `Vertrag-${booking.bookingNumber}.pdf`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
} else {
|
|
alert('Fehler beim Herunterladen des Vertrags');
|
|
}
|
|
} catch (error) {
|
|
alert('Fehler beim Herunterladen des Vertrags');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto bg-white rounded-xl shadow-lg overflow-hidden">
|
|
{/* Header */}
|
|
<div className="bg-red-600 text-white p-6">
|
|
<h1 className="text-2xl font-bold">SaveTheMoment Atlas</h1>
|
|
<p className="text-red-100">Mietvertrag für Fotobox</p>
|
|
</div>
|
|
|
|
{/* Contract Details */}
|
|
<div className="p-8 space-y-6">
|
|
<div>
|
|
<h2 className="text-xl font-bold text-gray-900 mb-4">Vertragsdaten</h2>
|
|
<div className="grid grid-cols-2 gap-4 bg-gray-50 p-4 rounded-lg">
|
|
<div>
|
|
<p className="text-sm text-gray-600">Buchungsnummer</p>
|
|
<p className="font-semibold">{booking.bookingNumber}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-600">Event-Datum</p>
|
|
<p className="font-semibold">{formatDate(booking.eventDate)}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-600">Location</p>
|
|
<p className="font-semibold">{booking.eventLocation || 'Nicht angegeben'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-600">Gesamtpreis</p>
|
|
<p className="font-semibold">{booking.calculatedPrice?.toFixed(2) || '0.00'} €</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="font-semibold text-gray-900 mb-2">Kundendaten</h3>
|
|
<div className="bg-gray-50 p-4 rounded-lg space-y-2">
|
|
<p><span className="text-gray-600">Name:</span> {booking.customerName}</p>
|
|
<p><span className="text-gray-600">E-Mail:</span> {booking.customerEmail}</p>
|
|
<p><span className="text-gray-600">Telefon:</span> {booking.customerPhone}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="font-semibold text-gray-900 mb-2">Vertragsbedingungen</h3>
|
|
<div className="bg-gray-50 p-4 rounded-lg space-y-2 text-sm">
|
|
<p>1. Der Vermieter verpflichtet sich, die Fotobox am Veranstaltungsort aufzubauen und nach der Veranstaltung wieder abzubauen.</p>
|
|
<p>2. Die Mietdauer beginnt mit dem Aufbau und endet mit dem Abbau der Fotobox.</p>
|
|
<p>3. Der Mieter haftet für Schäden, die während der Mietzeit an der Fotobox entstehen.</p>
|
|
<p>4. Der vereinbarte Mietpreis ist spätestens 7 Tage vor der Veranstaltung fällig.</p>
|
|
<p>5. Bei Stornierung weniger als 14 Tage vor der Veranstaltung wird eine Bearbeitungsgebühr von 50% erhoben.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* AGB Checkbox */}
|
|
<div className="border-t pt-6">
|
|
<label className="flex items-start gap-3 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={acceptTerms}
|
|
onChange={(e) => setAcceptTerms(e.target.checked)}
|
|
className="w-5 h-5 text-red-600 rounded mt-1"
|
|
/>
|
|
<span className="text-sm text-gray-700">
|
|
Ich habe die Vertragsbedingungen gelesen und akzeptiere diese. Mir ist bewusst, dass dieser Vertrag rechtsverbindlich ist.
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
{/* Signature Section */}
|
|
{!showSignature && !showUpload ? (
|
|
<div className="border-t pt-6 space-y-4">
|
|
<button
|
|
onClick={() => setShowSignature(true)}
|
|
disabled={!acceptTerms}
|
|
className="w-full px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed font-semibold"
|
|
>
|
|
Online unterschreiben
|
|
</button>
|
|
|
|
<div className="text-center text-gray-500 font-medium">
|
|
oder
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<button
|
|
onClick={handleDownloadContract}
|
|
className="w-full px-6 py-3 bg-white border-2 border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-semibold flex items-center justify-center gap-2"
|
|
>
|
|
<FiDownload />
|
|
Vertrag herunterladen
|
|
</button>
|
|
<button
|
|
onClick={() => setShowUpload(true)}
|
|
disabled={!acceptTerms}
|
|
className="w-full px-6 py-3 bg-white border-2 border-red-600 text-red-600 rounded-lg hover:bg-red-50 transition-colors disabled:border-gray-400 disabled:text-gray-400 disabled:cursor-not-allowed font-semibold flex items-center justify-center gap-2"
|
|
>
|
|
<FiUpload />
|
|
Unterschriebenen Vertrag hochladen
|
|
</button>
|
|
</div>
|
|
|
|
<p className="text-sm text-gray-600 text-center mt-3">
|
|
Laden Sie den Vertrag herunter, unterschreiben Sie ihn und laden Sie ihn dann wieder hoch
|
|
</p>
|
|
</div>
|
|
) : showSignature ? (
|
|
<div className="border-t pt-6">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Unterschrift</h3>
|
|
{!signatureData ? (
|
|
<SignaturePad
|
|
onSave={(data) => setSignatureData(data)}
|
|
onCancel={() => setShowSignature(false)}
|
|
/>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div className="border-2 border-green-500 rounded-lg p-4 bg-green-50">
|
|
<p className="text-green-800 font-semibold mb-2">✓ Unterschrift erfasst</p>
|
|
<img src={signatureData} alt="Unterschrift" className="max-h-32 border-t border-gray-300 pt-2" />
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => setSignatureData(null)}
|
|
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300"
|
|
>
|
|
Neue Unterschrift
|
|
</button>
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={submitting}
|
|
className="flex-1 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:bg-gray-400 font-semibold"
|
|
>
|
|
{submitting ? 'Wird übermittelt...' : 'Vertrag jetzt unterschreiben'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<div className="border-t pt-6">
|
|
<h3 className="font-semibold text-gray-900 mb-4">Unterschriebenen Vertrag hochladen</h3>
|
|
{!selectedFile ? (
|
|
<div className="space-y-4">
|
|
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center">
|
|
<FiUpload className="w-12 h-12 mx-auto text-gray-400 mb-4" />
|
|
<p className="text-gray-600 mb-4">
|
|
Wählen Sie die unterschriebene PDF-Datei aus
|
|
</p>
|
|
<input
|
|
type="file"
|
|
accept="application/pdf"
|
|
onChange={handleFileSelect}
|
|
className="hidden"
|
|
id="contract-upload"
|
|
/>
|
|
<label
|
|
htmlFor="contract-upload"
|
|
className="inline-block px-6 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors cursor-pointer font-semibold"
|
|
>
|
|
Datei auswählen
|
|
</label>
|
|
</div>
|
|
<button
|
|
onClick={() => setShowUpload(false)}
|
|
className="w-full px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300"
|
|
>
|
|
Zurück
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-4">
|
|
<div className="border-2 border-green-500 rounded-lg p-4 bg-green-50">
|
|
<p className="text-green-800 font-semibold mb-2">✓ Datei ausgewählt</p>
|
|
<p className="text-gray-700">{selectedFile.name}</p>
|
|
<p className="text-sm text-gray-600 mt-1">
|
|
Größe: {(selectedFile.size / 1024).toFixed(2)} KB
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<button
|
|
onClick={() => setSelectedFile(null)}
|
|
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300"
|
|
>
|
|
Andere Datei wählen
|
|
</button>
|
|
<button
|
|
onClick={handleUpload}
|
|
disabled={uploadProgress}
|
|
className="flex-1 px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:bg-gray-400 font-semibold"
|
|
>
|
|
{uploadProgress ? 'Wird hochgeladen...' : 'Jetzt hochladen'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="bg-gray-50 px-8 py-4 text-sm text-gray-600 border-t">
|
|
<p>
|
|
SaveTheMoment Atlas • {location.name} • {location.contactEmail} • {location.contactPhone}
|
|
</p>
|
|
<p className="mt-1">
|
|
Dieser Vertrag wird elektronisch gespeichert. Sie erhalten eine Kopie per E-Mail.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|