feat(ui): implement user notification system in customer dashboard

Integrate a new notification module into the customer dashboard to allow users to view and manage alerts.

- Add `get_notifications` and `mark_notification_read` methods to `IFNEX_User_Bridge` to interface with the API
- Introduce a new 'notifications' tab in the customer dashboard shortcode
- Implement notification badge logic to display unread counts in the sidebar menu
- Add new SVG icon for notifications
- Add comprehensive CSS styles for notification items, badges, and unread states
This commit is contained in:
Kazem Alghasi 2026-08-24 04:48:08 +03:30
parent 1c2ff9941e
commit fc1c2f023d
3 changed files with 173 additions and 2 deletions

View File

@ -663,4 +663,101 @@
align-items: center;
gap: 6px;
animation: ifnexFadeIn 0.25s ease;
}
/* ═══ Notifications ═══ */
.ifnx-notif-badge {
position: absolute;
top: -6px;
right: -10px;
background: #ef4444;
color: #fff;
font-size: 10px;
font-weight: 800;
min-width: 18px;
height: 18px;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
box-shadow: 0 2px 6px rgba(239,68,68,0.4);
line-height: 1;
}
.ifnx-notif-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.ifnx-notif-item {
display: flex;
gap: 14px;
padding: 16px 18px;
background: #fff;
border-radius: 14px;
border: 1px solid #eef2f7;
box-shadow: 0 1px 3px rgba(15,23,42,0.04);
transition: all 0.2s ease;
cursor: pointer;
}
.ifnx-notif-item:hover {
box-shadow: 0 6px 20px rgba(15,23,42,0.08);
transform: translateY(-1px);
}
.ifnx-notif-item.unread {
background: #fffbeb;
border-color: #fde68a;
}
.ifnx-notif-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
background: #e2e8f0;
}
.ifnx-notif-item.unread .ifnx-notif-dot {
background: #f59e0b;
box-shadow: 0 0 0 3px rgba(245,158,11,0.2);
}
.ifnx-notif-body { flex: 1; min-width: 0; }
.ifnx-notif-title {
font-size: 14px;
font-weight: 700;
color: #0f172a;
margin-bottom: 4px;
}
.ifnx-notif-item.unread .ifnx-notif-title { color: #92400e; }
.ifnx-notif-message {
font-size: 13px;
color: #64748b;
line-height: 1.6;
margin-bottom: 6px;
}
.ifnx-notif-link {
display: inline-block;
font-size: 12px;
font-weight: 600;
color: #f97316;
text-decoration: none;
margin-bottom: 6px;
}
.ifnx-notif-link:hover { text-decoration: underline; }
.ifnx-notif-time {
font-size: 11px;
color: #94a3b8;
direction: ltr;
display: inline-block;
}
@media (max-width: 640px) {
.ifnx-notif-item { padding: 12px 14px; }
}

View File

@ -1126,6 +1126,7 @@ function ifnex_icon($name, $size = 20) {
'truck' => '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12"/>',
'check' => '<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>',
'clock' => '<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/>',
'bell' => '<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"/>',
];
$path = $icons[$name] ?? $icons['dashboard'];
@ -1153,7 +1154,7 @@ function ifnex_customer_dashboard_shortcode($atts) {
}
$tab = sanitize_key($_GET['tab'] ?? 'dashboard');
$allowed_tabs = ['dashboard', 'orders', 'new-order', 'wallet', 'transactions', 'tracking', 'profile'];
$allowed_tabs = ['dashboard', 'orders', 'new-order', 'wallet', 'transactions', 'notifications', 'tracking', 'profile'];
if (!in_array($tab, $allowed_tabs)) $tab = 'dashboard';
$bridge = new IFNEX_User_Bridge();
@ -1181,12 +1182,14 @@ function ifnex_customer_dashboard_shortcode($atts) {
'wallet' => ['icon' => 'wallet', 'label' => 'کیف پول'],
'transactions' => ['icon' => 'chart', 'label' => 'تراکنش‌ها'],
'tracking' => ['icon' => 'tracking', 'label' => 'رهگیری مرسوله'],
'notifications' => ['icon' => 'bell', 'label' => 'اعلان‌ها'],
'profile' => ['icon' => 'user', 'label' => 'پروفایل'],
];
$tab_titles = [
'dashboard' => 'داشبورد', 'orders' => 'سفارشات من', 'new-order' => 'ثبت سفارش جدید',
'wallet' => 'کیف پول', 'transactions' => 'تراکنش‌ها', 'tracking' => 'رهگیری مرسوله', 'profile' => 'پروفایل',
'wallet' => 'کیف پول', 'transactions' => 'تراکنش‌ها', 'notifications' => 'اعلان‌ها',
'tracking' => 'رهگیری مرسوله', 'profile' => 'پروفایل',
];
ob_start();
@ -1206,6 +1209,26 @@ function ifnex_customer_dashboard_shortcode($atts) {
<span class="ifnx-menu-icon"><?php echo ifnex_icon($item['icon'], 19); ?></span>
<span><?php echo esc_html($item['label']); ?></span>
</a>
<?php endforeach; ?>
<?php
// دریافت تعداد نوتیفیکیشن‌های خوانده‌نشده
$unread_count = 0;
$notif_result = $bridge->get_notifications($user_id, 1);
if (!is_wp_error($notif_result)) {
$unread_count = $notif_result['unread_count'] ?? 0;
}
?>
<?php foreach ($menu_items as $key => $item): ?>
<a href="<?php echo esc_url(add_query_arg('tab', $key, $base_url)); ?>"
class="ifnx-menu-item <?php echo $tab === $key ? 'active' : ''; ?>">
<span class="ifnx-menu-icon" style="position:relative;">
<?php echo ifnex_icon($item['icon'], 19); ?>
<?php if ($key === 'notifications' && $unread_count > 0): ?>
<span class="ifnx-notif-badge"><?php echo $unread_count > 9 ? '9+' : $unread_count; ?></span>
<?php endif; ?>
</span>
<span><?php echo esc_html($item['label']); ?></span>
</a>
<?php endforeach; ?>
<div class="ifnx-menu-divider"></div>
<a href="<?php echo esc_url(wp_logout_url(home_url())); ?>" class="ifnx-menu-item logout">
@ -1301,6 +1324,34 @@ function ifnex_customer_dashboard_shortcode($atts) {
</div>
<?php elseif ($tab === 'transactions'): ?>
<?php echo do_shortcode('[ifnex_transactions per_page="20"]'); ?>
<?php elseif ($tab === 'notifications'): ?>
<?php
$all_notifications = $bridge->get_notifications($user_id, 30);
if (is_wp_error($all_notifications)):
?>
<div class="ifnx-card"><div class="ifnx-empty"><p>خطا در دریافت اعلان‌ها.</p></div></div>
<?php elseif (empty($all_notifications['data'])): ?>
<div class="ifnx-card"><div class="ifnx-empty">
<?php echo ifnex_icon('bell', 40); ?>
<p>اعلان جدیدی وجود ندارد.</p>
</div></div>
<?php else: ?>
<div class="ifnx-notif-list">
<?php foreach ($all_notifications['data'] as $notif): ?>
<div class="ifnx-notif-item <?php echo empty($notif['read_at']) ? 'unread' : ''; ?>" data-id="<?php echo esc_attr($notif['id']); ?>">
<div class="ifnx-notif-dot"></div>
<div class="ifnx-notif-body">
<div class="ifnx-notif-title"><?php echo esc_html($notif['title'] ?? $notif['data']['title'] ?? 'اعلان'); ?></div>
<div class="ifnx-notif-message"><?php echo esc_html($notif['body'] ?? $notif['data']['message'] ?? ''); ?></div>
<?php if (!empty($notif['data']['shipment_awb'])): ?>
<a href="<?php echo esc_url(add_query_arg(['tab' => 'orders', 'view' => $notif['data']['shipment_id']], $base_url)); ?>" class="ifnx-notif-link">مشاهده سفارش</a>
<?php endif; ?>
<div class="ifnx-notif-time"><?php echo esc_html($notif['created_at_jalali'] ?? $notif['created_at'] ?? ''); ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php elseif ($tab === 'tracking'): ?>
<?php echo do_shortcode('[ifnex_tracking_form]'); ?>
<?php elseif ($tab === 'profile'): ?>

View File

@ -322,6 +322,29 @@ class IFNEX_User_Bridge {
}
}
/**
* دریافت نوتیفیکیشن‌های کاربر
*/
public function get_notifications($user_id, $per_page = 20) {
$result = $this->authenticated_request($user_id, '/customer/notifications', 'GET', array(
'per_page' => $per_page,
));
if (is_wp_error($result)) {
return $result;
}
return $result;
}
/**
* علامت‌گذاری نوتیفیکیشن به عنوان خوانده‌شده
*/
public function mark_notification_read($user_id, $notification_id) {
$result = $this->authenticated_request($user_id, "/customer/notifications/{$notification_id}/read", 'POST');
return $result;
}
// ══════════════════════════════════════════════════════════════
// AJAX Handler برای لغو سفارش
// ══════════════════════════════════════════════════════════════