From e34d56e36a64015d63e7a4cd5ce8c58bf8ded18b Mon Sep 17 00:00:00 2001 From: Jose Lago Date: Sun, 17 May 2026 18:04:36 +0200 Subject: [PATCH 1/4] feat: Add manual email sending workflow and related database changes - Implemented a new n8n workflow for manual email sending, including webhook trigger, order data fetching, email building, and sending. - Added logic to format email content with customer and order details. - Introduced new columns in the sales_orders table to track email sending status. - Updated database functions to handle new rental types and email status. - Created new RPCs for updating email status and retrieving email details for sales orders. --- docker-compose.local.yml | 2 + docker-compose.yml | 4 + frontend/admin.html | 11 +- frontend/admin.js | 132 +++++- frontend/app.js | 70 ++-- frontend/i18n.js | 32 +- frontend/index.html | 37 +- frontend/nginx.conf | 5 +- n8n/bootstrap/bootstrap-n8n.sh | 15 + .../01-qualification-payment-email.json | 114 ++++- n8n/workflows/03-manual-email-send.json | 206 +++++++++ .../migrations/11-consolidate-km-rental.sql | 396 ++++++++++++++++++ .../migrations/12-email-sent-and-more.sql | 209 +++++++++ 13 files changed, 1127 insertions(+), 106 deletions(-) create mode 100644 n8n/workflows/03-manual-email-send.json create mode 100644 supabase/migrations/11-consolidate-km-rental.sql create mode 100644 supabase/migrations/12-email-sent-and-more.sql diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 5e8121b..5dc3ee8 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -25,6 +25,8 @@ services: - ./supabase/migrations/08-backend-pricing-and-security.sql:/sql/08-backend-pricing-and-security.sql:ro - ./supabase/migrations/09-site-settings.sql:/sql/09-site-settings.sql:ro - ./supabase/migrations/10-mietvertrag-workflow.sql:/sql/10-mietvertrag-workflow.sql:ro + - ./supabase/migrations/11-consolidate-km-rental.sql:/sql/11-consolidate-km-rental.sql:ro + - ./supabase/migrations/12-email-sent-and-more.sql:/sql/12-email-sent-and-more.sql:ro kong: volumes: diff --git a/docker-compose.yml b/docker-compose.yml index bb13dc9..db0ccad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -218,6 +218,8 @@ services: - /mnt/user/appdata/mc-cars/supabase/migrations/08-backend-pricing-and-security.sql:/sql/08-backend-pricing-and-security.sql:ro - /mnt/user/appdata/mc-cars/supabase/migrations/09-site-settings.sql:/sql/09-site-settings.sql:ro - /mnt/user/appdata/mc-cars/supabase/migrations/10-mietvertrag-workflow.sql:/sql/10-mietvertrag-workflow.sql:ro + - /mnt/user/appdata/mc-cars/supabase/migrations/11-consolidate-km-rental.sql:/sql/11-consolidate-km-rental.sql:ro + - /mnt/user/appdata/mc-cars/supabase/migrations/12-email-sent-and-more.sql:/sql/12-email-sent-and-more.sql:ro entrypoint: ["sh","-c"] command: - | @@ -244,6 +246,8 @@ services: psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/08-backend-pricing-and-security.sql psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/09-site-settings.sql psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/10-mietvertrag-workflow.sql + psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/11-consolidate-km-rental.sql + psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/12-email-sent-and-more.sql echo "post-init done." restart: "no" networks: [mccars] diff --git a/frontend/admin.html b/frontend/admin.html index 8e4e967..211a4c1 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -101,6 +101,7 @@ Name / E-Mail Fahrzeug Zeitraum + Miettyp Gesamtbetrag Status @@ -146,10 +147,12 @@ Name / E-Mail Fahrzeug Zeitraum + Miettyp Gesamtbetrag Kaution Miete Status + Email @@ -192,13 +195,13 @@
- + + -
- +
@@ -304,6 +307,6 @@ - + diff --git a/frontend/admin.js b/frontend/admin.js index 7990162..5039ef1 100644 --- a/frontend/admin.js +++ b/frontend/admin.js @@ -262,9 +262,9 @@ function loadForEdit(id) { vehicleForm.seats.value = v.seats; vehicleForm.daily_price_eur.value = v.daily_price_eur; vehicleForm.weekend_price_eur.value = v.weekend_price_eur || 0; - vehicleForm.max_daily_km.value = v.max_daily_km || 150; - vehicleForm.kaution_eur.value = v.kaution_eur || 5000; - vehicleForm.max_km_weekend.value = v.max_km_weekend || ''; + vehicleForm.included_km_per_day.value = v.included_km_per_day || 150; + vehicleForm.kaution_eur.value = v.kaution_eur || 5000; + vehicleForm.price_per_km_eur.value = v.price_per_km_eur || 1.50; vehicleForm.sort_order.value = v.sort_order; vehicleForm.location.value = v.location; vehicleForm.description_de.value = v.description_de; @@ -283,10 +283,10 @@ resetBtn.addEventListener("click", () => { vehicleForm.sort_order.value = 100; vehicleForm.location.value = "Steiermark (TBD)"; vehicleForm.seats.value = 2; - vehicleForm.max_daily_km.value = 150; + vehicleForm.included_km_per_day.value = 150; vehicleForm.weekend_price_eur.value = 0; vehicleForm.kaution_eur.value = 5000; - vehicleForm.max_km_weekend.value = ''; + vehicleForm.price_per_km_eur.value = 1.50; state.currentPhotoPath = null; updatePreview(""); formTitle.textContent = "Neues Fahrzeug"; @@ -309,9 +309,9 @@ vehicleForm.addEventListener("submit", async (e) => { seats: +fd.get("seats") || 2, daily_price_eur: +fd.get("daily_price_eur") || 0, weekend_price_eur: +fd.get("weekend_price_eur") || 0, - max_daily_km: +fd.get("max_daily_km") || 150, - kaution_eur: +fd.get("kaution_eur") || 5000, - max_km_weekend: fd.get("max_km_weekend") ? +fd.get("max_km_weekend") : null, + included_km_per_day: +fd.get("included_km_per_day") || 150, + kaution_eur: +fd.get("kaution_eur") || 5000, + price_per_km_eur: parseFloat(fd.get("price_per_km_eur")) || 1.50, sort_order: +fd.get("sort_order") || 100, location: fd.get("location") || "Steiermark (TBD)", description_de: fd.get("description_de") || "", @@ -404,6 +404,7 @@ function renderLeads() { ${esc(l.name)}
${esc(l.email)}${l.phone ? " · " + esc(l.phone) : ""} ${esc(l.vehicle_label || "—")} ${esc(l.date_from || "—")} → ${esc(l.date_to || "—")} + ${esc(l.rental_type || 'weekend')} ${totalStr} ${esc(l.status)} @@ -567,8 +568,9 @@ async function renderLeadTab(tab, l) {
${lang === "de" ? t("adminVatLabel") : t("adminVatLabelEn")}€ ${vat.toLocaleString("de-DE")}
${lang === "de" ? t("adminTotalLabel") : t("adminTotalLabelEn")}€ ${total.toLocaleString("de-DE")}
${lang === "de" ? t("adminDepositLabel") : t("adminDepositLabelEn")}€ ${deposit.toLocaleString("de-DE")}
-
${lang === "de" ? t("adminIncludedKmLabel") : t("adminIncludedKmLabelEn")}${((l.weekday_count || 0) * (state.vehicleMap.get(l.vehicle_id)?.max_daily_km || 150) + (l.weekend_day_count || 0) * (state.vehicleMap.get(l.vehicle_id)?.max_km_weekend || state.vehicleMap.get(l.vehicle_id)?.max_daily_km || 150))} km
-
${lang === "de" ? t("adminTotalDaysLabel") : t("adminTotalDaysLabelEn")}${l.total_days || 0}
+
${lang === "de" ? t("adminIncludedKmLabel") : t("adminIncludedKmLabelEn")}${(l.total_days || 0) * (state.vehicleMap.get(l.vehicle_id)?.included_km_per_day || 150)} km
+
${lang === "de" ? t("adminTotalDaysLabel") : t("adminTotalDaysLabelEn")}${l.total_days || 0}
+
${lang === "de" ? t("adminRentalType") : t("adminRentalTypeEn")}${esc(l.rental_type || 'weekend')}
`; } else if (tab === "documents") { const docs = await loadLeadAttachments(l.id); @@ -694,10 +696,12 @@ function renderOrders() { ${cust ? `${esc(cust.name)}
${esc(cust.email)}` : `${esc(o.customer_id?.slice(0, 8) || "—")}`} ${esc(o.vehicle_label || "—")} ${esc(o.date_from || "—")} → ${esc(o.date_to || "—")} + ${esc(o.rental_type || 'weekend')} ${totalStr} ${o.kaution_paid ? "✓" : "—"} ${o.rental_paid ? "✓" : "—"} ${o.rental_complete ? t("adminCompleteDone") : t("adminCompletePending")} + ${o.email_sent === 0 ? '—' : o.email_sent === 1 ? '✓' : '✗'} `; ordersTableBody.appendChild(tr); } @@ -719,6 +723,10 @@ async function openOrder(id) { const cust = state.customers.find(c => c.id === o.customer_id); const total = o.total_eur || 0; const deposit = o.deposit_eur || 0; + const emailSent = o.email_sent || 0; + const emailSentText = emailSent === 1 ? '✓' : emailSent === 2 ? '✗' : '—'; + const emailSentPillClass = emailSent === 1 ? 'active' : emailSent === 2 ? 'disqualified' : 'new'; + const isEmailLocked = emailSent === 1; orderDialogTitle.textContent = `${o.order_number || o.id.slice(0, 8)} · ${cust?.name || "—"}`; @@ -735,8 +743,19 @@ async function openOrder(id) {
${lang === "de" ? "Kunde" : "Customer"}
${cust ? `${esc(cust.name)} (${esc(cust.email)})` : esc(o.customer_id?.slice(0, 8) || "—")}
${lang === "de" ? "Fahrzeug" : "Vehicle"}
${esc(o.vehicle_label || "—")}
${lang === "de" ? "Zeitraum" : "Period"}
${esc(o.date_from || "—")} → ${esc(o.date_to || "—")}
-
${t("adminTotalLabel")}
€ ${total.toLocaleString("de-DE")}
-
${t("adminDepositLabel")}
€ ${deposit.toLocaleString("de-DE")}
+
${t("adminTotalLabel")}
+ ${o.rental_type === 'individuell' && !isEmailLocked + ? ` + ` + : '€ ' + total.toLocaleString("de-DE") + } +
+
${t("adminDepositLabel")}
${isEmailLocked + ? '€ ' + deposit.toLocaleString("de-DE") + : `` + }
+
${t("adminEmailSent")}
${emailSentText}
+ ${o.rental_type === 'individuell' && !isEmailLocked ? `
` : ''}
@@ -778,15 +797,77 @@ async function openOrder(id) { }); }); + // Dirty form tracking + let noteIsDirty = false; + const orderNoteEl = document.querySelector("#orderNote"); + const originalNoteValue = o.private_notes || ""; + const saveBtn = document.querySelector("#orderNoteSave"); + if (orderNoteEl && saveBtn) { + saveBtn.classList.add("ghost"); + orderNoteEl.addEventListener("input", () => { + noteIsDirty = orderNoteEl.value !== originalNoteValue; + if (noteIsDirty) { + saveBtn.classList.remove("ghost"); + saveBtn.style.backgroundColor = "var(--accent-strong)"; + saveBtn.style.color = "#fff"; + saveBtn.textContent = t("adminSaveNotes") + " (unsaved)"; + } else { + saveBtn.classList.add("ghost"); + saveBtn.style.backgroundColor = ""; + saveBtn.style.color = ""; + saveBtn.textContent = t("adminSaveNotes"); + } + }); + } + // Save notes document.querySelector("#orderNoteSave")?.addEventListener("click", async () => { const ok = await saveSalesOrderPrivateNotes(o.id, document.querySelector("#orderNote").value); if (ok) { + noteIsDirty = false; document.querySelector("#orderNoteSave").textContent = "✓"; setTimeout(() => { document.querySelector("#orderNoteSave").textContent = t("adminSaveNotes"); }, 1500); } }); + document.querySelector("#orderTotalSave")?.addEventListener("click", async () => { + const input = document.querySelector("#orderTotalInput"); + if (!input) return; + const { error } = await supabase.rpc("sales_order_set_total", { + p_so_id: o.id, + p_total_eur: +input.value, + }); + if (error) { + alert(error.message); + return; + } + await loadSalesOrders(); + await openOrder(id); // re-render + }); + + document.querySelector("#manualEmailSend")?.addEventListener("click", async () => { + const btn = document.querySelector("#manualEmailSend"); + btn.disabled = true; + + showToast(t("emailSentToast"), 3000); + + const n8nUrl = window.MCCARS_CONFIG?.N8N_WEBHOOK_URL || "http://localhost:55590/webhook/manual-email-send"; + try { + await fetch(n8nUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ sales_order_id: o.id }) + }); + } catch (webhookError) { + console.error("Webhook error:", webhookError); + } + + setTimeout(async () => { + await loadSalesOrders(); + await openOrder(id); + }, 3000); + }); + orderDialogFooter.innerHTML = ""; orderDialog.showModal(); orderDialogClose.addEventListener("click", () => orderDialog.close(), { once: true }); @@ -941,6 +1022,7 @@ async function renderCustomerTab(tab, c) {
${lang === "de" ? "Fahrzeug" : "Vehicle"}${esc(o.vehicle_label || "—")}
${lang === "de" ? "Zeitraum" : "Period"}${esc(o.date_from || "—")} → ${esc(o.date_to || "—")}
+
${lang === "de" ? "Miettyp" : "Rental type"}${esc(o.rental_type || 'weekend')}
@@ -974,8 +1056,32 @@ async function renderCustomerTab(tab, c) { const noteEl = customerDialogBody.querySelector(`[data-so-note="${btn.dataset.soSaveNote}"]`); const ok = await saveSalesOrderPrivateNotes(btn.dataset.soSaveNote, noteEl?.value || ""); if (ok) { + btn.classList.remove("ghost"); + btn.style.backgroundColor = ""; + btn.style.color = ""; btn.textContent = "✓"; - setTimeout(() => { btn.textContent = t("adminSaveNotes"); }, 1500); + setTimeout(() => { + btn.textContent = t("adminSaveNotes"); + }, 1500); + } + }); + }); + + customerDialogBody.querySelectorAll("[data-so-note]").forEach((noteEl) => { + const btn = customerDialogBody.querySelector(`[data-so-save-note="${noteEl.dataset.soNote}"]`); + const originalValue = noteEl.value; + noteEl.addEventListener("input", () => { + const isDirty = noteEl.value !== originalValue; + if (isDirty) { + btn.classList.remove("ghost"); + btn.style.backgroundColor = "var(--accent-strong)"; + btn.style.color = "#fff"; + btn.textContent = "Speichern (unsaved)"; + } else { + btn.classList.add("ghost"); + btn.style.backgroundColor = ""; + btn.style.color = ""; + btn.textContent = t("adminSaveNotes"); } }); }); diff --git a/frontend/app.js b/frontend/app.js index 2733c52..9d0cd6c 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1,5 +1,5 @@ import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.4"; -import { translations, REVIEWS, getLang, setLang, t, applyI18n } from "./i18n.js"; +import { translations, REVIEWS, getLang, setLang, t, applyI18n } from "./i18n.js?v=3"; const SUPA_URL = window.MCCARS_CONFIG?.SUPABASE_URL ?? ""; const SUPA_KEY = window.MCCARS_CONFIG?.SUPABASE_ANON_KEY || ""; @@ -212,11 +212,11 @@ function openDetails(id) {
${v.top_speed_kmh}${t("kmh")}
${escapeHtml(v.acceleration)}${t("accel")}
-
-
${v.seats}${t("seats")}
-
€ ${v.weekend_price_eur || v.daily_price_eur}${t("bpfWeekendRate")}
-
${v.max_daily_km || 150}${t("bpfMaxKm")}
-
+
+
${v.seats}${t("seats")}
+
€ ${v.weekend_price_eur || v.daily_price_eur}${t("bpfWeekendRate")}
+
${v.included_km_per_day || 150}${t("bpfInclKmPerDay")}
+
€ ${(v.kaution_eur || 5000).toLocaleString("de-DE")}${t("bpfDeposit")}
@@ -398,29 +398,45 @@ async function updateSidebar() { const vat = price.vat_eur; const total = price.total_eur; const deposit = price.deposit_eur; - const kmPerWeekday = price.max_daily_km; - const kmPerWeekendDay = price.max_km_weekend; - const includedKm = (weekdays * kmPerWeekday) + (weekendDays * kmPerWeekendDay); + const includedKmPerDay = price.included_km_per_day || 150; + const includedKm = totalDays * includedKmPerDay; const photoUrl = optimizedVehiclePhotoUrl(v.photo_url); - bpfSidebarPlaceholder.style.display = "none"; - bpfSidebarContent.style.display = "block"; - bpfSidebarContent.innerHTML = ` -

${t("bpfPriceOverview")}

-
${v.brand} ${v.model} · ${totalDays} ${t("bpfDays")}
- ${weekdays > 0 ? `
${t("bpfWeekdays")} (${weekdays} × € ${price.daily_price_eur})€ ${weekdayCost.toLocaleString("de-DE")}
` : ""} - ${weekendDays > 0 ? `
${t("bpfWeekendDays")} (${weekendDays} × € ${price.weekend_price_eur})€ ${weekendCost.toLocaleString("de-DE")}
` : ""} -
${t("bpfSubtotal")}€ ${subtotal.toLocaleString("de-DE")}
-
${t("bpfVat")}€ ${vat.toLocaleString("de-DE")}
-
${t("bpfTotal")}€ ${total.toLocaleString("de-DE")}
-
${t("bpfDeposit")}€ ${deposit.toLocaleString("de-DE")}
-
${t("bpfIncludedKm")}${includedKm} km
-
${t("bpfExtraKm")}€ 1,50${t("bpfPerKm")}
-
-

${escapeHtml(v.brand)} ${escapeHtml(v.model)}

-

${v.power_hp} ${t("hp")} • ${v.top_speed_kmh} ${t("kmh")} • ${escapeHtml(v.acceleration)}

- `; -} + if (totalDays > 2) { + // Individuell mode: show info banner instead of pricing + bpfSidebarPlaceholder.style.display = "none"; + bpfSidebarContent.style.display = "block"; + bpfSidebarContent.innerHTML = ` +

${t("bpfPriceOverview")}

+
+

${t("bpfIndividuellTitle")}

+

${t("bpfIndividuellDesc")}

+
+
+

${escapeHtml(v.brand)} ${escapeHtml(v.model)}

+

${v.power_hp} ${t("hp")} • ${v.top_speed_kmh} ${t("kmh")} • ${escapeHtml(v.acceleration)}

+ `; + } else { + bpfSidebarPlaceholder.style.display = "none"; + bpfSidebarContent.style.display = "block"; + bpfSidebarContent.innerHTML = ` +

${t("bpfPriceOverview")}

+
${v.brand} ${v.model} · ${totalDays} ${t("bpfDays")}
+ ${weekdays > 0 ? `
${t("bpfWeekdays")} (${weekdays} × € ${price.daily_price_eur})€ ${weekdayCost.toLocaleString("de-DE")}
` : ""} + ${weekendDays > 0 ? `
${t("bpfWeekendDays")} (${weekendDays} × € ${price.weekend_price_eur})€ ${weekendCost.toLocaleString("de-DE")}
` : ""} +
${t("bpfSubtotal")}€ ${subtotal.toLocaleString("de-DE")}
+
${t("bpfVat")}€ ${vat.toLocaleString("de-DE")}
+
${t("bpfTotal")}€ ${total.toLocaleString("de-DE")}
+
${t("bpfDeposit")}€ ${deposit.toLocaleString("de-DE")}
+
${t("bpfIncludedKm")}${includedKm} km
+
${t("bpfExtraKm")}€ ${(price.price_per_km_eur || 1.50).toFixed(2).replace('.', ',')}${t("bpfPerKm")}
+
+

${escapeHtml(v.brand)} ${escapeHtml(v.model)}

+

${v.power_hp} ${t("hp")} • ${v.top_speed_kmh} ${t("kmh")} • ${escapeHtml(v.acceleration)}

+ `; + } + + } bpfCar.addEventListener("change", updateSidebar); bpfFrom.addEventListener("change", updateSidebar); diff --git a/frontend/i18n.js b/frontend/i18n.js index 3d3d45e..07f0111 100644 --- a/frontend/i18n.js +++ b/frontend/i18n.js @@ -89,6 +89,7 @@ export const translations = { bpfWeekendRate: "Wochenendmiete", bpfWeekendDef: "Wochenende: Samstag 9:00 – Sonntag 20:00", bpfMaxKm: "Max. km/Tag", + bpfInclKmPerDay: "Inkl. km/Tag", bpfExtraKm: "Extra km", bpfPriceOverview: "Preisübersicht", bpfSelectForPrice: "Wähle Fahrzeug und Datum für eine Preisübersicht", @@ -176,7 +177,7 @@ export const translations = { adminVehicleTab: "Fahrzeug", adminPeriod: "Zeitraum", adminKaution: "Kaution (€)", - adminMaxKmWeekend: "Max. km/Wochenendtag", + adminMaxKmWeekend: "Inkl. km/Wochenende", adminTotalPrice: "Gesamtbetrag", adminLifetimeValueCol: "Gesamtwert", adminTabGeneral: "Allgemein", @@ -231,9 +232,20 @@ export const translations = { adminFirstContacted: "Erster Kontakt", adminFirstContactedEn: "First contacted", adminNote: "Notiz", - adminNoteEn: "Note", + adminNoteEn: "Note", adminSave: "Speichern", adminSaveEn: "Save", + adminPricePerKm: "Preis extra km (€)", + adminRentalType: "Miettyp", + rentalTypeWeekend: "Wochenende", + rentalTypeIndividuell: "Individuell", + adminSortOrder: "Ordnung", + adminEmailSent: "Email", + sendEmailButton: "E-Mail senden", + emailSentToast: "E-Mail wird erstellt und in Kürze gesendet...", + emailAlreadySent: "Bereits gesendet", + bpfIndividuellTitle: "Individuelle Mietdauer", + bpfIndividuellDesc: "Bei Mietdauer über 2 Tagen erstellen wir ein persönliches Angebot. Wir prüfen Verfügbarkeit und melden uns in Kürze per E-Mail bei Ihnen.", }, en: { navCars: "Fleet", @@ -323,7 +335,8 @@ export const translations = { bpfDailyRate: "Daily rate", bpfWeekendRate: "Weekend rate", bpfWeekendDef: "Weekend: Saturday 9 AM – Sunday 8 PM", - bpfMaxKm: "Max. km/day", + bpfMaxKm: "Max. km/day", + bpfInclKmPerDay: "Included km/day", bpfExtraKm: "Extra km", bpfPriceOverview: "Price overview", bpfSelectForPrice: "Select vehicle and date for a price overview", @@ -411,7 +424,7 @@ export const translations = { adminVehicleTab: "Vehicle", adminPeriod: "Period", adminKaution: "Deposit (€)", - adminMaxKmWeekend: "Max. km/weekend day", + adminMaxKmWeekend: "Included km/weekend", adminTotalPrice: "Total", adminLifetimeValueCol: "Lifetime", adminTabGeneral: "General", @@ -469,6 +482,17 @@ export const translations = { adminNoteEn: "Notiz", adminSave: "Save", adminSaveEn: "Speichern", + adminPricePerKm: "Extra km price (€)", + adminRentalType: "Rental type", + rentalTypeWeekend: "Weekend", + rentalTypeIndividuell: "Custom", + adminSortOrder: "Order", + adminEmailSent: "Email", + sendEmailButton: "Send Email", + emailSentToast: "Email is being prepared and will be sent shortly...", + emailAlreadySent: "Already sent", + bpfIndividuellTitle: "Custom Rental Duration", + bpfIndividuellDesc: "For rental periods over 2 days, we'll create a personalized quote. We'll check availability and get back to you via email shortly.", }, }; diff --git a/frontend/index.html b/frontend/index.html index 453baff..ae0e9c5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -113,7 +113,6 @@