divan/GanjooRazor/wwwroot/js/user-panel.js
Claude f09b9de84a User panel: Phase 0 shared UI foundations
- Add design-token stylesheet (wwwroot/css/user-panel.css): CSS variables
  derived from the existing p8.css palette (brand maroon, success/danger
  colors, radius, shadow), plus reusable component classes (card, button
  variants, alert, badge, empty state, pagination, list item, toast,
  confirm modal). Includes a prefers-color-scheme dark block compatible
  with the existing toggleColorScheme() mechanism in bk.js.
- Add wwwroot/js/user-panel.js: upConfirm() and upToast() helpers as
  styled, promise-based replacements for window.confirm()/alert(),
  falling back to the native dialogs if the modal/toast markup isn't
  present on a page.
- Add Areas/User/Pages/Shared/ partials: _Pagination (wraps the
  List<NameIdUrlImage> pattern already used by every list page),
  _EmptyState, _Toasts and _ConfirmModal (hosts, included once by the
  layout).
- Wire the new stylesheet/script and the toast/modal hosts into
  _UserPanelLayout.cshtml.
- Refactor Notifications.cshtml as a first demonstration: replaces the
  hand-rolled pagination loop, manual gray/lightsteelblue row
  alternation, and confirm()/alert() calls with the new components.

No backend/API changes. Rest of the User area is untouched pending
Phase 1+ (nav/dashboard, forms, remaining list pages, moderation
queues, editors).
2026-08-05 12:58:00 +03:30

91 lines
3.3 KiB
JavaScript

// User area (پیشخان کاربر) shared UI helpers.
// Provides upConfirm() and upToast() 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).
/**
* Shows a styled confirmation dialog and resolves to true/false.
* Falls back to window.confirm() if the modal markup isn't on the page.
* @param {string} message
* @param {{okText?: string, cancelText?: string, danger?: boolean}} [options]
* @returns {Promise<boolean>}
*/
function upConfirm(message, options) {
options = options || {};
return new Promise(function (resolve) {
var backdrop = document.getElementById('upConfirmBackdrop');
var titleEl = document.getElementById('upConfirmTitle');
var okBtn = document.getElementById('upConfirmOk');
var cancelBtn = document.getElementById('upConfirmCancel');
if (!backdrop || !titleEl || !okBtn || !cancelBtn) {
resolve(window.confirm(message));
return;
}
titleEl.textContent = message;
okBtn.textContent = options.okText || 'تأیید';
cancelBtn.textContent = options.cancelText || 'انصراف';
okBtn.className = 'up-btn ' + (options.danger === false ? 'up-btn--success' : 'up-btn--danger');
backdrop.hidden = false;
document.body.classList.add('up-modal-open');
function cleanup(result) {
backdrop.hidden = true;
document.body.classList.remove('up-modal-open');
okBtn.removeEventListener('click', onOk);
cancelBtn.removeEventListener('click', onCancel);
backdrop.removeEventListener('click', onBackdropClick);
document.removeEventListener('keydown', onKeyDown);
resolve(result);
}
function onOk() { cleanup(true); }
function onCancel() { cleanup(false); }
function onBackdropClick(e) { if (e.target === backdrop) cleanup(false); }
function onKeyDown(e) { if (e.key === 'Escape') cleanup(false); }
okBtn.addEventListener('click', onOk);
cancelBtn.addEventListener('click', onCancel);
backdrop.addEventListener('click', onBackdropClick);
document.addEventListener('keydown', onKeyDown);
okBtn.focus();
});
}
/**
* Shows a transient toast notification.
* Falls back to window.alert() if the toast container isn't on the page.
* @param {string} message
* @param {'success'|'error'|'info'} [type]
*/
function upToast(message, type) {
type = type || 'success';
var container = document.getElementById('upToastContainer');
if (!container) {
alert(message);
return;
}
var toast = document.createElement('div');
toast.className = 'up-toast up-toast--' + type;
toast.setAttribute('role', type === 'error' ? 'alert' : 'status');
toast.textContent = message;
container.appendChild(toast);
requestAnimationFrame(function () {
toast.classList.add('up-toast--visible');
});
setTimeout(function () {
toast.classList.remove('up-toast--visible');
setTimeout(function () {
toast.remove();
}, 250);
}, 4000);
}