Switch board to client-side fetching

Cards now load dynamically on each page visit instead of at build time,
so new cards appear immediately without redeploying the site.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dexx
2026-06-05 22:08:15 +03:00
parent ea273a4ac4
commit 27b36c36c4
+50 -82
View File
@@ -1,76 +1,12 @@
--- ---
import "../styles/global.css"; import "../styles/global.css";
type BoardCard = {
id: string | number;
title: string;
comment?: string | null;
imageUrl?: string | null;
published?: boolean;
};
type ApiResponse = {
docs?: BoardCard[];
};
type BoardItem = {
id: string | number;
title: string;
comment?: string | null;
image: {
alt: string;
url: string;
} | null;
};
const apiUrl = ( const apiUrl = (
import.meta.env.API_URL || import.meta.env.API_URL ||
import.meta.env.PUBLIC_API_URL || import.meta.env.PUBLIC_API_URL ||
"http://localhost:3001" "http://localhost:3001"
).replace(/\/$/, ""); ).replace(/\/$/, "");
async function getCards() {
try {
const response = await fetch(`${apiUrl}/api/cards`);
if (!response.ok) {
return [];
}
const data = (await response.json()) as ApiResponse;
return data.docs ?? [];
} catch {
return [];
}
}
function absolutize(url: string) {
if (/^https?:\/\//i.test(url)) {
return url;
}
return `${apiUrl}${url.startsWith("/") ? "" : "/"}${url}`;
}
function getImage(card: BoardCard) {
if (card.imageUrl) {
return {
alt: card.title,
url: absolutize(card.imageUrl)
};
}
return null;
}
const cards = await getCards();
const boardItems: BoardItem[] = cards.map((card) => ({
id: card.id,
title: card.title,
comment: card.comment,
image: getImage(card)
}));
const boardStamp = boardItems.length === 0 ? "Empty" : `${boardItems.length} filed`;
const adminUrl = `${apiUrl}/admin`; const adminUrl = `${apiUrl}/admin`;
--- ---
@@ -90,31 +26,63 @@ const adminUrl = `${apiUrl}/admin`;
<p class="summary">тут только самое позорное.</p> <p class="summary">тут только самое позорное.</p>
</div> </div>
<aside class="caseTag" aria-label="Board status"> <aside class="caseTag" aria-label="Board status">
<span>{boardStamp}</span> <span id="board-stamp">Loading...</span>
<strong>Elysia API</strong> <strong>Elysia API</strong>
<a class="loginButton" href={adminUrl}>Login</a> <a class="loginButton" href={adminUrl}>Login</a>
<small>admin / cards</small> <small>admin / cards</small>
</aside> </aside>
</section> </section>
<section class="board" aria-label="Published cards"> <section class="board" aria-label="Published cards" id="board">
{boardItems.length === 0 && <div class="notice">В админке пока нет опубликованных карточек.</div>} <div class="notice">Загрузка...</div>
{
boardItems.map((item, index) => {
return (
<article class="photoCard">
{item.image?.url && <img src={item.image.url} alt={item.image.alt} loading={index < 3 ? "eager" : "lazy"} />}
<div class="caption">
<span>#{String(index + 1).padStart(2, "0")}</span>
<strong>{item.title}</strong>
</div>
{item.comment && <p class="cardComment">{item.comment}</p>}
</article>
);
})
}
</section> </section>
</main> </main>
<script define:vars={{ apiUrl }}>
const API = apiUrl;
function absolutize(url) {
if (/^https?:\/\//i.test(url)) return url;
return `${API}${url.startsWith("/") ? "" : "/"}${url}`;
}
async function loadCards() {
const board = document.getElementById("board");
const stamp = document.getElementById("board-stamp");
try {
const res = await fetch(`${API}/api/cards`);
if (!res.ok) throw new Error();
const data = await res.json();
const cards = data.docs ?? [];
stamp.textContent = cards.length === 0
? "Empty"
: `${cards.length} filed`;
if (cards.length === 0) {
board.innerHTML =
'<div class="notice">В админке пока нет опубликованных карточек.</div>';
return;
}
board.innerHTML = cards.map((card, i) => {
const img = card.imageUrl
? `<img src="${absolutize(card.imageUrl)}" alt="${card.title.replace(/"/g, "&quot;")}" loading="${i < 3 ? "eager" : "lazy"}" />`
: "";
const comment = card.comment
? `<p class="cardComment">${card.comment.replace(/</g, "&lt;")}</p>`
: "";
return `<article class="photoCard">${img}<div class="caption"><span>#${String(i + 1).padStart(2, "0")}</span><strong>${card.title.replace(/</g, "&lt;")}</strong></div>${comment}</article>`;
}).join("");
} catch {
board.innerHTML =
'<div class="notice">Не удалось загрузить карточки.</div>';
stamp.textContent = "Error";
}
}
loadCards();
</script>
</body> </body>
</html> </html>