Files
contact.allucanget.biz/templates/admin_newsletter.html
zwitschi 56840ac313
All checks were successful
CI / test (3.11) (push) Successful in 9m26s
CI / build-image (push) Successful in 49s
feat(newsletter): Add subscription confirmation email functionality
- Implemented `send_subscription_confirmation` function to send a confirmation email upon subscription.
- Added a default HTML template for the confirmation email in settings.
- Updated the newsletter management page to handle subscription and unsubscription actions.
- Created new templates for embedding contact and newsletter forms.
- Added styles for the new templates and unified CSS styles across the application.
- Updated tests to reflect changes in the newsletter management API endpoints.
2025-10-30 12:38:26 +01:00

363 lines
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Newsletter Subscribers</title>
<link rel="stylesheet" href="/static/css/styles.css" />
<style>
body {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
h1 {
color: #333;
margin-bottom: 20px;
}
.nav {
margin-bottom: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
}
.nav a {
margin-right: 15px;
color: #007bff;
text-decoration: none;
}
.nav a:hover {
text-decoration: underline;
}
.filters {
margin-bottom: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 5px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.filters input,
.filters select {
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 150px;
}
.filters button {
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.filters button:hover {
background-color: #0056b3;
}
.subscribers-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.subscribers-table th,
.subscribers-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.subscribers-table th {
background-color: #f8f9fa;
font-weight: bold;
}
.subscribers-table tr:hover {
background-color: #f5f5f5;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-top: 20px;
}
.pagination button {
padding: 8px 12px;
border: 1px solid #ddd;
background-color: white;
cursor: pointer;
border-radius: 4px;
}
.pagination button:hover {
background-color: #f8f9fa;
}
.pagination button:disabled {
background-color: #e9ecef;
cursor: not-allowed;
}
.pagination .current-page {
font-weight: bold;
color: #007bff;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.message {
padding: 10px;
margin-bottom: 20px;
border-radius: 4px;
}
.message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.actions {
display: flex;
gap: 5px;
}
.btn {
padding: 4px 8px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover {
background-color: #c82333;
}
</style>
</head>
<body>
<div class="nav">
<a href="/admin/">Dashboard</a>
<a href="/admin/submissions">Contact Submissions</a>
<a href="/admin/settings">Settings</a>
<a href="/auth/logout">Logout</a>
</div>
<h1>Newsletter Subscribers</h1>
<div id="message"></div>
<div class="filters">
<input type="text" id="emailFilter" placeholder="Filter by email..." />
<select id="sortBy">
<option value="subscribed_at">Sort by Date</option>
<option value="email">Sort by Email</option>
</select>
<select id="sortOrder">
<option value="desc">Newest First</option>
<option value="asc">Oldest First</option>
</select>
<button onclick="applyFilters()">Apply Filters</button>
<button onclick="clearFilters()">Clear</button>
</div>
<div id="loading" class="loading">Loading subscribers...</div>
<table
id="subscribersTable"
class="subscribers-table"
style="display: none"
>
<thead>
<tr>
<th>Email</th>
<th>Subscribed Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="subscribersBody"></tbody>
</table>
<div id="pagination" class="pagination" style="display: none">
<button id="prevBtn" onclick="changePage(currentPage - 1)">
Previous
</button>
<span id="pageInfo"></span>
<button id="nextBtn" onclick="changePage(currentPage + 1)">Next</button>
</div>
<script>
let currentPage = 1;
let currentFilters = {
email: "",
sort_by: "subscribed_at",
sort_order: "desc",
};
// Load subscribers on page load
document.addEventListener("DOMContentLoaded", function () {
loadSubscribers();
});
function applyFilters() {
currentFilters.email = document
.getElementById("emailFilter")
.value.trim();
currentFilters.sort_by = document.getElementById("sortBy").value;
currentFilters.sort_order = document.getElementById("sortOrder").value;
currentPage = 1;
loadSubscribers();
}
function clearFilters() {
document.getElementById("emailFilter").value = "";
document.getElementById("sortBy").value = "subscribed_at";
document.getElementById("sortOrder").value = "desc";
currentFilters = {
email: "",
sort_by: "subscribed_at",
sort_order: "desc",
};
currentPage = 1;
loadSubscribers();
}
function loadSubscribers() {
document.getElementById("loading").style.display = "block";
document.getElementById("subscribersTable").style.display = "none";
document.getElementById("pagination").style.display = "none";
const params = new URLSearchParams({
page: currentPage,
per_page: 50,
sort_by: currentFilters.sort_by,
sort_order: currentFilters.sort_order,
});
if (currentFilters.email) {
params.append("email", currentFilters.email);
}
fetch(`/admin/api/newsletter?${params}`)
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
displaySubscribers(data.subscribers);
updatePagination(data.pagination);
} else {
showMessage(
"Error loading subscribers: " +
(data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error loading subscribers", "error");
})
.finally(() => {
document.getElementById("loading").style.display = "none";
});
}
function displaySubscribers(subscribers) {
const tbody = document.getElementById("subscribersBody");
tbody.innerHTML = "";
if (subscribers.length === 0) {
tbody.innerHTML =
'<tr><td colspan="3" style="text-align: center; padding: 40px; color: #666;">No subscribers found</td></tr>';
} else {
subscribers.forEach((subscriber) => {
const row = document.createElement("tr");
row.innerHTML = `
<td>${escapeHtml(subscriber.email)}</td>
<td>${new Date(
subscriber.subscribed_at
).toLocaleDateString()}</td>
<td class="actions">
<button class="btn btn-danger" onclick="unsubscribe('${escapeHtml(
subscriber.email
)}')">Unsubscribe</button>
</td>
`;
tbody.appendChild(row);
});
}
document.getElementById("subscribersTable").style.display = "table";
}
function updatePagination(pagination) {
const pageInfo = document.getElementById("pageInfo");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
pageInfo.textContent = `Page ${pagination.page} of ${pagination.pages} (${pagination.total} total)`;
prevBtn.disabled = pagination.page <= 1;
nextBtn.disabled = pagination.page >= pagination.pages;
document.getElementById("pagination").style.display = "flex";
}
function changePage(page) {
currentPage = page;
loadSubscribers();
}
function unsubscribe(email) {
if (!confirm(`Are you sure you want to unsubscribe ${email}?`)) {
return;
}
fetch("/api/newsletter", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email: email }),
})
.then((response) => response.json())
.then((data) => {
if (data.status === "ok") {
showMessage("Subscriber unsubscribed successfully", "success");
loadSubscribers(); // Reload the list
} else {
showMessage(
"Error unsubscribing: " + (data.message || "Unknown error"),
"error"
);
}
})
.catch((error) => {
console.error("Error:", error);
showMessage("Error unsubscribing subscriber", "error");
});
}
function showMessage(text, type) {
const messageDiv = document.getElementById("message");
messageDiv.className = `message ${type}`;
messageDiv.textContent = text;
messageDiv.style.display = "block";
setTimeout(() => {
messageDiv.style.display = "none";
}, 5000);
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>