This repository has no description
0

Configure Feed

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

resource page styling

+180 -46
+79 -11
src/components/Resources/Resources.css
··· 291 291 background-color: rgba(var(--text-rgb), 0.2); 292 292 } 293 293 294 - /* New toggle styling */ 295 - .new-filter { 296 - margin-left: auto; 294 + /* Toggle filters container - New */ 295 + .toggle-filters { 296 + display: flex; 297 + flex-wrap: wrap; 298 + gap: 1.5rem; 299 + width: 100%; 300 + justify-content: center; 301 + margin-top: 1rem; 302 + } 303 + 304 + /* New and Score toggle styling */ 305 + .new-filter, 306 + .score-filter { 307 + display: flex; 308 + align-items: center; 297 309 } 298 310 299 311 .toggle-label { ··· 309 321 cursor: pointer; 310 322 height: 20px; 311 323 margin: 0 8px 0 0; 312 - margin-right: 8px; 324 + margin-right: 8px; 313 325 position: relative; 314 326 transition: background-color .3s; 315 327 width: 38px; ··· 341 353 transform: translateX(14px); 342 354 } 343 355 356 + .toggle-label input[type="checkbox"]:focus { 357 + outline: none; 358 + box-shadow: 0 0 0 3px rgba(var(--button-bg-rgb), 0.2); 359 + } 360 + 344 361 .toggle-text { 345 362 font-size: .95rem; 346 363 font-weight: 600; ··· 431 448 /* Improved resource header styling */ 432 449 .resource-header { 433 450 display: flex; 434 - align-items: center; 451 + align-items: flex-start; 452 + justify-content: space-between; 435 453 gap: 8px; 436 454 margin-bottom: 10px; 437 455 } ··· 443 461 line-height: 1.3; 444 462 } 445 463 446 - /* Improved NEW badge styling */ 447 - .new-badge { 464 + /* Resource badges container */ 465 + .resource-badges { 466 + display: flex; 467 + flex-wrap: wrap; 468 + gap: 6px; 469 + flex-shrink: 0; 470 + margin-top: 3px; 471 + } 472 + 473 + /* Badge styling */ 474 + .new-badge, .score-badge { 448 475 align-items: center; 449 - animation: pulse 2s infinite; 450 - background-color: #666 !important; 451 476 border-radius: 5.9px; 452 477 color: var(--button-text); 453 478 display: inline-flex; ··· 460 485 margin-bottom: 4px; 461 486 } 462 487 488 + /* NEW badge styling */ 489 + .new-badge { 490 + animation: pulse 2s infinite; 491 + background-color: #666 !important; 492 + } 493 + 494 + /* SCORE badge styling */ 495 + .score-badge { 496 + background-color: var(--button-bg) !important; 497 + } 498 + 463 499 @keyframes pulse { 464 500 0% { 465 501 transform: scale(1); ··· 495 531 margin-top: auto; 496 532 } 497 533 534 + /* Categories display */ 535 + .resource-categories { 536 + display: flex; 537 + flex-wrap: wrap; 538 + gap: 6px; 539 + } 540 + 498 541 .resource-category { 499 542 font-size: 0.8rem; 500 543 background-color: var(--card-border); ··· 503 546 color: var(--text); 504 547 } 505 548 549 + /* Tags display */ 550 + .resource-tags { 551 + display: flex; 552 + flex-wrap: wrap; 553 + gap: 4px; 554 + margin-top: 8px; 555 + } 556 + 557 + .resource-tag { 558 + font-size: 0.75rem; 559 + background-color: rgba(var(--button-bg-rgb), 0.1); 560 + padding: 3px 6px; 561 + border-radius: 4px; 562 + color: var(--text); 563 + opacity: 0.8; 564 + } 565 + 506 566 /* Enhanced quality stars */ 507 567 .resource-quality { 508 568 display: flex; ··· 563 623 opacity: 0.8; 564 624 } 565 625 566 - #new-toggle:focus { 626 + #new-toggle:focus, 627 + #score-toggle:focus { 567 628 border-color: var(--card-border); 568 629 } 569 630 ··· 604 665 gap: 1rem; 605 666 } 606 667 668 + .toggle-filters { 669 + flex-direction: column; 670 + align-items: center; 671 + gap: 1rem; 672 + } 673 + 607 674 .category-filter-dropdown, 608 675 .quality-filter, 609 - .new-filter { 676 + .new-filter, 677 + .score-filter { 610 678 width: 100%; 611 679 } 612 680
+93 -32
src/components/Resources/Resources.js
··· 1 - // src/components/Resources/Resources.jsx - Updated for multiple categories 1 + // src/components/Resources/Resources.jsx 2 2 import React, { useState, useEffect, useMemo } from 'react'; 3 3 import './Resources.css'; 4 4 import ResourceLoader from './ResourceLoader'; ··· 10 10 const [activeCategory, setActiveCategory] = useState('All'); 11 11 const [searchQuery, setSearchQuery] = useState(''); 12 12 const [showNewOnly, setShowNewOnly] = useState(false); 13 + const [showScoreImpactOnly, setShowScoreImpactOnly] = useState(false); 13 14 const [isLoading, setIsLoading] = useState(true); 14 15 15 16 // Category emojis mapping ··· 36 37 const preferences = JSON.parse(savedPreferences); 37 38 setActiveCategory(preferences.activeCategory || 'All'); 38 39 setShowNewOnly(preferences.showNewOnly || false); 40 + setShowScoreImpactOnly(preferences.showScoreImpactOnly || false); 39 41 } catch (error) { 40 42 console.error('Error loading preferences:', error); 41 43 } ··· 46 48 useEffect(() => { 47 49 const preferences = { 48 50 activeCategory, 49 - showNewOnly 51 + showNewOnly, 52 + showScoreImpactOnly 50 53 }; 51 54 localStorage.setItem('resourcesPreferences', JSON.stringify(preferences)); 52 - }, [activeCategory, showNewOnly]); 55 + }, [activeCategory, showNewOnly, showScoreImpactOnly]); 53 56 54 57 // Fetch resources from Supabase 55 58 useEffect(() => { 56 59 async function fetchResources() { 57 60 setIsLoading(true); 58 61 try { 59 - // First fetch all resources (removed subcategories) 62 + // First fetch all resources 60 63 const { data: resourcesData, error: resourcesError } = await supabase 61 64 .from('resources') 62 65 .select('*') ··· 78 81 throw categoriesError; 79 82 } 80 83 84 + // Then fetch the tags for each resource 85 + const { data: resourceTags, error: tagsError } = await supabase 86 + .from('resource_tags') 87 + .select(` 88 + resource_id, 89 + tag:tags(id, name) 90 + `); 91 + 92 + if (tagsError) { 93 + throw tagsError; 94 + } 95 + 81 96 // Group categories by resource_id 82 97 const categoriesByResource = {}; 83 98 resourceCategories.forEach(item => { ··· 91 106 }); 92 107 }); 93 108 109 + // Group tags by resource_id 110 + const tagsByResource = {}; 111 + resourceTags.forEach(item => { 112 + if (!tagsByResource[item.resource_id]) { 113 + tagsByResource[item.resource_id] = []; 114 + } 115 + tagsByResource[item.resource_id].push({ 116 + id: item.tag.id, 117 + name: item.tag.name 118 + }); 119 + }); 120 + 94 121 // Transform data to match the expected format 95 122 const formattedResources = resourcesData.map(resource => { 96 123 // Get categories for this resource 97 124 const resourceCategoryList = categoriesByResource[resource.id] || []; 125 + // Get tags for this resource 126 + const resourceTagList = tagsByResource[resource.id] || []; 98 127 99 128 return { 100 129 ...resource, ··· 102 131 category: resourceCategoryList.length > 0 ? resourceCategoryList[0].name : 'Misc', 103 132 // Store all categories 104 133 categories: resourceCategoryList, 134 + // Store all tags 135 + tags: resourceTagList, 105 136 emoji: resourceCategoryList.length > 0 ? resourceCategoryList[0].emoji : '🔮', 106 137 url: addUTMParameters(resource.url) 107 138 }; ··· 128 159 return daysDiff < 14; 129 160 }; 130 161 162 + // Check if a resource impacts score 163 + const impactsScore = (resource) => { 164 + if (!resource.tags) return false; 165 + return resource.tags.some(tag => tag.name.toLowerCase() === 'score'); 166 + }; 167 + 131 168 // Add UTM parameters to URLs 132 169 const addUTMParameters = (url) => { 133 170 const separator = url.includes('?') ? '&' : '?'; ··· 180 217 return resource.categories && resource.categories.some(cat => cat.name === categoryName); 181 218 }; 182 219 183 - // Filter resources based on active category, search query, and new filter 220 + // Filter resources based on active category, search query, and filters 184 221 const filteredResources = useMemo(() => { 185 222 return resources.filter(resource => { 186 223 // Filter by category ··· 195 232 // Filter by "new" status if the toggle is active 196 233 const newMatch = !showNewOnly || isNewResource(resource.created_at); 197 234 198 - return categoryMatch && searchMatch && newMatch; 235 + // Filter by "impacts score" status if the toggle is active 236 + const scoreMatch = !showScoreImpactOnly || impactsScore(resource); 237 + 238 + return categoryMatch && searchMatch && newMatch && scoreMatch; 199 239 }); 200 - }, [resources, activeCategory, searchQuery, showNewOnly]); 240 + }, [resources, activeCategory, searchQuery, showNewOnly, showScoreImpactOnly]); 201 241 202 242 // Get featured resources 203 243 const featuredResources = useMemo(() => { ··· 225 265 if (!grouped[category.name]) { 226 266 grouped[category.name] = []; 227 267 } 228 - // Avoid duplicates (could happen if we process the same resource multiple times) 268 + // Avoid duplicates 229 269 if (!grouped[category.name].some(r => r.id === resource.id)) { 230 270 grouped[category.name].push(resource); 231 271 } ··· 296 336 </div> 297 337 </header> 298 338 299 - <div className="filter-disclaimer-container"> 300 - 339 + <div className="filter-controls-container"> 301 340 {/* Improved Filter Bar */} 302 - <div className="resources-filters"> 303 - <div className="filter-options"> 304 - <div className="filter-dropdowns"> 341 + <div className="filter-bar"> 342 + <div className="filter-section"> 305 343 {/* Category filter dropdown */} 306 - <div className="category-filter-dropdown"> 344 + <div className="filter-dropdown"> 307 345 <label htmlFor="category-select" className="filter-label">Category:</label> 308 346 <select 309 347 id="category-select" ··· 319 357 </select> 320 358 </div> 321 359 322 - {/* New resources toggle */} 323 - <div className="new-filter"> 324 - <label className="toggle-label" htmlFor="new-toggle"> 325 - <input 326 - id="new-toggle" 327 - type="checkbox" 328 - checked={showNewOnly} 329 - onChange={() => setShowNewOnly(!showNewOnly)} 330 - aria-label="Show only recently added resources" 331 - /> 332 - <span className="toggle-text">Recently Added</span> 333 - </label> 360 + {/* Toggle filters */} 361 + <div className="toggle-filters"> 362 + {/* New resources toggle */} 363 + <div className="toggle-filter"> 364 + <label className="toggle-label" htmlFor="new-toggle"> 365 + <input 366 + id="new-toggle" 367 + type="checkbox" 368 + checked={showNewOnly} 369 + onChange={() => setShowNewOnly(!showNewOnly)} 370 + aria-label="Show only recently added resources" 371 + /> 372 + <span className="toggle-text">Recently Added</span> 373 + </label> 374 + </div> 375 + 376 + {/* Score impact toggle */} 377 + <div className="toggle-filter"> 378 + <label className="toggle-label" htmlFor="score-toggle"> 379 + <input 380 + id="score-toggle" 381 + type="checkbox" 382 + checked={showScoreImpactOnly} 383 + onChange={() => setShowScoreImpactOnly(!showScoreImpactOnly)} 384 + aria-label="Show only resources that impact score" 385 + /> 386 + <span className="toggle-text">Impacts Score</span> 387 + </label> 388 + </div> 334 389 </div> 335 390 </div> 336 391 </div> 337 - </div> 338 392 339 393 <div className="resources-disclaimer"> 340 394 <div className="disclaimer-icon">⚠️</div> 341 395 <p><strong>Disclaimer:</strong> These resources are not affiliated with cred.blue or Bluesky. Use them at your own risk and exercise caution when providing access to your data.</p> 342 396 </div> 343 - 344 397 </div> 345 398 346 399 {/* Loading indication */} ··· 359 412 key={`featured-${index}`} 360 413 resource={resource} 361 414 isNew={isNewResource(resource.created_at)} 415 + impactsScore={impactsScore(resource)} 362 416 /> 363 417 ))} 364 418 </div> ··· 381 435 key={`${category}-${index}`} 382 436 resource={resource} 383 437 isNew={isNewResource(resource.created_at)} 438 + impactsScore={impactsScore(resource)} 384 439 /> 385 440 ))} 386 441 </div> ··· 398 453 key={index} 399 454 resource={resource} 400 455 isNew={isNewResource(resource.created_at)} 456 + impactsScore={impactsScore(resource)} 401 457 /> 402 458 ))} 403 459 </div> ··· 416 472 }; 417 473 418 474 // ResourceCard component for displaying individual resources 419 - const ResourceCard = ({ resource, isNew }) => { 475 + const ResourceCard = ({ resource, isNew, impactsScore }) => { 420 476 return ( 421 477 <a 422 478 href={resource.url} ··· 427 483 <div className="resource-content"> 428 484 <div className="resource-header"> 429 485 <h3 className="resource-name">{resource.name}</h3> 430 - {isNew && ( 431 - <span className="new-badge">NEW</span> 432 - )} 486 + <div className="resource-badges"> 487 + {isNew && ( 488 + <span className="new-badge">NEW</span> 489 + )} 490 + {impactsScore && ( 491 + <span className="score-badge">SCORE</span> 492 + )} 493 + </div> 433 494 </div> 434 495 <p className="resource-description">{resource.description}</p> 435 496 <p className="resource-domain">{resource.domain}</p>
+8 -3
src/components/ScoringMethodology/ScoringMethodology.css
··· 25 25 margin-top: 30px; 26 26 margin-bottom: 20px; 27 27 } 28 - 28 + 29 29 .methodology-page h3 { 30 - margin-top: 25px; 31 - margin-bottom: 15px; 30 + margin-bottom: 10px; 31 + margin-top: 20px; 32 32 } 33 33 34 34 .methodology-page p { ··· 38 38 39 39 .increase-score-list li { 40 40 margin-bottom: 5px; 41 + } 42 + 43 + .related-pages-title h3 { 44 + margin-bottom: 10px; 45 + margin-top: 20px; 41 46 } 42 47 43 48 .methodology-page-chart .score-gauge {