282 lines
7.8 KiB
Plaintext
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>
|