This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

update to work with new database structure

+116 -23
+116 -23
src/components/Resources/Resources.js
··· 1 - // src/components/Resources/Resources.jsx - Modified version 1 + // src/components/Resources/Resources.jsx - Updated for multiple categories 2 2 import React, { useState, useEffect, useMemo } from 'react'; 3 3 import './Resources.css'; 4 4 import ResourceLoader from './ResourceLoader'; ··· 59 59 async function fetchResources() { 60 60 setIsLoading(true); 61 61 try { 62 - // Fetch all resources with category and subcategory data 63 - const { data, error } = await supabase 62 + // First fetch all resources 63 + const { data: resourcesData, error: resourcesError } = await supabase 64 64 .from('resources') 65 65 .select(` 66 66 *, 67 - category:categories(id, name, emoji), 68 67 subcategory:subcategories(id, name) 69 68 `) 70 69 .order('position'); 71 70 72 - if (error) { 73 - throw error; 71 + if (resourcesError) { 72 + throw resourcesError; 73 + } 74 + 75 + // Then fetch the categories for each resource using the junction table 76 + const { data: resourceCategories, error: categoriesError } = await supabase 77 + .from('resources_categories') 78 + .select(` 79 + resource_id, 80 + category:categories(id, name, emoji) 81 + `); 82 + 83 + if (categoriesError) { 84 + throw categoriesError; 74 85 } 75 86 87 + // Group categories by resource_id 88 + const categoriesByResource = {}; 89 + resourceCategories.forEach(item => { 90 + if (!categoriesByResource[item.resource_id]) { 91 + categoriesByResource[item.resource_id] = []; 92 + } 93 + categoriesByResource[item.resource_id].push({ 94 + id: item.category.id, 95 + name: item.category.name, 96 + emoji: item.category.emoji 97 + }); 98 + }); 99 + 76 100 // Transform data to match the expected format 77 - const formattedResources = data.map(resource => ({ 78 - ...resource, 79 - category: resource.category.name, 80 - subcategory: resource.subcategory ? resource.subcategory.name : null, 81 - emoji: resource.category.emoji, 82 - url: addUTMParameters(resource.url) 83 - })); 101 + const formattedResources = resourcesData.map(resource => { 102 + // Get categories for this resource 103 + const resourceCategoryList = categoriesByResource[resource.id] || []; 104 + 105 + return { 106 + ...resource, 107 + // Primary category for backwards compatibility (use first category if available) 108 + category: resourceCategoryList.length > 0 ? resourceCategoryList[0].name : 'Misc', 109 + // Store all categories 110 + categories: resourceCategoryList, 111 + subcategory: resource.subcategory ? resource.subcategory.name : null, 112 + emoji: resourceCategoryList.length > 0 ? resourceCategoryList[0].emoji : '🔮', 113 + url: addUTMParameters(resource.url) 114 + }; 115 + }); 84 116 85 117 setResources(formattedResources); 86 118 } catch (error) { ··· 122 154 // Get all categories from resources 123 155 const categories = useMemo(() => { 124 156 if (resources.length === 0) return ['All']; 125 - const categoryNames = [...new Set(resources.map(item => item.category))]; 126 - return ['All', ...categoryNames]; 157 + 158 + // Extract all unique categories from all resources 159 + const allCategories = new Set(); 160 + resources.forEach(resource => { 161 + if (resource.categories && resource.categories.length > 0) { 162 + resource.categories.forEach(cat => allCategories.add(cat.name)); 163 + } 164 + }); 165 + 166 + return ['All', ...Array.from(allCategories).sort()]; 127 167 }, [resources]); 128 168 129 169 // Count resources per category 130 170 const categoryCounts = useMemo(() => { 131 171 const counts = { 'All': resources.length }; 172 + 132 173 resources.forEach(resource => { 133 - counts[resource.category] = (counts[resource.category] || 0) + 1; 174 + if (resource.categories && resource.categories.length > 0) { 175 + resource.categories.forEach(category => { 176 + counts[category.name] = (counts[category.name] || 0) + 1; 177 + }); 178 + } 134 179 }); 180 + 135 181 return counts; 136 182 }, [resources]); 137 183 184 + // Check if a resource belongs to a category 185 + const resourceHasCategory = (resource, categoryName) => { 186 + if (categoryName === 'All') return true; 187 + return resource.categories && resource.categories.some(cat => cat.name === categoryName); 188 + }; 189 + 138 190 // Filter resources based on active category, search query, quality filter, and new filter 139 191 const filteredResources = useMemo(() => { 140 192 return resources.filter(resource => { 141 193 // Filter by category 142 - const categoryMatch = activeCategory === 'All' || resource.category === activeCategory; 194 + const categoryMatch = resourceHasCategory(resource, activeCategory); 143 195 144 196 // Filter by search query 145 197 const searchMatch = ··· 169 221 if (activeCategory !== 'All') return {}; 170 222 171 223 const grouped = {}; 224 + 225 + // First, initialize all category groups 226 + categories.forEach(category => { 227 + if (category !== 'All') { 228 + grouped[category] = []; 229 + } 230 + }); 231 + 232 + // Then add resources to their respective categories 172 233 filteredResources.forEach(resource => { 173 - if (!grouped[resource.category]) { 174 - grouped[resource.category] = []; 234 + if (resource.categories && resource.categories.length > 0) { 235 + // Add resource to each of its categories 236 + resource.categories.forEach(category => { 237 + if (!grouped[category.name]) { 238 + grouped[category.name] = []; 239 + } 240 + // Avoid duplicates (could happen if we process the same resource multiple times) 241 + if (!grouped[category.name].some(r => r.id === resource.id)) { 242 + grouped[category.name].push(resource); 243 + } 244 + }); 245 + } else { 246 + // If no categories, add to Misc 247 + if (!grouped['Misc']) { 248 + grouped['Misc'] = []; 249 + } 250 + grouped['Misc'].push(resource); 175 251 } 176 - grouped[resource.category].push(resource); 177 252 }); 253 + 254 + // Remove empty categories 255 + Object.keys(grouped).forEach(category => { 256 + if (grouped[category].length === 0) { 257 + delete grouped[category]; 258 + } 259 + }); 260 + 178 261 return grouped; 179 - }, [filteredResources, activeCategory]); 262 + }, [filteredResources, activeCategory, categories]); 180 263 181 264 // Should show featured section only when All category is selected, no quality filter is active, and search query is empty 182 265 const shouldShowFeatured = activeCategory === 'All' && qualityFilter === 0 && searchQuery.trim() === ''; ··· 338 421 <div className="all-resources-section"> 339 422 <h2>All Resources ({filteredResources.length})</h2> 340 423 341 - {Object.keys(resourcesByCategory).map(category => ( 424 + {Object.keys(resourcesByCategory).sort().map(category => ( 342 425 <div key={category} className="category-section"> 343 426 <h3 className="category-header"> 344 427 {categoryEmojis[category] || '🔹'} {category} ({resourcesByCategory[category].length}) ··· 418 501 <p className="resource-description">{resource.description}</p> 419 502 <p className="resource-domain">{resource.domain}</p> 420 503 <div className="resource-meta"> 421 - <span className="resource-category">{resource.category}</span> 504 + <div className="resource-categories"> 505 + {resource.categories && resource.categories.length > 0 ? ( 506 + resource.categories.map((cat, idx) => ( 507 + <span key={idx} className="resource-category"> 508 + {cat.name} 509 + </span> 510 + )) 511 + ) : ( 512 + <span className="resource-category">Misc</span> 513 + )} 514 + </div> 422 515 <div className="resource-quality"> 423 516 {renderQualityStars(resource.quality)} 424 517 </div>