Enhance admin interface, security, and feed management with improved UX and authentication

This commit is contained in:
Young Lee
2025-02-27 18:04:01 -08:00
parent 8839aac24b
commit 56a8263f33
19 changed files with 2022 additions and 523 deletions
+43
View File
@@ -0,0 +1,43 @@
// Authentication helper functions
// Handles user authentication state
export const authHelpers = `
// Check if user is authenticated
function isAuthenticated() {
// Check localStorage first (client-side)
if (localStorage.getItem('authenticated') === 'true') {
return true;
}
// Check for cookie (server-side auth)
function getCookie(name) {
const value = \`; \${document.cookie}\`;
const parts = value.split(\`; \${name}=\`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
return getCookie('admin_auth') === 'true';
}
// Set authentication state
function setAuthenticated(value) {
localStorage.setItem('authenticated', value ? 'true' : 'false');
}
// Logout function
function logout() {
localStorage.removeItem('authenticated');
// Also clear the cookie by setting expiry in the past
document.cookie = 'admin_auth=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.location.href = '/admin/login';
}
// Check authentication on page load
document.addEventListener('DOMContentLoaded', () => {
const path = window.location.pathname;
if (path !== '/admin/login' && !isAuthenticated()) {
window.location.href = '/admin/login';
}
});
`;
+62
View File
@@ -0,0 +1,62 @@
// Clipboard functionality
// Handles copying text to clipboard with visual feedback
export const clipboardScripts = `
// Copy text to clipboard with animation feedback
function copyToClipboard(text, element) {
// Find the parent .copyable element and the content element
const copyableContainer = element.closest('.copyable');
const contentElement = copyableContainer?.querySelector('.copyable-content');
if (!copyableContainer || !contentElement) return;
navigator.clipboard.writeText(text).then(() => {
// Add the 'copied' class to the content element for success styling
contentElement.classList.add('copied');
// Remove the class after a delay (let CSS handle the transitions)
setTimeout(() => {
contentElement.classList.remove('copied');
}, 1500);
}).catch(err => {
console.error('Could not copy text: ', err);
});
}
// Initialize copyable elements
function setupCopyableElements() {
document.querySelectorAll('.copyable').forEach(container => {
const contentElement = container.querySelector('.copyable-content');
const valueElement = container.querySelector('.copyable-value');
if (contentElement && valueElement) {
const textToCopy = valueElement.getAttribute('data-copy') || valueElement.textContent.trim();
// Add click handler to the entire content area
contentElement.addEventListener('click', () => {
copyToClipboard(textToCopy, contentElement);
});
}
});
}
// Confirmation dialogs for deletion
function confirmDelete(feedId) {
if (confirm('Are you sure you want to delete this feed? This action cannot be undone.')) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/admin/feeds/' + feedId + '/delete';
document.body.appendChild(form);
form.submit();
}
}
function confirmDeleteEmail(emailKey, feedId) {
if (confirm('Are you sure you want to delete this email? This action cannot be undone.')) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/admin/emails/' + emailKey + '/delete?feedId=' + feedId;
document.body.appendChild(form);
form.submit();
}
}
`;
+17
View File
@@ -0,0 +1,17 @@
// Main scripts exports file
// Combines and re-exports all JavaScript functionality
import { modalScripts, emailViewScripts, initScripts } from './interactions';
import { clipboardScripts } from './clipboard';
import { authHelpers } from './auth';
// Combine all scripts into a single JavaScript string
export const interactiveScripts = `
${modalScripts}
${emailViewScripts}
${clipboardScripts}
${initScripts}
`;
// Re-export for modular usage if needed
export { modalScripts, emailViewScripts, initScripts, clipboardScripts, authHelpers };
+73
View File
@@ -0,0 +1,73 @@
// Interactive scripts for UI elements
// Handles modals, toggles, and other UI interactions
export const modalScripts = `
// Modal Functionality
function setupModals() {
const modalTriggers = document.querySelectorAll('[data-modal-target]');
modalTriggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.preventDefault();
const modalId = trigger.getAttribute('data-modal-target');
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('visible');
}
});
});
// Close modals when clicking outside or on close button
document.addEventListener('click', (e) => {
if (e.target.classList.contains('modal-bg') || e.target.classList.contains('modal-close')) {
const modal = e.target.closest('.modal-bg');
if (modal) {
modal.classList.remove('visible');
}
}
});
// Close modals with ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const visibleModals = document.querySelectorAll('.modal-bg.visible');
visibleModals.forEach(modal => {
modal.classList.remove('visible');
});
}
});
}
`;
export const emailViewScripts = `
// Toggle email view functions
function showRendered() {
document.getElementById('rendered-view').style.display = 'block';
document.getElementById('raw-view').style.display = 'none';
document.getElementById('rendered-button').classList.add('active');
document.getElementById('raw-button').classList.remove('active');
}
function showRaw() {
document.getElementById('rendered-view').style.display = 'none';
document.getElementById('raw-view').style.display = 'block';
document.getElementById('rendered-button').classList.remove('active');
document.getElementById('raw-button').classList.add('active');
}
`;
export const initScripts = `
// Initialize all interactive elements
function initInteractive() {
setupModals();
setupCopyableElements(); // Initialize copyable elements
// Make these functions globally available
window.showRendered = showRendered;
window.showRaw = showRaw;
}
// Run setup when DOM is fully loaded
document.addEventListener('DOMContentLoaded', initInteractive);
`;