Big T's Gallery – Admin

R2-backed gallery via Cloudflare Worker

API: https://api.bigtsmallenginerepair.com

Add gallery item

Gallery items

Drag items to reorder. Order is saved automatically.

Big T's Gallery – Admin

R2-backed gallery via Cloudflare Worker

API: https://api.bigtsmallenginerepair.com

Add gallery item

Gallery items

Drag items to reorder. Order is saved automatically.

const msg = document.getElementById("globalMessage"); const itemsContainer = document.getElementById("itemsContainer"); const listEmpty = document.getElementById("listEmpty"); function showMessage(type, text) { msg.className = "mb-6 rounded border px-4 py-3 text-sm"; msg.classList.remove("hidden"); msg.classList.add( type === "error" ? "bg-red-50 border-red-200 text-red-800" : "bg-green-50 border-green-200 text-green-800" ); msg.textContent = text; setTimeout(() => msg.classList.add("hidden"), 4000); } async function api(path, options = {}) { options.headers ||= {}; const res = await fetch(API_BASE + path, options); const text = await res.text(); const data = text ? JSON.parse(text) : null; if (!res.ok) throw new Error(data?.error || res.status); return data; } let sortable; async function loadItems() { const data = await api("/api/admin/items"); const items = data.items || []; itemsContainer.innerHTML = ""; listEmpty.classList.toggle("hidden", items.length > 0); items.forEach(item => { const div = document.createElement("div"); div.className = "flex gap-3 border rounded p-3 bg-white cursor-move"; div.dataset.id = item.id; const img = document.createElement("img"); img.src = item.image_url || ""; img.className = "w-24 h-24 object-cover rounded bg-slate-100"; const body = document.createElement("div"); body.className = "flex-1"; body.innerHTML = `
${item.title}
${item.category || ""}
${item.description || ""}
`; body.querySelector("button").onclick = async () => { if (!confirm("Delete this item?")) return; await api("/api/admin/items/" + item.id, { method: "DELETE" }); loadItems(); }; div.append(img, body); itemsContainer.append(div); }); if (sortable) sortable.destroy(); sortable = new Sortable(itemsContainer, { animation: 150, onEnd: saveOrder }); } async function saveOrder() { const order = [...itemsContainer.children].map(el => el.dataset.id); try { await api("/api/admin/items/reorder", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ order }) }); showMessage("success", "Order saved"); } catch { showMessage("error", "Failed to save order"); } } document.getElementById("reloadBtn").onclick = loadItems; document.getElementById("itemForm").onsubmit = async e => { e.preventDefault(); const fd = new FormData(); fd.append("title", title.value); fd.append("description", description.value); fd.append("category", category.value); if (price.value) fd.append("price", price.value); fd.append("featured", featured.checked ? "true" : "false"); if (imageFile.files[0]) fd.append("image", imageFile.files[0]); await api("/api/admin/items", { method: "POST", body: fd }); showMessage("success", "Item created"); e.target.reset(); loadItems(); }; loadItems();