eduardo4547's picture
Upload 150 files
cb5d9d0 verified
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Panel de Control SaaS</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-100 text-slate-900">
<div class="max-w-6xl mx-auto p-6 space-y-6">
<header
class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"
>
<div>
<h1 class="text-3xl font-bold">Panel de Control SaaS</h1>
<p class="text-slate-600">
Desde aquí puedes ver usuarios activos, generar nuevas API keys y
obtener el script de integración.
</p>
</div>
<div class="space-x-2">
<a
href="/preview"
class="inline-block px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>Vista previa</a
>
<button
id="open-app-button"
class="inline-block px-4 py-2 bg-slate-200 text-slate-900 rounded-lg hover:bg-slate-300"
>
Abrir app
</button>
</div>
</header>
<section class="grid gap-6 lg:grid-cols-2">
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Generar nueva API key</h2>
<div class="space-y-4">
<label class="block">
<span class="text-sm font-medium text-slate-700"
>Nombre del cliente</span
>
<input
id="client-name"
type="text"
placeholder="Ej. Tienda Sur"
class="mt-2 w-full rounded-2xl border border-slate-300 px-4 py-3 focus:border-blue-500 focus:outline-none"
/>
</label>
<button
id="generate-key"
class="w-full px-5 py-3 bg-green-600 text-white rounded-2xl hover:bg-green-700"
>
Generar API key
</button>
<div
id="generate-result"
class="hidden rounded-2xl border border-slate-200 bg-slate-50 p-4 text-slate-900"
></div>
</div>
</div>
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">
Instrucciones de integración
</h2>
<p class="text-slate-600 mb-4">
El desarrollador debe insertar un contenedor y el script del widget
en su HTML:
</p>
<pre
class="rounded-2xl bg-slate-950 p-4 text-slate-100 overflow-x-auto"
><code id="static-snippet"></code></pre>
<script>
document.getElementById("static-snippet").textContent =
'<div id="contenedor-saas" data-client-id="ID_UNICO_DEL_CLIENTE_001"></div>\n' +
'<script src="' + window.location.origin + '/widget.js"><\/script>';
</script>
<p class="text-sm text-slate-500 mt-4">
Reemplaza <strong>ID_UNICO_DEL_CLIENTE_001</strong> por la API key
generada.
</p>
</div>
</section>
<section class="grid gap-6 lg:grid-cols-2">
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Clientes registrados</h2>
<div id="clients-list" class="space-y-3 text-slate-700"></div>
</div>
<div class="bg-white rounded-3xl p-6 shadow-lg">
<h2 class="text-xl font-semibold mb-4">Usuarios activos</h2>
<div id="active-list" class="space-y-3 text-slate-700"></div>
</div>
</section>
</div>
<script>
async function loadData() {
const [keysRes, activeRes] = await Promise.all([
fetch("/api/keys"),
fetch("/api/active-sessions"),
]);
const keysData = await keysRes.json();
const activeData = await activeRes.json();
const clientsList = document.getElementById("clients-list");
clientsList.innerHTML = keysData.keys
.map(
(key) => `
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="font-semibold">${key.nombre}</div>
<div class="text-sm text-slate-500">API key: <code>${key.client_id}</code></div>
<div class="text-sm text-slate-500">Color primario: ${key.color_primario}</div>
<div class="text-sm text-slate-500">Creado: ${key.created_at}</div>
</div>
`,
)
.join("");
const activeList = document.getElementById("active-list");
if (activeData.active_sessions.length === 0) {
activeList.innerHTML =
'<p class="text-sm text-slate-500">No hay sesiones activas aún.</p>';
} else {
activeList.innerHTML = activeData.active_sessions
.map(
(item) => `
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="font-semibold">${item.client_id}</div>
<div class="text-sm text-slate-500">Último acceso: ${item.last_seen}</div>
</div>
`,
)
.join("");
}
}
document
.getElementById("generate-key")
.addEventListener("click", async () => {
const nameInput = document.getElementById("client-name");
const name = nameInput.value.trim();
if (!name) {
alert("Ingresa el nombre del cliente antes de generar la API key.");
return;
}
const formData = new URLSearchParams();
formData.append("nombre", name);
const response = await fetch("/api/generate-key", {
method: "POST",
body: formData,
});
const result = await response.json();
const resultBox = document.getElementById("generate-result");
resultBox.classList.remove("hidden");
const escapedSnippet = result.snippet
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
resultBox.innerHTML = `
<p class="font-semibold text-slate-900">API key generada:</p>
<p class="mt-2"><code>${result.client_id}</code></p>
<p class="mt-3 font-semibold text-slate-900">Snippet para el cliente:</p>
<pre class="mt-2 rounded-2xl bg-slate-950 p-4 text-slate-100 overflow-x-auto"><code>${escapedSnippet}</code></pre>
`;
nameInput.value = "";
await loadData();
});
loadData();
document
.getElementById("open-app-button")
.addEventListener("click", async () => {
const clientId = "ID_UNICO_DEL_CLIENTE_001";
try {
const res = await fetch("/api/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `client_id=${encodeURIComponent(clientId)}`,
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "No se pudo generar token.");
}
window.open(
`/app?token=${encodeURIComponent(data.token)}`,
"_blank",
);
} catch (error) {
alert("No se pudo abrir el app: " + error.message);
}
});
</script>
</body>
</html>