Remove unused templates for newsletter creation, settings, and submissions; update unsubscribe confirmation link; add tests for email templates API.
This commit is contained in:
15
templates/_base.html
Normal file
15
templates/_base.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}Newsletter Subscribers{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
{% block extra_styles %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% include "_nav.html" %}
|
||||
<h1>{% block heading %}Newsletter Subscribers{% endblock %}</h1>
|
||||
{% block content %}{% endblock %}
|
||||
{% block extra_scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
53
templates/_nav.html
Normal file
53
templates/_nav.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<nav class="nav">
|
||||
<!--
|
||||
admin_dashboard.html
|
||||
admin_newsletter.html
|
||||
admin_newsletter_create.html
|
||||
admin_settings.html
|
||||
admin_submissions.html
|
||||
admin_embeds.html
|
||||
|
||||
newsletter_manage.html
|
||||
unsubscribe_confirmation.html
|
||||
-->
|
||||
<a
|
||||
href="/admin/"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Dashboard</a
|
||||
>
|
||||
<a
|
||||
href="/admin/submissions"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Contact Submissions</a
|
||||
>
|
||||
<a
|
||||
href="/admin/newsletter"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Subscribers</a
|
||||
>
|
||||
<a
|
||||
href="/admin/newsletter/create"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Create Newsletter</a
|
||||
>
|
||||
<a
|
||||
href="/admin/embeds"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Embeds</a
|
||||
>
|
||||
<a
|
||||
href="/admin/email-templates"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Email Templates</a
|
||||
>
|
||||
<a
|
||||
href="/admin/settings"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Settings</a
|
||||
>
|
||||
<a
|
||||
href="{{ url_for('auth.logout') }}"
|
||||
style="color: #007bff; text-decoration: none"
|
||||
>Logout</a
|
||||
>
|
||||
</nav>
|
||||
68
templates/admin/admin_dashboard.html
Normal file
68
templates/admin/admin_dashboard.html
Normal file
@@ -0,0 +1,68 @@
|
||||
{% extends "_base.html" %} {% block title %}Admin Dashboard{% endblock %} {%
|
||||
block heading %}Admin Dashboard{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" /> {% endblock %} {% block
|
||||
content %}
|
||||
<div id="message"></div>
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<h3 id="contact-count">--</h3>
|
||||
<p>Contact Submissions</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newsletter-count">--</h3>
|
||||
<p>Newsletter Subscribers</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="settings-count">--</h3>
|
||||
<p>App Settings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="dashboard-card">
|
||||
<h2>Contact Form Submissions</h2>
|
||||
<p>View and manage contact form submissions from your website visitors.</p>
|
||||
<a href="/admin/submissions">Manage Submissions</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Newsletter Subscribers</h2>
|
||||
<p>
|
||||
Manage newsletter subscriptions and send newsletters to your subscribers.
|
||||
</p>
|
||||
<a href="/admin/newsletter">Manage Subscribers</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Application Settings</h2>
|
||||
<p>Configure application settings and environment variables.</p>
|
||||
<a href="/admin/settings">Manage Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Create Newsletter</h2>
|
||||
<p>Create and send newsletters to your subscribers.</p>
|
||||
<a href="/admin/newsletter/create">Create Newsletter</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Embeddable Forms</h2>
|
||||
<p>
|
||||
Instructions for embedding contact and newsletter forms on other websites.
|
||||
</p>
|
||||
<a href="/admin/embeds">View Embed Codes</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logout">
|
||||
<a href="/auth/logout">Logout</a>
|
||||
</div>
|
||||
{% endblock %}{% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
<script>
|
||||
// Load stats when page loads
|
||||
if (typeof window.admin.loadDashboardStats === "function") {
|
||||
window.admin.loadDashboardStats();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
21
templates/admin/admin_email_templates.html
Normal file
21
templates/admin/admin_email_templates.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "_base.html" %} {% block title %}Email Templates{% endblock %} {%
|
||||
block heading %}Email Templates{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" /> {% endblock %} {% block
|
||||
content %}
|
||||
<div class="settings-management">
|
||||
<h2>Newsletter Confirmation Template</h2>
|
||||
<p>Edit the HTML template used for the newsletter confirmation email.</p>
|
||||
<div id="message"></div>
|
||||
<form id="templateForm">
|
||||
<div class="form-group">
|
||||
<label for="newsletterTemplate">Template HTML</label>
|
||||
<textarea id="newsletterTemplate" rows="15" cols="80"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary" type="submit">Save Template</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %} {% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
93
templates/admin/admin_embeds.html
Normal file
93
templates/admin/admin_embeds.html
Normal file
@@ -0,0 +1,93 @@
|
||||
{% extends "_base.html" %} {% block title %}Embeddable Forms{% endblock %} {%
|
||||
block heading %}Embeddable Forms{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" /> {% endblock %} {% block
|
||||
content %}
|
||||
<div class="settings-management">
|
||||
<div id="contactForm" style="display: flex; justify-content: space-between">
|
||||
<div id="contactFormSettings" style="min-height: 300px">
|
||||
<h2>Contact Form</h2>
|
||||
<p>
|
||||
Use the following HTML code to embed the contact form on other websites:
|
||||
</p>
|
||||
<div class="embed-config">
|
||||
<label
|
||||
>Width: <input id="contactWidth" type="text" value="400"
|
||||
/></label>
|
||||
<label
|
||||
>Height: <input id="contactHeight" type="text" value="600"
|
||||
/></label>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="saveEmbedSetting('embed_contact_width','contactWidth')"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="saveEmbedSetting('embed_contact_height','contactHeight')"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<textarea id="iframeCode" class="iframe-code" readonly></textarea>
|
||||
<button class="btn btn-secondary" onclick="copyIframeCode()">
|
||||
Copy Contact Iframe
|
||||
</button>
|
||||
</div>
|
||||
<div id="contactFormPreview"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<div
|
||||
id="newsletterForm"
|
||||
style="display: flex; justify-content: space-between"
|
||||
>
|
||||
<div id="newsletterFormSettings" style="min-height: 400px">
|
||||
<h2>Newsletter Subscription Form</h2>
|
||||
<p>
|
||||
Use the following HTML code to embed the newsletter subscription form on
|
||||
other websites:
|
||||
</p>
|
||||
<div class="embed-config">
|
||||
<label
|
||||
>Width: <input id="newsletterWidth" type="text" value="600"
|
||||
/></label>
|
||||
<label
|
||||
>Height: <input id="newsletterHeight" type="text" value="300"
|
||||
/></label>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="saveEmbedSetting('embed_newsletter_width','newsletterWidth')"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="saveEmbedSetting('embed_newsletter_height','newsletterHeight')"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="iframeNewsletterCode"
|
||||
class="iframe-code"
|
||||
readonly
|
||||
></textarea>
|
||||
<button class="btn btn-secondary" onclick="copyNewsletterIframeCode()">
|
||||
Copy Newsletter Iframe
|
||||
</button>
|
||||
</div>
|
||||
<div id="newsletterFormPreview"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-management">
|
||||
<p>
|
||||
Replace <code>http://your-server-domain</code> with your actual server
|
||||
domain and port (e.g., https://yourdomain.com).
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %} {% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
</div>
|
||||
42
templates/admin/admin_newsletter.html
Normal file
42
templates/admin/admin_newsletter.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "_base.html" %} {% block title %}Newsletter Subscribers{% endblock %}
|
||||
{% block heading %}Newsletter Subscribers{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" /> {% endblock %} {% block
|
||||
content %}
|
||||
|
||||
<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>
|
||||
|
||||
{% endblock %} {% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
98
templates/admin/admin_newsletter_create.html
Normal file
98
templates/admin/admin_newsletter_create.html
Normal file
@@ -0,0 +1,98 @@
|
||||
{% extends "_base.html" %} {% block title %}Create Newsletter{% endblock %} {%
|
||||
block heading %}Create Newsletter{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" /> {% endblock %} {% block
|
||||
content %}
|
||||
<div id="message"></div>
|
||||
|
||||
<div class="newsletter-stats">
|
||||
<div class="stat-card">
|
||||
<h4>Total Subscribers</h4>
|
||||
<div class="number" id="totalSubscribers">--</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h4>Active Subscribers</h4>
|
||||
<div class="number" id="activeSubscribers">--</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h4>Last Sent</h4>
|
||||
<div class="number" id="lastSent">--</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="newsletterForm">
|
||||
<div class="form-section">
|
||||
<h2>Newsletter Details</h2>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="subject">Subject Line *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
placeholder="Enter newsletter subject"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="senderName">Sender Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="senderName"
|
||||
name="sender_name"
|
||||
placeholder="Your Name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">Content *</label>
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
required
|
||||
placeholder="Write your newsletter content here..."
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="sendDate">Send Date (optional)</label>
|
||||
<input type="datetime-local" id="sendDate" name="send_date" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" name="status">
|
||||
<option value="draft">Draft</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
<option value="sent">Sent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h2>Actions</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="previewNewsletter()"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveDraft()">
|
||||
Save Draft
|
||||
</button>
|
||||
<button type="button" class="btn btn-success" onclick="sendNewsletter()">
|
||||
Send Newsletter
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" onclick="clearForm()">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="previewSection" class="newsletter-preview hidden">
|
||||
<h3>Newsletter Preview</h3>
|
||||
<div id="previewContent"></div>
|
||||
</div>
|
||||
{% endblock %}{% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
59
templates/admin/admin_settings.html
Normal file
59
templates/admin/admin_settings.html
Normal file
@@ -0,0 +1,59 @@
|
||||
{% extends "_base.html" %} {% block title %}Settings{% endblock %} {% block
|
||||
heading %}Application Settings{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||
{% endblock %} {% block content %}
|
||||
<div class="settings-cards">
|
||||
{% for category, category_settings in settings.items() %}
|
||||
<div class="settings-card">
|
||||
<h2 class="card-title">{{ category }}</h2>
|
||||
<div class="card-body">
|
||||
{% for key, value in category_settings.items() %}
|
||||
<div class="setting"><strong>{{ key }}:</strong> {{ value }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Dynamic Settings Management</h2>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<div class="settings-list" id="settingsList">
|
||||
<p>Loading settings...</p>
|
||||
</div>
|
||||
|
||||
<h3>Add New Setting</h3>
|
||||
<form id="addSettingForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="newKey">Setting Key:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="newKey"
|
||||
name="key"
|
||||
required
|
||||
placeholder="e.g., maintenance_mode"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newValue">Setting Value:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="newValue"
|
||||
name="value"
|
||||
required
|
||||
placeholder="e.g., false"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Add Setting</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %} {% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
80
templates/admin/admin_submissions.html
Normal file
80
templates/admin/admin_submissions.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{% extends "_base.html" %} {% block title %}Contact Submissions{% endblock %} {%
|
||||
block heading %}Contact Form Submissions{% endblock %} {% block extra_styles %}
|
||||
<link rel="stylesheet" href="/static/css/admin.css" />
|
||||
<style>
|
||||
.filters {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.filters form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: end;
|
||||
}
|
||||
</style>
|
||||
{% endblock %} {% block content %}
|
||||
<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>
|
||||
|
||||
{% endblock %} {% block extra_scripts %}
|
||||
<script src="/static/js/admin.js"></script>
|
||||
{% endblock %}
|
||||
@@ -1,119 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Dashboard</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
<style>
|
||||
body {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Admin Dashboard</h1>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<h3 id="contact-count">--</h3>
|
||||
<p>Contact Submissions</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="newsletter-count">--</h3>
|
||||
<p>Newsletter Subscribers</p>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h3 id="settings-count">--</h3>
|
||||
<p>App Settings</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="dashboard-card">
|
||||
<h2>Contact Form Submissions</h2>
|
||||
<p>
|
||||
View and manage contact form submissions from your website visitors.
|
||||
</p>
|
||||
<a href="/admin/submissions">Manage Submissions</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Newsletter Subscribers</h2>
|
||||
<p>
|
||||
Manage newsletter subscriptions and send newsletters to your
|
||||
subscribers.
|
||||
</p>
|
||||
<a href="/admin/newsletter">Manage Subscribers</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Application Settings</h2>
|
||||
<p>Configure application settings and environment variables.</p>
|
||||
<a href="/admin/settings">Manage Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Create Newsletter</h2>
|
||||
<p>Create and send newsletters to your subscribers.</p>
|
||||
<a href="/admin/newsletter/create">Create Newsletter</a>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<h2>Embeddable Forms</h2>
|
||||
<p>
|
||||
Instructions for embedding contact and newsletter forms on other
|
||||
websites.
|
||||
</p>
|
||||
<a href="/admin/embeds">View Embed Codes</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logout">
|
||||
<a href="/auth/logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load dashboard statistics
|
||||
async function loadStats() {
|
||||
try {
|
||||
// Load contact submissions count
|
||||
const contactResponse = await fetch(
|
||||
"/admin/api/contact?page=1&per_page=1"
|
||||
);
|
||||
if (contactResponse.ok) {
|
||||
const contactData = await contactResponse.json();
|
||||
document.getElementById("contact-count").textContent =
|
||||
contactData.pagination.total;
|
||||
}
|
||||
|
||||
// Load newsletter subscribers count
|
||||
const newsletterResponse = await fetch(
|
||||
"/admin/api/newsletter?page=1&per_page=1"
|
||||
);
|
||||
if (newsletterResponse.ok) {
|
||||
const newsletterData = await newsletterResponse.json();
|
||||
document.getElementById("newsletter-count").textContent =
|
||||
newsletterData.pagination.total;
|
||||
}
|
||||
|
||||
// Load settings count
|
||||
const settingsResponse = await fetch("/admin/api/settings");
|
||||
if (settingsResponse.ok) {
|
||||
const settingsData = await settingsResponse.json();
|
||||
document.getElementById("settings-count").textContent = Object.keys(
|
||||
settingsData.settings
|
||||
).length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load dashboard stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load stats when page loads
|
||||
loadStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,112 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Embeddable Forms</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
<style>
|
||||
.iframe-code {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="logout">
|
||||
<a
|
||||
href="/admin/"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Dashboard</a
|
||||
>
|
||||
<a
|
||||
href="/admin/submissions"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>View Submissions</a
|
||||
>
|
||||
<a
|
||||
href="/admin/settings"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Settings</a
|
||||
>
|
||||
<a
|
||||
href="{{ url_for('auth.logout') }}"
|
||||
style="color: #007bff; text-decoration: none"
|
||||
>Logout</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<h1>Embeddable Forms</h1>
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Contact Form</h2>
|
||||
<p>
|
||||
Use the following HTML code to embed the contact form on other websites:
|
||||
</p>
|
||||
<textarea id="iframeCode" class="iframe-code" readonly>
|
||||
<iframe src="http://your-server-domain/embed/contact" width="600" height="400" frameborder="0" allowfullscreen></iframe>
|
||||
</textarea>
|
||||
<button class="btn btn-secondary" onclick="copyIframeCode()">
|
||||
Copy Contact Iframe
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Newsletter Subscription Form</h2>
|
||||
<p>
|
||||
Use the following HTML code to embed the newsletter subscription form on
|
||||
other websites:
|
||||
</p>
|
||||
<textarea id="iframeNewsletterCode" class="iframe-code" readonly>
|
||||
<iframe src="http://your-server-domain/embed/newsletter" width="600" height="300" frameborder="0" allowfullscreen></iframe>
|
||||
</textarea>
|
||||
<button class="btn btn-secondary" onclick="copyNewsletterIframeCode()">
|
||||
Copy Newsletter Iframe
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<p>
|
||||
Replace <code>http://your-server-domain</code> with your actual server
|
||||
domain and port (e.g., https://yourdomain.com).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyIframeCode() {
|
||||
const textarea = document.getElementById("iframeCode");
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
showMessage("Contact iframe code copied to clipboard!", "success");
|
||||
}
|
||||
|
||||
function copyNewsletterIframeCode() {
|
||||
const textarea = document.getElementById("iframeNewsletterCode");
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
showMessage("Newsletter iframe code copied to clipboard!", "success");
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
// Create message div if it doesn't exist
|
||||
let messageDiv = document.getElementById("message");
|
||||
if (!messageDiv) {
|
||||
messageDiv = document.createElement("div");
|
||||
messageDiv.id = "message";
|
||||
document.body.insertBefore(messageDiv, document.body.firstChild);
|
||||
}
|
||||
messageDiv.className = `message ${type}`;
|
||||
messageDiv.textContent = text;
|
||||
messageDiv.style.display = "block";
|
||||
|
||||
setTimeout(() => {
|
||||
messageDiv.style.display = "none";
|
||||
}, 5000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,362 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,458 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Create Newsletter</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;
|
||||
}
|
||||
.form-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.form-section h2 {
|
||||
margin-top: 0;
|
||||
color: #555;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.form-group textarea {
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
.btn-success:hover {
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.message.info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
.newsletter-preview {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.newsletter-preview h3 {
|
||||
margin-top: 0;
|
||||
color: #555;
|
||||
}
|
||||
.newsletter-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-card h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.stat-card .number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<a href="/admin/">Dashboard</a>
|
||||
<a href="/admin/newsletter">Subscribers</a>
|
||||
<a href="/admin/settings">Settings</a>
|
||||
<a href="/auth/logout">Logout</a>
|
||||
</div>
|
||||
|
||||
<h1>Create Newsletter</h1>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<div class="newsletter-stats">
|
||||
<div class="stat-card">
|
||||
<h4>Total Subscribers</h4>
|
||||
<div class="number" id="totalSubscribers">--</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h4>Active Subscribers</h4>
|
||||
<div class="number" id="activeSubscribers">--</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<h4>Last Sent</h4>
|
||||
<div class="number" id="lastSent">--</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="newsletterForm">
|
||||
<div class="form-section">
|
||||
<h2>Newsletter Details</h2>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="subject">Subject Line *</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
placeholder="Enter newsletter subject"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="senderName">Sender Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="senderName"
|
||||
name="sender_name"
|
||||
placeholder="Your Name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="content">Content *</label>
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
required
|
||||
placeholder="Write your newsletter content here..."
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="sendDate">Send Date (optional)</label>
|
||||
<input type="datetime-local" id="sendDate" name="send_date" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="status">Status</label>
|
||||
<select id="status" name="status">
|
||||
<option value="draft">Draft</option>
|
||||
<option value="scheduled">Scheduled</option>
|
||||
<option value="sent">Sent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<h2>Actions</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="previewNewsletter()"
|
||||
>
|
||||
Preview
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveDraft()">
|
||||
Save Draft
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success"
|
||||
onclick="sendNewsletter()"
|
||||
>
|
||||
Send Newsletter
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" onclick="clearForm()">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="previewSection" class="newsletter-preview hidden">
|
||||
<h3>Newsletter Preview</h3>
|
||||
<div id="previewContent"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let newsletterStats = {};
|
||||
|
||||
// Load newsletter stats on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadNewsletterStats();
|
||||
});
|
||||
|
||||
function loadNewsletterStats() {
|
||||
// Load subscriber count
|
||||
fetch("/admin/api/newsletter?page=1&per_page=1")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
document.getElementById("totalSubscribers").textContent =
|
||||
data.pagination.total;
|
||||
document.getElementById("activeSubscribers").textContent =
|
||||
data.pagination.total; // For now, assume all are active
|
||||
newsletterStats.totalSubscribers = data.pagination.total;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error loading subscriber stats:", error);
|
||||
});
|
||||
|
||||
// For now, set last sent as N/A
|
||||
document.getElementById("lastSent").textContent = "N/A";
|
||||
}
|
||||
|
||||
function previewNewsletter() {
|
||||
const subject = document.getElementById("subject").value.trim();
|
||||
const content = document.getElementById("content").value.trim();
|
||||
const senderName = document.getElementById("senderName").value.trim();
|
||||
|
||||
if (!subject || !content) {
|
||||
showMessage("Subject and content are required for preview.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const previewContent = document.getElementById("previewContent");
|
||||
previewContent.innerHTML = `
|
||||
<h2>${escapeHtml(subject)}</h2>
|
||||
${
|
||||
senderName
|
||||
? `<p><strong>From:</strong> ${escapeHtml(senderName)}</p>`
|
||||
: ""
|
||||
}
|
||||
<div style="margin-top: 20px; line-height: 1.6;">
|
||||
${content.replace(/\n/g, "<br>")}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById("previewSection").classList.remove("hidden");
|
||||
showMessage("Newsletter preview generated.", "info");
|
||||
}
|
||||
|
||||
function saveDraft() {
|
||||
const formData = new FormData(document.getElementById('newsletterForm'));
|
||||
const newsletterData = {
|
||||
subject: formData.get('subject'),
|
||||
content: formData.get('content'),
|
||||
sender_name: formData.get('sender_name'),
|
||||
send_date: formData.get('send_date'),
|
||||
status: 'draft'
|
||||
};
|
||||
|
||||
if (!newsletterData.subject || !newsletterData.content) {
|
||||
showMessage('Subject and content are required.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/admin/api/newsletters', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newsletterData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'ok') {
|
||||
showMessage('Newsletter draft saved successfully!', 'success');
|
||||
} else {
|
||||
showMessage(data.message || 'Failed to save draft.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error saving draft:', error);
|
||||
showMessage('Failed to save draft.', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function sendNewsletter() {
|
||||
const formData = new FormData(document.getElementById('newsletterForm'));
|
||||
const newsletterData = {
|
||||
subject: formData.get('subject'),
|
||||
content: formData.get('content'),
|
||||
sender_name: formData.get('sender_name'),
|
||||
send_date: formData.get('send_date'),
|
||||
status: 'sent'
|
||||
};
|
||||
|
||||
if (!newsletterData.subject || !newsletterData.content) {
|
||||
showMessage('Subject and content are required.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Are you sure you want to send this newsletter to ${newsletterStats.totalSubscribers || 0} subscribers?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First save the newsletter
|
||||
fetch('/admin/api/newsletters', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newsletterData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'ok') {
|
||||
const newsletterId = data.newsletter_id;
|
||||
// Now send it
|
||||
return fetch(`/admin/api/newsletters/${newsletterId}/send`, {
|
||||
method: 'POST'
|
||||
});
|
||||
} else {
|
||||
throw new Error(data.message || 'Failed to save newsletter.');
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'ok') {
|
||||
showMessage(`Newsletter sent successfully to ${data.sent_count} subscribers!`, 'success');
|
||||
} else {
|
||||
showMessage(data.message || 'Failed to send newsletter.', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error sending newsletter:', error);
|
||||
showMessage('Failed to send newsletter.', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function clearForm() {
|
||||
if (
|
||||
confirm(
|
||||
"Are you sure you want to clear the form? All unsaved changes will be lost."
|
||||
)
|
||||
) {
|
||||
document.getElementById("newsletterForm").reset();
|
||||
document.getElementById("previewSection").classList.add("hidden");
|
||||
showMessage("Form cleared.", "info");
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -1,337 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Settings</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
<style>
|
||||
.setting strong {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
.iframe-code {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-family: monospace;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="logout">
|
||||
<a
|
||||
href="/admin/"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Dashboard</a
|
||||
>
|
||||
<a
|
||||
href="/admin/submissions"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>View Submissions</a
|
||||
>
|
||||
<a
|
||||
href="/admin/embeds"
|
||||
style="color: #007bff; text-decoration: none; margin-right: 20px"
|
||||
>Embeds</a
|
||||
>
|
||||
<a
|
||||
href="{{ url_for('auth.logout') }}"
|
||||
style="color: #007bff; text-decoration: none"
|
||||
>Logout</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<h1>Application Settings</h1>
|
||||
|
||||
{% for category, category_settings in settings.items() %}
|
||||
<div class="setting-group">
|
||||
<h2>{{ category }}</h2>
|
||||
{% for key, value in category_settings.items() %}
|
||||
<div class="setting"><strong>{{ key }}:</strong> {{ value }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Dynamic Settings Management</h2>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<div class="settings-list" id="settingsList">
|
||||
<p>Loading settings...</p>
|
||||
</div>
|
||||
|
||||
<h3>Add New Setting</h3>
|
||||
<form id="addSettingForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="newKey">Setting Key:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="newKey"
|
||||
name="key"
|
||||
required
|
||||
placeholder="e.g., maintenance_mode"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newValue">Setting Value:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="newValue"
|
||||
name="value"
|
||||
required
|
||||
placeholder="e.g., false"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Add Setting</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Embeddable Forms</h2>
|
||||
<p>
|
||||
Manage embeddable forms on the <a href="/admin/embeds">Embeds page</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-management">
|
||||
<h2>Email Templates</h2>
|
||||
<div id="emailTemplates">
|
||||
<p>Loading email templates...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let appSettings = {};
|
||||
|
||||
// Load settings on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadSettings();
|
||||
});
|
||||
|
||||
// Handle add setting form
|
||||
document
|
||||
.getElementById("addSettingForm")
|
||||
.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
const key = formData.get("key").trim();
|
||||
const value = formData.get("value").trim();
|
||||
|
||||
if (!key || !value) {
|
||||
showMessage("Both key and value are required.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
addSetting(key, value);
|
||||
});
|
||||
|
||||
function loadSettings() {
|
||||
fetch("/admin/api/settings")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
appSettings = data.settings;
|
||||
displaySettings();
|
||||
} else {
|
||||
showMessage(
|
||||
"Error loading settings: " + (data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error loading settings", "error");
|
||||
});
|
||||
}
|
||||
|
||||
function displaySettings() {
|
||||
const container = document.getElementById("settingsList");
|
||||
|
||||
if (Object.keys(appSettings).length === 0) {
|
||||
container.innerHTML = "<p>No dynamic settings configured.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsHtml = Object.entries(appSettings)
|
||||
.map(([key, value]) => {
|
||||
const isTemplate = key === "newsletter_confirmation_template";
|
||||
const inputType = isTemplate ? "textarea" : "input";
|
||||
const inputAttrs = isTemplate
|
||||
? 'rows="10" cols="50"'
|
||||
: 'type="text"';
|
||||
return `
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<strong>${escapeHtml(key)}:</strong> ${
|
||||
isTemplate
|
||||
? "<em>HTML template</em>"
|
||||
: escapeHtml(value.substring(0, 50)) +
|
||||
(value.length > 50 ? "..." : "")
|
||||
}
|
||||
</div>
|
||||
<div class="setting-actions">
|
||||
<button class="btn btn-secondary" onclick="editSetting('${escapeHtml(
|
||||
key
|
||||
)}')">Edit</button>
|
||||
<button class="btn btn-danger" onclick="deleteSetting('${escapeHtml(
|
||||
key
|
||||
)}')">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-form" id="edit-${escapeHtml(key)}">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Key:</label>
|
||||
<input type="text" value="${escapeHtml(key)}" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>New Value:</label>
|
||||
<${inputType} id="edit-value-${escapeHtml(
|
||||
key
|
||||
)}" ${inputAttrs} required>${escapeHtml(value)}</${inputType}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary" onclick="updateSetting('${escapeHtml(
|
||||
key
|
||||
)}')">Update</button>
|
||||
<button class="btn btn-secondary" onclick="cancelEdit('${escapeHtml(
|
||||
key
|
||||
)}')">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
container.innerHTML = settingsHtml;
|
||||
}
|
||||
|
||||
function addSetting(key, value) {
|
||||
fetch(`/admin/api/settings/${encodeURIComponent(key)}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ value: value }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
appSettings[key] = value;
|
||||
displaySettings();
|
||||
document.getElementById("addSettingForm").reset();
|
||||
showMessage("Setting added successfully!", "success");
|
||||
} else {
|
||||
showMessage(
|
||||
"Error adding setting: " + (data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error adding setting", "error");
|
||||
});
|
||||
}
|
||||
|
||||
function editSetting(key) {
|
||||
document.getElementById(`edit-${key}`).style.display = "block";
|
||||
}
|
||||
|
||||
function cancelEdit(key) {
|
||||
document.getElementById(`edit-${key}`).style.display = "none";
|
||||
}
|
||||
|
||||
function updateSetting(key) {
|
||||
const newValue = document
|
||||
.getElementById(`edit-value-${key}`)
|
||||
.value.trim();
|
||||
|
||||
if (!newValue) {
|
||||
showMessage("Value cannot be empty.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/admin/api/settings/${encodeURIComponent(key)}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ value: newValue }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
appSettings[key] = newValue;
|
||||
displaySettings();
|
||||
cancelEdit(key);
|
||||
showMessage("Setting updated successfully!", "success");
|
||||
} else {
|
||||
showMessage(
|
||||
"Error updating setting: " + (data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error updating setting", "error");
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSetting(key) {
|
||||
if (!confirm(`Are you sure you want to delete the setting "${key}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/admin/api/settings/${encodeURIComponent(key)}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
delete appSettings[key];
|
||||
displaySettings();
|
||||
showMessage("Setting deleted successfully!", "success");
|
||||
} else {
|
||||
showMessage(
|
||||
"Error deleting setting: " + (data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error deleting setting", "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>
|
||||
@@ -1,303 +0,0 @@
|
||||
<!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>
|
||||
@@ -32,7 +32,7 @@
|
||||
If you unsubscribed by mistake or have any questions, please feel free
|
||||
to contact us.
|
||||
</p>
|
||||
<a href="/" class="back-link">Return to Homepage</a>
|
||||
<a href="/newsletter/manage" class="back-link">Return to Newsletter</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user