Skip to content

Instantly share code, notes, and snippets.

@do-me
Created January 4, 2026 17:29
Show Gist options
  • Select an option

  • Save do-me/bffbabf41fd47c0e4129534ce2fe2b37 to your computer and use it in GitHub Desktop.

Select an option

Save do-me/bffbabf41fd47c0e4129534ce2fe2b37 to your computer and use it in GitHub Desktop.
supabase guest form
<!doctype html>
<html lang="en" class="bg-slate-50">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Guest List Manager</title>
<!-- 1. Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 2. Supabase Client via CDN -->
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
</head>
<body class="font-sans text-slate-800">
<div class="container mx-auto max-w-4xl p-4 sm:p-6 lg:p-8">
<!-- A. HEADER -->
<header class="mb-8">
<h1 class="text-3xl font-bold text-slate-900">
Guest List Manager
</h1>
<p class="text-slate-600 mt-1">
A complete interface to manage your guests.
</p>
</header>
<main class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- B. FORM FOR CREATE & UPDATE -->
<div class="md:col-span-1">
<form
id="guest-form"
class="bg-white p-6 rounded-lg shadow-sm"
>
<h2 id="form-title" class="text-2xl font-bold mb-4">
Add New Guest
</h2>
<input type="hidden" id="guest-id" />
<!-- Form Fields -->
<div class="space-y-4">
<!-- Name & Family Name -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label
for="name"
class="block text-sm font-medium text-slate-700"
>First Name</label
>
<input
type="text"
id="name"
name="name"
required
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="Caloggero"
/>
</div>
<div>
<label
for="family_name"
class="block text-sm font-medium text-slate-700"
>Family Name</label
>
<input
type="text"
id="family_name"
name="family_name"
required
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="Siciliano"
/>
</div>
</div>
<!-- Attending & Children -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label
for="attending"
class="block text-sm font-medium text-slate-700"
>Attending?</label
>
<select
id="attending"
name="attending"
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
>
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
<div>
<label
for="children"
class="block text-sm font-medium text-slate-700"
>Children</label
>
<input
type="number"
id="children"
name="children"
min="0"
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="0"
/>
</div>
</div>
<!-- Allergies -->
<div>
<label
for="allergies"
class="block text-sm font-medium text-slate-700"
>Allergies</label
>
<input
type="text"
id="allergies"
name="allergies"
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="Fish, nuts, etc."
/>
</div>
<!-- Notes -->
<div>
<label
for="notes"
class="block text-sm font-medium text-slate-700"
>Notes</label
>
<textarea
id="notes"
name="notes"
rows="3"
class="mt-1 block w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder="Special requests..."
></textarea>
</div>
</div>
<!-- Form Action Buttons -->
<div class="mt-6 flex items-center gap-4">
<button
type="submit"
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Save Guest
</button>
<button
type="button"
id="cancel-edit-btn"
class="hidden inline-flex justify-center rounded-md border border-slate-300 bg-white py-2 px-4 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
Cancel
</button>
</div>
</form>
</div>
<!-- C. TABLE FOR READING & DELETING -->
<div class="md:col-span-2">
<div class="bg-white overflow-hidden shadow-sm rounded-lg">
<div id="guest-list" class="divide-y divide-slate-200">
<!-- Guest rows will be injected here -->
</div>
</div>
</div>
</main>
</div>
<!-- D. JAVASCRIPT LOGIC -->
<script type="module">
// --- 1. SETUP ---
const supabaseUrl = "https://erger....supabase.co";
const supabaseKey =
"eyJhbGciOiJIUzI1NqdpbpQ...";
const _supabase = supabase.createClient(supabaseUrl, supabaseKey);
// --- 2. DOM ELEMENT REFERENCES ---
const guestList = document.getElementById("guest-list");
const guestForm = document.getElementById("guest-form");
const formTitle = document.getElementById("form-title");
const guestIdInput = document.getElementById("guest-id");
const cancelEditBtn = document.getElementById("cancel-edit-btn");
// --- 3. READ (Fetch and Display Guests) ---
const fetchGuests = async () => {
const { data: guests, error } = await _supabase
.from("guests")
.select("*")
.order("created_at", { ascending: false });
if (error) {
console.error("Error fetching guests:", error);
return;
}
guestList.innerHTML = "";
if (guests.length === 0) {
guestList.innerHTML = `<div class="p-4 text-center text-slate-500">No guests found. Add one!</div>`;
} else {
for (const guest of guests) {
const guestEl = document.createElement("div");
guestEl.id = `guest-${guest.id}`;
guestEl.className =
"p-4 flex flex-col sm:flex-row justify-between sm:items-center gap-2";
guestEl.innerHTML = `
<div class="flex-1">
<p class="font-semibold text-slate-800">${guest.name} ${guest.family_name}</p>
<p class="text-sm text-slate-600">
<span class="font-medium">Attending:</span> ${guest.attending ? "Yes" : "No"} |
<span class="font-medium">Children:</span> ${guest.children ?? 0}
${guest.allergies ? `| <span class="font-medium">Allergies:</span> ${guest.allergies}` : ""}
</p>
${guest.notes ? `<p class="mt-1 text-xs text-slate-500 bg-slate-100 p-2 rounded-md"><span class="font-medium">Notes:</span> ${guest.notes}</p>` : ""}
</div>
<div class="flex gap-2 self-end sm:self-center">
<button data-id="${guest.id}" class="edit-btn text-sm font-medium text-blue-600 hover:text-blue-800">Edit</button>
<button data-id="${guest.id}" class="delete-btn text-sm font-medium text-red-600 hover:text-red-800">Delete</button>
</div>
`;
guestList.appendChild(guestEl);
}
}
};
// --- 4. CREATE and UPDATE ---
const handleFormSubmit = async (event) => {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const guestData = {
name: formData.get("name"),
family_name: formData.get("family_name"),
attending: formData.get("attending") === "true",
children: parseInt(formData.get("children")) || null, // Parse to int, default to null
allergies: formData.get("allergies") || null,
notes: formData.get("notes") || null,
};
const id = guestIdInput.value;
let error;
if (id) {
// UPDATE
const { error: updateError } = await _supabase
.from("guests")
.update(guestData)
.eq("id", id);
error = updateError;
} else {
// CREATE
const { error: insertError } = await _supabase
.from("guests")
.insert([guestData]);
error = insertError;
}
if (error) {
alert("Error: " + error.message);
} else {
alert(id ? "Guest updated!" : "Guest added!");
resetForm();
await fetchGuests();
}
};
// --- 5. DELETE ---
const handleDeleteGuest = async (id) => {
if (!confirm("Are you sure?")) return;
const { error } = await _supabase
.from("guests")
.delete()
.eq("id", id);
if (error) alert("Error: " + error.message);
else {
alert("Guest deleted!");
document.getElementById(`guest-${id}`).remove();
}
};
// --- 6. Form State Management (for editing) ---
const prepareFormForEdit = (guest) => {
formTitle.textContent = "Edit Guest";
guestIdInput.value = guest.id;
guestForm.elements["name"].value = guest.name;
guestForm.elements["family_name"].value = guest.family_name;
guestForm.elements["attending"].value = guest.attending;
guestForm.elements["children"].value = guest.children || "";
guestForm.elements["allergies"].value = guest.allergies || "";
guestForm.elements["notes"].value = guest.notes || "";
cancelEditBtn.classList.remove("hidden");
window.scrollTo({ top: 0, behavior: "smooth" });
};
const resetForm = () => {
formTitle.textContent = "Add New Guest";
guestForm.reset();
guestIdInput.value = "";
cancelEditBtn.classList.add("hidden");
};
// --- 7. EVENT LISTENERS ---
guestForm.addEventListener("submit", handleFormSubmit);
cancelEditBtn.addEventListener("click", resetForm);
guestList.addEventListener("click", async (event) => {
const target = event.target;
const id = target.dataset.id;
if (!id) return;
if (target.classList.contains("delete-btn"))
await handleDeleteGuest(id);
if (target.classList.contains("edit-btn")) {
const { data: guest, error } = await _supabase
.from("guests")
.select("*")
.eq("id", id)
.single();
if (error)
alert(
"Could not fetch guest details: " + error.message,
);
else prepareFormForEdit(guest);
}
});
// --- 8. INITIAL LOAD ---
fetchGuests();
</script>
</body>
</html>
@do-me
Copy link
Author

do-me commented Jan 4, 2026

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment