Files
fuz-site/src/pages/kontakt/index.astro

282 lines
7.8 KiB
Plaintext

---
import DefaultLayout from "../../layouts/DefaultLayout.astro";
import MapGoogle from "../../components/maps/MapGoogle.astro";
import Markdown from "../../islands/Markdown.jsx";
import { loadYaml } from "../../lib/astro-helpers";
import "../../styles/contact.css";
type ContactData = {
title: string;
description: string;
contactFormTitle: string;
lat: number;
lng: number;
markerTitle: string;
markerAddress: string;
maps: { mapId: string };
form: {
firstName: { placeholder: string };
lastName: { placeholder: string };
email: { placeholder: string };
phone: { placeholder: string };
subject: { placeholder: string };
message: { placeholder: string; rows: number };
rodo: {
label: string;
policyLink: string;
policyTitle: string;
policyText: string;
};
submit: { label: string; title: string };
successMessage: string;
errorMessage: string;
};
};
const seo = loadYaml("./src/content/contact/seo.yaml");
const data = loadYaml<ContactData>("./src/content/contact/contact.yaml");
const apiKey = import.meta.env.PUBLIC_GOOGLE_MAPS_KEY;
const form = data.form;
---
<DefaultLayout seo={seo}>
<section class="f-section">
<div class="f-contact-grid">
{/* row 1: tytuły */}
<h1 class="f-section-title m-0">{data.title}</h1>
<h1 class="f-section-title m-0">{data.contactFormTitle}</h1>
{/* row 2: treść */}
<div class="f-contact-item">
<Markdown text={data.description} />
</div>
<div id="form">
<form id="contactForm" class="f-contact-form">
<div class="f-contact-form-inner">
<input
type="text"
name="firstName"
placeholder={form.firstName.placeholder}
class="f-input"
required
/>
<input
type="text"
name="lastName"
placeholder={form.lastName.placeholder}
class="f-input"
required
/>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<input
type="email"
name="email"
placeholder={form.email.placeholder}
class="f-input"
required
autocomplete="email"
/>
<input
type="tel"
name="phone"
placeholder={form.phone.placeholder}
class="f-input"
required
autocomplete="tel"
/>
</div>
<input
type="text"
name="subject"
placeholder={form.subject.placeholder}
class="f-input"
required
/>
<textarea
name="message"
rows={form.message.rows}
placeholder={form.message.placeholder}
class="f-input"
required></textarea>
<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 />
<span>
{form.rodo.label}
<a href={form.rodo.policyLink} title={form.rodo.policyTitle}>
{form.rodo.policyText}
</a>.
</span>
</label>
<button
type="submit"
class="btn btn-primary w-full py-3"
title={form.submit.title}
>
{form.submit.label}
</button>
</form>
</div>
</div>
<div class="mt-10">
<div class="f-contact-map">
<MapGoogle
apiKey={apiKey}
lat={data.lat}
lon={data.lng}
zoom={16}
title={data.markerTitle}
description={data.markerAddress}
showMarker={true}
mode="contact"
mapStyleId={data.maps.mapId}
/>
</div>
</div>
<div id="toast" class="f-toast"></div>
</section>
<script
is:inline
define:vars={{ siteKey: import.meta.env.PUBLIC_RECAPTCHA_SITE_KEY }}
>
window.FUZ_RECAPTCHA_KEY = siteKey;
const s = document.createElement("script");
s.src = "https://www.google.com/recaptcha/api.js?render=" + siteKey;
s.async = true;
document.head.appendChild(s);
</script>
<script
is:inline
define:vars={{
successMsg: form.successMessage,
errorMsg: form.errorMessage,
}}
>
document.addEventListener("DOMContentLoaded", () => {
const formEl = document.getElementById("contactForm");
const toast = document.getElementById("toast");
if (!formEl) return;
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);
}
function clearOfferDraft() {
try {
localStorage.removeItem(LS_KEY);
sessionStorage.removeItem(SS_INJECTED_KEY);
const wrap = document.getElementById("offerSummaryWrap");
if (wrap) wrap.classList.add("hidden");
const offerSummary = document.getElementById("offerSummary");
if (offerSummary) offerSummary.value = "";
} catch {}
}
function hydrateOfferIntoForm() {
try {
if (sessionStorage.getItem(SS_INJECTED_KEY) === "1") return;
const raw = localStorage.getItem(LS_KEY);
if (!raw) return;
const payload = JSON.parse(raw);
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()
? payload.message
: null;
if (!offerText) return;
if (wrap) wrap.classList.remove("hidden");
if (subject && !subject.value) {
subject.value = `Zapytanie: ${payload?.pkg?.name || "Oferta"}`;
}
if (offerSummary) offerSummary.value = offerText;
sessionStorage.setItem(SS_INJECTED_KEY, "1");
} catch {}
}
hydrateOfferIntoForm();
window.addEventListener("pagehide", clearOfferDraft);
window.addEventListener("beforeunload", clearOfferDraft);
formEl.addEventListener("submit", async (e) => {
if (!formEl.reportValidity()) return;
e.preventDefault();
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>