415 lines
15 KiB
HTML

<!-- OnlyPrompt - Post Detail: dynamic prompt loaded via API -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OnlyPrompt - Post Detail</title>
<link rel="stylesheet" href="../css/variables.css" />
<link rel="stylesheet" href="../css/base.css" />
<link rel="stylesheet" href="../css/sidebar.css" />
<link rel="stylesheet" href="../css/login.css" />
<link rel="stylesheet" href="../css/topbar.css" />
<link rel="stylesheet" href="../css/post-detail.css" />
<script src="../js/profile-shared.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
</head>
<body>
<div
class="layout"
style="display: flex; min-height: 100vh; background: var(--bg)"
>
<div id="sidebar-container"></div>
<div style="flex: 1; display: flex; flex-direction: column">
<div id="topbar-container"></div>
<main class="post-detail-main">
<div class="post-detail-container" id="detail-content">
<!-- Loading -->
<div
id="detail-loading"
style="text-align: center; padding: 60px 20px; color: #64748b"
>
<i
class="bi bi-hourglass-split"
style="font-size: 2.5rem; display: block; margin-bottom: 12px"
></i>
<p>Loading prompt...</p>
</div>
<!-- Error -->
<div
id="detail-error"
style="
display: none;
text-align: center;
padding: 60px 20px;
color: #ef4444;
"
>
<i
class="bi bi-exclamation-circle"
style="font-size: 2.5rem; display: block; margin-bottom: 12px"
></i>
<h3 id="detail-error-title">Prompt not found</h3>
<p
id="detail-error-msg"
style="color: #64748b; margin-top: 8px"
></p>
<button
onclick="history.back()"
style="
margin-top: 20px;
padding: 10px 24px;
background: #6366f1;
color: #fff;
border: none;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
"
>
Go Back
</button>
</div>
<!-- Content (hidden until loaded) -->
<div id="detail-body" style="display: none">
<!-- Header -->
<div class="post-header">
<div
style="
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
"
>
<div
id="creator-avatar"
style="
width: 42px;
height: 42px;
border-radius: 50%;
background: #6366f1;
color: #fff;
font-weight: 700;
font-size: 1.1rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
"
></div>
<div>
<span
id="creator-name"
style="font-weight: 600; font-size: 0.95rem"
></span>
<span
id="prompt-date"
style="display: block; font-size: 0.8rem; color: #94a3b8"
></span>
</div>
<div style="margin-left: auto">
<span id="tier-badge"></span>
<button
id="edit-prompt-btn"
style="
display: none;
margin-left: 10px;
padding: 6px 14px;
border: none;
border-radius: 10px;
background: #f1f5f9;
color: #334155;
font-weight: 700;
cursor: pointer;
"
>
Edit
</button>
</div>
</div>
<h1 class="post-title" id="prompt-title"></h1>
<div class="post-meta">
<span class="category" id="prompt-category"></span>
<span class="updated" id="prompt-updated"></span>
</div>
<div class="post-stats">
<span id="prompt-rating-stat"></span>
</div>
</div>
<!-- Description -->
<div class="prompt-section" id="desc-section">
<h2>DESCRIPTION</h2>
<div class="prompt-content">
<p id="prompt-description"></p>
</div>
</div>
<!-- Prompt Content (only if accessible) -->
<div class="prompt-section" id="prompt-content-section">
<h2>PROMPT</h2>
<div
class="prompt-content"
id="prompt-body"
style="
white-space: pre-wrap;
font-family: monospace;
background: #f8fafc;
border-radius: 10px;
padding: 16px;
font-size: 0.9rem;
line-height: 1.7;
"
></div>
</div>
<!-- Example Output -->
<div class="example-section" id="example-section" style="display: none">
<h2>EXAMPLE OUTPUT</h2>
<div class="example-content">
<div id="example-output-text" class="example-output-text" style="white-space: pre-wrap"></div>
<div id="example-image" class="example-image" style="display: none">
<img id="example-image-img" src="" alt="Example output image">
</div>
</div>
</div>
<!-- Locked section (shown instead of prompt if no access) -->
<div
id="locked-section"
style="
display: none;
text-align: center;
padding: 40px 20px;
background: #f8fafc;
border-radius: 12px;
margin-bottom: 28px;
"
>
<i
class="bi bi-lock-fill"
style="
font-size: 2.5rem;
color: #94a3b8;
display: block;
margin-bottom: 12px;
"
></i>
<h3 style="margin-bottom: 8px">
This prompt requires a subscription
</h3>
<p style="color: #64748b; margin-bottom: 20px">
Subscribe to <strong id="locked-creator"></strong> to access
this prompt.
</p>
<button
id="locked-subscribe-btn"
style="
padding: 12px 28px;
background: #6366f1;
color: #fff;
border: none;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
"
>
Subscribe <span id="locked-tier-name"></span>
</button>
</div>
<!-- Rating -->
<div class="rating-section" id="rating-section">
<div class="rating-stars" id="rating-display"></div>
</div>
</div>
</div>
</main>
</div>
</div>
<script type="module">
fetch("/sidebar.html")
.then((r) => r.text())
.then((data) => {
document.getElementById("sidebar-container").innerHTML = data;
document
.querySelectorAll("#sidebar-container .sidebar a")
.forEach((l) => l.classList.remove("active"));
});
fetch("/topbar.html")
.then((r) => r.text())
.then(
(data) =>
(document.getElementById("topbar-container").innerHTML = data),
);
const params = new URLSearchParams(location.search);
const id = params.get("id");
if (!id) {
showError("No prompt ID provided", "Add ?id=... to the URL.");
} else {
loadPrompt(id);
}
async function loadPrompt(id) {
try {
const res = await fetch(`/api/v1/prompts/${id}`);
if (res.status === 401) {
location.href = "/login";
return;
}
if (res.status === 404) {
showError(
"Prompt not found",
"This prompt does not exist or you do not have access.",
);
return;
}
if (!res.ok) throw new Error(`Server error ${res.status}`);
const p = await res.json();
renderPrompt(p);
} catch (e) {
showError("Could not load prompt", e.message);
}
}
function renderPrompt(p) {
document.title = `${p.title} — OnlyPrompt`;
document.getElementById("creator-avatar").textContent = p.creatorName
.charAt(0)
.toUpperCase();
document.getElementById("creator-name").textContent = p.creatorName;
document.getElementById("prompt-date").textContent = new Date(
p.timeStamp,
).toLocaleDateString("de-CH", {
year: "numeric",
month: "long",
day: "numeric",
});
document.getElementById("prompt-title").textContent = p.title;
document.getElementById("prompt-category").textContent =
p.categoryName || "Uncategorized";
document.getElementById("prompt-updated").textContent =
`Updated: ${new Date(p.timeStamp).toLocaleDateString()}`;
document.getElementById("prompt-description").textContent =
p.description;
document.getElementById("prompt-body").textContent = p.content;
document.getElementById("prompt-rating-stat").innerHTML =
`<i class="bi ${p.isLiked ? 'bi-heart-fill' : 'bi-heart'}" style="color:#ef4444;"></i> ${p.likeCount || 0} likes
<span style="margin-left:12px;"><i class="bi ${p.isSaved ? 'bi-bookmark-fill' : 'bi-bookmark'}" style="color:#f59e0b;"></i> ${p.saveCount || 0} saves</span>`;
renderExamples(p);
renderOwnerActions(p);
// Tier badge
const badge = document.getElementById("tier-badge");
if (p.price != null && Number(p.price) > 0) {
badge.innerHTML = `<span style="background:#fef3c7;color:#92400e;border-radius:20px;padding:4px 14px;font-size:0.8rem;font-weight:600;">$${Number(p.price).toFixed(2)}</span>`;
} else if (p.tierName) {
badge.innerHTML = `<span style="background:#f1f5f9;color:#475569;border-radius:20px;padding:4px 14px;font-size:0.8rem;font-weight:600;"><i class="bi bi-lock-fill"></i> ${p.tierName}</span>`;
} else {
badge.innerHTML = `<span style="background:#dcfce7;color:#166534;border-radius:20px;padding:4px 14px;font-size:0.8rem;font-weight:600;">Free</span>`;
}
// Rating
if (p.averageRating != null) {
const stars = Math.round(p.averageRating);
document.getElementById("rating-display").innerHTML =
`<span style="color:#f59e0b;font-size:1.1rem;">${"★".repeat(stars)}${"☆".repeat(5 - stars)}</span>
<span style="margin-left:8px;font-weight:600;">${p.averageRating.toFixed(1)}</span>
<span style="color:#94a3b8;font-size:0.85rem;margin-left:4px;">/ 5.0</span>`;
document.getElementById("prompt-rating-stat").innerHTML =
`<i class="bi ${p.isLiked ? 'bi-heart-fill' : 'bi-heart'}" style="color:#ef4444;"></i> ${p.likeCount || 0} likes
<span style="margin-left:12px;"><i class="bi ${p.isSaved ? 'bi-bookmark-fill' : 'bi-bookmark'}" style="color:#f59e0b;"></i> ${p.saveCount || 0} saves</span>
<span style="margin-left:12px;"><i class="bi bi-star-fill" style="color:#f59e0b;"></i> ${p.averageRating.toFixed(1)} rating</span>`;
} else {
document.getElementById("rating-display").innerHTML =
'<span style="color:#94a3b8;font-size:0.9rem;">No ratings yet</span>';
}
// Content visibility
if (p.content) {
document.getElementById("prompt-content-section").style.display =
"block";
document.getElementById("locked-section").style.display = "none";
} else {
document.getElementById("prompt-content-section").style.display =
"none";
document.getElementById("locked-section").style.display = "block";
document.getElementById("locked-creator").textContent = p.creatorName;
document.getElementById("locked-tier-name").textContent = p.tierName
? `${p.tierName}`
: "";
}
document.getElementById("detail-loading").style.display = "none";
document.getElementById("detail-body").style.display = "block";
}
async function renderOwnerActions(p) {
try {
const response = await fetch("/api/v1/auth/me", {
credentials: "same-origin",
});
if (!response.ok) return;
const user = await response.json();
if (user.id !== p.creatorId) return;
const editBtn = document.getElementById("edit-prompt-btn");
editBtn.style.display = "inline-block";
editBtn.onclick = () => {
location.href = `/create?id=${p.id}`;
};
} catch {
// Keep edit hidden if the current user cannot be loaded.
}
}
function renderExamples(p) {
const section = document.getElementById("example-section");
const text = document.getElementById("example-output-text");
const imageWrap = document.getElementById("example-image");
const image = document.getElementById("example-image-img");
if (!p.exampleOutput && !p.exampleImageUrl) {
section.style.display = "none";
return;
}
section.style.display = "block";
text.textContent = p.exampleOutput || "";
text.style.display = p.exampleOutput ? "block" : "none";
if (p.exampleImageUrl) {
image.src = p.exampleImageUrl;
imageWrap.style.display = "block";
} else {
image.removeAttribute("src");
imageWrap.style.display = "none";
}
}
function showError(title, msg) {
document.getElementById("detail-loading").style.display = "none";
document.getElementById("detail-error").style.display = "block";
document.getElementById("detail-error-title").textContent = title;
document.getElementById("detail-error-msg").textContent = msg;
}
</script>
</body>
</html>