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:
@@ -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, """)}" loading="${i < 3 ? "eager" : "lazy"}" />`
|
||||||
|
: "";
|
||||||
|
const comment = card.comment
|
||||||
|
? `<p class="cardComment">${card.comment.replace(/</g, "<")}</p>`
|
||||||
|
: "";
|
||||||
|
return `<article class="photoCard">${img}<div class="caption"><span>#${String(i + 1).padStart(2, "0")}</span><strong>${card.title.replace(/</g, "<")}</strong></div>${comment}</article>`;
|
||||||
|
}).join("");
|
||||||
|
} catch {
|
||||||
|
board.innerHTML =
|
||||||
|
'<div class="notice">Не удалось загрузить карточки.</div>';
|
||||||
|
stamp.textContent = "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCards();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user