Frontend Code Snippets
Key JavaScript & HTML patterns used in KawokoWebz for API calls, auth, dynamic content, and forms.
1. API Client (js/api.js) JavaScript
/** KawokoWebz - API Client */
const API_BASE = window.location.origin + "/api";
async function apiGet(endpoint) {
const res = await fetch(API_BASE + endpoint);
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Request failed");
return data;
}
async function apiPost(endpoint, body) {
const res = await fetch(API_BASE + endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
return res.json();
}
window.KawokoAPI = {
getVillages: () => apiGet("/villages"),
getNews: (opts = {}) => {
const params = new URLSearchParams();
if (opts.village) params.set("village", opts.village);
if (opts.limit) params.set("limit", opts.limit);
return apiGet("/news" + (params.toString() ? "?" + params : ""));
},
getEvents: () => apiGet("/events?upcoming=1"),
submitContact: (data) => apiPost("/contact", data),
getMe: () => apiGet("/auth/me"),
logout: () => apiPost("/auth/logout", {}),
};
2. Dynamic Auth UI in Toolbar (js/main.js) JavaScript
async function updateAuthUI() {
const result = await KawokoAPI.getMe();
const toolbar = document.querySelector(".toolbar .container");
if (!toolbar) return;
toolbar.querySelectorAll(".auth-link").forEach(el => el.remove());
if (result.authenticated && result.user) {
const name = result.user.full_name || result.user.username;
const a1 = document.createElement("a");
a1.href = "/account";
a1.className = "auth-link";
a1.textContent = "👤 " + name;
const a2 = document.createElement("a");
a2.href = "#";
a2.className = "auth-link";
a2.textContent = "Logout";
a2.addEventListener("click", async (e) => {
e.preventDefault();
await KawokoAPI.logout();
window.location.reload();
});
toolbar.appendChild(a1);
toolbar.appendChild(a2);
} else {
// Show Login + Register links
...
}
}
updateAuthUI();
3. Login Form Handler (login.html) JavaScript
document.getElementById("login-form").addEventListener("submit", async (e) => {
e.preventDefault();
const btn = document.getElementById("submit-btn");
btn.disabled = true;
btn.textContent = "Signing in…";
const payload = {
username: document.getElementById("username").value.trim(),
password: document.getElementById("password").value
};
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.success) {
window.location.href = "/account";
} else {
document.getElementById("msg").textContent = data.error;
btn.disabled = false;
btn.textContent = "Sign In";
}
});
4. Registration Form Handler (register.html) JavaScript
document.getElementById("register-form").addEventListener("submit", async (e) => {
e.preventDefault();
const password = document.getElementById("password").value;
const password2 = document.getElementById("password2").value;
if (password !== password2) {
showError("Passwords do not match.");
return;
}
const payload = {
username: document.getElementById("username").value.trim(),
email: document.getElementById("email").value.trim(),
password: password,
full_name: document.getElementById("full_name").value.trim(),
phone: document.getElementById("phone").value.trim(),
village: document.getElementById("village").value
};
const res = await fetch("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const data = await res.json();
if (data.success) {
window.location.href = "/account"; // auto-logged in
} else {
showError(data.error);
}
});
5. Load News from Database (js/main.js) JavaScript
async function loadNews() {
const container = document.getElementById("news-list");
if (!container || !window.KawokoAPI) return;
const result = await KawokoAPI.getNews({ limit: 5 });
if (!result.success || !result.data.length) {
container.innerHTML = '<p>No news available yet.</p>';
return;
}
container.innerHTML = result.data.map(item => `
<article class="news-card">
<h3>${escapeHtml(item.title)}</h3>
<p class="news-meta">
${item.published_at ? new Date(item.published_at).toLocaleDateString() : ''}
${item.village_slug ? ' · ' + item.village_slug : ''}
</p>
<p>${escapeHtml(item.body.substring(0, 160))}…</p>
</article>
`).join('');
}
function escapeHtml(str) {
if (!str) return '';
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
6. Contact Form → Database (js/main.js) JavaScript
function setupContactForm() {
const form = document.getElementById("contact-form");
if (!form) return;
form.addEventListener("submit", async (e) => {
e.preventDefault();
const btn = form.querySelector('button[type="submit"]');
const status = document.getElementById("contact-status");
btn.disabled = true;
btn.textContent = "Sending…";
const data = {
name: form.name.value.trim(),
email: form.email.value.trim(),
phone: form.phone?.value.trim() || "",
village: form.village?.value.trim() || "",
subject: form.subject?.value.trim() || "",
message: form.message.value.trim(),
};
const result = await KawokoAPI.submitContact(data);
if (result.success) {
status.textContent = result.message;
status.style.color = "var(--primary)";
form.reset();
} else {
status.textContent = result.error || "Something went wrong.";
status.style.color = "#c0392b";
}
btn.disabled = false;
btn.textContent = "Send Message";
});
}
7. Gallery Slideshow (js/main.js) JavaScript
const slides = document.querySelectorAll(".slide");
const dots = document.querySelectorAll(".dot");
let currentSlide = 0;
let slideInterval;
function showSlide(index) {
slides.forEach(s => s.classList.remove("active"));
dots.forEach(d => d.classList.remove("active"));
if (index >= slides.length) currentSlide = 0;
else if (index < 0) currentSlide = slides.length - 1;
else currentSlide = index;
slides[currentSlide].classList.add("active");
if (dots[currentSlide]) dots[currentSlide].classList.add("active");
}
function nextSlide() { showSlide(currentSlide + 1); }
function startSlideshow() {
slideInterval = setInterval(nextSlide, 5000);
}
// Pause on hover
document.querySelector(".slideshow-container")
?.addEventListener("mouseenter", () => clearInterval(slideInterval));
document.querySelector(".slideshow-container")
?.addEventListener("mouseleave", startSlideshow);
showSlide(0);
startSlideshow();
8. Contact Form HTML Structure HTML
<form id="contact-form">
<input type="text" name="name" required placeholder="Your Name">
<input type="email" name="email" required placeholder="Email">
<select name="village">
<option value="">— Select village —</option>
<option value="Kigaba">Kigaba</option>
<!-- ... other villages ... -->
</select>
<textarea name="message" required rows=5></textarea>
<button type="submit" class="btn btn-primary">Send Message</button>
<p id="contact-status"></p>
</form>
Full source files live in js/api.js, js/main.js, login.html, register.html, and account.html.
← Back to KawokoWebz