divan/GanjooRazor/Areas/User/Pages/Notifications.cshtml
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

130 lines
5.7 KiB
Plaintext

@page
@model GanjooRazor.Areas.User.Pages.NotificationsModel
@using DNTPersianUtils.Core
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "اعلان‌های من";
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
}
<div class="up-page-header">
<h1>@ViewData["Title"]</h1>
</div>
@if (Model.LastError != "")
{
<div class="up-alert up-alert--danger">@Model.LastError</div>
}
else
{
<script>
async function deleteNotification(notificationId) {
var ok = await upConfirm('آیا از حذف این اعلان اطمینان دارید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: '?handler=Notification',
data: { id: notificationId },
success: function () {
$('#notification-' + notificationId).remove();
upToast('اعلان حذف شد.', 'success');
},
error: function (e) {
upToast(e.responseText || 'حذف اعلان با خطا مواجه شد.', 'error');
}
});
}
function markReadNotification(notificationId) {
$.ajax({
type: "PUT",
url: '?handler=MarkRead',
data: { id: notificationId },
success: function () {
var item = document.getElementById('notification-' + notificationId);
if (item) item.classList.remove('up-list-item--unread');
var btn = document.getElementById('unread-' + notificationId);
if (btn) btn.remove();
},
error: function (e) {
upToast(e.responseText || 'ثبت خوانده شدن اعلان با خطا مواجه شد.', 'error');
}
});
}
async function markAllRead() {
var ok = await upConfirm('آیا از علامتگذاری همهٔ اعلان‌ها به عنوان خوانده شده اطمینان دارید؟', { danger: false, okText: 'بله، علامتگذاری کن' });
if (!ok) return;
$.ajax({
type: "PUT",
url: '?handler=MarkAllRead',
error: function (e) {
upToast(e.responseText || 'خطایی رخ داد.', 'error');
},
success: function () {
location.reload();
},
});
}
async function deleteAllRead() {
var ok = await upConfirm('آیا از حذف همهٔ اعلان‌های خوانده شده اطمینان دارید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: '?handler=AllRead',
error: function (e) {
upToast(e.responseText || 'خطایی رخ داد.', 'error');
},
success: function () {
location.reload();
},
});
}
</script>
<partial name="_Pagination" model="Model.PaginationLinks" />
@if (Model.Notifications.Count == 0)
{
<partial name="_EmptyState" model="@("در حال حاضر اعلانی وجود ندارد. در صورتی که کاربری با زدن دکمهٔ «پاسخگویی» برای حاشیهٔ شما پاسخی بنویسد یا شما با پیشنهاد آهنگ مرتبط با شعر یا روش دیگری در گنجور مشارکت کنید و مشارکت شما بررسی شود در قسمت «اعلان‌های من» با دریافت یک اعلان از این رویدادها مطلع می‌شوید.")" />
}
else
{
<div class="up-btn-group up-mt-0" style="margin-bottom: var(--up-space-3);">
<a role="button" onclick="markAllRead()" class="up-btn up-btn--ghost">همه را خواندم</a>
<a role="button" onclick="deleteAllRead()" class="up-btn up-btn--ghost">حذف همهٔ خوانده شده‌ها</a>
</div>
<div class="up-list">
@foreach (var notification in Model.Notifications)
{
bool unread = notification.Status == RSecurityBackend.Models.Notification.NotificationStatus.Unread;
<div class="up-list-item @(unread ? "up-list-item--unread" : "")" id="notification-@notification.Id">
<div>
<strong>@notification.Subject</strong>
<span class="up-list-item__meta">@notification.DateTime.ToFriendlyPersianDateTextify()</span>
</div>
<div id="notification-text-@notification.Id">
@Html.Raw(notification.HtmlText)
</div>
<div class="up-list-item__actions">
<a role="button" onclick="deleteNotification('@notification.Id')" class="up-icon-btn" title="حذف" aria-label="حذف این اعلان">
<i class="noindent-info-button delete-icon"></i>
</a>
@if (unread)
{
<a role="button" onclick="markReadNotification('@notification.Id')" class="up-icon-btn" title="خواندم" aria-label="علامت‌گذاری به عنوان خوانده شده" id="unread-@notification.Id">
<i class="noindent-info-button done-icon"></i>
</a>
}
</div>
</div>
}
</div>
<partial name="_Pagination" model="Model.PaginationLinks" />
}
}