/* ============================================ GrubFlip — Feed & Meal Cards Renders the meal feed with filtering, pagination, and card interactions. ============================================ */ const Feed = (() => { 'use strict'; const { api, $, $$, el, timeUntil, timeAgo, starsHTML, debounce, toast } = GrubFlip; // --- State --- let meals = []; let pagination = { page: 1, per_page: 20, total: 0, has_more: false }; let loading = false; let filters = { dietary: [], cuisine: '', trade_type: '', sort: 'newest', }; const DIETARY_OPTIONS = [ 'vegan', 'vegetarian', 'gluten-free', 'dairy', 'dairy-free', 'nut-free', 'halal', 'kosher', 'high-protein', 'low-carb', 'spicy' ]; // --- Meal Card Rendering --- function renderMealCard(meal) { const card = el('article', { className: 'meal-card', dataset: { mealId: meal.id } }); // Image const imageWrap = el('div', { className: 'meal-card__image-wrap' }); const img = el('img', { className: 'meal-card__image', src: meal.image_thumb_url || meal.image_url, alt: meal.title, loading: 'lazy', }); imageWrap.appendChild(img); // Trade type badge const tradeLabel = meal.trade_type === 'swap' ? 'Swap' : meal.trade_type; imageWrap.appendChild(el('span', { className: 'meal-card__trade-type', textContent: tradeLabel, })); // Expiry countdown if (meal.expires_at) { const remaining = timeUntil(meal.expires_at); if (remaining !== 'expired') { imageWrap.appendChild(el('span', { className: 'meal-card__expiry', innerHTML: ` ${remaining}`, })); } } card.appendChild(imageWrap); // Body const body = el('div', { className: 'meal-card__body' }); body.appendChild(el('h3', { className: 'meal-card__title', textContent: meal.title })); body.appendChild(el('p', { className: 'meal-card__desc', textContent: meal.description })); // Tags const tagsWrap = el('div', { className: 'meal-card__tags' }); if (meal.cuisine) { tagsWrap.appendChild(el('span', { className: 'meal-card__tag meal-card__tag--cuisine', textContent: meal.cuisine, })); } for (const tag of (meal.dietary_tags || []).slice(0, 3)) { tagsWrap.appendChild(el('span', { className: 'meal-card__tag', textContent: tag, })); } body.appendChild(tagsWrap); // Footer — user + distance const footer = el('div', { className: 'meal-card__footer' }); const userSection = el('div', { className: 'meal-card__user' }); userSection.appendChild(el('img', { className: 'meal-card__avatar', src: meal.user.avatar_url, alt: meal.user.display_name, loading: 'lazy', })); const userInfo = el('div', { className: 'meal-card__user-info' }); userInfo.appendChild(el('span', { className: 'meal-card__user-name', textContent: meal.user.display_name, })); const metaHTML = `${starsHTML(meal.user.rating)} · ${meal.user.trade_count} trades`; userInfo.appendChild(el('span', { className: 'meal-card__user-meta', innerHTML: metaHTML, })); userSection.appendChild(userInfo); footer.appendChild(userSection); if (meal.distance_km != null) { footer.appendChild(el('span', { className: 'meal-card__distance', innerHTML: ` ${meal.distance_km} km`, })); } body.appendChild(footer); card.appendChild(body); // Click handler — open detail modal card.addEventListener('click', () => { if (typeof MealDetail !== 'undefined') { MealDetail.open(meal); } else { toast(`Opening ${meal.title}...`); } }); return card; } // --- Skeleton Card --- function renderSkeleton() { const card = el('div', { className: 'meal-card' }); card.innerHTML = `
Try adjusting your filters or check back later