FHGR_Frontend_Group_3_Project/journey-edit.html
2026-03-29 15:02:05 +02:00

504 lines
19 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>Edit Journey 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 */
.editor-container {
max-width: 1000px;
margin: var(--size-6) auto;
padding: 0 var(--size-4);
}
.editor-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: 120px;
resize: vertical;
}
.marker-card {
background: var(--surface-2);
border-radius: var(--radius-2);
padding: var(--size-4);
margin-bottom: var(--size-4);
border: 1px solid var(--surface-4);
position: relative;
}
.marker-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--size-2);
cursor: move;
}
.marker-header h4 {
margin: 0;
font-size: var(--font-size-3);
}
.remove-marker {
background: none;
border: none;
color: var(--red-6);
cursor: pointer;
font-size: var(--font-size-3);
}
.remove-marker:hover {
color: var(--red-8);
}
.marker-coords {
font-size: var(--font-size-0);
color: var(--gray-6);
margin-bottom: var(--size-2);
}
.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-success {
background: var(--green-7);
}
.btn-success:hover {
background: var(--green-8);
}
.btn-danger {
background: var(--red-7);
}
.btn-danger:hover {
background: var(--red-8);
}
.btn-outline {
background: transparent;
border: 1px solid var(--surface-4);
color: var(--text-2);
box-shadow: none;
}
.btn-outline:hover {
background: var(--surface-3);
}
.button-group {
display: flex;
gap: var(--size-3);
margin-top: var(--size-6);
flex-wrap: wrap;
}
.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">Blog</a>
</nav>
<div class="user-menu" id="user-menu"></div>
</div>
</div>
</header>
<main class="editor-container">
<div class="editor-form">
<h2 id="form-title">Edit Journey</h2>
<form id="journey-form">
<div class="form-group">
<label for="journey-title">Title</label>
<input type="text" id="journey-title" required>
</div>
<div class="form-group">
<label for="journey-description">Description</label>
<textarea id="journey-description" rows="4"></textarea>
</div>
<div class="form-group">
<label for="journey-visibility">Visibility</label>
<select id="journey-visibility">
<option value="private">Private (only you)</option>
<option value="public">Public (anyone can view)</option>
<option value="shared">Shared (with specific users)</option>
</select>
</div>
<h3><i class="fas fa-map-marker-alt"></i> Chapters (Markers)</h3>
<div id="markers-container"></div>
<div class="button-group">
<button type="button" id="add-marker" class="btn btn-outline"><i class="fas fa-plus"></i> Add Chapter</button>
</div>
<div class="button-group">
<button type="submit" class="btn btn-primary"><i class="fas fa-save"></i> Save Journey</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>
// ==================== GLOBALS ====================
let currentJourneyId = null;
let markersData = [];
const urlParams = new URLSearchParams(window.location.search);
const journeyId = urlParams.get('id');
const isNew = urlParams.has('new');
// ==================== UI HELPERS ====================
function showToast(message, isError = false) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.backgroundColor = isError ? 'var(--red-7)' : 'var(--green-7)';
toast.style.display = 'block';
setTimeout(() => { toast.style.display = 'none'; }, 3000);
}
// ==================== RENDER MARKERS ====================
function renderMarkers() {
const container = document.getElementById('markers-container');
if (!markersData.length) {
container.innerHTML = '<p class="empty-state" style="text-align:center; color: var(--gray-6);">No chapters yet. Click "Add Chapter" to create one.</p>';
return;
}
container.innerHTML = markersData.map((marker, idx) => `
<div class="marker-card" data-marker-index="${idx}">
<div class="marker-header">
<h4><i class="fas fa-map-pin"></i> Chapter ${idx+1}</h4>
<button type="button" class="remove-marker" data-index="${idx}"><i class="fas fa-trash-alt"></i></button>
</div>
<div class="marker-coords">
<i class="fas fa-location-dot"></i> ${marker.lat.toFixed(6)}, ${marker.lng.toFixed(6)}
</div>
<div class="form-group">
<label>Chapter Title</label>
<input type="text" class="marker-title" data-index="${idx}" value="${escapeHtml(marker.title || '')}" placeholder="Title">
</div>
<div class="form-group">
<label>Date (optional)</label>
<input type="date" class="marker-date" data-index="${idx}" value="${marker.date || ''}">
</div>
<div class="form-group">
<label>Description</label>
<textarea class="marker-description" data-index="${idx}" rows="3" placeholder="Write about this chapter...">${escapeHtml(marker.description || '')}</textarea>
</div>
<div class="form-group">
<label>Image URL</label>
<input type="text" class="marker-image" data-index="${idx}" value="${escapeHtml(marker.image || '')}" placeholder="https://...">
</div>
<div class="form-group">
<label>Video URL</label>
<input type="text" class="marker-video" data-index="${idx}" value="${escapeHtml(marker.videoUrl || '')}" placeholder="https://youtu.be/...">
</div>
</div>
`).join('');
// Attach remove listeners
document.querySelectorAll('.remove-marker').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt(btn.dataset.index);
markersData.splice(idx, 1);
renderMarkers();
});
});
// Attach input listeners to update markersData
document.querySelectorAll('.marker-title').forEach(input => {
input.addEventListener('input', (e) => {
const idx = parseInt(input.dataset.index);
markersData[idx].title = input.value;
});
});
document.querySelectorAll('.marker-date').forEach(input => {
input.addEventListener('input', (e) => {
const idx = parseInt(input.dataset.index);
markersData[idx].date = input.value;
});
});
document.querySelectorAll('.marker-description').forEach(textarea => {
textarea.addEventListener('input', (e) => {
const idx = parseInt(textarea.dataset.index);
markersData[idx].description = textarea.value;
});
});
document.querySelectorAll('.marker-image').forEach(input => {
input.addEventListener('input', (e) => {
const idx = parseInt(input.dataset.index);
markersData[idx].image = input.value;
});
});
document.querySelectorAll('.marker-video').forEach(input => {
input.addEventListener('input', (e) => {
const idx = parseInt(input.dataset.index);
markersData[idx].videoUrl = input.value;
});
});
}
// ==================== LOAD JOURNEY ====================
async function loadJourney(id) {
console.log('Loading journey with id:', id);
try {
const res = await fetch(`${API_BASE}/journeys/${id}`, { credentials: 'include' });
if (!res.ok) {
console.error('Failed to fetch journey, status:', res.status);
throw new Error('Journey not found');
}
const journey = await res.json();
console.log('Journey data:', journey);
currentJourneyId = journey.id;
document.getElementById('journey-title').value = journey.title;
document.getElementById('journey-description').value = journey.description || '';
document.getElementById('journey-visibility').value = journey.visibility || 'private';
markersData = journey.markers || [];
console.log('Markers data loaded:', markersData);
renderMarkers();
document.getElementById('form-title').textContent = 'Edit Journey';
} catch (err) {
console.error('Error loading journey:', err);
showToast('Error loading journey: ' + err.message, true);
// Optionally redirect after a delay
setTimeout(() => window.location.href = 'blog-list.html', 2000);
}
}
// ==================== SAVE JOURNEY ====================
async function saveJourney(event) {
event.preventDefault();
const title = document.getElementById('journey-title').value.trim();
const description = document.getElementById('journey-description').value.trim();
const visibility = document.getElementById('journey-visibility').value;
if (!title) {
showToast('Please enter a title', true);
return;
}
// Build markers array from current form data (already in markersData)
const markers = markersData.map(m => ({
lat: m.lat,
lng: m.lng,
title: m.title || '',
date: m.date || '',
description: m.description || '',
image: m.image || '',
videoUrl: m.videoUrl || ''
}));
const payload = { title, description, markers, visibility };
console.log('Saving payload:', payload);
try {
let res;
if (isNew) {
res = await fetch(`${API_BASE}/journeys`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
credentials: 'include'
});
} else {
res = await fetch(`${API_BASE}/journeys/${currentJourneyId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
credentials: 'include'
});
}
if (!res.ok) throw new Error('Save failed');
const data = await res.json();
console.log('Save response:', data);
showToast('Journey saved!');
// Redirect to read view
window.location.href = `blog-post.html?id=${data.id}`;
} catch (err) {
console.error('Error saving journey:', err);
showToast('Error saving journey: ' + err.message, true);
}
}
// ==================== ADD MARKER ====================
function addMarker() {
console.log('Adding new marker');
markersData.push({
lat: 46.8182,
lng: 8.2275,
title: 'New Chapter',
date: '',
description: '',
image: '',
videoUrl: ''
});
renderMarkers();
}
// ==================== INITIALIZATION ====================
document.addEventListener('DOMContentLoaded', async () => {
const authenticated = await checkAuthAndRedirect();
if (!authenticated) return;
updateUserMenu();
if (!isNew && journeyId) {
loadJourney(journeyId);
} else if (isNew) {
currentJourneyId = null;
document.getElementById('form-title').textContent = 'New Journey';
markersData = [];
renderMarkers();
} else {
window.location.href = 'blog-list.html';
}
document.getElementById('journey-form').addEventListener('submit', saveJourney);
document.getElementById('add-marker').addEventListener('click', addMarker);
});
</script>
</body>
</html>