369 lines
13 KiB
HTML
369 lines
13 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Blog Post – 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" />
|
||
|
||
<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 {
|
||
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 */
|
||
.post-container {
|
||
max-width: 800px;
|
||
margin: var(--size-6) auto;
|
||
padding: 0 var(--size-4);
|
||
}
|
||
.post-form {
|
||
background: var(--surface-1);
|
||
border-radius: var(--radius-3);
|
||
padding: var(--size-6);
|
||
box-shadow: var(--shadow-2);
|
||
}
|
||
.form-group {
|
||
margin-bottom: var(--size-4);
|
||
}
|
||
label {
|
||
display: block;
|
||
margin-bottom: var(--size-2);
|
||
font-weight: var(--font-weight-6);
|
||
color: var(--gray-8);
|
||
}
|
||
input[type="text"],
|
||
textarea,
|
||
select {
|
||
width: 100%;
|
||
padding: var(--size-2) var(--size-3);
|
||
border: 1px solid var(--surface-4);
|
||
border-radius: var(--radius-2);
|
||
background: var(--surface-2);
|
||
color: var(--text-1);
|
||
font-family: inherit;
|
||
font-size: var(--font-size-2);
|
||
}
|
||
textarea {
|
||
min-height: 200px;
|
||
resize: vertical;
|
||
}
|
||
.image-preview {
|
||
margin-top: var(--size-2);
|
||
max-width: 100%;
|
||
border-radius: var(--radius-2);
|
||
overflow: hidden;
|
||
}
|
||
.image-preview img {
|
||
max-width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
}
|
||
.button-group {
|
||
display: flex;
|
||
gap: var(--size-3);
|
||
margin-top: var(--size-6);
|
||
}
|
||
.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(--gray-7);
|
||
color: white;
|
||
text-decoration: none;
|
||
}
|
||
.btn-primary {
|
||
background: var(--indigo-7);
|
||
}
|
||
.btn-primary:hover {
|
||
background: var(--indigo-8);
|
||
}
|
||
.btn-danger {
|
||
background: var(--red-7);
|
||
}
|
||
.btn-danger:hover {
|
||
background: var(--red-8);
|
||
}
|
||
.btn:hover {
|
||
box-shadow: var(--shadow-3);
|
||
}
|
||
.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;
|
||
}
|
||
</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);">
|
||
<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="post-container">
|
||
<div class="post-form">
|
||
<h2 id="form-title">Edit Post</h2>
|
||
<form id="post-form">
|
||
<div class="form-group">
|
||
<label for="post-title">Title</label>
|
||
<input type="text" id="post-title" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="post-content">Content</label>
|
||
<textarea id="post-content" rows="10" required></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="post-journey">Associated Journey ID (optional)</label>
|
||
<input type="text" id="post-journey" placeholder="e.g., 3" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="post-image">Image URL or Upload</label>
|
||
<input type="text" id="post-image-url" placeholder="https://..." />
|
||
<div style="margin: 8px 0; text-align: center">or</div>
|
||
<input type="file" id="image-upload" accept="image/*" />
|
||
<div class="image-preview" id="image-preview"></div>
|
||
</div>
|
||
<div class="button-group">
|
||
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> Save</button>
|
||
<button type="button" id="delete-post" class="btn btn-danger"><i class="fas fa-trash"></i> Delete</button>
|
||
<a href="blog-list.html" class="btn"><i class="fas fa-times"></i> Cancel</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</main>
|
||
|
||
<div id="toast" class="toast"></div>
|
||
|
||
<script src="js/auth.js"></script>
|
||
<script>
|
||
let currentPostId = null;
|
||
|
||
// ==================== POST CRUD ====================
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const postId = urlParams.get('id');
|
||
const isNew = urlParams.has('new');
|
||
|
||
async function loadPost(id) {
|
||
try {
|
||
const res = await fetch(`${API_BASE}/blog-posts/${id}`, { credentials: 'include' });
|
||
if (!res.ok) throw new Error('Post not found');
|
||
const post = await res.json();
|
||
currentPostId = post.id;
|
||
document.getElementById('post-title').value = post.title;
|
||
document.getElementById('post-content').value = post.content;
|
||
document.getElementById('post-journey').value = post.journeyId || '';
|
||
document.getElementById('post-image-url').value = post.image || '';
|
||
updateImagePreview(post.image);
|
||
document.getElementById('form-title').textContent = 'Edit Post';
|
||
} catch (err) {
|
||
showToast('Error loading post', true);
|
||
}
|
||
}
|
||
|
||
function updateImagePreview(url) {
|
||
const preview = document.getElementById('image-preview');
|
||
if (url) {
|
||
preview.innerHTML = `<img src="${url}" alt="Preview" style="max-width:100%; border-radius: var(--radius-2);">`;
|
||
} else {
|
||
preview.innerHTML = '';
|
||
}
|
||
}
|
||
|
||
async function savePost(event) {
|
||
event.preventDefault();
|
||
const title = document.getElementById('post-title').value.trim();
|
||
const content = document.getElementById('post-content').value.trim();
|
||
const journeyId = document.getElementById('post-journey').value.trim();
|
||
let image = document.getElementById('post-image-url').value.trim();
|
||
|
||
if (!title || !content) {
|
||
showToast('Title and content are required');
|
||
return;
|
||
}
|
||
|
||
const payload = { title, content, journeyId: journeyId || null, image: image || null };
|
||
|
||
try {
|
||
let url, method;
|
||
if (isNew) {
|
||
url = `${API_BASE}/blog-posts`;
|
||
method = 'POST';
|
||
} else {
|
||
url = `${API_BASE}/blog-posts/${currentPostId}`;
|
||
method = 'PUT';
|
||
}
|
||
|
||
const res = await fetch(url, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload),
|
||
credentials: 'include'
|
||
});
|
||
if (!res.ok) throw new Error('Save failed');
|
||
const data = await res.json();
|
||
showToast('Post saved successfully');
|
||
if (isNew) {
|
||
window.location.href = `blog-post-edit.html?id=${data.id}`;
|
||
}
|
||
} catch (err) {
|
||
showToast('Error saving post', true);
|
||
}
|
||
}
|
||
|
||
async function deletePost() {
|
||
if (!currentPostId) return;
|
||
if (!confirm('Delete this post?')) return;
|
||
try {
|
||
const res = await fetch(`${API_BASE}/blog-posts/${currentPostId}`, {
|
||
method: 'DELETE',
|
||
credentials: 'include'
|
||
});
|
||
if (!res.ok) throw new Error('Delete failed');
|
||
showToast('Post deleted');
|
||
setTimeout(() => { window.location.href = 'blog-list.html'; }, 1000);
|
||
} catch (err) {
|
||
showToast('Error deleting post', true);
|
||
}
|
||
}
|
||
|
||
// Image upload (convert to base64)
|
||
document.getElementById('image-upload').addEventListener('change', function(e) {
|
||
const file = e.target.files[0];
|
||
if (!file) return;
|
||
const reader = new FileReader();
|
||
reader.onload = function(evt) {
|
||
const base64 = evt.target.result;
|
||
document.getElementById('post-image-url').value = base64;
|
||
updateImagePreview(base64);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
|
||
document.getElementById('post-form').addEventListener('submit', savePost);
|
||
document.getElementById('delete-post').addEventListener('click', deletePost);
|
||
|
||
// ==================== INITIALIZATION ====================
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
const authenticated = await checkAuthAndRedirect();
|
||
if (!authenticated) return;
|
||
updateUserMenu();
|
||
|
||
if (!isNew && postId) {
|
||
loadPost(postId);
|
||
} else if (isNew) {
|
||
currentPostId = null;
|
||
document.getElementById('form-title').textContent = 'New Post';
|
||
document.getElementById('delete-post').style.display = 'none';
|
||
} else {
|
||
// No id and not new – redirect to list
|
||
window.location.href = 'blog-list.html';
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |