Renovation estimate form
Ask a contractor for a renovation estimate.
Un archivo de hasta 24 KB se envía como texto con la solicitud. Si es más grande, pon un enlace. Formgong no guarda un archivo aparte.
Vista previa
Código del formulario
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Renovation estimate form</title>
<style>
body { font-family: "Segoe UI", sans-serif; color: #12263a; margin: 0; background: #fff; }
main { max-width: 640px; margin: 0 auto; padding: 32px 20px 64px; }
h1 { font-size: 28px; font-weight: 650; letter-spacing: -0.02em; line-height: 1.2; }
h2 { font-size: 18px; margin: 18px 0 0; }
p { line-height: 1.5; }
form { display: grid; gap: 14px; position: relative; }
.field { display: grid; gap: 6px; }
label span, .check span { font-size: 14px; }
input, select, textarea { font: inherit; color: inherit; padding: 10px 12px; border: 1px solid #b4bdce; border-radius: 8px; width: 100%; box-sizing: border-box; background: #fff; }
textarea { min-height: 96px; }
.check { display: flex; gap: 8px; align-items: flex-start; }
.check input { width: auto; margin-top: 4px; }
button { background: #3c4ad8; color: #fff; border: 0; border-radius: 8px; padding: 12px 18px; font: inherit; cursor: pointer; }
.note { background: #fff6eb; padding: 12px 14px; border-radius: 8px; }
form [data-show-if] { display: none; }
form:has(select[name="work"] option[value="other"]:checked) [data-show-if="work"][data-show-equals="other"] { display: grid; }
</style>
</head>
<body>
<main>
<h1 data-i18n="title">Renovation estimate form</h1>
<p data-i18n="lede">Ask a contractor for a renovation estimate.</p>
<form id="fg-form" action="https://formgong.com/submit" method="POST">
<input type="hidden" name="access_key" value="fk_your_access_key">
<div aria-hidden="true" style="position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden"><label>Leave this field empty<input name="botcheck" tabindex="-1" autocomplete="off"></label></div>
<section><h2 data-i18n="section.place">Place</h2>
<div class="field"><label for="fg-property_type"><span data-i18n="property_type">Property</span></label><select id="fg-property_type" name="property_type" required><option value="">—</option><option value="apartment" data-i18n="property_type.apartment">Apartment</option><option value="house" data-i18n="property_type.house">House</option><option value="office" data-i18n="property_type.office">Office</option></select></div>
<div class="field"><label for="fg-city"><span data-i18n="city">City</span></label><input id="fg-city" name="city" type="text" required maxlength="80" autocomplete="address-level2"></div>
<div class="field"><label for="fg-area_m2"><span data-i18n="area_m2">Area, m²</span></label><input id="fg-area_m2" name="area_m2" type="number" required min="1" max="10000"></div></section>
<section><h2 data-i18n="section.work">Work</h2>
<div class="field"><label for="fg-work"><span data-i18n="work">Work</span></label><select id="fg-work" name="work" required><option value="">—</option><option value="kitchen" data-i18n="work.kitchen">Kitchen</option><option value="bathroom" data-i18n="work.bathroom">Bathroom</option><option value="full" data-i18n="work.full">Whole place</option><option value="other" data-i18n="work.other">Other</option></select></div>
<div class="field" data-show-if="work" data-show-equals="other"><label for="fg-work_other"><span data-i18n="work_other">What should be renovated?</span></label><input id="fg-work_other" name="work_other" type="text" maxlength="120"></div>
<div class="field"><label for="fg-timing"><span data-i18n="timing">When?</span></label><select id="fg-timing" name="timing" required><option value="">—</option><option value="asap" data-i18n="timing.asap">As soon as possible</option><option value="soon" data-i18n="timing.soon">In 1–3 months</option><option value="flexible" data-i18n="timing.flexible">Flexible</option></select></div>
<div class="field"><label for="fg-budget"><span data-i18n="budget">Budget</span></label><select id="fg-budget" name="budget" required><option value="">—</option><option value="under_5" data-i18n="budget.under_5">Under 5,000</option><option value="mid" data-i18n="budget.mid">5,000–15,000</option><option value="high" data-i18n="budget.high">15,000–40,000</option><option value="unsure" data-i18n="budget.unsure">Not sure</option></select></div></section>
<section><h2 data-i18n="section.contact">Contact</h2>
<div class="field"><label for="fg-name"><span data-i18n="name">Name</span></label><input id="fg-name" name="name" type="text" required maxlength="80" autocomplete="name"></div>
<div class="field"><label for="fg-email"><span data-i18n="email">Email</span></label><input id="fg-email" name="email" type="email" required maxlength="120" autocomplete="email"></div>
<div class="field"><label for="fg-phone"><span data-i18n="phone">Phone</span></label><input id="fg-phone" name="phone" type="tel" required maxlength="40" autocomplete="tel"></div></section>
<section><h2 data-i18n="section.plans">Plans</h2>
<div class="field"><label for="fg-plans"><span data-i18n="plans">A plan or photo, up to 24 KB</span></label><input id="fg-plans" name="plans" type="file" accept="image/*,.pdf"></div>
<div class="field"><label for="fg-plans_url"><span data-i18n="plans_url">Or a link</span></label><input id="fg-plans_url" name="plans_url" type="url" maxlength="300"></div>
<div class="field"><label for="fg-details"><span data-i18n="details">What should the estimate cover?</span></label><textarea id="fg-details" name="details" maxlength="800"></textarea></div></section>
<section><h2 data-i18n="section.consent">Consent</h2>
<div class="field"><label class="check"><input id="fg-consent_contact" name="consent_contact" type="checkbox" value="yes" required><span data-i18n="consent_contact">I agree to be contacted about this request.</span></label></div>
<div class="field"><label class="check"><input id="fg-consent_privacy" name="consent_privacy" type="checkbox" value="yes" required><span data-i18n="consent_privacy">I agree that these answers are stored so the request can be handled.</span></label></div></section>
<!-- Turnstile hook: enable Turnstile on this form in Formgong, then set your site key.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div>
-->
<button type="submit" data-i18n="submit">Send</button>
<p data-status role="status"></p>
</form>
</main>
<script type="application/json" id="fg-labels">{"uk":{"title":"Форма запиту кошторису ремонту","lede":"Запит кошторису ремонту в підрядника.","submit":"Надіслати","thanks":"Надіслано. Дякуємо.","file_too_big":"Файл більший за 24 КБ. Додайте посилання.","section.place":"Об'єкт","property_type":"Об'єкт","property_type.apartment":"Квартира","property_type.house":"Будинок","property_type.office":"Офіс","city":"Місто","area_m2":"Площа, м²","section.work":"Роботи","work":"Роботи","work.kitchen":"Кухня","work.bathroom":"Ванна","work.full":"Усе приміщення","work.other":"Інше","work_other":"Що ремонтувати?","timing":"Коли?","timing.asap":"Якнайшвидше","timing.soon":"За 1–3 місяці","timing.flexible":"Гнучко","budget":"Бюджет","budget.under_5":"До 5 000","budget.mid":"5 000–15 000","budget.high":"15 000–40 000","budget.unsure":"Ще не знаю","section.contact":"Контакт","name":"Ім'я","email":"Електронна пошта","phone":"Телефон","section.plans":"Креслення","plans":"План або фото, до 24 КБ","plans_url":"Або посилання","details":"Що має покрити кошторис?","section.consent":"Згода","consent_contact":"Погоджуюсь, щоб зі мною зв'язалися щодо цієї заявки.","consent_privacy":"Погоджуюсь, що ці відповіді зберігаються, щоб заявку можна було обробити."},"en":{"title":"Renovation estimate form","lede":"Ask a contractor for a renovation estimate.","submit":"Send","thanks":"Sent. Thank you.","file_too_big":"That file is over 24 KB. Paste a link instead.","section.place":"Place","property_type":"Property","property_type.apartment":"Apartment","property_type.house":"House","property_type.office":"Office","city":"City","area_m2":"Area, m²","section.work":"Work","work":"Work","work.kitchen":"Kitchen","work.bathroom":"Bathroom","work.full":"Whole place","work.other":"Other","work_other":"What should be renovated?","timing":"When?","timing.asap":"As soon as possible","timing.soon":"In 1–3 months","timing.flexible":"Flexible","budget":"Budget","budget.under_5":"Under 5,000","budget.mid":"5,000–15,000","budget.high":"15,000–40,000","budget.unsure":"Not sure","section.contact":"Contact","name":"Name","email":"Email","phone":"Phone","section.plans":"Plans","plans":"A plan or photo, up to 24 KB","plans_url":"Or a link","details":"What should the estimate cover?","section.consent":"Consent","consent_contact":"I agree to be contacted about this request.","consent_privacy":"I agree that these answers are stored so the request can be handled."},"pl":{"title":"Formularz wyceny remontu","lede":"Prośba do wykonawcy o wycenę remontu.","submit":"Wyślij","thanks":"Wysłano. Dziękujemy.","file_too_big":"Plik ma ponad 24 KB. Wklej link.","section.place":"Obiekt","property_type":"Nieruchomość","property_type.apartment":"Mieszkanie","property_type.house":"Dom","property_type.office":"Biuro","city":"Miasto","area_m2":"Powierzchnia, m²","section.work":"Prace","work":"Zakres","work.kitchen":"Kuchnia","work.bathroom":"Łazienka","work.full":"Całość","work.other":"Inne","work_other":"Co remontować?","timing":"Kiedy?","timing.asap":"Jak najszybciej","timing.soon":"Za 1–3 miesiące","timing.flexible":"Elastycznie","budget":"Budżet","budget.under_5":"Do 5 000","budget.mid":"5 000–15 000","budget.high":"15 000–40 000","budget.unsure":"Nie wiem","section.contact":"Kontakt","name":"Imię","email":"E-mail","phone":"Telefon","section.plans":"Rysunki","plans":"Rysunek albo zdjęcie, do 24 KB","plans_url":"Albo link","details":"Co wycena ma obejmować?","section.consent":"Zgoda","consent_contact":"Zgadzam się na kontakt w sprawie tego zgłoszenia.","consent_privacy":"Zgadzam się, aby te odpowiedzi zostały zapisane w celu obsługi zgłoszenia."},"tr":{"title":"Tadilat keşif formu","lede":"Bir yükleniciden tadilat keşfi isteyin.","submit":"Gönder","thanks":"Gönderildi. Teşekkürler.","file_too_big":"Dosya 24 KB'den büyük. Bağlantı ekleyin.","section.place":"Yer","property_type":"Yer","property_type.apartment":"Daire","property_type.house":"Müstakil ev","property_type.office":"Ofis","city":"Şehir","area_m2":"Alan, m²","section.work":"İş","work":"İş","work.kitchen":"Mutfak","work.bathroom":"Banyo","work.full":"Tüm mekan","work.other":"Diğer","work_other":"Neresi yenilensin?","timing":"Ne zaman?","timing.asap":"En kısa sürede","timing.soon":"1–3 ay içinde","timing.flexible":"Esnek","budget":"Bütçe","budget.under_5":"5.000 altı","budget.mid":"5.000–15.000","budget.high":"15.000–40.000","budget.unsure":"Emin değilim","section.contact":"İletişim","name":"Ad","email":"E-posta","phone":"Telefon","section.plans":"Plan","plans":"Plan ya da fotoğraf, en fazla 24 KB","plans_url":"Ya da bağlantı","details":"Keşif neyi kapsasın?","section.consent":"Onay","consent_contact":"Bu talep hakkında benimle iletişime geçilmesini kabul ediyorum.","consent_privacy":"Bu yanıtların talebin işlenmesi için saklanmasını kabul ediyorum."}}</script>
<script>
(function () {
var MAX = 24576;
var form = document.getElementById("fg-form");
var packNode = document.getElementById("fg-labels");
if (!form || !packNode) return;
var pack = JSON.parse(packNode.textContent);
function code() {
var q = new URLSearchParams(location.search).get("lang") || "";
var raw = (q || document.documentElement.lang || (navigator.language || "en")).slice(0, 2).toLowerCase();
return pack[raw] ? raw : "en";
}
function apply() {
var dict = pack[code()];
document.querySelectorAll("[data-i18n]").forEach(function (node) {
var key = node.getAttribute("data-i18n");
if (key && typeof dict[key] === "string") node.textContent = dict[key];
});
}
apply();
var status = form.querySelector("[data-status]");
form.addEventListener("submit", function (event) {
event.preventDefault();
var dict = pack[code()];
var body = {};
var files = [];
new FormData(form).forEach(function (value, key) {
if (typeof File !== "undefined" && value instanceof File) {
if (value.size) files.push([key, value]);
return;
}
body[key] = String(value);
});
if (files.some(function (pair) { return pair[1].size > MAX; })) {
if (status) status.textContent = dict.file_too_big;
return;
}
Promise.all(files.map(function (pair) {
return readFile(pair[1]).then(function (data) {
body[pair[0] + "_filename"] = pair[1].name;
body[pair[0] + "_data"] = data;
});
})).then(function () {
return fetch(form.action, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body)
});
}).then(function (response) { return response.json(); }).then(function (result) {
if (status) status.textContent = result && result.success ? dict.thanks : ((result && result.message) || "error");
}).catch(function () {
if (status) status.textContent = "error";
});
});
function readFile(file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function () {
var raw = String(reader.result || "");
var comma = raw.indexOf(",");
resolve(comma === -1 ? raw : raw.slice(comma + 1));
};
reader.onerror = function () { reject(reader.error); };
reader.readAsDataURL(file);
});
}
})();
</script>
</body>
</html>
// Generated from src/content/industries.ts — npm run templates:write
import { useState } from "react";
const ENDPOINT = "https://formgong.com/submit";
const FILE_MAX = 24576;
const LABELS = {
"uk": {
"title": "Форма запиту кошторису ремонту",
"lede": "Запит кошторису ремонту в підрядника.",
"submit": "Надіслати",
"thanks": "Надіслано. Дякуємо.",
"file_too_big": "Файл більший за 24 КБ. Додайте посилання.",
"section.place": "Об'єкт",
"property_type": "Об'єкт",
"property_type.apartment": "Квартира",
"property_type.house": "Будинок",
"property_type.office": "Офіс",
"city": "Місто",
"area_m2": "Площа, м²",
"section.work": "Роботи",
"work": "Роботи",
"work.kitchen": "Кухня",
"work.bathroom": "Ванна",
"work.full": "Усе приміщення",
"work.other": "Інше",
"work_other": "Що ремонтувати?",
"timing": "Коли?",
"timing.asap": "Якнайшвидше",
"timing.soon": "За 1–3 місяці",
"timing.flexible": "Гнучко",
"budget": "Бюджет",
"budget.under_5": "До 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Ще не знаю",
"section.contact": "Контакт",
"name": "Ім'я",
"email": "Електронна пошта",
"phone": "Телефон",
"section.plans": "Креслення",
"plans": "План або фото, до 24 КБ",
"plans_url": "Або посилання",
"details": "Що має покрити кошторис?",
"section.consent": "Згода",
"consent_contact": "Погоджуюсь, щоб зі мною зв'язалися щодо цієї заявки.",
"consent_privacy": "Погоджуюсь, що ці відповіді зберігаються, щоб заявку можна було обробити."
},
"en": {
"title": "Renovation estimate form",
"lede": "Ask a contractor for a renovation estimate.",
"submit": "Send",
"thanks": "Sent. Thank you.",
"file_too_big": "That file is over 24 KB. Paste a link instead.",
"section.place": "Place",
"property_type": "Property",
"property_type.apartment": "Apartment",
"property_type.house": "House",
"property_type.office": "Office",
"city": "City",
"area_m2": "Area, m²",
"section.work": "Work",
"work": "Work",
"work.kitchen": "Kitchen",
"work.bathroom": "Bathroom",
"work.full": "Whole place",
"work.other": "Other",
"work_other": "What should be renovated?",
"timing": "When?",
"timing.asap": "As soon as possible",
"timing.soon": "In 1–3 months",
"timing.flexible": "Flexible",
"budget": "Budget",
"budget.under_5": "Under 5,000",
"budget.mid": "5,000–15,000",
"budget.high": "15,000–40,000",
"budget.unsure": "Not sure",
"section.contact": "Contact",
"name": "Name",
"email": "Email",
"phone": "Phone",
"section.plans": "Plans",
"plans": "A plan or photo, up to 24 KB",
"plans_url": "Or a link",
"details": "What should the estimate cover?",
"section.consent": "Consent",
"consent_contact": "I agree to be contacted about this request.",
"consent_privacy": "I agree that these answers are stored so the request can be handled."
},
"pl": {
"title": "Formularz wyceny remontu",
"lede": "Prośba do wykonawcy o wycenę remontu.",
"submit": "Wyślij",
"thanks": "Wysłano. Dziękujemy.",
"file_too_big": "Plik ma ponad 24 KB. Wklej link.",
"section.place": "Obiekt",
"property_type": "Nieruchomość",
"property_type.apartment": "Mieszkanie",
"property_type.house": "Dom",
"property_type.office": "Biuro",
"city": "Miasto",
"area_m2": "Powierzchnia, m²",
"section.work": "Prace",
"work": "Zakres",
"work.kitchen": "Kuchnia",
"work.bathroom": "Łazienka",
"work.full": "Całość",
"work.other": "Inne",
"work_other": "Co remontować?",
"timing": "Kiedy?",
"timing.asap": "Jak najszybciej",
"timing.soon": "Za 1–3 miesiące",
"timing.flexible": "Elastycznie",
"budget": "Budżet",
"budget.under_5": "Do 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Nie wiem",
"section.contact": "Kontakt",
"name": "Imię",
"email": "E-mail",
"phone": "Telefon",
"section.plans": "Rysunki",
"plans": "Rysunek albo zdjęcie, do 24 KB",
"plans_url": "Albo link",
"details": "Co wycena ma obejmować?",
"section.consent": "Zgoda",
"consent_contact": "Zgadzam się na kontakt w sprawie tego zgłoszenia.",
"consent_privacy": "Zgadzam się, aby te odpowiedzi zostały zapisane w celu obsługi zgłoszenia."
},
"tr": {
"title": "Tadilat keşif formu",
"lede": "Bir yükleniciden tadilat keşfi isteyin.",
"submit": "Gönder",
"thanks": "Gönderildi. Teşekkürler.",
"file_too_big": "Dosya 24 KB'den büyük. Bağlantı ekleyin.",
"section.place": "Yer",
"property_type": "Yer",
"property_type.apartment": "Daire",
"property_type.house": "Müstakil ev",
"property_type.office": "Ofis",
"city": "Şehir",
"area_m2": "Alan, m²",
"section.work": "İş",
"work": "İş",
"work.kitchen": "Mutfak",
"work.bathroom": "Banyo",
"work.full": "Tüm mekan",
"work.other": "Diğer",
"work_other": "Neresi yenilensin?",
"timing": "Ne zaman?",
"timing.asap": "En kısa sürede",
"timing.soon": "1–3 ay içinde",
"timing.flexible": "Esnek",
"budget": "Bütçe",
"budget.under_5": "5.000 altı",
"budget.mid": "5.000–15.000",
"budget.high": "15.000–40.000",
"budget.unsure": "Emin değilim",
"section.contact": "İletişim",
"name": "Ad",
"email": "E-posta",
"phone": "Telefon",
"section.plans": "Plan",
"plans": "Plan ya da fotoğraf, en fazla 24 KB",
"plans_url": "Ya da bağlantı",
"details": "Keşif neyi kapsasın?",
"section.consent": "Onay",
"consent_contact": "Bu talep hakkında benimle iletişime geçilmesini kabul ediyorum.",
"consent_privacy": "Bu yanıtların talebin işlenmesi için saklanmasını kabul ediyorum."
}
};
export function IndustryForm({ lang = "en", accessKey = "fk_your_access_key" }) {
const t = LABELS[lang] || LABELS.en;
const [status, setStatus] = useState("");
const [values, setValues] = useState({ work: "" });
function setField(name, value) {
setValues((current) => ({ ...current, [name]: value }));
}
async function onSubmit(event) {
const form = event.currentTarget;
event.preventDefault();
const body = {};
const files = [];
for (const [key, value] of new FormData(form).entries()) {
if (value instanceof File) {
if (value.size) files.push([key, value]);
continue;
}
body[key] = String(value);
}
if (files.some((pair) => pair[1].size > FILE_MAX)) {
setStatus(t.file_too_big);
return;
}
for (const [key, file] of files) {
body[key + "_filename"] = file.name;
body[key + "_data"] = await readFile(file);
}
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body),
});
const result = await response.json();
setStatus(result.success ? t.thanks : (result.message || "error"));
}
return (
<form onSubmit={onSubmit}>
<h1>{t.title}</h1>
<p>{t.lede}</p>
<input type="hidden" name="access_key" value={accessKey} />
<div aria-hidden="true" style={{ position: "absolute", left: "-10000px", width: 1, height: 1, overflow: "hidden" }}>
<input name="botcheck" tabIndex={-1} autoComplete="off" />
</div>
<section><h2>{t["section.place"]}</h2>
<label><span>{t["property_type"]}</span><select name="property_type" required><option value="">—</option><option value="apartment">{t["property_type.apartment"]}</option><option value="house">{t["property_type.house"]}</option><option value="office">{t["property_type.office"]}</option></select></label>
<label><span>{t["city"]}</span><input name="city" type="text" required maxLength={80} autoComplete="address-level2" /></label>
<label><span>{t["area_m2"]}</span><input name="area_m2" type="number" required min={1} max={10000} /></label></section>
<section><h2>{t["section.work"]}</h2>
<label><span>{t["work"]}</span><select name="work" required value={values["work"]} onChange={(event) => setField("work", event.target.value)}><option value="">—</option><option value="kitchen">{t["work.kitchen"]}</option><option value="bathroom">{t["work.bathroom"]}</option><option value="full">{t["work.full"]}</option><option value="other">{t["work.other"]}</option></select></label>
{values["work"] === "other" ? (<label><span>{t["work_other"]}</span><input name="work_other" type="text" maxLength={120} /></label>) : null}
<label><span>{t["timing"]}</span><select name="timing" required><option value="">—</option><option value="asap">{t["timing.asap"]}</option><option value="soon">{t["timing.soon"]}</option><option value="flexible">{t["timing.flexible"]}</option></select></label>
<label><span>{t["budget"]}</span><select name="budget" required><option value="">—</option><option value="under_5">{t["budget.under_5"]}</option><option value="mid">{t["budget.mid"]}</option><option value="high">{t["budget.high"]}</option><option value="unsure">{t["budget.unsure"]}</option></select></label></section>
<section><h2>{t["section.contact"]}</h2>
<label><span>{t["name"]}</span><input name="name" type="text" required maxLength={80} autoComplete="name" /></label>
<label><span>{t["email"]}</span><input name="email" type="email" required maxLength={120} autoComplete="email" /></label>
<label><span>{t["phone"]}</span><input name="phone" type="tel" required maxLength={40} autoComplete="tel" /></label></section>
<section><h2>{t["section.plans"]}</h2>
<label><span>{t["plans"]}</span><input name="plans" type="file" accept="image/*,.pdf" /></label>
<label><span>{t["plans_url"]}</span><input name="plans_url" type="url" maxLength={300} /></label>
<label><span>{t["details"]}</span><textarea name="details" maxLength={800} /></label></section>
<section><h2>{t["section.consent"]}</h2>
<label className="check"><input name="consent_contact" type="checkbox" value="yes" required /><span>{t["consent_contact"]}</span></label>
<label className="check"><input name="consent_privacy" type="checkbox" value="yes" required /><span>{t["consent_privacy"]}</span></label></section>
{/* Turnstile hook: enable Turnstile on this form in Formgong, load https://challenges.cloudflare.com/turnstile/v0/api.js, and render <div className="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY" /> */}
<button type="submit">{t.submit}</button>
<p>{status}</p>
</form>
);
}
function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const raw = String(reader.result || "");
const comma = raw.indexOf(",");
resolve(comma === -1 ? raw : raw.slice(comma + 1));
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
<!-- Generated from src/content/industries.ts — npm run templates:write -->
<script setup>
import { reactive, ref, computed } from "vue";
const props = defineProps({
lang: { type: String, default: "en" },
accessKey: { type: String, default: "fk_your_access_key" },
});
const ENDPOINT = "https://formgong.com/submit";
const FILE_MAX = 24576;
const LABELS = {
"uk": {
"title": "Форма запиту кошторису ремонту",
"lede": "Запит кошторису ремонту в підрядника.",
"submit": "Надіслати",
"thanks": "Надіслано. Дякуємо.",
"file_too_big": "Файл більший за 24 КБ. Додайте посилання.",
"section.place": "Об'єкт",
"property_type": "Об'єкт",
"property_type.apartment": "Квартира",
"property_type.house": "Будинок",
"property_type.office": "Офіс",
"city": "Місто",
"area_m2": "Площа, м²",
"section.work": "Роботи",
"work": "Роботи",
"work.kitchen": "Кухня",
"work.bathroom": "Ванна",
"work.full": "Усе приміщення",
"work.other": "Інше",
"work_other": "Що ремонтувати?",
"timing": "Коли?",
"timing.asap": "Якнайшвидше",
"timing.soon": "За 1–3 місяці",
"timing.flexible": "Гнучко",
"budget": "Бюджет",
"budget.under_5": "До 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Ще не знаю",
"section.contact": "Контакт",
"name": "Ім'я",
"email": "Електронна пошта",
"phone": "Телефон",
"section.plans": "Креслення",
"plans": "План або фото, до 24 КБ",
"plans_url": "Або посилання",
"details": "Що має покрити кошторис?",
"section.consent": "Згода",
"consent_contact": "Погоджуюсь, щоб зі мною зв'язалися щодо цієї заявки.",
"consent_privacy": "Погоджуюсь, що ці відповіді зберігаються, щоб заявку можна було обробити."
},
"en": {
"title": "Renovation estimate form",
"lede": "Ask a contractor for a renovation estimate.",
"submit": "Send",
"thanks": "Sent. Thank you.",
"file_too_big": "That file is over 24 KB. Paste a link instead.",
"section.place": "Place",
"property_type": "Property",
"property_type.apartment": "Apartment",
"property_type.house": "House",
"property_type.office": "Office",
"city": "City",
"area_m2": "Area, m²",
"section.work": "Work",
"work": "Work",
"work.kitchen": "Kitchen",
"work.bathroom": "Bathroom",
"work.full": "Whole place",
"work.other": "Other",
"work_other": "What should be renovated?",
"timing": "When?",
"timing.asap": "As soon as possible",
"timing.soon": "In 1–3 months",
"timing.flexible": "Flexible",
"budget": "Budget",
"budget.under_5": "Under 5,000",
"budget.mid": "5,000–15,000",
"budget.high": "15,000–40,000",
"budget.unsure": "Not sure",
"section.contact": "Contact",
"name": "Name",
"email": "Email",
"phone": "Phone",
"section.plans": "Plans",
"plans": "A plan or photo, up to 24 KB",
"plans_url": "Or a link",
"details": "What should the estimate cover?",
"section.consent": "Consent",
"consent_contact": "I agree to be contacted about this request.",
"consent_privacy": "I agree that these answers are stored so the request can be handled."
},
"pl": {
"title": "Formularz wyceny remontu",
"lede": "Prośba do wykonawcy o wycenę remontu.",
"submit": "Wyślij",
"thanks": "Wysłano. Dziękujemy.",
"file_too_big": "Plik ma ponad 24 KB. Wklej link.",
"section.place": "Obiekt",
"property_type": "Nieruchomość",
"property_type.apartment": "Mieszkanie",
"property_type.house": "Dom",
"property_type.office": "Biuro",
"city": "Miasto",
"area_m2": "Powierzchnia, m²",
"section.work": "Prace",
"work": "Zakres",
"work.kitchen": "Kuchnia",
"work.bathroom": "Łazienka",
"work.full": "Całość",
"work.other": "Inne",
"work_other": "Co remontować?",
"timing": "Kiedy?",
"timing.asap": "Jak najszybciej",
"timing.soon": "Za 1–3 miesiące",
"timing.flexible": "Elastycznie",
"budget": "Budżet",
"budget.under_5": "Do 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Nie wiem",
"section.contact": "Kontakt",
"name": "Imię",
"email": "E-mail",
"phone": "Telefon",
"section.plans": "Rysunki",
"plans": "Rysunek albo zdjęcie, do 24 KB",
"plans_url": "Albo link",
"details": "Co wycena ma obejmować?",
"section.consent": "Zgoda",
"consent_contact": "Zgadzam się na kontakt w sprawie tego zgłoszenia.",
"consent_privacy": "Zgadzam się, aby te odpowiedzi zostały zapisane w celu obsługi zgłoszenia."
},
"tr": {
"title": "Tadilat keşif formu",
"lede": "Bir yükleniciden tadilat keşfi isteyin.",
"submit": "Gönder",
"thanks": "Gönderildi. Teşekkürler.",
"file_too_big": "Dosya 24 KB'den büyük. Bağlantı ekleyin.",
"section.place": "Yer",
"property_type": "Yer",
"property_type.apartment": "Daire",
"property_type.house": "Müstakil ev",
"property_type.office": "Ofis",
"city": "Şehir",
"area_m2": "Alan, m²",
"section.work": "İş",
"work": "İş",
"work.kitchen": "Mutfak",
"work.bathroom": "Banyo",
"work.full": "Tüm mekan",
"work.other": "Diğer",
"work_other": "Neresi yenilensin?",
"timing": "Ne zaman?",
"timing.asap": "En kısa sürede",
"timing.soon": "1–3 ay içinde",
"timing.flexible": "Esnek",
"budget": "Bütçe",
"budget.under_5": "5.000 altı",
"budget.mid": "5.000–15.000",
"budget.high": "15.000–40.000",
"budget.unsure": "Emin değilim",
"section.contact": "İletişim",
"name": "Ad",
"email": "E-posta",
"phone": "Telefon",
"section.plans": "Plan",
"plans": "Plan ya da fotoğraf, en fazla 24 KB",
"plans_url": "Ya da bağlantı",
"details": "Keşif neyi kapsasın?",
"section.consent": "Onay",
"consent_contact": "Bu talep hakkında benimle iletişime geçilmesini kabul ediyorum.",
"consent_privacy": "Bu yanıtların talebin işlenmesi için saklanmasını kabul ediyorum."
}
};
const t = computed(() => LABELS[props.lang] || LABELS.en);
const status = ref("");
const values = reactive({ work: "" });
function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const raw = String(reader.result || "");
const comma = raw.indexOf(",");
resolve(comma === -1 ? raw : raw.slice(comma + 1));
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
async function onSubmit(event) {
const form = event.currentTarget;
event.preventDefault();
const body = {};
const files = [];
for (const [key, value] of new FormData(form).entries()) {
if (value instanceof File) {
if (value.size) files.push([key, value]);
continue;
}
body[key] = String(value);
}
if (files.some((pair) => pair[1].size > FILE_MAX)) {
status.value = t.value.file_too_big;
return;
}
for (const [key, file] of files) {
body[key + "_filename"] = file.name;
body[key + "_data"] = await readFile(file);
}
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body),
});
const result = await response.json();
status.value = result.success ? t.value.thanks : (result.message || "error");
}
</script>
<template>
<form @submit.prevent="onSubmit">
<h1>{{ t.title }}</h1>
<p>{{ t.lede }}</p>
<input type="hidden" name="access_key" :value="accessKey">
<div aria-hidden="true" style="position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden">
<input name="botcheck" tabindex="-1" autocomplete="off">
</div>
<section><h2>{{ t["section.place"] }}</h2>
<label><span>{{ t["property_type"] }}</span><select name="property_type" required><option value="">—</option><option value="apartment">{{ t["property_type.apartment"] }}</option><option value="house">{{ t["property_type.house"] }}</option><option value="office">{{ t["property_type.office"] }}</option></select></label>
<label><span>{{ t["city"] }}</span><input name="city" type="text" required maxlength="80" autocomplete="address-level2"></label>
<label><span>{{ t["area_m2"] }}</span><input name="area_m2" type="number" required min="1" max="10000"></label></section>
<section><h2>{{ t["section.work"] }}</h2>
<label><span>{{ t["work"] }}</span><select name="work" required v-model="values.work"><option value="">—</option><option value="kitchen">{{ t["work.kitchen"] }}</option><option value="bathroom">{{ t["work.bathroom"] }}</option><option value="full">{{ t["work.full"] }}</option><option value="other">{{ t["work.other"] }}</option></select></label>
<template v-if="values["work"] === 'other'"><label><span>{{ t["work_other"] }}</span><input name="work_other" type="text" maxlength="120"></label></template>
<label><span>{{ t["timing"] }}</span><select name="timing" required><option value="">—</option><option value="asap">{{ t["timing.asap"] }}</option><option value="soon">{{ t["timing.soon"] }}</option><option value="flexible">{{ t["timing.flexible"] }}</option></select></label>
<label><span>{{ t["budget"] }}</span><select name="budget" required><option value="">—</option><option value="under_5">{{ t["budget.under_5"] }}</option><option value="mid">{{ t["budget.mid"] }}</option><option value="high">{{ t["budget.high"] }}</option><option value="unsure">{{ t["budget.unsure"] }}</option></select></label></section>
<section><h2>{{ t["section.contact"] }}</h2>
<label><span>{{ t["name"] }}</span><input name="name" type="text" required maxlength="80" autocomplete="name"></label>
<label><span>{{ t["email"] }}</span><input name="email" type="email" required maxlength="120" autocomplete="email"></label>
<label><span>{{ t["phone"] }}</span><input name="phone" type="tel" required maxlength="40" autocomplete="tel"></label></section>
<section><h2>{{ t["section.plans"] }}</h2>
<label><span>{{ t["plans"] }}</span><input name="plans" type="file" accept="image/*,.pdf"></label>
<label><span>{{ t["plans_url"] }}</span><input name="plans_url" type="url" maxlength="300"></label>
<label><span>{{ t["details"] }}</span><textarea name="details" maxlength="800"></textarea></label></section>
<section><h2>{{ t["section.consent"] }}</h2>
<label class="check"><input name="consent_contact" type="checkbox" value="yes" required><span>{{ t["consent_contact"] }}</span></label>
<label class="check"><input name="consent_privacy" type="checkbox" value="yes" required><span>{{ t["consent_privacy"] }}</span></label></section>
<!-- Turnstile hook: enable Turnstile on this form in Formgong, add the api.js script, and a div.cf-turnstile with data-sitekey="YOUR_TURNSTILE_SITE_KEY". -->
<button type="submit">{{ t.submit }}</button>
<p>{{ status }}</p>
</form>
</template>
<!-- Generated from src/content/industries.ts — npm run templates:write -->
<script>
let { lang = "en", accessKey = "fk_your_access_key" } = $props();
const ENDPOINT = "https://formgong.com/submit";
const FILE_MAX = 24576;
const LABELS = {
"uk": {
"title": "Форма запиту кошторису ремонту",
"lede": "Запит кошторису ремонту в підрядника.",
"submit": "Надіслати",
"thanks": "Надіслано. Дякуємо.",
"file_too_big": "Файл більший за 24 КБ. Додайте посилання.",
"section.place": "Об'єкт",
"property_type": "Об'єкт",
"property_type.apartment": "Квартира",
"property_type.house": "Будинок",
"property_type.office": "Офіс",
"city": "Місто",
"area_m2": "Площа, м²",
"section.work": "Роботи",
"work": "Роботи",
"work.kitchen": "Кухня",
"work.bathroom": "Ванна",
"work.full": "Усе приміщення",
"work.other": "Інше",
"work_other": "Що ремонтувати?",
"timing": "Коли?",
"timing.asap": "Якнайшвидше",
"timing.soon": "За 1–3 місяці",
"timing.flexible": "Гнучко",
"budget": "Бюджет",
"budget.under_5": "До 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Ще не знаю",
"section.contact": "Контакт",
"name": "Ім'я",
"email": "Електронна пошта",
"phone": "Телефон",
"section.plans": "Креслення",
"plans": "План або фото, до 24 КБ",
"plans_url": "Або посилання",
"details": "Що має покрити кошторис?",
"section.consent": "Згода",
"consent_contact": "Погоджуюсь, щоб зі мною зв'язалися щодо цієї заявки.",
"consent_privacy": "Погоджуюсь, що ці відповіді зберігаються, щоб заявку можна було обробити."
},
"en": {
"title": "Renovation estimate form",
"lede": "Ask a contractor for a renovation estimate.",
"submit": "Send",
"thanks": "Sent. Thank you.",
"file_too_big": "That file is over 24 KB. Paste a link instead.",
"section.place": "Place",
"property_type": "Property",
"property_type.apartment": "Apartment",
"property_type.house": "House",
"property_type.office": "Office",
"city": "City",
"area_m2": "Area, m²",
"section.work": "Work",
"work": "Work",
"work.kitchen": "Kitchen",
"work.bathroom": "Bathroom",
"work.full": "Whole place",
"work.other": "Other",
"work_other": "What should be renovated?",
"timing": "When?",
"timing.asap": "As soon as possible",
"timing.soon": "In 1–3 months",
"timing.flexible": "Flexible",
"budget": "Budget",
"budget.under_5": "Under 5,000",
"budget.mid": "5,000–15,000",
"budget.high": "15,000–40,000",
"budget.unsure": "Not sure",
"section.contact": "Contact",
"name": "Name",
"email": "Email",
"phone": "Phone",
"section.plans": "Plans",
"plans": "A plan or photo, up to 24 KB",
"plans_url": "Or a link",
"details": "What should the estimate cover?",
"section.consent": "Consent",
"consent_contact": "I agree to be contacted about this request.",
"consent_privacy": "I agree that these answers are stored so the request can be handled."
},
"pl": {
"title": "Formularz wyceny remontu",
"lede": "Prośba do wykonawcy o wycenę remontu.",
"submit": "Wyślij",
"thanks": "Wysłano. Dziękujemy.",
"file_too_big": "Plik ma ponad 24 KB. Wklej link.",
"section.place": "Obiekt",
"property_type": "Nieruchomość",
"property_type.apartment": "Mieszkanie",
"property_type.house": "Dom",
"property_type.office": "Biuro",
"city": "Miasto",
"area_m2": "Powierzchnia, m²",
"section.work": "Prace",
"work": "Zakres",
"work.kitchen": "Kuchnia",
"work.bathroom": "Łazienka",
"work.full": "Całość",
"work.other": "Inne",
"work_other": "Co remontować?",
"timing": "Kiedy?",
"timing.asap": "Jak najszybciej",
"timing.soon": "Za 1–3 miesiące",
"timing.flexible": "Elastycznie",
"budget": "Budżet",
"budget.under_5": "Do 5 000",
"budget.mid": "5 000–15 000",
"budget.high": "15 000–40 000",
"budget.unsure": "Nie wiem",
"section.contact": "Kontakt",
"name": "Imię",
"email": "E-mail",
"phone": "Telefon",
"section.plans": "Rysunki",
"plans": "Rysunek albo zdjęcie, do 24 KB",
"plans_url": "Albo link",
"details": "Co wycena ma obejmować?",
"section.consent": "Zgoda",
"consent_contact": "Zgadzam się na kontakt w sprawie tego zgłoszenia.",
"consent_privacy": "Zgadzam się, aby te odpowiedzi zostały zapisane w celu obsługi zgłoszenia."
},
"tr": {
"title": "Tadilat keşif formu",
"lede": "Bir yükleniciden tadilat keşfi isteyin.",
"submit": "Gönder",
"thanks": "Gönderildi. Teşekkürler.",
"file_too_big": "Dosya 24 KB'den büyük. Bağlantı ekleyin.",
"section.place": "Yer",
"property_type": "Yer",
"property_type.apartment": "Daire",
"property_type.house": "Müstakil ev",
"property_type.office": "Ofis",
"city": "Şehir",
"area_m2": "Alan, m²",
"section.work": "İş",
"work": "İş",
"work.kitchen": "Mutfak",
"work.bathroom": "Banyo",
"work.full": "Tüm mekan",
"work.other": "Diğer",
"work_other": "Neresi yenilensin?",
"timing": "Ne zaman?",
"timing.asap": "En kısa sürede",
"timing.soon": "1–3 ay içinde",
"timing.flexible": "Esnek",
"budget": "Bütçe",
"budget.under_5": "5.000 altı",
"budget.mid": "5.000–15.000",
"budget.high": "15.000–40.000",
"budget.unsure": "Emin değilim",
"section.contact": "İletişim",
"name": "Ad",
"email": "E-posta",
"phone": "Telefon",
"section.plans": "Plan",
"plans": "Plan ya da fotoğraf, en fazla 24 KB",
"plans_url": "Ya da bağlantı",
"details": "Keşif neyi kapsasın?",
"section.consent": "Onay",
"consent_contact": "Bu talep hakkında benimle iletişime geçilmesini kabul ediyorum.",
"consent_privacy": "Bu yanıtların talebin işlenmesi için saklanmasını kabul ediyorum."
}
};
let status = $state("");
let values = $state({ work: "" });
const t = $derived(LABELS[lang] || LABELS.en);
function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const raw = String(reader.result || "");
const comma = raw.indexOf(",");
resolve(comma === -1 ? raw : raw.slice(comma + 1));
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
async function onSubmit(event) {
const form = event.currentTarget;
event.preventDefault();
const body = {};
const files = [];
for (const [key, value] of new FormData(form).entries()) {
if (value instanceof File) {
if (value.size) files.push([key, value]);
continue;
}
body[key] = String(value);
}
if (files.some((pair) => pair[1].size > FILE_MAX)) {
status = t.file_too_big;
return;
}
for (const [key, file] of files) {
body[key + "_filename"] = file.name;
body[key + "_data"] = await readFile(file);
}
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify(body),
});
const result = await response.json();
status = result.success ? t.thanks : (result.message || "error");
}
</script>
<form onsubmit={onSubmit}>
<h1>{t.title}</h1>
<p>{t.lede}</p>
<input type="hidden" name="access_key" value={accessKey}>
<div aria-hidden="true" style="position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden">
<input name="botcheck" tabindex="-1" autocomplete="off">
</div>
<section><h2>{t["section.place"]}</h2>
<label><span>{t["property_type"]}</span><select name="property_type" required><option value="">—</option><option value="apartment">{t["property_type.apartment"]}</option><option value="house">{t["property_type.house"]}</option><option value="office">{t["property_type.office"]}</option></select></label>
<label><span>{t["city"]}</span><input name="city" type="text" required maxlength="80"></label>
<label><span>{t["area_m2"]}</span><input name="area_m2" type="number" required min="1" max="10000"></label></section>
<section><h2>{t["section.work"]}</h2>
<label><span>{t["work"]}</span><select name="work" required bind:value={values.work}><option value="">—</option><option value="kitchen">{t["work.kitchen"]}</option><option value="bathroom">{t["work.bathroom"]}</option><option value="full">{t["work.full"]}</option><option value="other">{t["work.other"]}</option></select></label>
{#if values.work === "other"}<label><span>{t["work_other"]}</span><input name="work_other" type="text" maxlength="120"></label>{/if}
<label><span>{t["timing"]}</span><select name="timing" required><option value="">—</option><option value="asap">{t["timing.asap"]}</option><option value="soon">{t["timing.soon"]}</option><option value="flexible">{t["timing.flexible"]}</option></select></label>
<label><span>{t["budget"]}</span><select name="budget" required><option value="">—</option><option value="under_5">{t["budget.under_5"]}</option><option value="mid">{t["budget.mid"]}</option><option value="high">{t["budget.high"]}</option><option value="unsure">{t["budget.unsure"]}</option></select></label></section>
<section><h2>{t["section.contact"]}</h2>
<label><span>{t["name"]}</span><input name="name" type="text" required maxlength="80"></label>
<label><span>{t["email"]}</span><input name="email" type="email" required maxlength="120"></label>
<label><span>{t["phone"]}</span><input name="phone" type="tel" required maxlength="40"></label></section>
<section><h2>{t["section.plans"]}</h2>
<label><span>{t["plans"]}</span><input name="plans" type="file" accept="image/*,.pdf"></label>
<label><span>{t["plans_url"]}</span><input name="plans_url" type="url" maxlength="300"></label>
<label><span>{t["details"]}</span><textarea name="details" maxlength="800"></textarea></label></section>
<section><h2>{t["section.consent"]}</h2>
<label class="check"><input name="consent_contact" type="checkbox" value="yes" required><span>{t["consent_contact"]}</span></label>
<label class="check"><input name="consent_privacy" type="checkbox" value="yes" required><span>{t["consent_privacy"]}</span></label></section>
<!-- Turnstile hook: enable Turnstile on this form in Formgong, add the api.js script, and a div.cf-turnstile with data-sitekey="YOUR_TURNSTILE_SITE_KEY". -->
<button type="submit">{t.submit}</button>
<p>{status}</p>
</form>