2026-03-29 15:02:05 +02:00

325 lines
11 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog Journey Mapper</title>
<!-- Open Props CSS -->
<link rel="stylesheet" href="https://unpkg.com/open-props"/>
<link rel="stylesheet" href="https://unpkg.com/open-props/normalize.min.css"/>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Responsive Design -->
<link rel="stylesheet" href="css/responsive.css">
<style>
* { box-sizing: border-box; }
body {
font-family: var(--font-sans);
background: var(--gray-0);
color: var(--gray-9);
line-height: var(--font-lineheight-3);
margin: 0;
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header */
.site-header {
background: var(--gray-9);
padding: var(--size-4) var(--size-6);
border-bottom: 1px solid var(--surface-4);
}
.site-header .container {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: var(--size-2);
}
.site-title {
margin: 0;
font-size: var(--font-size-4);
font-weight: var(--font-weight-6);
}
.site-title a {
color: var(--indigo-4);
text-decoration: none;
}
.site-nav {
display: flex;
gap: var(--size-4);
}
.site-nav a {
color: var(--gray-2);
text-decoration: none;
font-weight: var(--font-weight-5);
transition: color 0.2s;
padding: var(--size-1) var(--size-2);
border-radius: var(--radius-2);
}
.site-nav a:hover,
.site-nav a.active {
color: var(--indigo-4);
background: var(--surface-2);
}
/* User menu */
.user-menu {
display: flex;
align-items: center;
gap: var(--size-2);
}
.user-menu .username {
color: var(--gray-2);
font-weight: var(--font-weight-5);
}
.logout-btn {
background: var(--indigo-7);
color: white;
border: none;
border-radius: var(--radius-2);
padding: var(--size-1) var(--size-3);
cursor: pointer;
font-size: var(--font-size-1);
font-weight: var(--font-weight-5);
transition: background 0.2s;
}
.logout-btn:hover {
background: var(--indigo-8);
}
/* Main content */
.blog-container {
max-width: 1200px;
margin: var(--size-6) auto;
padding: 0 var(--size-4);
}
.blog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--size-6);
}
.blog-header h1 {
margin: 0;
font-size: var(--font-size-6);
color: var(--indigo-8);
}
.btn {
padding: var(--size-2) var(--size-4);
border: none;
border-radius: var(--radius-2);
cursor: pointer;
font-size: var(--font-size-2);
font-weight: var(--font-weight-5);
display: inline-flex;
align-items: center;
gap: var(--size-2);
transition: all 0.2s var(--ease-2);
box-shadow: var(--shadow-2);
background: var(--indigo-7);
color: white;
text-decoration: none;
}
.btn:hover {
background: var(--indigo-8);
box-shadow: var(--shadow-3);
}
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: var(--size-6);
}
.post-card {
background: var(--surface-1);
border-radius: var(--radius-3);
overflow: hidden;
box-shadow: var(--shadow-2);
transition: transform 0.2s;
}
.post-card:hover {
transform: translateY(-4px);
}
.post-card-image {
width: 100%;
height: 200px;
object-fit: cover;
background: var(--surface-3);
}
.post-card-content {
padding: var(--size-4);
}
.post-card-title {
margin: 0 0 var(--size-2) 0;
font-size: var(--font-size-4);
}
.post-card-title a {
color: var(--gray-9);
text-decoration: none;
}
.post-card-title a:hover {
color: var(--indigo-7);
}
.post-card-meta {
color: var(--gray-6);
font-size: var(--font-size-1);
margin-bottom: var(--size-3);
}
.post-card-excerpt {
color: var(--gray-7);
line-height: var(--font-lineheight-3);
}
.empty-state {
text-align: center;
color: var(--gray-6);
padding: var(--size-8);
font-style: italic;
}
.toast {
position: fixed;
bottom: var(--size-4);
right: var(--size-4);
background: var(--green-7);
color: white;
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-2);
display: none;
z-index: 1100;
}
.filter-tabs {
display: flex;
gap: var(--size-2);
}
.filter-btn {
background: var(--surface-3);
color: var(--text-2);
border: none;
border-radius: var(--radius-2);
padding: var(--size-1) var(--size-3);
cursor: pointer;
font-size: var(--font-size-1);
font-weight: var(--font-weight-5);
transition: all 0.2s;
}
.filter-btn.active {
background: var(--indigo-7);
color: white;
}
.filter-btn:hover {
background: var(--surface-4);
}
</style>
</head>
<body>
<header class="site-header">
<div class="container">
<h1 class="site-title"><a href="map-page.html">Journey Mapper</a></h1>
<div style="display: flex; align-items: center; gap: var(--size-4);">
<div class="filter-tabs">
<button class="filter-btn active" data-filter="all">All</button>
<button class="filter-btn" data-filter="own">My Posts</button>
<button class="filter-btn" data-filter="public">Public Posts</button>
</div>
<nav class="site-nav">
<a href="map-page.html">Map</a>
<a href="blog-list.html" class="active">Blog</a>
</nav>
<div class="user-menu" id="user-menu"></div>
</div>
</div>
</header>
<main class="blog-container">
<div class="blog-header">
<h1><i class="fas fa-newspaper"></i> Blog Posts</h1>
<a href="map-page.html" class="btn"><i class="fas fa-plus"></i> New Journey</a>
</div>
<div id="posts-grid" class="posts-grid">
<!-- Posts loaded dynamically -->
</div>
</main>
<div id="toast" class="toast"></div>
<script src="js/auth.js"></script>
<script>
let allJourneys = [];
// ==================== LOAD JOURNEYS ====================
async function loadJourneys() {
try {
const res = await fetch(`${API_BASE}/journeys`, { credentials: 'include' });
if (!res.ok) throw new Error('Failed to fetch journeys');
const journeys = await res.json();
allJourneys = journeys;
renderJourneys(journeys);
} catch (err) {
console.error(err);
document.getElementById('posts-grid').innerHTML = '<p class="empty-state">Failed to load journeys. Make sure the backend is running.</p>';
}
}
function renderJourneys(journeys) {
const container = document.getElementById('posts-grid');
if (!journeys.length) {
container.innerHTML = `
<div class="empty-state">
<p>No journeys yet. Create one on the map!</p>
</div>
`;
return;
}
container.innerHTML = journeys.map(journey => `
<article class="post-card">
${journey.image ? `<img class="post-card-image" src="${journey.image}" alt="${journey.title}">` : '<div class="post-card-image" style="background: var(--surface-3); display: flex; align-items: center; justify-content: center;"><i class="fas fa-image" style="font-size: 3rem; color: var(--gray-5);"></i></div>'}
<div class="post-card-content">
<h2 class="post-card-title"><a href="blog-post.html?id=${journey.id}">${escapeHtml(journey.title)}</a></h2>
<div class="post-card-meta">
<i class="fas fa-calendar-alt"></i> ${new Date(journey.created_at).toLocaleDateString()}
${journey.markers ? `<span style="margin-left: 12px;"><i class="fas fa-map-marker-alt"></i> ${journey.markers.length} chapters</span>` : ''}
${journey.visibility === 'public' ? '<span class="badge">Public</span>' : ''}
</div>
<div class="post-card-excerpt">${escapeHtml(journey.description || journey.markers?.[0]?.text?.substring(0, 150) + '…')}</div>
</div>
</article>
`).join('');
}
function filterJourneys(filter) {
if (filter === 'all') {
renderJourneys(allJourneys);
} else if (filter === 'own') {
renderJourneys(allJourneys.filter(j => j.owner_id === currentUser?.id));
} else if (filter === 'public') {
renderJourneys(allJourneys.filter(j => j.visibility === 'public'));
}
}
// ==================== INITIALIZATION ====================
document.addEventListener('DOMContentLoaded', async () => {
const authenticated = await checkAuthAndRedirect();
if (!authenticated) return;
updateUserMenu();
loadJourneys();
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
filterJourneys(btn.dataset.filter);
});
});
});
</script>
</body>
</html>