Podmiana image w hero, Kontakt pole readonly dla danych z ofert dodanie do tresci maila

This commit is contained in:
dm
2025-12-16 05:00:33 +01:00
parent f213b74caf
commit 6c91584fe1
6 changed files with 91 additions and 79 deletions

View File

@@ -10,6 +10,7 @@ function esc(str = "") {
function buildHtmlMail(form) {
const when = new Date().toLocaleString("pl-PL");
const offerText = (form.offerSummary || "").trim()
return `<!DOCTYPE html>
<html lang="pl">
@@ -79,6 +80,17 @@ function buildHtmlMail(form) {
${esc(form.message)}
</div>
${offerText ? `
<div style="font-size:14px;color:#111827;margin:8px 0;font-weight:700;">
Wybrana oferta:
</div>
<div style="font-size:14px;color:#111827;white-space:pre-line;line-height:1.6;
background:#f8fafc;border:1px solid #e5e7eb;border-radius:12px;padding:14px;">
${esc(offerText)}
</div>
` : ``}
<div style="margin-top:16px;font-size:12px;color:#6b7280;">
Wysłano: ${when}
</div>

View File

@@ -1,16 +1,17 @@
---
import path from "node:path";
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import MapGoogle from "../../components/maps/MapGoogle.astro";
import { loadYamlFile } from "../../lib/loadYaml";
import yaml from "js-yaml";
import fs from "fs";
const data = yaml.load(
fs.readFileSync("./src/content/contact/contact.yaml", "utf8"),
type SeoYaml = any;
const seo = loadYamlFile<SeoYaml>(
path.join(process.cwd(), "src", "content", "contact", "seo.yaml"),
);
const seo = yaml.load(
fs.readFileSync("./src/content/internet-swiatlowodowy/seo.yaml", "utf8"),
type ContactData = any;
const data = loadYamlFile<ContactData>(
path.join(process.cwd(), "src", "content", "contact", "contact.yaml"),
);
const apiKey = import.meta.env.PUBLIC_GOOGLE_MAPS_KEY;
@@ -25,8 +26,9 @@ const form = data.form;
<div class="f-contact-item" set:html={data.description} />
</div>
<div>
<div id="form">
<h2 class="f-section-title">{data.contactFormTitle}</h2>
<form id="contactForm" class="f-contact-form">
<div class="f-contact-form-inner">
<input
@@ -77,7 +79,20 @@ const form = data.form;
rows={form.message.rows}
placeholder={form.message.placeholder}
class="f-input"
required></textarea>
required
></textarea>
<!-- widoczne tylko gdy jest oferta -->
<div id="offerSummaryWrap" class="hidden">
<textarea
id="offerSummary"
name="offerSummary"
rows="6"
class="f-input"
readonly
placeholder="Wybrana oferta pojawi się tutaj."
></textarea>
</div>
<label class="f-rodo">
<input type="checkbox" name="rodo" required />
@@ -117,8 +132,6 @@ const form = data.form;
</div>
<div id="toast" class="f-toast"></div>
<input type="hidden" name="offerConfig" id="offerConfig" />
</section>
<!-- ReCaptcha v3 -->
@@ -142,59 +155,32 @@ const form = data.form;
}}
>
document.addEventListener("DOMContentLoaded", () => {
const form = document.getElementById("contactForm");
const formEl = document.getElementById("contactForm");
const toast = document.getElementById("toast");
if (!formEl) return;
if (!form) return;
form.addEventListener("submit", async (e) => {
if (!form.reportValidity()) return;
e.preventDefault();
const data = Object.fromEntries(new FormData(form).entries());
data.rodo = form.rodo.checked;
const token = await grecaptcha.execute(window.FUZ_RECAPTCHA_KEY, {
action: "submit",
});
data.recaptcha = token;
const resp = await fetch("/api/contact/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
const json = await resp.json();
showToast(
json.ok ? successMsg : errorMsg,
json.ok ? "success" : "error",
);
if (json.ok) form.reset();
});
const LS_KEY = "fuz_offer_config_v1";
const SS_INJECTED_KEY = "fuz_offer_injected_v1";
function showToast(msg, type) {
if (!toast) return;
toast.innerHTML = `<div class="f-toast-msg ${type}">${msg}</div>`;
toast.classList.remove("visible");
void toast.offsetWidth;
toast.classList.add("visible");
setTimeout(() => toast.classList.remove("visible"), 3000);
}
// ----------
const LS_KEY = "fuz_offer_config_v1";
const SS_INJECTED_KEY = "fuz_offer_injected_v1";
function clearOfferDraft() {
try {
localStorage.removeItem(LS_KEY);
sessionStorage.removeItem(SS_INJECTED_KEY);
const hidden = document.getElementById("offerConfig");
if (hidden) hidden.value = "";
const wrap = document.getElementById("offerSummaryWrap");
if (wrap) wrap.classList.add("hidden");
const offerSummary = document.getElementById("offerSummary");
if (offerSummary) offerSummary.value = "";
} catch {}
}
@@ -207,9 +193,9 @@ const form = data.form;
const payload = JSON.parse(raw);
const msg = document.querySelector('textarea[name="message"]');
const subject = document.querySelector('input[name="subject"]');
const hidden = document.getElementById("offerConfig");
const subject = formEl.querySelector('input[name="subject"]');
const wrap = document.getElementById("offerSummaryWrap");
const offerSummary = document.getElementById("offerSummary");
const offerText =
typeof payload?.message === "string" && payload.message.trim()
@@ -218,19 +204,13 @@ const form = data.form;
if (!offerText) return;
if (wrap) wrap.classList.remove("hidden");
if (subject && !subject.value) {
subject.value = `Zapytanie: ${payload?.pkg?.name || "Oferta"}`;
}
if (msg) {
const marker = "Wybrana oferta:";
const alreadyHas = msg.value && msg.value.includes(marker);
if (!alreadyHas) {
msg.value = (msg.value ? msg.value + "\n\n" : "") + offerText;
}
}
if (hidden) hidden.value = offerText;
if (offerSummary) offerSummary.value = offerText;
sessionStorage.setItem(SS_INJECTED_KEY, "1");
} catch {}
@@ -238,21 +218,43 @@ const form = data.form;
hydrateOfferIntoForm();
function onSubmitSuccessCleanup() {
clearOfferDraft();
}
window.addEventListener("pagehide", clearOfferDraft);
window.addEventListener("beforeunload", clearOfferDraft);
function onLeaveCleanup() {
clearOfferDraft();
}
formEl.addEventListener("submit", async (e) => {
if (!formEl.reportValidity()) return;
e.preventDefault();
window.addEventListener("pagehide", onLeaveCleanup);
window.addEventListener("beforeunload", onLeaveCleanup);
if (json.ok) {
form.reset();
onSubmitSuccessCleanup();
}
// ----------
try {
const data = Object.fromEntries(new FormData(formEl).entries());
data.rodo = formEl.rodo?.checked === true;
const token = await grecaptcha.execute(window.FUZ_RECAPTCHA_KEY, {
action: "submit",
});
data.recaptcha = token;
const resp = await fetch("/api/contact/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
const json = await resp.json().catch(() => ({}));
showToast(
json?.ok ? successMsg : errorMsg,
json?.ok ? "success" : "error",
);
if (json?.ok) {
formEl.reset();
clearOfferDraft();
}
} catch {
showToast(errorMsg, "error");
}
});
});
</script>
</DefaultLayout>