- 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.
304 lines
8.9 KiB
HTML
304 lines
8.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Contact Submissions</title>
|
|
<link rel="stylesheet" href="/static/css/styles.css" />
|
|
<style>
|
|
body {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
.filters {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.filters form {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
align-items: end;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="nav">
|
|
<a href="/admin/">Dashboard</a>
|
|
<a href="/admin/settings">Settings</a>
|
|
<a href="{{ url_for('auth.logout') }}">Logout</a>
|
|
</div>
|
|
|
|
<h1>Contact Form Submissions</h1>
|
|
|
|
<div id="message"></div>
|
|
|
|
<div class="filters">
|
|
<form id="filterForm">
|
|
<div>
|
|
<label for="email">Email Filter:</label>
|
|
<input
|
|
type="text"
|
|
id="email"
|
|
name="email"
|
|
placeholder="Filter by email"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label for="date_from">Date From:</label>
|
|
<input type="date" id="date_from" name="date_from" />
|
|
</div>
|
|
<div>
|
|
<label for="date_to">Date To:</label>
|
|
<input type="date" id="date_to" name="date_to" />
|
|
</div>
|
|
<div>
|
|
<label for="per_page">Items per page:</label>
|
|
<select id="per_page" name="per_page">
|
|
<option value="25">25</option>
|
|
<option value="50" selected>50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit">Apply Filters</button>
|
|
<button type="button" class="clear-btn" onclick="clearFilters()">
|
|
Clear
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="loading" class="loading" style="display: none">Loading...</div>
|
|
|
|
<table id="submissionsTable">
|
|
<thead>
|
|
<tr>
|
|
<th data-sort="id">ID</th>
|
|
<th data-sort="name">Name</th>
|
|
<th data-sort="email">Email</th>
|
|
<th data-sort="company">Company</th>
|
|
<th>Message</th>
|
|
<th data-sort="created_at">Date</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="submissionsBody">
|
|
<tr>
|
|
<td colspan="7" class="no-data">Loading submissions...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="pagination" id="pagination"></div>
|
|
|
|
<script>
|
|
let currentPage = 1;
|
|
let currentSortBy = "created_at";
|
|
let currentSortOrder = "desc";
|
|
|
|
// Load submissions on page load
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
loadSubmissions();
|
|
});
|
|
|
|
// Handle filter form submission
|
|
document
|
|
.getElementById("filterForm")
|
|
.addEventListener("submit", function (e) {
|
|
e.preventDefault();
|
|
currentPage = 1;
|
|
loadSubmissions();
|
|
});
|
|
|
|
// Handle table header sorting
|
|
document.querySelectorAll("th[data-sort]").forEach((header) => {
|
|
header.addEventListener("click", function () {
|
|
const sortBy = this.dataset.sort;
|
|
if (currentSortBy === sortBy) {
|
|
currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
|
|
} else {
|
|
currentSortBy = sortBy;
|
|
currentSortOrder = "asc";
|
|
}
|
|
currentPage = 1;
|
|
loadSubmissions();
|
|
});
|
|
});
|
|
|
|
function clearFilters() {
|
|
document.getElementById("email").value = "";
|
|
document.getElementById("date_from").value = "";
|
|
document.getElementById("date_to").value = "";
|
|
document.getElementById("per_page").value = "50";
|
|
currentPage = 1;
|
|
currentSortBy = "created_at";
|
|
currentSortOrder = "desc";
|
|
loadSubmissions();
|
|
}
|
|
|
|
function loadSubmissions() {
|
|
const loading = document.getElementById("loading");
|
|
const table = document.getElementById("submissionsTable");
|
|
|
|
loading.style.display = "block";
|
|
table.style.opacity = "0.5";
|
|
|
|
const params = new URLSearchParams({
|
|
page: currentPage,
|
|
per_page: document.getElementById("per_page").value,
|
|
sort_by: currentSortBy,
|
|
sort_order: currentSortOrder,
|
|
email: document.getElementById("email").value,
|
|
date_from: document.getElementById("date_from").value,
|
|
date_to: document.getElementById("date_to").value,
|
|
});
|
|
|
|
fetch(`/api/contact?${params}`)
|
|
.then((response) => response.json())
|
|
.then((data) => {
|
|
if (data.status === "ok") {
|
|
displaySubmissions(data.submissions);
|
|
displayPagination(data.pagination);
|
|
} else {
|
|
showMessage(
|
|
"Error loading submissions: " +
|
|
(data.message || "Unknown error"),
|
|
"error"
|
|
);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error:", error);
|
|
showMessage("Error loading submissions", "error");
|
|
})
|
|
.finally(() => {
|
|
loading.style.display = "none";
|
|
table.style.opacity = "1";
|
|
});
|
|
}
|
|
|
|
function displaySubmissions(submissions) {
|
|
const tbody = document.getElementById("submissionsBody");
|
|
|
|
if (submissions.length === 0) {
|
|
tbody.innerHTML =
|
|
'<tr><td colspan="7" class="no-data">No submissions found</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = submissions
|
|
.map(
|
|
(submission) => `
|
|
<tr>
|
|
<td>${submission.id}</td>
|
|
<td>${escapeHtml(submission.name)}</td>
|
|
<td>${escapeHtml(submission.email)}</td>
|
|
<td>${escapeHtml(submission.company || "")}</td>
|
|
<td class="submission-details">${escapeHtml(
|
|
submission.message
|
|
)}</td>
|
|
<td>${new Date(submission.created_at).toLocaleString()}</td>
|
|
<td><button class="delete-btn" onclick="deleteSubmission(${
|
|
submission.id
|
|
})">Delete</button></td>
|
|
</tr>
|
|
`
|
|
)
|
|
.join("");
|
|
}
|
|
|
|
function displayPagination(pagination) {
|
|
const paginationDiv = document.getElementById("pagination");
|
|
|
|
if (pagination.pages <= 1) {
|
|
paginationDiv.innerHTML = "";
|
|
return;
|
|
}
|
|
|
|
let buttons = [];
|
|
|
|
// Previous button
|
|
buttons.push(
|
|
`<button ${
|
|
pagination.page <= 1 ? "disabled" : ""
|
|
} onclick="changePage(${pagination.page - 1})">Previous</button>`
|
|
);
|
|
|
|
// Page numbers
|
|
const startPage = Math.max(1, pagination.page - 2);
|
|
const endPage = Math.min(pagination.pages, pagination.page + 2);
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
buttons.push(
|
|
`<button class="${
|
|
i === pagination.page ? "active" : ""
|
|
}" onclick="changePage(${i})">${i}</button>`
|
|
);
|
|
}
|
|
|
|
// Next button
|
|
buttons.push(
|
|
`<button ${
|
|
pagination.page >= pagination.pages ? "disabled" : ""
|
|
} onclick="changePage(${pagination.page + 1})">Next</button>`
|
|
);
|
|
|
|
paginationDiv.innerHTML = buttons.join("");
|
|
}
|
|
|
|
function changePage(page) {
|
|
currentPage = page;
|
|
loadSubmissions();
|
|
window.scrollTo(0, 0);
|
|
}
|
|
|
|
function deleteSubmission(id) {
|
|
if (!confirm("Are you sure you want to delete this submission?")) {
|
|
return;
|
|
}
|
|
|
|
fetch(`/api/contact/${id}`, {
|
|
method: "DELETE",
|
|
})
|
|
.then((response) => response.json())
|
|
.then((data) => {
|
|
if (data.status === "ok") {
|
|
showMessage("Submission deleted successfully", "success");
|
|
loadSubmissions(); // Reload the current page
|
|
} else {
|
|
showMessage(
|
|
"Error deleting submission: " +
|
|
(data.message || "Unknown error"),
|
|
"error"
|
|
);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error:", error);
|
|
showMessage("Error deleting submission", "error");
|
|
});
|
|
}
|
|
|
|
function showMessage(text, type) {
|
|
const messageDiv = document.getElementById("message");
|
|
messageDiv.className = `message ${type}`;
|
|
messageDiv.textContent = text;
|
|
messageDiv.style.display = "block";
|
|
|
|
// Auto-hide after 5 seconds
|
|
setTimeout(() => {
|
|
messageDiv.style.display = "none";
|
|
}, 5000);
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement("div");
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|