mirror of
https://github.com/tmdinosaurcenter/kiosk-guestbook.git
synced 2026-06-04 01:18:12 -06:00
feat: add PWA support and mobile admin card layout
All pages: manifest link, apple-mobile-web-app meta tags, theme-color, viewport-fit=cover, overscroll-behavior:none, safe-area padding, 16px input font-size to prevent iOS zoom, SW registration. admin.html: card-per-entry layout on small screens (d-md-none) with name, location, timestamp, newsletter status, email, comment, and delete button. Desktop table unchanged (d-none d-md-block).
This commit is contained in:
+97
-41
@@ -2,9 +2,22 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<title>Guestbook Admin</title>
|
<title>Guestbook Admin</title>
|
||||||
|
|
||||||
|
<!-- PWA -->
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<link rel="apple-touch-icon" href="/static/images/logo.png" />
|
||||||
|
<meta name="theme-color" content="#3a9cb8" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
html, body { overscroll-behavior: none; }
|
||||||
|
body { padding-bottom: env(safe-area-inset-bottom); }
|
||||||
|
input, select, textarea { font-size: 16px !important; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
@@ -19,47 +32,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<!-- Mobile card list (phones) -->
|
||||||
<table class="table table-bordered table-hover bg-white">
|
<div class="d-md-none">
|
||||||
<thead class="table-dark">
|
{% for g in guests %}
|
||||||
<tr>
|
<div class="card mb-2 shadow-sm">
|
||||||
<th>ID</th>
|
<div class="card-body py-2 px-3">
|
||||||
<th>Name</th>
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<th>Email</th>
|
<div>
|
||||||
<th>Location</th>
|
<strong>{{ g[1] }} {{ g[2] }}</strong>
|
||||||
<th>Comment</th>
|
<div class="text-muted small">{{ g[4] }} · {{ g[7] | localtime }}</div>
|
||||||
<th>Newsletter</th>
|
<div class="small mt-1">Newsletter: {{ 'Yes' if g[6] else 'No' }}</div>
|
||||||
<th>Timestamp</th>
|
{% if g[3] %}
|
||||||
<th></th>
|
<div class="text-muted small">{{ g[3] }}</div>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for g in guests %}
|
|
||||||
<tr>
|
|
||||||
<td class="text-muted">{{ g[0] }}</td>
|
|
||||||
<td>{{ g[1] }} {{ g[2] }}</td>
|
|
||||||
<td>{{ g[3] or '—' }}</td>
|
|
||||||
<td>{{ g[4] }}</td>
|
|
||||||
<td>{{ g[5] or '—' }}</td>
|
|
||||||
<td>{{ 'Yes' if g[6] else 'No' }}</td>
|
|
||||||
<td class="text-nowrap">{{ g[7] | localtime }}</td>
|
|
||||||
<td>
|
|
||||||
{% if current_user.role != 'viewer' %}
|
|
||||||
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
|
|
||||||
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
|
||||||
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
{% if g[5] %}
|
||||||
</tr>
|
<div class="text-muted small fst-italic">{{ g[5] }}</div>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<tr>
|
</div>
|
||||||
<td colspan="8" class="text-center text-muted">No entries found.</td>
|
{% if current_user.role != 'viewer' %}
|
||||||
</tr>
|
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
|
||||||
{% endfor %}
|
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
|
||||||
</tbody>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
</table>
|
<button type="submit" class="btn btn-danger btn-sm ms-2">Delete</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted text-center">No entries found.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop table (hidden on phones) -->
|
||||||
|
<div class="d-none d-md-block">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover bg-white">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Comment</th>
|
||||||
|
<th>Newsletter</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for g in guests %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">{{ g[0] }}</td>
|
||||||
|
<td>{{ g[1] }} {{ g[2] }}</td>
|
||||||
|
<td>{{ g[3] or '—' }}</td>
|
||||||
|
<td>{{ g[4] }}</td>
|
||||||
|
<td>{{ g[5] or '—' }}</td>
|
||||||
|
<td>{{ 'Yes' if g[6] else 'No' }}</td>
|
||||||
|
<td class="text-nowrap">{{ g[7] | localtime }}</td>
|
||||||
|
<td>
|
||||||
|
{% if current_user.role != 'viewer' %}
|
||||||
|
<form method="POST" action="{{ url_for('admin_delete', entry_id=g[0]) }}?page={{ page }}"
|
||||||
|
onsubmit="return confirm('Delete entry for {{ g[1] }} {{ g[2] }}?')">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||||
|
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="text-center text-muted">No entries found.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if total_pages > 1 %}
|
{% if total_pages > 1 %}
|
||||||
@@ -80,5 +128,13 @@
|
|||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,9 +2,22 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<title>Guestbook Admin — Login</title>
|
<title>Guestbook Admin — Login</title>
|
||||||
|
|
||||||
|
<!-- PWA -->
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<link rel="apple-touch-icon" href="/static/images/logo.png" />
|
||||||
|
<meta name="theme-color" content="#3a9cb8" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
html, body { overscroll-behavior: none; }
|
||||||
|
body { padding-bottom: env(safe-area-inset-bottom); }
|
||||||
|
input.form-control { font-size: 16px !important; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<div class="container py-5" style="max-width: 400px;">
|
<div class="container py-5" style="max-width: 400px;">
|
||||||
@@ -31,5 +44,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,9 +2,22 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<title>Guestbook Admin — Users</title>
|
<title>Guestbook Admin — Users</title>
|
||||||
|
|
||||||
|
<!-- PWA -->
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<link rel="apple-touch-icon" href="/static/images/logo.png" />
|
||||||
|
<meta name="theme-color" content="#3a9cb8" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<style>
|
||||||
|
html, body { overscroll-behavior: none; }
|
||||||
|
body { padding-bottom: env(safe-area-inset-bottom); }
|
||||||
|
input.form-control, select.form-select { font-size: 16px !important; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
<div class="container py-4" style="max-width: 700px;">
|
<div class="container py-4" style="max-width: 700px;">
|
||||||
@@ -76,5 +89,13 @@
|
|||||||
Admins can view and delete entries. Viewers can only view.
|
Admins can view and delete entries. Viewers can only view.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,9 +3,17 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
<title>${SITE_TITLE}</title>
|
<title>${SITE_TITLE}</title>
|
||||||
|
|
||||||
|
<!-- PWA -->
|
||||||
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
|
<link rel="apple-touch-icon" href="/static/images/logo.png" />
|
||||||
|
<meta name="theme-color" content="#3a9cb8" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="${SITE_TITLE}" />
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
@@ -20,6 +28,23 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PWA / iOS kiosk */
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent iOS auto-zoom on input focus (requires >= 16px) */
|
||||||
|
input.form-control,
|
||||||
|
textarea.form-control,
|
||||||
|
select.form-select {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Scrolling marquee styles */
|
/* Scrolling marquee styles */
|
||||||
.scrolling-wrapper {
|
.scrolling-wrapper {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -29,6 +54,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-top: 1px solid #dee2e6;
|
border-top: 1px solid #dee2e6;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrolling-content {
|
.scrolling-content {
|
||||||
@@ -172,6 +198,15 @@
|
|||||||
<!-- Bootstrap JS (optional) -->
|
<!-- Bootstrap JS (optional) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Service worker registration -->
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user