📍 Kawoko Town Council, Uganda

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