feat: Add AGB PDF attachment to booking confirmation emails

- Extend email system to support file attachments
- Add sendEmailWithAGB() function that automatically attaches AGB.pdf
- Implement AGB PDF caching for better performance
- Update booking confirmation email template with AGB notice
- Add visual highlight box in HTML email with AGB information
- Update email subject to indicate AGB attachment
- Include AGB reference in both HTML and text versions
- Ensure legal compliance by automatically sending terms with confirmations

Changes:
- email.ts: Add attachment support and AGB PDF integration
- email-templates.ts: Add AGB notice to confirmation emails
- bookings.ts: Use sendEmailWithAGB for confirmed bookings
- German localization for admin treatments component
This commit is contained in:
2025-09-30 11:18:23 +02:00
parent bb04e5a118
commit a1935aae02
4 changed files with 77 additions and 27 deletions

View File

@@ -9,8 +9,8 @@ export function AdminTreatments() {
name: "",
description: "",
duration: 60,
price: 5000, // $50.00 in cents
category: "Manicure",
price: 5000, // 50,00 in Cent
category: "Manire",
});
const { data: treatments } = useQuery(
@@ -29,7 +29,7 @@ export function AdminTreatments() {
queryClient.treatments.remove.mutationOptions()
);
const categories = ["Manicure", "Pedicure", "Nail Art", "Extensions", "Other"];
const categories = ["Manire", "Pedire", "Nageldesign", "Verlängerungen", "Sonstiges"];
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -61,7 +61,7 @@ export function AdminTreatments() {
description: "",
duration: 60,
price: 5000,
category: "Manicure",
category: "Manire",
});
};
@@ -86,26 +86,26 @@ export function AdminTreatments() {
return (
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">Manage Treatments</h2>
<h2 className="text-2xl font-bold text-gray-900">Behandlungen verwalten</h2>
<button
onClick={() => setShowForm(true)}
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 font-medium"
>
Add Treatment
Behandlung hinzufügen
</button>
</div>
{showForm && (
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
<h3 className="text-lg font-semibold mb-4">
{editingTreatment ? "Edit Treatment" : "Add New Treatment"}
{editingTreatment ? "Behandlung bearbeiten" : "Neue Behandlung hinzufügen"}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Treatment Name *
Behandlungsname *
</label>
<input
type="text"
@@ -117,7 +117,7 @@ export function AdminTreatments() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Category *
Kategorie *
</label>
<select
value={formData.category}
@@ -134,7 +134,7 @@ export function AdminTreatments() {
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Description *
Beschreibung *
</label>
<textarea
value={formData.description}
@@ -148,7 +148,7 @@ export function AdminTreatments() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Duration (minutes) *
Dauer (Minuten) *
</label>
<input
type="number"
@@ -163,7 +163,7 @@ export function AdminTreatments() {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Price ($) *
Preis () *
</label>
<input
type="number"
@@ -183,14 +183,14 @@ export function AdminTreatments() {
disabled={isCreating || isUpdating}
className="bg-pink-600 text-white px-4 py-2 rounded-md hover:bg-pink-700 disabled:opacity-50 font-medium"
>
{isCreating || isUpdating ? "Saving..." : (editingTreatment ? "Update" : "Create")}
{isCreating || isUpdating ? "Speichern..." : (editingTreatment ? "Aktualisieren" : "Erstellen")}
</button>
<button
type="button"
onClick={handleCancel}
className="bg-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-400 font-medium"
>
Cancel
Abbrechen
</button>
</div>
</form>
@@ -202,19 +202,19 @@ export function AdminTreatments() {
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Treatment
Behandlung
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Category
Kategorie
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Duration
Dauer
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Price
Preis
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
Aktionen
</th>
</tr>
</thead>
@@ -231,17 +231,17 @@ export function AdminTreatments() {
{treatment.category}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{treatment.duration} min
{treatment.duration} Min
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
${(treatment.price / 100).toFixed(2)}
{(treatment.price / 100).toFixed(2)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<button
onClick={() => handleEdit(treatment)}
className="text-pink-600 hover:text-pink-900"
>
Edit
Bearbeiten
</button>
<button
onClick={() => {
@@ -251,7 +251,7 @@ export function AdminTreatments() {
}}
className="text-red-600 hover:text-red-900"
>
Delete
Löschen
</button>
</td>
</tr>