diff --git a/GanjooRazor/Areas/User/Pages/Editor.cshtml b/GanjooRazor/Areas/User/Pages/Editor.cshtml
index 4c47da27..c79ff9b7 100644
--- a/GanjooRazor/Areas/User/Pages/Editor.cshtml
+++ b/GanjooRazor/Areas/User/Pages/Editor.cshtml
@@ -1007,7 +1007,7 @@
location.reload(true);
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
$('#savecorrections').show();
$('#saving').text('');
}
@@ -1024,7 +1024,7 @@
id: poemId
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
},
success: function (rhyme) {
$(elementId).val(rhyme);
@@ -1049,7 +1049,7 @@
poemid: id,
},
error: function (e) {
- upToast(e.responseText || 'خطا رخ داد.', 'error');
+ upShowError(e.responseText || 'خطا رخ داد.');
},
success: function () {
location.reload(true);
@@ -1074,7 +1074,7 @@
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
},
success: function () {
location.reload(true);
@@ -1161,7 +1161,7 @@
location.href = '/?p=' + String(id);
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
$('#breakpoem-' + String(vOrder)).show();
}
@@ -1182,7 +1182,7 @@
upToast('انجام شد.');
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
}
});
@@ -1210,7 +1210,7 @@
id: id,
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
},
success: function () {
upToast('انجام شد.');
@@ -1235,7 +1235,7 @@
id: id,
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
},
success: function () {
upToast('انجام شد.');
@@ -1268,7 +1268,7 @@
location.reload();
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
}
});
@@ -1289,7 +1289,7 @@
location.reload();
},
error: function (e) {
- upToast(e.responseText || String(e), 'error');
+ upShowError(e.responseText || String(e));
}
});
diff --git a/GanjooRazor/Pages/Shared/_ErrorModal.cshtml b/GanjooRazor/Pages/Shared/_ErrorModal.cshtml
new file mode 100644
index 00000000..a0ad797b
--- /dev/null
+++ b/GanjooRazor/Pages/Shared/_ErrorModal.cshtml
@@ -0,0 +1,14 @@
+@* Styled, non-auto-dismissing display for a service error's full text (these often carry the
+ backend's exception message and stack trace, which upToast's few-second toast never gave
+ anyone enough time to actually read before it faded out). Included once by
+ _UserPanelLayout.cshtml. Trigger with JS: upShowError(message) *@
+
+
+
خطایی در ارتباط با سرویس رخ داد:
+
+
+
+
+
+
+
diff --git a/GanjooRazor/Pages/Shared/_UserPanelLayout.cshtml b/GanjooRazor/Pages/Shared/_UserPanelLayout.cshtml
index c517edb4..1ac47209 100644
--- a/GanjooRazor/Pages/Shared/_UserPanelLayout.cshtml
+++ b/GanjooRazor/Pages/Shared/_UserPanelLayout.cshtml
@@ -205,10 +205,11 @@
+
-
+
@await RenderSectionAsync("Scripts", required: false)
diff --git a/GanjooRazor/wwwroot/css/user-panel.css b/GanjooRazor/wwwroot/css/user-panel.css
index 93cdbcc1..4d64377a 100644
--- a/GanjooRazor/wwwroot/css/user-panel.css
+++ b/GanjooRazor/wwwroot/css/user-panel.css
@@ -475,6 +475,33 @@ body.up-modal-open {
overflow: hidden;
}
+/* ---------- error modal (persistent service-error display, see _ErrorModal.cshtml) ---------- */
+
+.up-modal--error {
+ max-width: 700px;
+ text-align: start;
+}
+
+.up-modal__details {
+ max-height: 50vh;
+ overflow: auto;
+ white-space: pre-wrap;
+ word-break: break-word;
+ /* backend error text/stack traces are English - keep them LTR regardless of the page's own
+ RTL direction, same reasoning as .linputText elsewhere in this file */
+ direction: ltr;
+ text-align: left;
+ font-family: 'Consolas', 'Courier New', monospace;
+ font-size: 0.82rem;
+ line-height: 1.5;
+ color: var(--up-text);
+ background: var(--up-bg);
+ border: 1px solid var(--up-border);
+ border-radius: var(--up-radius-sm);
+ padding: var(--up-space-2);
+ margin: 0 0 var(--up-space-4);
+}
+
/* ---------- dashboard quick links ---------- */
.up-quick-links {
diff --git a/GanjooRazor/wwwroot/js/user-panel.js b/GanjooRazor/wwwroot/js/user-panel.js
index 65e2e651..735732d8 100644
--- a/GanjooRazor/wwwroot/js/user-panel.js
+++ b/GanjooRazor/wwwroot/js/user-panel.js
@@ -1,8 +1,8 @@
// User area (پیشخان کاربر) shared UI helpers.
-// Provides upConfirm() and upToast() as drop-in, styled replacements for
+// Provides upConfirm(), upToast() and upShowError() as drop-in, styled replacements for
// window.confirm()/window.alert(), used across the panel's AJAX actions.
-// Requires the markup from Areas/User/Pages/Shared/_ConfirmModal.cshtml and
-// _Toasts.cshtml to be present on the page (included once by _UserPanelLayout).
+// Requires the markup from Pages/Shared/_ConfirmModal.cshtml, _Toasts.cshtml and
+// _ErrorModal.cshtml to be present on the page (included once by _UserPanelLayout).
/**
* Shows a styled confirmation dialog and resolves to true/false.
@@ -88,3 +88,72 @@ function upToast(message, type) {
}, 250);
}, 4000);
}
+
+/**
+ * Shows a service error's full text (often a backend exception message plus its stack trace) in
+ * a modal that stays on screen until closed, with a copy button - unlike upToast(), which fades
+ * out after 4 seconds and gives no way to get back the text it just showed. Falls back to
+ * upToast() if the modal markup isn't on the page.
+ * @param {string} message
+ */
+function upShowError(message) {
+ message = message == null ? '' : String(message);
+ var backdrop = document.getElementById('upErrorBackdrop');
+ var detailsEl = document.getElementById('upErrorDetails');
+ var closeBtn = document.getElementById('upErrorClose');
+ var copyBtn = document.getElementById('upErrorCopy');
+
+ if (!backdrop || !detailsEl || !closeBtn || !copyBtn) {
+ upToast(message, 'error');
+ return;
+ }
+
+ detailsEl.textContent = message;
+
+ backdrop.hidden = false;
+ document.body.classList.add('up-modal-open');
+
+ function cleanup() {
+ backdrop.hidden = true;
+ document.body.classList.remove('up-modal-open');
+ closeBtn.removeEventListener('click', onClose);
+ copyBtn.removeEventListener('click', onCopy);
+ backdrop.removeEventListener('click', onBackdropClick);
+ document.removeEventListener('keydown', onKeyDown);
+ }
+
+ function onClose() { cleanup(); }
+ function onBackdropClick(e) { if (e.target === backdrop) cleanup(); }
+ function onKeyDown(e) { if (e.key === 'Escape') cleanup(); }
+
+ function onCopy() {
+ var restoreText = copyBtn.textContent;
+ function showCopied(ok) {
+ copyBtn.textContent = ok ? '✓ کپی شد' : 'کپی ممکن نشد';
+ setTimeout(function () { copyBtn.textContent = restoreText; }, 1500);
+ }
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(message).then(function () { showCopied(true); }, function () { showCopied(false); });
+ } else {
+ // clipboard API unavailable (e.g. non-HTTPS context) - fall back to a temporary,
+ // off-screen textarea and the legacy execCommand copy path
+ var ta = document.createElement('textarea');
+ ta.value = message;
+ ta.style.position = 'fixed';
+ ta.style.left = '-9999px';
+ document.body.appendChild(ta);
+ ta.select();
+ var ok = false;
+ try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
+ document.body.removeChild(ta);
+ showCopied(ok);
+ }
+ }
+
+ closeBtn.addEventListener('click', onClose);
+ copyBtn.addEventListener('click', onCopy);
+ backdrop.addEventListener('click', onBackdropClick);
+ document.addEventListener('keydown', onKeyDown);
+
+ closeBtn.focus();
+}