FHGR_Frontend_Group_3_Project/blog-post-edit.html

368 lines
13 KiB
HTML
Raw 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 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');
// Redirect to the read page
window.location.href = `blog-post.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>