Menu
Public repository / 40f5f122d654d8a118dade55a8a401f5889e8c83
AkurAI-Platform / frontend/suite-tiles.js
Latest change b1ab07177739f3aa7251fa530ed2bd463402b5c5 - feat: complete managed workspace control plane by Ólafur Búi Ólafsson
// Suite tiles — catalog-driven product card rendering.
// Fetches catalog data from the payment API and fills tile grids.
// Category headers are server-rendered (full i18n); only product tiles are JS-generated.
const BUILD_BASE = "https://akurai-build.olibuijr.com/repos/";
const LABELS = {
open: "Open",
status: "Status",
inDev: "In development",
source: "Build",
};
function tileHtml(product) {
const isDev = product.visibility === "dev";
const hasUrl = product.url && !isDev;
let actionHtml = "";
if (isDev) {
actionHtml = `<span class="tile-flag">${LABELS.inDev}</span>`;
} else if (hasUrl) {
const label =
product.actionKey === "app.action.status"
? LABELS.status
: LABELS.open;
actionHtml = `<a class="tile-open" href="${product.url}">${label}<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17 17 7M9 7h8v8"/></svg></a>`;
} else {
actionHtml = `<span class="tile-flag">${product.kind}</span>`;
}
let srcHtml = `<a class="tile-src" href="${BUILD_BASE}${product.buildRepo}">${LABELS.source}: ${product.buildRepo}</a>`;
if (product.secondaryBuildRepo) {
srcHtml += `<a class="tile-src" href="${BUILD_BASE}${product.secondaryBuildRepo}">${LABELS.source}: ${product.secondaryBuildRepo}</a>`;
}
const icon = product.id === "managed-workspace" ? "platform" : product.id;
return `<article class="tile">
<span class="tile-icon"><img src="/img/icons/${icon}.svg" alt="" width="48" height="48" loading="lazy"></span>
<div class="tile-body">
<div class="tile-top">
<h4>${product.name}</h4>
<span class="tile-kind">${product.kind}</span>
</div>
<p class="tile-prop">${product.description}</p>
</div>
<div class="tile-foot">
${actionHtml}
${srcHtml}
</div>
</article>`;
}
// Boot: fetch catalog, populate empty tile grids.
const suiteRoot = document.querySelector(".suite");
if (suiteRoot) {
fetch("/api/payments/catalog", {
headers: { accept: "application/json" },
})
.then((r) => {
if (!r.ok) throw new Error("catalog_fetch_failed");
return r.json();
})
.then((data) => {
for (const product of data.products) {
if (product.visibility === "hidden") continue;
const grid = suiteRoot.querySelector(
`.cat-grid[data-cat="${product.category}"]`
);
if (grid) {
grid.insertAdjacentHTML("beforeend", tileHtml(product));
}
}
})
.catch(() => {
// Keep existing content on failure (already server-rendered)
});
}