bpb-website/admin.js
chris 07b83c7ae8 Feature: Create Admin UI with Node.js Backend
This commit introduces a web-based admin UI to manage the store's status, backed by a simple Node.js/Express server for file writing.

Key features:
- **Admin UI (, ):** A form to update the scrolling message and closed status. It provides a user-friendly experience with loading states, in-page feedback, and change detection.
- **Node.js Backend ():** A simple Express server that serves the static site and provides a  endpoint. This endpoint receives data from the admin UI, authenticates it, and writes it to .
- **Enhanced Security:** The password is no longer hardcoded in the client-side JavaScript. Authentication is handled server-side, and the password is read from a  file for local development or an environment variable in production.
- **Project Setup (, ):** The project is now a formal Node.js project with dependencies (, , ) and a  file to exclude .
2025-11-12 14:19:34 -05:00

112 lines
4.3 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const scrollingMessageInput = document.getElementById('scrolling-message');
const isClosedCheckbox = document.getElementById('is-closed-checkbox');
const closedMessageField = document.getElementById('closed-message-field');
const closedMessageTextarea = document.getElementById('closed-message');
const passwordInput = document.getElementById('password');
const saveButton = document.getElementById('save-button');
const spinner = document.getElementById('spinner');
const feedbackMessage = document.getElementById('feedback-message');
let initialData = {};
// --- UI Logic ---
isClosedCheckbox.addEventListener('change', () => {
closedMessageField.style.display = isClosedCheckbox.checked ? 'block' : 'none';
});
function checkForChanges() {
const currentData = {
message: scrollingMessageInput.value,
isClosed: isClosedCheckbox.checked,
closedMessage: closedMessageTextarea.value
};
const hasChanged = JSON.stringify(initialData) !== JSON.stringify(currentData);
saveButton.disabled = !hasChanged;
}
[scrollingMessageInput, isClosedCheckbox, closedMessageTextarea].forEach(el => {
el.addEventListener('input', checkForChanges);
el.addEventListener('change', checkForChanges);
});
// --- Data Handling ---
function loadInitialData() {
fetch('update.json')
.then(response => response.json())
.then(data => {
const update = data[0];
initialData = { ...update }; // Store initial state
scrollingMessageInput.value = update.message;
isClosedCheckbox.checked = update.isClosed;
closedMessageTextarea.value = update.closedMessage;
isClosedCheckbox.dispatchEvent(new Event('change'));
saveButton.disabled = true; // Start with button disabled
})
.catch(error => {
console.error('Error loading initial data:', error);
showFeedback('Error loading current status. Please check the console.', 'is-danger');
});
}
function showFeedback(message, type) {
feedbackMessage.textContent = message;
feedbackMessage.className = `notification ${type}`;
feedbackMessage.classList.remove('is-hidden');
setTimeout(() => {
feedbackMessage.classList.add('is-hidden');
}, 5000);
}
saveButton.addEventListener('click', () => {
const password = passwordInput.value;
if (!password) {
showFeedback('Please enter the password to save changes.', 'is-warning');
return;
}
// Show loading state
saveButton.disabled = true;
spinner.style.display = 'inline-block';
const newUpdateData = [
{
message: scrollingMessageInput.value,
isClosed: isClosedCheckbox.checked,
closedMessage: closedMessageTextarea.value
}
];
fetch('/api/update-status', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ password: password, data: newUpdateData }),
})
.then(response => response.json())
.then(result => {
if (result.success) {
showFeedback('Success! The store status has been updated.', 'is-success');
initialData = { ...newUpdateData[0] }; // Update initial state to current
passwordInput.value = ''; // Clear password field
} else {
showFeedback(`Error: ${result.message}`, 'is-danger');
}
})
.catch(error => {
console.error('Error sending update:', error);
showFeedback('An error occurred. Check the console and make sure the server is running.', 'is-danger');
})
.finally(() => {
// Hide loading state
spinner.style.display = 'none';
// Re-enable button only if there are still changes (e.g. if save failed)
checkForChanges();
});
});
// Load the data when the page loads
loadInitialData();
});