payfrit-works/admin/beacons.cfm
John Mizerek d4e0ae1162 Add branding features: header upload and brand color picker
- Add uploadHeader.cfm API for 1200px header images
- Add saveBrandColor.cfm API for hex color storage
- Add Branding section to menu builder sidebar
- Fix header upload path and permissions
- Various beacon and service point API improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 12:14:24 -08:00

231 lines
6.4 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="text/html; charset=utf-8" reset="true">
<cfoutput>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Payfrit Admin - Beacons</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
input, button, select { padding: 8px; margin: 6px 0; width: 420px; max-width: 100%; }
button { width: auto; cursor: pointer; }
table { border-collapse: collapse; width: 100%; margin-top: 12px; }
th, td { border: 1px solid ##ddd; padding: 8px; }
th { background: ##f5f5f5; text-align: left; }
.row { display: flex; gap: 24px; flex-wrap: wrap; }
.card { border: 1px solid ##ddd; padding: 14px; border-radius: 10px; flex: 1; min-width: 320px; }
pre { background: ##111; color: ##0f0; padding: 10px; overflow: auto; border-radius: 8px; min-height: 140px; }
.warn { color: ##b00; font-weight: bold; }
.ok { color: ##060; font-weight: bold; }
</style>
</head>
<body>
<h2>Beacons</h2>
<div class="warn">Required: BeaconName</div>
<div class="ok" id="jsStatus">(JS not loaded yet)</div>
<div class="row">
<div class="card">
<h3>Create / Update</h3>
<div>
<label>BeaconID (blank = create)</label><br>
<input id="BeaconID" placeholder="e.g. 123">
</div>
<div>
<label>BeaconName (required)</label><br>
<input id="BeaconName" placeholder="Front Door" required>
</div>
<div>
<label>UUID (iBeacon-style)</label><br>
<input id="UUID" placeholder="E2C56DB5-DFFB-48D2-B060-D0F5A71096E0">
</div>
<div>
<label>NamespaceId (Eddystone-UID first 20 hex chars)</label><br>
<input id="NamespaceId" placeholder="0A1B2C3D4E5F60718293">
</div>
<div>
<label>InstanceId (Eddystone-UID last 12 hex chars)</label><br>
<input id="InstanceId" placeholder="A1B2C3D4E5F6">
</div>
<div>
<label>IsActive</label><br>
<select id="IsActive">
<option value="1" selected>1 (Active)</option>
<option value="0">0 (Inactive)</option>
</select>
</div>
<button type="button" onclick="saveBeacon()">Save</button>
<button type="button" onclick="refresh()">Refresh List</button>
</div>
<div class="card">
<h3>Delete (soft)</h3>
<div>
<label>BeaconID</label><br>
<input id="DelBeaconID" placeholder="e.g. 123">
</div>
<button type="button" onclick="deleteBeacon()">Delete</button>
<h3>Response</h3>
<pre id="resp"></pre>
</div>
</div>
<h3>Beacon List (click row to load)</h3>
<table>
<thead>
<tr>
<th>BeaconID</th>
<th>Name</th>
<th>UUID</th>
<th>NamespaceId</th>
<th>InstanceId</th>
<th>IsActive</th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
<script>
(function boot(){
document.getElementById("jsStatus").textContent = "JS loaded OK - BusinessID: #request.BusinessID#";
})();
const BUSINESS_ID = #val(request.BusinessID)#;
function show(o){
document.getElementById("resp").textContent = JSON.stringify(o, null, 2);
}
const API_BASE = "/biz.payfrit.com/api";
async function api(path, bodyObj) {
const fullPath = API_BASE + path;
bodyObj = bodyObj || {};
bodyObj.BusinessID = BUSINESS_ID;
try {
const res = await fetch(fullPath, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(bodyObj)
});
const txt = await res.text();
try {
const parsed = JSON.parse(txt);
parsed._httpStatus = res.status;
parsed._path = fullPath;
return parsed;
} catch (e) {
return {
OK:false,
ERROR:"non_json",
_httpStatus: res.status,
_path: fullPath,
RAW: txt
};
}
} catch (e) {
return { OK:false, ERROR:"network_error", MESSAGE:String(e), _path: fullPath };
}
}
function valIntOrNull(id){
const v = (document.getElementById(id).value||"").trim();
if (!v) return null;
const n = parseInt(v, 10);
return isNaN(n) ? null : n;
}
function escapeHtml(s){
return (""+s)
.replaceAll("&","&amp;")
.replaceAll("<","&lt;")
.replaceAll(">","&gt;")
.replaceAll('"',"&quot;")
.replaceAll("'","&##039;");
}
function loadIntoForm(b){
document.getElementById("BeaconID").value = b.BeaconID || "";
document.getElementById("BeaconName").value = b.BeaconName || "";
document.getElementById("UUID").value = b.UUID || "";
document.getElementById("NamespaceId").value = b.NamespaceId || "";
document.getElementById("InstanceId").value = b.InstanceId || "";
document.getElementById("IsActive").value = ("" + (b.IsActive ?? 1));
document.getElementById("DelBeaconID").value = b.BeaconID || "";
}
async function refresh() {
const out = await api("/beacons/list.cfm", {});
show(out);
const tbody = document.getElementById("rows");
tbody.innerHTML = "";
const items = out.BEACONS || [];
for (const b of items) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${b.BeaconID}</td>
<td>${escapeHtml(b.BeaconName||"")}</td>
<td>${escapeHtml(b.UUID||"")}</td>
<td>${escapeHtml(b.NamespaceId||"")}</td>
<td>${escapeHtml(b.InstanceId||"")}</td>
<td>${b.IsActive}</td>
`;
tr.style.cursor = "pointer";
tr.onclick = () => loadIntoForm(b);
tbody.appendChild(tr);
}
}
async function saveBeacon() {
const name = (document.getElementById("BeaconName").value || "").trim();
if (!name) {
show({ OK:false, ERROR:"missing_beacon_name", MESSAGE:"BeaconName is required" });
return;
}
const body = {
BeaconID: valIntOrNull("BeaconID"),
BeaconName: name,
UUID: (document.getElementById("UUID").value || "").trim(),
NamespaceId: (document.getElementById("NamespaceId").value || "").trim(),
InstanceId: (document.getElementById("InstanceId").value || "").trim(),
IsActive: parseInt(document.getElementById("IsActive").value, 10)
};
if (!body.BeaconID) delete body.BeaconID;
const out = await api("/beacons/save.cfm", body);
show(out);
await refresh();
}
async function deleteBeacon() {
const id = valIntOrNull("DelBeaconID");
if (!id) { show({OK:false,ERROR:"missing_beacon_id"}); return; }
const out = await api("/beacons/delete.cfm", { BeaconID: id });
show(out);
await refresh();
}
refresh();
</script>
</body>
</html>
</cfoutput>