Add instant fiber availability check to website

Adds a quick address/postal code lookup that queries
intra.cuitunet.fi API to show if fiber is available,
directly in the contact section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 02:00:25 +02:00
parent a29399979a
commit d286ce2eb3
2 changed files with 141 additions and 1 deletions

View File

@@ -227,7 +227,16 @@
<div class="availability-text"> <div class="availability-text">
<span class="overline">Saatavuuskysely & yhteydenotto</span> <span class="overline">Saatavuuskysely & yhteydenotto</span>
<h2>Ota yhteyttä tai tarkista saatavuus</h2> <h2>Ota yhteyttä tai tarkista saatavuus</h2>
<p>Kerro osoitteesi niin selvitämme valokuidun saatavuuden, tai kysy mitä tahansa palveluistamme. Vastaamme 12 arkipäivässä. Yhteydenotto ei velvoita mihinkään.</p> <p>Tarkista heti onko kuituliittymä saatavilla osoitteessasi, tai täytä lomake niin otamme yhteyttä 12 arkipäivässä.</p>
<div class="availability-check">
<h3>Pikasaatavuustarkistus</h3>
<div class="availability-check-form">
<input type="text" id="check-address" placeholder="Osoite tai postinumero">
<button type="button" class="btn btn-sm" id="check-btn">Tarkista</button>
</div>
<div id="check-result" class="check-result" style="display:none;"></div>
</div>
</div> </div>
<form class="form" id="availability-form"> <form class="form" id="availability-form">
<div style="position:absolute;left:-9999px;top:-9999px;"> <div style="position:absolute;left:-9999px;top:-9999px;">
@@ -300,6 +309,61 @@ document.querySelector('.nav-toggle')?.addEventListener('click', function() {
document.querySelector('.nav')?.classList.toggle('open'); document.querySelector('.nav')?.classList.toggle('open');
}); });
// Pikasaatavuustarkistus
(function() {
const API_URL = 'https://intra.cuitunet.fi/api.php';
const API_KEY = '3de64ed2a3ece1c0f497345e41e8e76d';
const checkInput = document.getElementById('check-address');
const checkBtn = document.getElementById('check-btn');
const checkResult = document.getElementById('check-result');
async function checkAvailability() {
const q = checkInput.value.trim();
if (!q) return;
checkResult.className = 'check-result loading';
checkResult.style.display = 'block';
checkResult.innerHTML = '<p>Tarkistetaan saatavuutta...</p>';
try {
const isZip = /^\d{5}$/.test(q);
const param = isZip ? 'postinumero=' + encodeURIComponent(q) : 'osoite=' + encodeURIComponent(q);
const res = await fetch(API_URL + '?action=saatavuus&key=' + encodeURIComponent(API_KEY) + '&' + param);
const data = await res.json();
if (data.error) {
checkResult.className = 'check-result nok';
checkResult.innerHTML = '<h4>Virhe</h4><p>' + data.error + '</p>';
return;
}
if (data.saatavilla) {
let html = '<h4>Kuituliittymä on saatavilla!</h4>';
html += '<p>Löysimme ' + data.maara + ' kohde' + (data.maara > 1 ? 'tta' : 'n') + ':</p>';
data.kohteet.forEach(function(k) {
html += '<div class="check-result-address">';
html += '<strong>' + k.osoite + '</strong>';
if (k.postinumero) html += ', ' + k.postinumero;
if (k.kaupunki) html += ' ' + k.kaupunki;
if (k.nopeus) html += ' — ' + k.nopeus;
html += '</div>';
});
checkResult.className = 'check-result ok';
checkResult.innerHTML = html;
} else {
checkResult.className = 'check-result nok';
checkResult.innerHTML = '<h4>Ei saatavuutta vielä</h4><p>Kuituliittymää ei löytynyt osoitteellasi, mutta verkkomme laajenee jatkuvasti. Täytä lomake niin kerromme kun kuitu tulee saataville!</p>';
}
} catch (e) {
checkResult.className = 'check-result nok';
checkResult.innerHTML = '<h4>Yhteysvirhe</h4><p>Tarkistusta ei voitu suorittaa. Yritä hetken kuluttua uudelleen.</p>';
}
}
checkBtn?.addEventListener('click', checkAvailability);
checkInput?.addEventListener('keydown', function(e) { if (e.key === 'Enter') checkAvailability(); });
})();
// Saatavuuskysely-lomake // Saatavuuskysely-lomake
document.getElementById('availability-form')?.addEventListener('submit', async function(e) { document.getElementById('availability-form')?.addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();

View File

@@ -556,6 +556,82 @@ img {
box-shadow: 0 0 0 3px rgba(232, 137, 29, 0.1); box-shadow: 0 0 0 3px rgba(232, 137, 29, 0.1);
} }
/* ---- Availability Check ---- */
.availability-check {
margin-top: 32px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 24px;
}
.availability-check h3 {
font-size: 1rem;
font-weight: 600;
color: var(--dark);
margin-bottom: 12px;
}
.availability-check-form {
display: flex;
gap: 8px;
}
.availability-check-form input {
flex: 1;
padding: 11px 14px;
border: 1.5px solid var(--border);
border-radius: 8px;
font-size: 0.95rem;
font-family: inherit;
transition: border-color 0.2s, box-shadow 0.2s;
}
.availability-check-form input:focus {
outline: none;
border-color: var(--orange);
box-shadow: 0 0 0 3px rgba(232, 137, 29, 0.1);
}
.check-result {
margin-top: 12px;
padding: 14px 16px;
border-radius: 8px;
font-size: 0.9rem;
line-height: 1.5;
}
.check-result.ok {
background: #ecfdf5;
border: 1px solid #a7f3d0;
color: #065f46;
}
.check-result.nok {
background: #fef2f2;
border: 1px solid #fecaca;
color: #991b1b;
}
.check-result.loading {
background: var(--bg-alt);
border: 1px solid var(--border);
color: var(--text-light);
}
.check-result h4 {
font-size: 0.95rem;
margin-bottom: 4px;
}
.check-result-address {
background: rgba(255,255,255,0.6);
padding: 6px 10px;
border-radius: 6px;
margin-top: 6px;
font-size: 0.85rem;
}
/* ---- Footer ---- */ /* ---- Footer ---- */
.footer { .footer {
background: var(--dark-soft); background: var(--dark-soft);