generated from nathanwoodburn/python-webserver-template
All checks were successful
Build Docker / BuildImage (push) Successful in 36s
216 lines
8.1 KiB
HTML
216 lines
8.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Kite Watch - Nathan.Woodburn/</title>
|
|
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
|
<link rel="stylesheet" href="/assets/css/index.css">
|
|
</head>
|
|
|
|
<body>
|
|
<div class="header">
|
|
<h1>Kite Watch</h1>
|
|
<p class="tagline">Find perfect places to fly your kite!</p>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="card">
|
|
<h2>Add a Kite Flying Location</h2>
|
|
<div id="notification" class="notification hidden"></div>
|
|
<form id="location-form">
|
|
<div class="form-group">
|
|
<label for="location-name">Location Name *</label>
|
|
<input type="text" id="location-name" required placeholder="Arboretum, Weston Park, etc.">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="location-description">Notes</label>
|
|
<textarea id="location-description" placeholder="Wind conditions, accessibility, tips, etc."></textarea>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="rating">Rating (1-5):</label>
|
|
<div class="rating-container">
|
|
<input type="range" id="rating" name="rating" min="1" max="5" value="3" step="1" class="form-control">
|
|
<span id="rating-value">3</span>
|
|
</div>
|
|
</div>
|
|
<button type="submit" id="submit-button">Submit Location</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Great Places to Fly Kites</h2>
|
|
<div id="locations-list">
|
|
<p class="loading">Loading locations...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Load locations when the page loads
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadLocations();
|
|
|
|
// Handle form submission
|
|
document.getElementById('location-form').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
|
|
const name = document.getElementById('location-name').value;
|
|
const description = document.getElementById('location-description').value;
|
|
const rating = parseInt(document.getElementById('rating').value);
|
|
|
|
if (!name) {
|
|
showNotification('Please enter a location name', 'error');
|
|
return;
|
|
}
|
|
|
|
// Submit the location
|
|
submitLocation(name, description, rating);
|
|
});
|
|
});
|
|
|
|
// Function to show notifications instead of alerts
|
|
function showNotification(message, type = 'success') {
|
|
const notification = document.getElementById('notification');
|
|
notification.textContent = message;
|
|
notification.className = `notification ${type}`;
|
|
|
|
// Auto hide after 5 seconds
|
|
setTimeout(() => {
|
|
notification.className = 'notification hidden';
|
|
}, 5000);
|
|
}
|
|
|
|
// Function to load locations from the API
|
|
function loadLocations() {
|
|
const locationsList = document.getElementById('locations-list');
|
|
|
|
fetch('/api/locations')
|
|
.then(response => response.json())
|
|
.then(locations => {
|
|
locationsList.innerHTML = '';
|
|
|
|
if (locations.length === 0) {
|
|
locationsList.innerHTML = '<p>No locations added yet. Be the first!</p>';
|
|
return;
|
|
}
|
|
|
|
// Sort locations with newest first
|
|
locations.sort((a, b) => new Date(b.date_added) - new Date(a.date_added));
|
|
|
|
locations.forEach(location => {
|
|
const date = new Date(location.date_added);
|
|
const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
|
|
const locationElement = document.createElement('div');
|
|
locationElement.className = 'location-item';
|
|
locationElement.innerHTML = `
|
|
<h3>${location.name}</h3>
|
|
<p>${location.description || 'No notes provided'}</p>
|
|
<p class="date">Added on: ${formattedDate}</p>
|
|
<p class="rating">Rating: ${location.rating}</p>
|
|
`;
|
|
|
|
locationsList.appendChild(locationElement);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading locations:', error);
|
|
locationsList.innerHTML = '<p>Error loading locations. Please try again.</p>';
|
|
});
|
|
}
|
|
|
|
// Function to submit a new location
|
|
function submitLocation(name, description, rating) {
|
|
const submitButton = document.getElementById('submit-button');
|
|
submitButton.disabled = true;
|
|
submitButton.textContent = 'Submitting...';
|
|
|
|
fetch('/api/locations', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ name, description, rating })
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
// Parse the error response to get the specific error message
|
|
return response.json().then(errorData => {
|
|
throw new Error(errorData.error || 'Failed to submit location');
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
// Clear form
|
|
document.getElementById('location-name').value = '';
|
|
document.getElementById('location-description').value = '';
|
|
document.getElementById('rating').value = 3;
|
|
document.getElementById('rating-value').textContent = 3;
|
|
|
|
// Reload locations
|
|
loadLocations();
|
|
showNotification('Location added successfully!');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error submitting location:', error);
|
|
// Display the specific error message from the server
|
|
showNotification(error.message || 'Failed to add location. Please try again.', 'error');
|
|
})
|
|
.finally(() => {
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = 'Submit Location';
|
|
});
|
|
}
|
|
|
|
document.getElementById('rating').addEventListener('input', function() {
|
|
document.getElementById('rating-value').textContent = this.value;
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.rating-container {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
#rating {
|
|
flex: 1;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
#rating-value {
|
|
font-weight: bold;
|
|
width: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Notification styling */
|
|
.notification {
|
|
padding: 10px 15px;
|
|
border-radius: 4px;
|
|
margin-bottom: 15px;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.notification.success {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.notification.error {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.notification.hidden {
|
|
display: none;
|
|
}
|
|
</style>
|
|
</body>
|
|
|
|
</html> |