501 lines
19 KiB
HTML
501 lines
19 KiB
HTML
<!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">
|
||
|
||
<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> |