Spaces:
Running
Running
📄 أمر تنفيذي: استكمال الوظائف الناقصة في تطبيق المتاجر الوطني 📌 الهدف: تنفيذ الوظائف التالية لضمان تجربة مستخدم متكاملة تشمل الطلبات، التقييم، الدعم الفني، الإشعارات، لوحة المشرف، وحماية البيانات. 🧩 المهام المطلوبة: 1. نظام الطلبات: - تصميم واجهة "طلب جديد" تشمل اختيار المنتج، الكمية، الدفع، والعنوان. - إنشاء قاعدة بيانات للطلبات (الحالة: pending, confirmed, delivered). - ربط الطلبات بواجهة المستخدم والمتجر عبر API موحد. - عرض سجل الطلبات في صفحة "طلباتي". 2. نظام التقييم والمراجعة: - واجهة تقييم بعد كل طلب (نجوم + تعليق). - حفظ التقييمات وربطها بالمتجر. - عرض متوسط التقييم في بطاقة المتجر. 3. نظام الدعم الفني: - دمج خدمة دردشة فورية (Firebase Chat أو WebSocket). - واجهة "الدعم الفني" تشمل إرسال رسالة، رفع صورة، متابعة الحالة. - لوحة تذاكر للمشرفين للرد على المستخدمين. 4. نظام الإشعارات الديناميكية: - إعداد خدمة إرسال إشعارات (FCM أو OneSignal). - لوحة تحكم لإرسال الإشعارات حسب القسم أو المتجر. - دعم الإشعارات التفاعلية (فتح صفحة معينة عند الضغط). 5. لوحة تحكم للمشرفين: - إدارة المتاجر، الطلبات، التقييمات، والإشعارات. - حماية الدخول بصلاحيات خاصة. - دعم الإحصائيات والرسوم البيانية. 6. حماية البيانات والخصوصية: - صفحة "الخصوصية" تشمل السياسات والموافقة. - دعم حذف الحساب وتصدير البيانات. - تشفير بيانات الجلسات والمفضلة محليًا. 🛠 التقنيات المقترحة: - الطلبات: RESTful API + SQLite أو MongoDB - التقييم: Firebase أو قاعدة بيانات محلية - الدعم الفني: Firebase Chat أو WebSocket - الإشعارات: FCM أو OneSignal - لوحة المشرف: React Admin أو لوحة مخصصة - الحماية: JWT + تشفير محلي 📅 الجدول الزمني المقترح: - الأسبوع 1: الطلبات والتقييم - الأسبوع 2: الدعم الفني والإشعارات - الأسبوع 3: لوحة المشرف والخصوصية - الأسبوع 4: اختبار شامل وتوثيق 📎 ملاحظات: - يجب توثيق كل وظيفة بشكل مستقل. - الالتزام بالتصميم الموحد واللغة العربية. - ضمان قابلية التوسع والتخصيص مستقبلاً. - Initial Deployment
6245a17 verified | <html lang="ar" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>مسار المستهلك</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| fontSize: { | |
| 'xxs': '0.6rem', | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- Rating Stars Template --> | |
| <template id="ratingStarsTemplate"> | |
| <div class="flex items-center"> | |
| <div class="flex"> | |
| <i class="fas fa-star text-yellow-400"></i> | |
| <i class="fas fa-star text-yellow-400"></i> | |
| <i class="fas fa-star text-yellow-400"></i> | |
| <i class="fas fa-star text-yellow-400"></i> | |
| <i class="fas fa-star text-yellow-400"></i> | |
| </div> | |
| <span class="text-xs text-gray-500 mr-1">(0)</span> | |
| </div> | |
| </template> | |
| <style> | |
| body { | |
| font-family: 'Tajawal', sans-serif; | |
| transition: all 0.3s ease; | |
| } | |
| .slide-in { | |
| animation: slideIn 0.3s forwards; | |
| } | |
| .slide-out { | |
| animation: slideOut 0.3s forwards; | |
| } | |
| @keyframes slideIn { | |
| from { transform: translateX(100%); } | |
| to { transform: translateX(0); } | |
| } | |
| @keyframes slideOut { | |
| from { transform: translateX(0); } | |
| to { transform: translateX(100%); } | |
| } | |
| .ticker { | |
| animation: ticker 20s linear infinite; | |
| } | |
| @keyframes ticker { | |
| 0% { transform: translateX(100%); } | |
| 100% { transform: translateX(-100%); } | |
| } | |
| .dark-mode { | |
| background-color: #1a202c; | |
| color: #f7fafc; | |
| } | |
| .dark-mode .sidebar { | |
| background-color: #2d3748; | |
| } | |
| .dark-mode .card { | |
| background-color: #2d3748; | |
| } | |
| /* New styles for smaller icons and modern design */ | |
| .icon-sm { | |
| font-size: 1.1rem; | |
| } | |
| .store-card { | |
| min-width: 120px; | |
| height: 120px; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| transition: all 0.2s ease; | |
| } | |
| .store-card:hover { | |
| transform: translateY(-5px); | |
| } | |
| /* Bottom nav item animation */ | |
| nav a { | |
| transition: all 0.2s ease; | |
| } | |
| nav a:hover, nav a.active { | |
| color: #3b82f6; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 text-gray-800"> | |
| <!-- Navigation Sidebar --> | |
| <div class="fixed right-0 top-0 h-full w-64 bg-white shadow-lg z-50 transform translate-x-full sidebar transition-transform duration-300"> | |
| <div class="p-4 border-b border-gray-200 dark:border-gray-700"> | |
| <div class="flex items-center justify-between"> | |
| <h2 class="text-xl font-bold">مسار المستهلك</h2> | |
| <button id="closeSidebar" class="text-gray-500 hover:text-gray-700 dark:text-gray-300 dark:hover:text-white"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <nav class="p-4"> | |
| <ul class="space-y-4"> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-home ml-2"></i> | |
| <span>الصفحة الرئيسية</span> | |
| </a> | |
| </li> | |
| <li> | |
| <button id="categoriesBtn" class="flex items-center justify-between w-full p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-th-large ml-2"></i> | |
| <span>الأقسام</span> | |
| </div> | |
| <i class="fas fa-chevron-down text-xs"></i> | |
| </button> | |
| <ul id="categoriesDropdown" class="hidden pl-4 mt-1 space-y-1"> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">السوبر ماركت والأسواق العامة</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">الملابس والأزياء</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">الأثاث والمفروشات</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">الإلكترونيات والتقنية</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">العناية الشخصية والجمال</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">المطاعم والمقاهي</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">الصيدليات والرعاية الصحية</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">الخدمات المنزلية والصيانة</a></li> | |
| <li><a href="#" class="block p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">شركات التوصيل</a></li> | |
| </ul> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-tags ml-2"></i> | |
| <span>العروض والخصومات</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-cog ml-2"></i> | |
| <span>الإعدادات</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-question-circle ml-2"></i> | |
| <span>مركز المساعدة</span> | |
| </a> | |
| </li> | |
| <li> | |
| <a href="/admin" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"> | |
| <i class="fas fa-tachometer-alt ml-2"></i> | |
| <span>لوحة التحكم</span> | |
| </a> | |
| </li> | |
| <li id="authMenuItem"> | |
| <a href="#" id="authBtn" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-blue-600 dark:text-blue-400"> | |
| <i class="fas fa-sign-in-alt ml-2" id="authIcon"></i> | |
| <span id="authText">تسجيل الدخول</span> | |
| </a> | |
| </li> | |
| <div id="userDropdown" class="hidden p-4 border-t border-gray-200 dark:border-gray-700"> | |
| <div class="flex items-center mb-4"> | |
| <img id="userAvatar" src="https://via.placeholder.com/40" class="rounded-full w-10 h-10 mr-3" alt="User"> | |
| <div> | |
| <p id="userName" class="font-medium"></p> | |
| <p id="userEmail" class="text-sm text-gray-500 dark:text-gray-400"></p> | |
| </div> | |
| </div> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-cog ml-2"></i> الإعدادات</a></li> | |
| <li><a href="#" class="flex items-center p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700"><i class="fas fa-shield-alt ml-2"></i> الأمان</a></li> | |
| <li><a href="#" id="logoutBtn" class="flex items-center p-2 rounded-lg text-red-500 hover:bg-red-50 dark:hover:bg-red-900"><i class="fas fa-sign-out-alt ml-2"></i> تسجيل الخروج</a></li> | |
| </ul> | |
| </div> | |
| </ul> | |
| </nav> | |
| </div> | |
| <!-- Notifications Dropdown --> | |
| <div id="notificationsDropdown" class="fixed right-4 top-16 w-72 bg-white dark:bg-gray-800 rounded-lg shadow-lg z-50 hidden border border-gray-200 dark:border-gray-700"> | |
| <div class="p-3 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"> | |
| <h3 class="font-bold">الإشعارات</h3> | |
| <button id="markAllRead" class="text-blue-500 text-sm">تمييز الكل كمقروء</button> | |
| </div> | |
| <div class="max-h-96 overflow-y-auto"> | |
| <div id="notificationsList" class="p-2"> | |
| <!-- Notifications will be loaded dynamically --> | |
| <div class="text-center py-4 text-gray-500">لا توجد إشعارات جديدة</div> | |
| </div> | |
| </div> | |
| <div class="p-2 border-t border-gray-200 dark:border-gray-700 text-center"> | |
| <a href="/notifications" class="text-blue-500 text-sm">عرض جميع الإشعارات</a> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="container mx-auto px-4 pb-16"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center py-4 sticky top-0 bg-white dark:bg-gray-800 z-40 shadow-sm"> | |
| <button id="menuBtn" class="text-gray-700 dark:text-gray-300"> | |
| <i class="fas fa-bars text-2xl"></i> | |
| </button> | |
| <h1 class="text-2xl font-bold text-blue-600 dark:text-blue-400">مسار المستهلك</h1> | |
| <div class="flex items-center space-x-4 space-x-reverse"> | |
| <button id="notificationsBtn" class="relative text-gray-700 dark:text-gray-300"> | |
| <i class="fas fa-bell text-xl"></i> | |
| <span id="notificationBadge" class="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center hidden">0</span> | |
| </button> | |
| <button id="themeToggle" class="text-gray-700 dark:text-gray-300"> | |
| <i class="fas fa-moon text-xl"></i> | |
| </button> | |
| </header> | |
| <!-- Ticker Slider --> | |
| <div class="bg-blue-100 dark:bg-blue-900 p-2 mb-6 rounded-lg overflow-hidden"> | |
| <div class="flex items-center whitespace-nowrap ticker"> | |
| <div class="mx-4 text-blue-800 dark:text-blue-200"> | |
| <span class="font-bold">عرض خاص:</span> خصم ٣٠٪ على جميع المنتجات في سوق الجمال حتى نهاية الشهر | |
| </div> | |
| <div class="mx-4 text-blue-800 dark:text-blue-200"> | |
| <span class="font-bold">عرض جديد:</span> هاتف سامسونج جالكسي S23 مع هدية مجانية | |
| </div> | |
| <div class="mx-4 text-blue-800 dark:text-blue-200"> | |
| <span class="font-bold">عرض خاص:</span> توصيل مجاني لجميع الطلبات فوق ١٠٠ ريال | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quick Access Cards --> | |
| <h2 class="text-xl font-bold mb-4">الإمكانيات السريعة</h2> | |
| <div class="relative mb-8"> | |
| <div class="overflow-x-auto scrollbar-hide"> | |
| <div class="flex space-x-4 pb-4" id="cardsContainer"> | |
| <!-- Cards will be added dynamically --> | |
| </div> | |
| </div> | |
| <button id="showMore" class="absolute left-0 bottom-0 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition"> | |
| عرض المزيد <i class="fas fa-chevron-left mr-2"></i> | |
| </button> | |
| </div> | |
| <!-- Favorite Stores --> | |
| <h2 class="text-xl font-bold mb-4">متاجري المفضلة</h2> | |
| <div class="relative mb-8"> | |
| <div class="overflow-x-auto scrollbar-hide"> | |
| <div class="flex space-x-4 pb-4" id="favoritesContainer"> | |
| <!-- Favorite stores will be added dynamically --> | |
| </div> | |
| </div> | |
| <button id="addFavorite" class="absolute left-0 bottom-0 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition"> | |
| إضافة متجر جديد <i class="fas fa-plus mr-2"></i> | |
| </button> | |
| </div> | |
| <!-- Stores Section (hidden by default) --> | |
| <div id="storesSection" class="hidden mb-8"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold" id="storeSectionTitle">المتاجر</h2> | |
| <button id="backToCategories" class="text-blue-600 dark:text-blue-400"> | |
| <i class="fas fa-arrow-left mr-2"></i> الرجوع للأقسام | |
| </button> | |
| </div> | |
| <!-- Search Bar --> | |
| <div class="mb-4 relative"> | |
| <input type="text" id="storeSearch" placeholder="ابحث عن متجر..." | |
| class="w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 dark:bg-gray-700"> | |
| <i class="fas fa-search absolute left-3 top-3.5 text-gray-400"></i> | |
| </div> | |
| <!-- Stores Grid --> | |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4" id="storesGrid"> | |
| <!-- Stores will be loaded dynamically --> | |
| </div> | |
| </div> | |
| <!-- Store View Container (hidden by default) --> | |
| <div id="storeViewContainer" class="hidden fixed inset-0 bg-white dark:bg-gray-800 z-50 overflow-y-auto"> | |
| <div class="container mx-auto px-4 py-4"> | |
| <div class="flex justify-between items-center mb-4 sticky top-0 bg-white dark:bg-gray-800 py-2 z-10"> | |
| <h2 class="text-xl font-bold" id="storeNameHeader"></h2> | |
| <button id="closeStoreView" class="text-red-500"> | |
| <i class="fas fa-times text-xl"></i> | |
| </button> | |
| </div> | |
| <!-- WebView Container --> | |
| <div id="storeWebView" class="w-full h-screen"> | |
| <!-- Store content will be loaded here via WebView/API --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Offers Section --> | |
| <h2 class="text-xl font-bold mb-4">عروض اليوم</h2> | |
| <div class="grid md:grid-cols-2 gap-4 mb-8"> | |
| <div class="bg-gradient-to-r from-blue-500 to-blue-700 text-white p-6 rounded-lg"> | |
| <h3 class="text-xl font-bold mb-2">خصم يصل إلى ٤٠٪</h3> | |
| <p class="mb-4">على جميع الأجهزة المنزلية في سوق الإلكترونيات</p> | |
| <button class="bg-white text-blue-600 px-4 py-2 rounded-lg font-bold">عرض التفاصيل</button> | |
| </div> | |
| <div class="bg-gradient-to-r from-green-500 to-green-700 text-white p-6 rounded-lg"> | |
| <h3 class="text-xl font-bold mb-2">٢+١ مجانًا</h3> | |
| <p class="mb-4">على منتجات العناية بالبشرة في صيدليات النهدي</p> | |
| <button class="bg-white text-green-600 px-4 py-2 rounded-lg font-bold">عرض التفاصيل</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Sample data for quick access cards | |
| const quickAccessItems = [ | |
| { icon: 'fa-search', title: 'بحث سريع', color: 'bg-blue-100', textColor: 'text-blue-600' }, | |
| { icon: 'fa-history', title: 'آخر العروض', color: 'bg-green-100', textColor: 'text-green-600' }, | |
| { icon: 'fa-star', title: 'المفضلة', color: 'bg-yellow-100', textColor: 'text-yellow-600' }, | |
| { icon: 'fa-map-marker-alt', title: 'أقرب متجر', color: 'bg-red-100', textColor: 'text-red-600' }, | |
| { icon: 'fa-bell', title: 'التنبيهات', color: 'bg-purple-100', textColor: 'text-purple-600' }, | |
| { icon: 'fa-wallet', title: 'المحفظة', color: 'bg-indigo-100', textColor: 'text-indigo-600' }, | |
| ]; | |
| // DOM Elements | |
| const menuBtn = document.getElementById('menuBtn'); | |
| const closeSidebar = document.getElementById('closeSidebar'); | |
| const sidebar = document.querySelector('.sidebar'); | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const cardsContainer = document.getElementById('cardsContainer'); | |
| const showMore = document.getElementById('showMore'); | |
| const body = document.body; | |
| // Initialize cards | |
| function initCards() { | |
| quickAccessItems.forEach(item => { | |
| const card = document.createElement('div'); | |
| card.className = `flex-shrink-0 w-24 h-24 ${item.color} ${item.textColor} rounded-lg p-3 shadow-md flex flex-col items-center justify-center`; | |
| card.innerHTML = ` | |
| <i class="fas ${item.icon} text-base mb-1"></i> | |
| <span class="font-bold text-xxs">${item.title}</span> | |
| `; | |
| cardsContainer.appendChild(card); | |
| }); | |
| } | |
| // Toggle categories dropdown | |
| document.getElementById('categoriesBtn').addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| document.getElementById('categoriesDropdown').classList.toggle('hidden'); | |
| this.querySelector('.fa-chevron-down').classList.toggle('fa-rotate-180'); | |
| }); | |
| // Close dropdown when clicking outside | |
| document.addEventListener('click', function(e) { | |
| if (!e.target.closest('#categoriesBtn') && !e.target.closest('#categoriesDropdown')) { | |
| document.getElementById('categoriesDropdown').classList.add('hidden'); | |
| document.querySelector('#categoriesBtn .fa-chevron-down').classList.remove('fa-rotate-180'); | |
| } | |
| }); | |
| // Toggle sidebar | |
| menuBtn.addEventListener('click', () => { | |
| sidebar.classList.remove('translate-x-full'); | |
| sidebar.classList.add('slide-in'); | |
| }); | |
| closeSidebar.addEventListener('click', () => { | |
| sidebar.classList.add('translate-x-full'); | |
| sidebar.classList.add('slide-out'); | |
| }); | |
| // Toggle theme | |
| themeToggle.addEventListener('click', () => { | |
| body.classList.toggle('dark-mode'); | |
| const icon = themeToggle.querySelector('i'); | |
| if (body.classList.contains('dark-mode')) { | |
| icon.classList.replace('fa-moon', 'fa-sun'); | |
| localStorage.setItem('theme', 'dark'); | |
| } else { | |
| icon.classList.replace('fa-sun', 'fa-moon'); | |
| localStorage.setItem('theme', 'light'); | |
| } | |
| }); | |
| // Check for saved theme preference | |
| if (localStorage.getItem('theme') === 'dark') { | |
| body.classList.add('dark-mode'); | |
| themeToggle.querySelector('i').classList.replace('fa-moon', 'fa-sun'); | |
| } | |
| // Show more button | |
| showMore.addEventListener('click', () => { | |
| alert('سيتم عرض جميع البطاقات في صفحة منفصلة'); | |
| }); | |
| // Store data (mock - should be replaced with API calls) | |
| const storesData = { | |
| 'supermarkets': [ | |
| { id: 'market1', name: 'سوق الرياض', logo: 'fa-store', apiUrl: 'https://api.example.com/markets/1' }, | |
| { id: 'market2', name: 'هايبر بنده', logo: 'fa-shopping-basket', apiUrl: 'https://api.example.com/markets/2' } | |
| ], | |
| 'fashion': [ | |
| { id: 'fashion1', name: 'زد', logo: 'fa-tshirt', apiUrl: 'https://api.example.com/fashion/1' } | |
| ], | |
| 'furniture': [ | |
| { id: 'furniture1', name: 'إيكيا', logo: 'fa-couch', apiUrl: 'https://api.example.com/furniture/1' } | |
| ], | |
| 'electronics': [ | |
| { id: 'electronics1', name: 'إكسترا', logo: 'fa-laptop', apiUrl: 'https://api.example.com/electronics/1' } | |
| ], | |
| 'beauty': [ | |
| { id: 'beauty1', name: 'سيفورا', logo: 'fa-spa', apiUrl: 'https://api.example.com/beauty/1' } | |
| ], | |
| 'restaurants': [ | |
| { id: 'restaurant1', name: 'مطاعم هرفي', logo: 'fa-utensils', apiUrl: 'https://api.example.com/restaurants/1' } | |
| ], | |
| 'pharmacies': [ | |
| { id: 'pharmacy1', name: 'صيدليات النهدي', logo: 'fa-prescription-bottle-alt', apiUrl: 'https://api.example.com/pharmacies/1' } | |
| ], | |
| 'services': [ | |
| { id: 'service1', name: 'نجارون', logo: 'fa-tools', apiUrl: 'https://api.example.com/services/1' } | |
| ], | |
| 'delivery': [ | |
| { id: 'delivery1', name: 'هنقرستيشن', logo: 'fa-motorcycle', apiUrl: 'https://api.example.com/delivery/1' } | |
| ] | |
| }; | |
| // Current session state | |
| const appState = { | |
| currentCategory: null, | |
| currentStore: null, | |
| storeSessions: JSON.parse(localStorage.getItem('storeSessions')) || {} | |
| }; | |
| // Auth State | |
| const authState = { | |
| isAuthenticated: false, | |
| accessToken: null, | |
| refreshToken: null, | |
| userInfo: null, | |
| tokenExpiry: null | |
| }; | |
| // Sample favorite stores data | |
| const favoriteStores = [ | |
| { id: 'fav1', name: 'سوق الرياض', icon: 'fa-store' }, | |
| { id: 'fav2', name: 'النهدي', icon: 'fa-prescription-bottle-alt' }, | |
| { id: 'fav3', name: 'إلكترونيات', icon: 'fa-laptop' } | |
| ]; | |
| // Initialize favorite stores | |
| function initFavorites() { | |
| const container = document.getElementById('favoritesContainer'); | |
| favoriteStores.forEach(store => { | |
| const element = document.createElement('div'); | |
| element.className = 'store-card bg-white dark:bg-gray-700 p-4 rounded-lg shadow-md flex-shrink-0'; | |
| element.innerHTML = ` | |
| <i class="fas ${store.icon} text-base text-blue-500 mb-2"></i> | |
| <span class="text-sm font-medium">${store.name}</span> | |
| `; | |
| container.appendChild(element); | |
| }); | |
| } | |
| // Initialize the app | |
| document.addEventListener('DOMContentLoaded', () => { | |
| initCards(); | |
| initFavorites(); | |
| setupStoreView(); | |
| loadSavedSessions(); | |
| initAuth(); | |
| // Set first nav item as active | |
| document.querySelector('nav a').classList.add('active'); | |
| }); | |
| // Auth Functions | |
| function initAuth() { | |
| // Check for existing auth in localStorage | |
| const savedAuth = localStorage.getItem('unifiedAuth'); | |
| if (savedAuth) { | |
| const authData = JSON.parse(savedAuth); | |
| if (new Date(authData.tokenExpiry) > new Date()) { | |
| authState.isAuthenticated = true; | |
| authState.accessToken = authData.accessToken; | |
| authState.refreshToken = authData.refreshToken; | |
| authState.userInfo = authData.userInfo; | |
| authState.tokenExpiry = new Date(authData.tokenExpiry); | |
| updateAuthUI(); | |
| } else { | |
| // Token expired, try refresh | |
| refreshToken(authData.refreshToken); | |
| } | |
| } | |
| // Setup auth button handlers | |
| document.getElementById('authBtn').addEventListener('click', toggleAuthModal); | |
| document.getElementById('closeLoginModal').addEventListener('click', () => { | |
| document.getElementById('loginModal').classList.add('hidden'); | |
| }); | |
| document.getElementById('loginBtn').addEventListener('click', handleLogin); | |
| document.getElementById('sendOtpBtn').addEventListener('click', sendOTP); | |
| document.getElementById('verifyOtpBtn').addEventListener('click', verifyOTP); | |
| document.getElementById('logoutBtn').addEventListener('click', handleLogout); | |
| } | |
| function toggleAuthModal() { | |
| if (authState.isAuthenticated) { | |
| document.getElementById('userDropdown').classList.toggle('hidden'); | |
| } else { | |
| document.getElementById('loginModal').classList.remove('hidden'); | |
| } | |
| } | |
| async function handleLogin() { | |
| const email = document.getElementById('emailInput').value; | |
| const password = document.getElementById('passwordInput').value; | |
| try { | |
| // In real app, this would call your OAuth 2.0/OpenID Connect endpoint | |
| const response = await mockLogin(email, password); | |
| authState.isAuthenticated = true; | |
| authState.accessToken = response.access_token; | |
| authState.refreshToken = response.refresh_token; | |
| authState.userInfo = response.user_info; | |
| authState.tokenExpiry = new Date(new Date().getTime() + (response.expires_in * 1000)); | |
| localStorage.setItem('unifiedAuth', JSON.stringify(authState)); | |
| updateAuthUI(); | |
| document.getElementById('loginModal').classList.add('hidden'); | |
| // Check if 2FA is required | |
| if (response.requires_2fa) { | |
| document.getElementById('twoFAModal').classList.remove('hidden'); | |
| } | |
| } catch (error) { | |
| alert('فشل تسجيل الدخول: ' + error.message); | |
| } | |
| } | |
| function mockLogin(email, password) { | |
| return new Promise((resolve, reject) => { | |
| // Simulate API call | |
| setTimeout(() => { | |
| if (email && password) { | |
| resolve({ | |
| access_token: 'mock_access_token_' + Math.random().toString(36).substr(2, 9), | |
| refresh_token: 'mock_refresh_token_' + Math.random().toString(36).substr(2, 9), | |
| expires_in: 3600, | |
| token_type: 'Bearer', | |
| user_info: { | |
| name: 'محمد أحمد', | |
| email: email, | |
| phone: '0501234567', | |
| avatar: 'https://via.placeholder.com/150' | |
| }, | |
| requires_2fa: false | |
| }); | |
| } else { | |
| reject(new Error('البريد الإلكتروني أو كلمة المرور غير صحيحة')); | |
| } | |
| }, 1000); | |
| }); | |
| } | |
| function updateAuthUI() { | |
| if (authState.isAuthenticated) { | |
| document.getElementById('authIcon').classList.replace('fa-sign-in-alt', 'fa-user-circle'); | |
| document.getElementById('authText').textContent = authState.userInfo.name.split(' ')[0]; | |
| document.getElementById('userName').textContent = authState.userInfo.name; | |
| document.getElementById('userEmail').textContent = authState.userInfo.email; | |
| document.getElementById('userAvatar').src = authState.userInfo.avatar; | |
| } else { | |
| document.getElementById('authIcon').classList.replace('fa-user-circle', 'fa-sign-in-alt'); | |
| document.getElementById('authText').textContent = 'تسجيل الدخول'; | |
| document.getElementById('userDropdown').classList.add('hidden'); | |
| } | |
| } | |
| function handleLogout() { | |
| // Call logout endpoint | |
| fetch('/auth/logout', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${authState.accessToken}` | |
| } | |
| }); | |
| // Clear auth state | |
| authState.isAuthenticated = false; | |
| authState.accessToken = null; | |
| authState.refreshToken = null; | |
| authState.userInfo = null; | |
| authState.tokenExpiry = null; | |
| localStorage.removeItem('unifiedAuth'); | |
| updateAuthUI(); | |
| } | |
| function refreshToken(refreshToken) { | |
| // Implement token refresh logic | |
| console.log('Refreshing token...'); | |
| } | |
| function sendOTP() { | |
| const mobile = document.getElementById('mobileInput').value; | |
| if (!mobile) { | |
| alert('من فضلك أدخل رقم الجوال'); | |
| return; | |
| } | |
| // Simulate sending OTP | |
| document.getElementById('otpMobileNumber').textContent = '+966' + mobile; | |
| document.getElementById('loginBtn').classList.add('hidden'); | |
| document.getElementById('otpSection').classList.remove('hidden'); | |
| document.getElementById('sendOtpBtn').style.display = 'none'; | |
| } | |
| function verifyOTP() { | |
| // Verify OTP logic would go here | |
| alert('تم التحقق بنجاح'); | |
| document.getElementById('loginModal').classList.add('hidden'); | |
| } | |
| // Handle category navigation | |
| function setupCategoryNavigation() { | |
| // Set up category links in dropdown | |
| document.querySelectorAll('#categoriesDropdown a').forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const categoryName = link.textContent.trim(); | |
| // Map readable category names to data keys | |
| const categoryMap = { | |
| 'السوبر ماركت والأسواق العامة': 'supermarkets', | |
| 'الملابس والأزياء': 'fashion', | |
| 'الأثاث والمفروشات': 'furniture', | |
| 'الإلكترونيات والتقنية': 'electronics', | |
| 'العناية الشخصية والجمال': 'beauty', | |
| 'المطاعم والمقاهي': 'restaurants', | |
| 'الصيدليات والرعاية الصحية': 'pharmacies', | |
| 'الخدمات المنزلية والصيانة': 'services', | |
| 'شركات التوصيل': 'delivery' | |
| }; | |
| const category = categoryMap[categoryName]; | |
| if (category) { | |
| appState.currentCategory = category; | |
| showStoresForCategory(category); | |
| sidebar.classList.add('translate-x-full'); | |
| } | |
| }); | |
| }); | |
| document.getElementById('backToCategories').addEventListener('click', () => { | |
| document.getElementById('storesSection').classList.add('hidden'); | |
| document.querySelector('#categoriesContainer').classList.remove('hidden'); | |
| }); | |
| } | |
| // Show stores for selected category | |
| function showStoresForCategory(category) { | |
| document.querySelector('#categoriesContainer').classList.add('hidden'); | |
| const storesSection = document.getElementById('storesSection'); | |
| storesSection.classList.remove('hidden'); | |
| document.getElementById('storeSectionTitle').textContent = `متاجر ${category}`; | |
| const storesGrid = document.getElementById('storesGrid'); | |
| storesGrid.innerHTML = ''; | |
| // Load stores from API - using mock data here | |
| const stores = storesData[category] || []; | |
| stores.forEach(store => { | |
| const storeElement = document.createElement('div'); | |
| storeElement.className = 'store-card bg-white dark:bg-gray-700 p-4 rounded-lg shadow-md text-center cursor-pointer hover:shadow-lg transition'; | |
| storeElement.dataset.storeId = store.id; | |
| storeElement.innerHTML = ` | |
| <i class="fas ${store.logo} text-2xl text-blue-500 mb-2"></i> | |
| <h3 class="font-bold">${store.name}</h3> | |
| ${appState.storeSessions[store.id] ? | |
| '<span class="text-xs text-green-500 block mt-1">لديك جلسة سابقة</span>' : ''} | |
| `; | |
| storeElement.addEventListener('click', () => { | |
| openStore(store); | |
| }); | |
| storesGrid.appendChild(storeElement); | |
| }); | |
| } | |
| // Open store in WebView | |
| function openStore(store) { | |
| appState.currentStore = store.id; | |
| const storeView = document.getElementById('storeViewContainer'); | |
| storeView.classList.remove('hidden'); | |
| document.getElementById('storeNameHeader').textContent = store.name; | |
| // Load store content - in a real app this would be a WebView or API call | |
| const storeContent = document.getElementById('storeWebView'); | |
| storeContent.innerHTML = ` | |
| <iframe src="${store.apiUrl}" class="w-full h-full" | |
| frameborder="0" allowfullscreen></iframe> | |
| `; | |
| // Restore session if available | |
| if (appState.storeSessions[store.id]) { | |
| // In a real app, would restore scroll position and state | |
| console.log(`Restoring session for ${store.name}`); | |
| } | |
| } | |
| // Setup store view controls | |
| function setupStoreView() { | |
| document.getElementById('closeStoreView').addEventListener('click', () => { | |
| // Save current session before closing | |
| if (appState.currentStore) { | |
| appState.storeSessions[appState.currentStore] = { | |
| lastVisited: new Date().toISOString(), | |
| // In a real app, would save scroll position and other state | |
| url: window.location.href | |
| }; | |
| localStorage.setItem('storeSessions', JSON.stringify(appState.storeSessions)); | |
| } | |
| document.getElementById('storeViewContainer').classList.add('hidden'); | |
| }); | |
| } | |
| // Load saved sessions | |
| function loadSavedSessions() { | |
| if (Object.keys(appState.storeSessions).length > 0) { | |
| // Could show a quick access section for stores with saved sessions | |
| console.log('Loaded saved store sessions:', appState.storeSessions); | |
| } | |
| } | |
| // Search functionality | |
| document.getElementById('storeSearch').addEventListener('input', (e) => { | |
| const searchTerm = e.target.value.toLowerCase(); | |
| const storeCards = document.querySelectorAll('.store-card'); | |
| storeCards.forEach(card => { | |
| const storeName = card.querySelector('h3').textContent.toLowerCase(); | |
| card.style.display = storeName.includes(searchTerm) ? 'block' : 'none'; | |
| }); | |
| }); | |
| // Handle swipe gestures for mobile | |
| let touchStartX = 0; | |
| let touchEndX = 0; | |
| document.addEventListener('touchstart', e => { | |
| touchStartX = e.changedTouches[0].screenX; | |
| }, false); | |
| document.addEventListener('touchend', e => { | |
| touchEndX = e.changedTouches[0].screenX; | |
| handleSwipe(); | |
| }, false); | |
| function handleSwipe() { | |
| if (touchStartX - touchEndX > 50) { | |
| // Swipe left - open sidebar | |
| sidebar.classList.remove('translate-x-full'); | |
| sidebar.classList.add('slide-in'); | |
| } | |
| if (touchEndX - touchStartX > 50 && !sidebar.classList.contains('translate-x-full')) { | |
| // Swipe right - close sidebar | |
| sidebar.classList.add('translate-x-full'); | |
| sidebar.classList.add('slide-out'); | |
| } | |
| } | |
| </script> | |
| <!-- Bottom Navigation Bar --> | |
| <nav class="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 flex justify-around items-center py-3 z-40"> | |
| <a href="#" class="flex flex-col items-center text-blue-600 dark:text-blue-400"> | |
| <i class="fas fa-home text-xl mb-1"></i> | |
| <span class="text-xs">الرئيسية</span> | |
| </a> | |
| <a href="#" class="flex flex-col items-center text-gray-600 dark:text-gray-400"> | |
| <i class="fas fa-search text-xl mb-1"></i> | |
| <span class="text-xs">البحث</span> | |
| </a> | |
| <a href="#" class="flex flex-col items-center text-gray-600 dark:text-gray-400"> | |
| <i class="fas fa-shopping-bag text-xl mb-1"></i> | |
| <span class="text-xs">طلباتي</span> | |
| </a> | |
| <a href="#" class="flex flex-col items-center text-gray-600 dark:text-gray-400"> | |
| <i class="fas fa-user text-xl mb-1"></i> | |
| <span class="text-xs">ملفي</span> | |
| </a> | |
| <a href="#" id="liveSupportBtn" class="flex flex-col items-center text-gray-600 dark:text-gray-400"> | |
| <i class="fas fa-headset text-xl mb-1"></i> | |
| <span class="text-xs">المساعدة</span> | |
| </a> | |
| </nav> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=mohammed2449/m" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |