diff --git a/resources/views/certificate-backend/index.blade.php b/resources/views/certificate-backend/index.blade.php
index 7237e35e8..94c528cf3 100644
--- a/resources/views/certificate-backend/index.blade.php
+++ b/resources/views/certificate-backend/index.blade.php
@@ -5,11 +5,11 @@
-
+
{{-- Table --}}
Loading…
@@ -97,15 +97,12 @@
(function() {
const editionSelect = document.getElementById('edition-select');
const typeSlug = '{{ $typeSlug }}';
+ const basePath = '{{ url("/admin/certificate-backend") }}'.replace(/\/$/, '');
let currentPage = 1;
let searchQuery = '';
- function baseUrl() {
- return '{{ route("certificate_backend.index", ["edition" => $edition, "type" => $typeSlug]) }}'.replace(/\d{4}$/, editionSelect.value).replace(/excellence|super-organiser/, typeSlug);
- }
-
function apiUrl(path, params = {}) {
- const u = new URL('{{ url("/admin/certificate-backend") }}' + path, window.location.origin);
+ const u = new URL(basePath + path.replace(/^\//, ''), window.location.origin);
u.searchParams.set('edition', editionSelect.value);
u.searchParams.set('type', typeSlug);
Object.entries(params).forEach(([k, v]) => { if (v !== undefined && v !== '') u.searchParams.set(k, v); });
@@ -116,23 +113,41 @@ function csrf() {
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
}
+ function handleResponse(r) {
+ const contentType = r.headers.get('content-type') || '';
+ const isJson = contentType.indexOf('application/json') !== -1;
+ if (!r.ok) {
+ return (isJson ? r.json() : r.text()).then(function(data) {
+ const msg = (data && data.message) ? data.message : (typeof data === 'string' ? data.substring(0, 200) : 'Request failed (' + r.status + ')');
+ throw new Error(r.status === 419 ? 'Session expired. Please refresh the page and try again.' : (r.status === 403 ? 'Access denied.' : msg));
+ });
+ }
+ return isJson ? r.json() : r.text().then(function() { return {}; });
+ }
+
function fetchJson(url, options = {}) {
- const opts = { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } };
+ const opts = { method: 'GET', headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' };
if (options.method === 'POST') {
opts.method = 'POST';
opts.headers['Content-Type'] = 'application/json';
opts.headers['X-CSRF-TOKEN'] = csrf();
opts.body = JSON.stringify(options.body || {});
}
- return fetch(url, opts).then(r => r.json());
+ return fetch(url, opts).then(handleResponse);
}
function postJson(url, body = {}) {
return fetch(url, {
method: 'POST',
- headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrf(), 'X-Requested-With': 'XMLHttpRequest' },
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'X-CSRF-TOKEN': csrf(),
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
body: JSON.stringify(body)
- }).then(r => r.json());
+ }).then(handleResponse);
}
function loadStatus() {
@@ -163,12 +178,12 @@ function loadList(page = 1) {
'
' + (row.certificate_generated ? 'Yes' : 'No') + (row.certificate_generation_error ? ' (error)' : '') + ' | ' +
'
' + (row.certificate_sent ? (row.notified_at || 'Yes') : 'No') + (row.certificate_sent_error ? ' (error)' : '') + ' | ' +
'
' + (row.certificate_url ? 'Open' : '–') + ' | ' +
- '
' + (row.certificate_url ? '' : '–') + ' | ';
+ '
' + (row.certificate_url ? '' : '–') + ' | ';
tbody.appendChild(tr);
});
pagination(data.current_page, data.last_page, data.total);
- }).catch(() => {
- document.getElementById('table-loading').textContent = 'Error loading list.';
+ }).catch(function(err) {
+ document.getElementById('table-loading').innerHTML = 'Error loading list. ' + (err.message || '');
});
}
@@ -185,9 +200,9 @@ function pagination(current, last, total) {
const el = document.getElementById('pagination');
if (last <= 1) { el.innerHTML = ''; return; }
let html = '';
- if (current > 1) html += '
';
+ if (current > 1) html += '
';
html += ' Page ' + current + ' of ' + last + ' (' + total + ' total) ';
- if (current < last) html += '
';
+ if (current < last) html += '
';
el.innerHTML = html;
el.querySelectorAll('.page-btn').forEach(btn => btn.addEventListener('click', function() { currentPage = parseInt(this.dataset.page, 10); loadList(currentPage); }));
}
@@ -197,24 +212,26 @@ function pagination(current, last, total) {
});
document.getElementById('btn-generate').addEventListener('click', function() {
- this.disabled = true;
+ const btn = this;
+ btn.disabled = true;
postJson(apiUrl('/generate/start')).then(r => {
alert(r.message || (r.ok ? 'Started.' : 'Error'));
- this.disabled = false;
loadStatus();
- }).catch(() => { this.disabled = false; });
+ }).catch(function(err) {
+ alert(err.message || 'Request failed.');
+ }).finally(function() { btn.disabled = false; });
});
document.getElementById('btn-cancel').addEventListener('click', function() {
- postJson(apiUrl('/generate/cancel')).then(r => { alert(r.message); loadStatus(); });
+ postJson(apiUrl('/generate/cancel')).then(r => { alert(r.message); loadStatus(); }).catch(function(err) { alert(err.message || 'Request failed.'); });
});
document.getElementById('btn-send').addEventListener('click', function() {
- postJson(apiUrl('/send/start')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); });
+ postJson(apiUrl('/send/start')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }).catch(function(err) { alert(err.message || 'Request failed.'); });
});
document.getElementById('btn-resend-all-failed').addEventListener('click', function() {
- postJson(apiUrl('/resend-all-failed')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); });
+ postJson(apiUrl('/resend-all-failed')).then(r => { alert(r.message); loadStatus(); loadList(currentPage); }).catch(function(err) { alert(err.message || 'Request failed.'); });
});
document.getElementById('btn-manual-generate').addEventListener('click', function() {
@@ -225,7 +242,7 @@ function pagination(current, last, total) {
postJson(apiUrl('/manual-create-send'), { user_email: email, generate_only: true }).then(r => {
resultEl.textContent = r.ok ? ('Generated. ' + (r.certificate_url ? 'URL: ' + r.certificate_url : '')) : r.message;
if (r.ok) { loadStatus(); loadList(currentPage); }
- }).catch(() => { resultEl.textContent = 'Request failed.'; });
+ }).catch(function(err) { resultEl.textContent = err.message || 'Request failed.'; });
});
document.getElementById('btn-manual-send').addEventListener('click', function() {
@@ -234,9 +251,9 @@ function pagination(current, last, total) {
if (!email) { resultEl.textContent = 'Enter email.'; return; }
resultEl.textContent = 'Sending…';
postJson(apiUrl('/manual-create-send'), { user_email: email, send_only: true }).then(r => {
- resultEl.textContent = r.ok ? 'Email queued.' : r.message;
+ resultEl.textContent = r.ok ? (r.message || 'Email sent.') : r.message;
if (r.ok) { loadStatus(); loadList(currentPage); }
- }).catch(() => { resultEl.textContent = 'Request failed.'; });
+ }).catch(function(err) { resultEl.textContent = err.message || 'Request failed.'; });
});
document.getElementById('btn-search').addEventListener('click', function() {
@@ -251,12 +268,12 @@ function pagination(current, last, total) {
if (!btn) return;
const id = btn.dataset.id;
btn.disabled = true;
- postJson('{{ url("/admin/certificate-backend/resend") }}/' + id, {}).then(r => {
+ const resendUrl = '{{ url("/admin/certificate-backend/resend") }}'.replace(/\/$/, '') + '/' + id;
+ postJson(resendUrl, {}).then(r => {
alert(r.message);
- btn.disabled = false;
loadStatus();
loadList(currentPage);
- }).catch(() => { btn.disabled = false; });
+ }).catch(function(err) { alert(err.message || 'Request failed.'); }).finally(function() { btn.disabled = false; });
});
loadStatus();