feat(ui): implement modern customer dashboard

Implement a new, modern customer dashboard interface including:
- A new `[ifnex_customer_dashboard]` shortcode.
- A redesigned sidebar with user profile information and navigation.
- A statistics overview section with icon-based cards.
- Modern SVG icon system (Heroicons style) for dashboard elements.
- Updated CSS with a modern design language (gradients, shadows, and flexbox layouts).
- Global asset enqueuing to ensure consistent styling and AJAX availability across all plugin-related pages.
This commit is contained in:
Kazem Alghasi 2026-08-12 03:52:35 +03:30
parent cd8021e6e1
commit f08981da31
3 changed files with 365 additions and 0 deletions

View File

@ -370,4 +370,135 @@
@media print {
.ifnex-detail-actions { display: none !important; }
.ifnex-order-detail { max-width: 100%; }
}
/* ═══ Modern Customer Dashboard (ifnx) ═══ */
.ifnx-dashboard {
display: flex; gap: 24px; max-width: 1240px; margin: 0 auto; padding: 24px 0;
font-family: inherit;
}
/* ── Sidebar ─ */
.ifnx-sidebar {
width: 260px; flex-shrink: 0; align-self: flex-start;
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
border-radius: 20px; overflow: hidden;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.25);
}
.ifnx-user-card { padding: 28px 20px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.08); }
.ifnx-avatar {
width: 64px; height: 64px; margin: 0 auto 12px; border-radius: 50%;
background: linear-gradient(135deg, #f97316, #fb923c);
display: flex; align-items: center; justify-content: center; color: #fff;
box-shadow: 0 6px 18px rgba(249, 115, 22, 0.4);
}
.ifnx-user-name { color: #fff; font-weight: 700; font-size: 16px; }
.ifnx-user-email { color: rgba(255,255,255,0.55); font-size: 12px; margin-top: 4px; direction: ltr; }
.ifnx-menu { padding: 12px 0; display: flex; flex-direction: column; }
.ifnx-menu-item {
display: flex; align-items: center; gap: 12px;
padding: 13px 22px; color: rgba(255,255,255,0.72);
text-decoration: none; font-size: 14px; font-weight: 500;
border-right: 3px solid transparent; transition: all 0.25s ease;
}
.ifnx-menu-item:hover { background: rgba(255,255,255,0.06); color: #fff; }
.ifnx-menu-item.active {
background: linear-gradient(90deg, rgba(249,115,22,0.18), transparent);
border-right-color: #f97316; color: #fff; font-weight: 700;
}
.ifnx-menu-icon { display: flex; align-items: center; }
.ifnx-menu-divider { height: 1px; background: rgba(255,255,255,0.08); margin: 10px 20px; }
.ifnx-menu-item.logout { color: #f87171; }
.ifnx-menu-item.logout:hover { background: rgba(248,113,113,0.1); color: #f87171; }
/* ── Content ── */
.ifnx-content { flex: 1; min-width: 0; }
.ifnx-content-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 20px; flex-wrap: wrap; gap: 8px;
}
.ifnx-content-header h2 { margin: 0; color: #0f172a; font-size: 22px; font-weight: 800; }
.ifnx-breadcrumb { color: #94a3b8; font-size: 13px; }
/* ── Stats ─ */
.ifnx-stats {
display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 16px; margin-bottom: 24px;
}
.ifnx-stat {
background: #fff; border-radius: 16px; padding: 18px;
display: flex; align-items: center; gap: 14px;
box-shadow: 0 1px 3px rgba(15,23,42,0.06), 0 8px 24px rgba(15,23,42,0.05);
border: 1px solid #eef2f7; transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.ifnx-stat:hover { transform: translateY(-3px); box-shadow: 0 12px 28px rgba(15,23,42,0.1); }
.ifnx-stat-icon {
width: 48px; height: 48px; border-radius: 14px; flex-shrink: 0;
background: var(--tint); color: var(--clr);
display: flex; align-items: center; justify-content: center;
}
.ifnx-stat-value { display: block; font-size: 20px; font-weight: 800; color: #0f172a; }
.ifnx-stat-label { display: block; font-size: 12px; color: #64748b; margin-top: 2px; }
/* ── Cards ── */
.ifnx-card {
background: #fff; border-radius: 16px; padding: 22px;
border: 1px solid #eef2f7;
box-shadow: 0 1px 3px rgba(15,23,42,0.06), 0 8px 24px rgba(15,23,42,0.05);
}
.ifnx-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.ifnx-card-header h3 { margin: 0; color: #0f172a; font-size: 16px; font-weight: 700; }
.ifnx-link { color: #f97316; font-size: 13px; text-decoration: none; font-weight: 600; }
.ifnx-link:hover { text-decoration: underline; }
.ifnx-empty { text-align: center; padding: 40px 0; color: #94a3b8; }
.ifnx-empty svg { opacity: 0.4; margin-bottom: 12px; }
.ifnx-empty p { margin-bottom: 16px; }
.ifnx-order-list { display: flex; flex-direction: column; gap: 10px; }
.ifnx-order-row {
display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
padding: 14px 16px; background: #f8fafc; border-radius: 12px;
border: 1px solid #eef2f7; transition: background 0.2s;
}
.ifnx-order-row:hover { background: #f1f5f9; }
.ifnx-order-awb code { font-weight: 700; color: #0f172a; background: #fff; padding: 4px 10px; border-radius: 8px; border: 1px solid #e2e8f0; }
.ifnx-order-route { color: #64748b; font-size: 13px; flex: 1; }
.ifnx-order-amount { font-weight: 700; color: #0f172a; }
.ifnx-order-amount small { color: #94a3b8; font-weight: 400; }
/* ── Wallet Hero ── */
.ifnx-wallet-hero {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 60%, #334155 100%);
border-radius: 20px; padding: 40px; color: #fff; text-align: center;
position: relative; overflow: hidden;
box-shadow: 0 15px 40px rgba(15,23,42,0.3);
}
.ifnx-wallet-hero::before {
content: ''; position: absolute; top: -60px; left: -60px;
width: 200px; height: 200px; border-radius: 50%;
background: radial-gradient(circle, rgba(249,115,22,0.35), transparent 70%);
}
.ifnx-wallet-label { font-size: 14px; color: rgba(255,255,255,0.65); margin-bottom: 10px; }
.ifnx-wallet-amount { font-size: 42px; font-weight: 800; }
.ifnx-wallet-amount small { font-size: 16px; font-weight: 400; color: rgba(255,255,255,0.6); }
.ifnx-wallet-actions { margin-top: 26px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.ifnx-wallet-btn {
background: linear-gradient(135deg, #f97316, #fb923c); color: #fff;
padding: 12px 26px; border-radius: 12px; text-decoration: none;
font-weight: 700; font-size: 14px; box-shadow: 0 6px 18px rgba(249,115,22,0.4);
transition: transform 0.2s;
}
.ifnx-wallet-btn:hover { transform: translateY(-2px); }
.ifnx-wallet-btn.ghost { background: rgba(255,255,255,0.1); box-shadow: none; border: 1px solid rgba(255,255,255,0.2); }
/* ── Responsive ── */
@media (max-width: 900px) {
.ifnx-dashboard { flex-direction: column; }
.ifnx-sidebar { width: 100%; }
.ifnx-menu { flex-direction: row; flex-wrap: wrap; padding: 10px; }
.ifnx-menu-item { border-right: none; border-bottom: 3px solid transparent; padding: 10px 14px; }
.ifnx-menu-item.active { border-bottom-color: #f97316; }
.ifnx-menu-divider { display: none; }
}

View File

@ -1096,4 +1096,207 @@ function ifnex_order_detail_shortcode($atts) {
</script>
<?php
return ob_get_clean();
}
// ══════════════════════════════════════════════════════════════
// آیکون‌های SVG مدرن (سبک Heroicons)
// ══════════════════════════════════════════════════════════════
function ifnex_icon($name, $size = 20) {
$icons = [
'dashboard' => '<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75"/>',
'orders' => '<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-9-5.25L3 7.5m18 0l-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/>',
'plus' => '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
'wallet' => '<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a2.25 2.25 0 00-2.25-2.25H15a3 3 0 11-6 0H5.25A2.25 2.25 0 003 12m18 0v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6m18 0V9M3 12V9m18 0a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 9m18 0V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v3"/>',
'chart' => '<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"/>',
'tracking' => '<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"/>',
'user' => '<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"/>',
'logout' => '<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"/>',
'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"/>',
];
$path = $icons[$name] ?? $icons['dashboard'];
return '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.7" stroke="currentColor" width="' . $size . '" height="' . $size . '">' . $path . '</svg>';
}
// ══════════════════════════════════════════════════════════════
// شورت‌کد پنل مشتری (Customer Dashboard)
// ══════════════════════════════════════════════════════════════
add_shortcode('ifnex_customer_dashboard', 'ifnex_customer_dashboard_shortcode');
function ifnex_customer_dashboard_shortcode($atts) {
if (!is_user_logged_in()) {
ob_start();
?>
<div class="ifnex-login-prompt">
<h3>🔐 ورود لازم است</h3>
<p>برای دسترسی به پنل کاربری، ابتدا وارد حساب خود شوید.</p>
<a href="<?php echo esc_url(wp_login_url(get_permalink())); ?>" class="ifnex-btn ifnex-btn-primary">ورود به حساب</a>
</div>
<?php
return ob_get_clean();
}
$tab = sanitize_key($_GET['tab'] ?? 'dashboard');
$allowed_tabs = ['dashboard', 'orders', 'new-order', 'wallet', 'transactions', 'tracking', 'profile'];
if (!in_array($tab, $allowed_tabs)) $tab = 'dashboard';
$bridge = new IFNEX_User_Bridge();
$user_id = get_current_user_id();
$current_user = wp_get_current_user();
$profile = $bridge->get_customer_profile($user_id);
$wallet_balance = 0; $stats = [];
if (!is_wp_error($profile)) {
$wallet_balance = $profile['wallet']['balance'] ?? 0;
$stats = $profile['stats'] ?? [];
}
$recent_orders = [];
if ($tab === 'dashboard') {
$orders_result = $bridge->get_customer_orders($user_id, 3);
if (!is_wp_error($orders_result)) $recent_orders = $orders_result['data'] ?? [];
}
$base_url = get_permalink();
$menu_items = [
'dashboard' => ['icon' => 'dashboard', 'label' => 'داشبورد'],
'orders' => ['icon' => 'orders', 'label' => 'سفارشات من'],
'new-order' => ['icon' => 'plus', 'label' => 'ثبت سفارش جدید'],
'wallet' => ['icon' => 'wallet', 'label' => 'کیف پول'],
'transactions' => ['icon' => 'chart', 'label' => 'تراکنش‌ها'],
'tracking' => ['icon' => 'tracking', 'label' => 'رهگیری مرسوله'],
'profile' => ['icon' => 'user', 'label' => 'پروفایل'],
];
$tab_titles = [
'dashboard' => 'داشبورد', 'orders' => 'سفارشات من', 'new-order' => 'ثبت سفارش جدید',
'wallet' => 'کیف پول', 'transactions' => 'تراکنش‌ها', 'tracking' => 'رهگیری مرسوله', 'profile' => 'پروفایل',
];
ob_start();
?>
<div class="ifnx-dashboard">
<!-- سایدبار -->
<aside class="ifnx-sidebar">
<div class="ifnx-user-card">
<div class="ifnx-avatar"><?php echo ifnex_icon('user', 26); ?></div>
<div class="ifnx-user-name"><?php echo esc_html($current_user->display_name); ?></div>
<div class="ifnx-user-email"><?php echo esc_html($current_user->user_email); ?></div>
</div>
<nav class="ifnx-menu">
<?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"><?php echo ifnex_icon($item['icon'], 19); ?></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">
<span class="ifnx-menu-icon"><?php echo ifnex_icon('logout', 19); ?></span>
<span>خروج از حساب</span>
</a>
</nav>
</aside>
<!-- محتوا -->
<main class="ifnx-content">
<div class="ifnx-content-header">
<h2><?php echo esc_html($tab_titles[$tab]); ?></h2>
<span class="ifnx-breadcrumb">پنل کاربری / <?php echo esc_html($tab_titles[$tab]); ?></span>
</div>
<?php if ($tab === 'dashboard'): ?>
<div class="ifnx-stats">
<div class="ifnx-stat" style="--tint:#ecfdf5; --clr:#10b981;">
<div class="ifnx-stat-icon"><?php echo ifnex_icon('wallet', 22); ?></div>
<div class="ifnx-stat-info">
<span class="ifnx-stat-value"><?php echo number_format($wallet_balance); ?></span>
<span class="ifnx-stat-label">موجودی کیف پول (ریال)</span>
</div>
</div>
<div class="ifnx-stat" style="--tint:#eff6ff; --clr:#3b82f6;">
<div class="ifnx-stat-icon"><?php echo ifnex_icon('orders', 22); ?></div>
<div class="ifnx-stat-info">
<span class="ifnx-stat-value"><?php echo number_format($stats['total_orders'] ?? 0); ?></span>
<span class="ifnx-stat-label">کل سفارشات</span>
</div>
</div>
<div class="ifnx-stat" style="--tint:#fffbeb; --clr:#f59e0b;">
<div class="ifnx-stat-icon"><?php echo ifnex_icon('clock', 22); ?></div>
<div class="ifnx-stat-info">
<span class="ifnx-stat-value"><?php echo number_format($stats['pending_orders'] ?? 0); ?></span>
<span class="ifnx-stat-label">در انتظار پرداخت</span>
</div>
</div>
<div class="ifnx-stat" style="--tint:#f5f3ff; --clr:#8b5cf6;">
<div class="ifnx-stat-icon"><?php echo ifnex_icon('truck', 22); ?></div>
<div class="ifnx-stat-info">
<span class="ifnx-stat-value"><?php echo number_format($stats['active_orders'] ?? 0); ?></span>
<span class="ifnx-stat-label">در حال پردازش</span>
</div>
</div>
<div class="ifnx-stat" style="--tint:#ecfdf5; --clr:#059669;">
<div class="ifnx-stat-icon"><?php echo ifnex_icon('check', 22); ?></div>
<div class="ifnx-stat-info">
<span class="ifnx-stat-value"><?php echo number_format($stats['delivered_orders'] ?? 0); ?></span>
<span class="ifnx-stat-label">تحویل شده</span>
</div>
</div>
</div>
<div class="ifnx-card">
<div class="ifnx-card-header">
<h3>آخرین سفارشات</h3>
<a href="<?php echo esc_url(add_query_arg('tab', 'orders', $base_url)); ?>" class="ifnx-link">مشاهده همه </a>
</div>
<?php if (empty($recent_orders)): ?>
<div class="ifnx-empty">
<?php echo ifnex_icon('orders', 40); ?>
<p>هنوز سفارشی ثبت نکرده‌اید.</p>
<a href="<?php echo esc_url(add_query_arg('tab', 'new-order', $base_url)); ?>" class="ifnex-btn ifnex-btn-primary">ثبت اولین سفارش</a>
</div>
<?php else: ?>
<div class="ifnx-order-list">
<?php foreach ($recent_orders as $order): ?>
<div class="ifnx-order-row">
<div class="ifnx-order-awb"><code><?php echo esc_html($order['awb_no']); ?></code></div>
<div class="ifnx-order-route"><?php echo esc_html($order['from_country']['name'] ?? '—'); ?> ← <?php echo esc_html($order['to_country']['name'] ?? '—'); ?></div>
<div class="ifnx-order-amount"><?php echo number_format($order['total_fee']); ?> <small>ریال</small></div>
<span class="ifnex-badge ifnex-badge-<?php echo esc_attr($order['status']['color']); ?>"><?php echo esc_html($order['status']['label']); ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php elseif ($tab === 'orders'): ?>
<?php echo do_shortcode('[ifnex_orders_list]'); ?>
<?php elseif ($tab === 'new-order'): ?>
<?php echo do_shortcode('[ifnex_order_form]'); ?>
<?php elseif ($tab === 'wallet'): ?>
<div class="ifnx-wallet-hero">
<div class="ifnx-wallet-label">موجودی فعلی کیف پول</div>
<div class="ifnx-wallet-amount"><?php echo number_format($wallet_balance); ?> <small>ریال</small></div>
<div class="ifnx-wallet-actions">
<a href="<?php echo esc_url(home_url('/wallet/')); ?>" class="ifnx-wallet-btn"> شارژ کیف پول</a>
<a href="<?php echo esc_url(add_query_arg('tab', 'transactions', $base_url)); ?>" class="ifnx-wallet-btn ghost">مشاهده تراکنش‌ها</a>
</div>
</div>
<?php elseif ($tab === 'transactions'): ?>
<?php echo do_shortcode('[ifnex_transactions]'); ?>
<?php elseif ($tab === 'tracking'): ?>
<?php echo do_shortcode('[ifnex_tracking_form]'); ?>
<?php elseif ($tab === 'profile'): ?>
<?php echo do_shortcode('[ifnex_user_profile]'); ?>
<?php endif; ?>
</main>
</div>
<?php
return ob_get_clean();
}

View File

@ -445,6 +445,7 @@ function ifnex_enqueue_order_assets() {
has_shortcode($post->post_content, 'ifnex_user_profile') ||
has_shortcode($post->post_content, 'ifnex_order_payment') ||
has_shortcode($post->post_content, 'ifnex_order_detail');
has_shortcode($post->post_content, 'ifnex_customer_dashboard');
if ($has_order_shortcodes) {
// ✅ اصلاح: dirname(__FILE__) یک سطح بالاتر می‌رود
@ -547,4 +548,34 @@ function ifnex_enqueue_payment_assets() {
'orders_url' => home_url('/my-orders/'),
));
}
}
// ══════════════════════════════════════════════════════════════
// لود سراسری CSS و متغیرهای AJAX برای همه صفحات IFNEX
// این تابع همیشه اجرا می‌شود تا هیچ شورت‌کدی بدون استایل نماند
// ══════════════════════════════════════════════════════════════
add_action('wp_enqueue_scripts', 'ifnex_enqueue_global_assets', 20);
function ifnex_enqueue_global_assets() {
$plugin_url = plugin_dir_url(dirname(__FILE__));
// لود CSS اصلی (نسخه را بالا بردیم تا cache مرورگر بشکند)
wp_enqueue_style(
'ifnex-orders-css',
$plugin_url . 'assets/css/ifnex-orders.css',
array(),
'1.4.0'
);
// لود jQuery
wp_enqueue_script('jquery');
// متغیرهای AJAX برای همه صفحات
wp_localize_script('jquery', 'ifnex_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('ifnex_ajax_nonce'),
'orders_url' => home_url('/my-account/?tab=orders'),
'home_url' => home_url('/'),
));
}