Skip to content

feat(resources): add music packs tab with embedded player and file br…#48

Merged
creatorcluster merged 3 commits intocreatorcluster:mainfrom
Coder-soft:main
Mar 31, 2026
Merged

feat(resources): add music packs tab with embedded player and file br…#48
creatorcluster merged 3 commits intocreatorcluster:mainfrom
Coder-soft:main

Conversation

@Coder-soft
Copy link
Copy Markdown

@Coder-soft Coder-soft commented Mar 31, 2026

…owser

Introduce a new "Music Packs" tab to the resources hub featuring:

  • Embedded YouTube player with thumbnail previews
  • File browser interface for navigating categories and subcategories
  • Search functionality across categories and channels
  • Infinite scroll loading for better performance
  • Updated hero section to highlight the new music packs feature
  • Added Montserrat font for improved typography

The tab organizes music links from a JSON data source into a hierarchical structure, allowing users to browse and play music directly within the interface.

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced Music Packs showcase on the hero with curated external music resources and quick-access links
    • Added new Music Packs tab to the Resources Hub featuring browsable music links, category organization, search functionality, and infinite pagination
    • Enhanced application typography with updated font styling

…owser

Introduce a new "Music Packs" tab to the resources hub featuring:
- Embedded YouTube player with thumbnail previews
- File browser interface for navigating categories and subcategories
- Search functionality across categories and channels
- Infinite scroll loading for better performance
- Updated hero section to highlight the new music packs feature
- Added Montserrat font for improved typography

The tab organizes music links from a JSON data source into a hierarchical structure, allowing users to browse and play music directly within the interface.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@Coder-soft has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 12 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 12 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56fcbf1d-a543-4180-a8d2-e1c34c93bffc

📥 Commits

Reviewing files that changed from the base of the PR and between ece42a3 and b0adc67.

📒 Files selected for processing (3)
  • .github/workflows/export-resources.yml
  • .gitignore
  • src/components/resources/MusicPacksTab.tsx
📝 Walkthrough

Walkthrough

New Music Packs feature: Hero redesigned to showcase music tracks, a new MusicPacksTab component fetches and paginates music link data with sidebar filtering and search, ResourcesHub gains a "music-packs" tab, and Montserrat font was added to global styles.

Changes

Cohort / File(s) Summary
Hero Component Redesign
src/components/Hero.tsx
Replaced previous hero content with a two-column "Music Packs" layout, added showcaseTracks rendering of external "Open Link" cards, updated motion/parallax params and decoration styling, adjusted typography, and swapped CTAs to /resources?tab=music-packs and /resources. Added external-link and folder icons.
Music Packs Feature
src/components/resources/MusicPacksTab.tsx
New default-exported component that fetches /data/music-links.json, flattens categories→channels→messages→links, supports search and sidebar category/channel selection, paginates with IntersectionObserver (increments visibleCount), lazy-switches thumbnails→iframes per-card, and opens links in new tabs.
Resources Hub Integration
src/pages/ResourcesHub.tsx
Added 'music-packs' to activeTab options, imported MusicPacksTab and IconMusic, wired URL ?tab=music-packs handling, and added a "Music Packs" tab button (with "NEW" badge) plus conditional animated rendering.
Styling Updates
src/global.css
Added Montserrat font (bold) import alongside existing VT323 and minecraftia imports.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ResourcesHub as ResourcesHub
    participant MusicPacksTab as MusicPacksTab
    participant Loader as DataLoader
    participant Sidebar as Sidebar
    participant Grid as GridRenderer

    User->>ResourcesHub: Click "Music Packs" tab
    ResourcesHub->>MusicPacksTab: Mount & render
    MusicPacksTab->>Loader: Fetch /data/music-links.json
    Loader-->>MusicPacksTab: Return categories/channels/links
    MusicPacksTab->>Sidebar: Render categories/channels (counts)
    MusicPacksTab->>Grid: Render initial page (visibleCount)
    User->>Sidebar: Select category/channel or search
    Sidebar-->>MusicPacksTab: Update filter state
    MusicPacksTab->>Grid: Reset visibleCount, render filtered items
    User->>Grid: Scroll -> sentinel visible
    Grid->>Grid: IntersectionObserver increments visibleCount
    Grid-->>User: Render additional cards
    User->>Grid: Click card
    Grid-->>User: Open link in new tab (or activate iframe)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Hopping through packs with a twitch of my ear,

Tracks bloom like carrots, both far and near.
Filters and scrolls make a playful parade,
Hero to hub, a melody made. 🎶

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title describes the main feature (music packs tab with embedded player) but is truncated and doesn't fully convey all aspects, though it captures the primary change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

@musarrat950 is attempting to deploy a commit to the yamura3's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/components/Hero.tsx (2)

7-29: Hardcoded showcase data may become stale.

These YouTube links and thumbnails are hardcoded. If any video is removed or made private, the showcase will display broken content. Consider loading this from a shared data source or adding error handling for thumbnail failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Hero.tsx` around lines 7 - 29, The showcaseTracks array in
Hero.tsx contains hardcoded YouTube links and thumbnails which can become stale;
replace this static data with a fetch from a shared data source (e.g., an API or
centralized JSON module) or at minimum add runtime validation and fallback
handling where showcaseTracks is used (e.g., in the Hero component render logic)
to detect missing/403/404 thumbnails or unreachable video URLs and substitute a
default thumbnail and/or hide the broken item; update references to
showcaseTracks so the component consumes the fetched/validated list and ensure
errors are logged and non-blocking.

156-169: Hardcoded statistics may drift from actual data.

The "2,268 Links" statistic is hardcoded here but the actual count comes from /data/music-links.json in MusicPacksTab. These values will diverge as the data source changes.

Consider either:

  1. Fetching the actual count from the data source
  2. Removing the specific count in favor of a descriptive label
  3. Adding a comment noting this needs manual updates
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Hero.tsx` around lines 156 - 169, The hardcoded "2,268" in the
Hero component's motion.div should be replaced with a dynamic value or made
non-numeric; either import or accept the actual count from the same source as
MusicPacksTab (e.g., import musicLinks from '/data/music-links.json' and compute
musicLinks.length) or add a prop like linksCount to Hero and render that instead
(with a sensible fallback), or replace the number with a descriptive label;
update the <motion.div> block where the three stat tiles are rendered (the
current static <p> with "2,268") to use the chosen dynamic variable or fallback
and add a short inline comment if you leave the value static to indicate it must
be manually updated.
src/components/resources/MusicPacksTab.tsx (2)

102-121: Consider adding AbortController to prevent state updates after unmount.

The fetch call lacks cleanup handling. If the component unmounts before the fetch completes (e.g., user navigates away quickly), setMusicLinksData and setIsLoading will be called on an unmounted component.

♻️ Proposed fix with AbortController
 useEffect(() => {
+  const controller = new AbortController();
+
   const loadMusicLinks = async () => {
     try {
-      const response = await fetch('/data/music-links.json');
+      const response = await fetch('/data/music-links.json', { signal: controller.signal });
       const data: MusicLinksData = await response.json();
       setMusicLinksData(data);
       const firstCategory = data.categories?.[0]?.name || null;
       setSelectedCategory(firstCategory);
       if (firstCategory) {
         setExpandedCategories(new Set([firstCategory]));
       }
     } catch {
+      if (controller.signal.aborted) return;
       setMusicLinksData({ categories: [] });
     } finally {
+      if (!controller.signal.aborted) {
         setIsLoading(false);
+      }
     }
   };

   loadMusicLinks();
+  return () => controller.abort();
 }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` around lines 102 - 121, The fetch
in the useEffect's loadMusicLinks() can update state after unmount; create an
AbortController, pass controller.signal to fetch, and on cleanup call
controller.abort(); inside loadMusicLinks catch aborts (or check
controller.signal.aborted) and avoid calling setMusicLinksData,
setSelectedCategory, setExpandedCategories, or setIsLoading when the request was
aborted so you don't update state on an unmounted component.

396-404: Rendering iframes eagerly may cause performance issues with many visible items.

Each YouTube iframe loads immediately when rendered, even if it's below the viewport fold. With PAGE_SIZE = 24 and infinite scroll, users could have dozens of iframes loading simultaneously, consuming significant memory and bandwidth.

Consider lazy-loading iframes using IntersectionObserver per card, or showing thumbnails by default with a click-to-play pattern.

💡 Alternative: Click-to-play pattern
// Add state to track which items have been "activated"
const [activatedEmbeds, setActivatedEmbeds] = useState<Set<string>>(new Set());

// In the render:
{embedInfo.isYoutube && embedInfo.embedUrl ? (
  activatedEmbeds.has(item.id) ? (
    <iframe
      src={embedInfo.embedUrl}
      title={item.link}
      className="w-full h-full"
      loading="lazy"
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowFullScreen
    />
  ) : (
    <button
      onClick={(e) => {
        e.preventDefault();
        setActivatedEmbeds(prev => new Set(prev).add(item.id));
      }}
      className="w-full h-full relative"
    >
      <img
        src={embedInfo.thumbnailUrl}
        alt={item.link}
        className="w-full h-full object-cover"
        loading="lazy"
      />
      <div className="absolute inset-0 flex items-center justify-center bg-black/30">
        <IconPlayerPlay className="h-12 w-12 text-white" />
      </div>
    </button>
  )
) : /* ... */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` around lines 396 - 404, The
YouTube iframe is rendered eagerly (embedInfo.embedUrl within the MusicPacksTab
render) which can load many iframes at once; change rendering to lazy-activate
embeds by maintaining a state like activatedEmbeds (Set<string>) with setter
setActivatedEmbeds and, for each card (use item.id to identify), render a
thumbnail + play button (e.g., IconPlayerPlay) that on click adds item.id to
activatedEmbeds and replaces the thumbnail with the iframe (keeping
loading="lazy" and existing allow props); alternatively, implement an
IntersectionObserver per card to only mount the iframe when the card enters the
viewport—apply this change around the block that currently checks
embedInfo.isYoutube && embedInfo.embedUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/Hero.tsx`:
- Around line 7-29: The showcaseTracks array in Hero.tsx contains hardcoded
YouTube links and thumbnails which can become stale; replace this static data
with a fetch from a shared data source (e.g., an API or centralized JSON module)
or at minimum add runtime validation and fallback handling where showcaseTracks
is used (e.g., in the Hero component render logic) to detect missing/403/404
thumbnails or unreachable video URLs and substitute a default thumbnail and/or
hide the broken item; update references to showcaseTracks so the component
consumes the fetched/validated list and ensure errors are logged and
non-blocking.
- Around line 156-169: The hardcoded "2,268" in the Hero component's motion.div
should be replaced with a dynamic value or made non-numeric; either import or
accept the actual count from the same source as MusicPacksTab (e.g., import
musicLinks from '/data/music-links.json' and compute musicLinks.length) or add a
prop like linksCount to Hero and render that instead (with a sensible fallback),
or replace the number with a descriptive label; update the <motion.div> block
where the three stat tiles are rendered (the current static <p> with "2,268") to
use the chosen dynamic variable or fallback and add a short inline comment if
you leave the value static to indicate it must be manually updated.

In `@src/components/resources/MusicPacksTab.tsx`:
- Around line 102-121: The fetch in the useEffect's loadMusicLinks() can update
state after unmount; create an AbortController, pass controller.signal to fetch,
and on cleanup call controller.abort(); inside loadMusicLinks catch aborts (or
check controller.signal.aborted) and avoid calling setMusicLinksData,
setSelectedCategory, setExpandedCategories, or setIsLoading when the request was
aborted so you don't update state on an unmounted component.
- Around line 396-404: The YouTube iframe is rendered eagerly
(embedInfo.embedUrl within the MusicPacksTab render) which can load many iframes
at once; change rendering to lazy-activate embeds by maintaining a state like
activatedEmbeds (Set<string>) with setter setActivatedEmbeds and, for each card
(use item.id to identify), render a thumbnail + play button (e.g.,
IconPlayerPlay) that on click adds item.id to activatedEmbeds and replaces the
thumbnail with the iframe (keeping loading="lazy" and existing allow props);
alternatively, implement an IntersectionObserver per card to only mount the
iframe when the card enters the viewport—apply this change around the block that
currently checks embedInfo.isYoutube && embedInfo.embedUrl.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7e10a6cd-3139-46da-9e84-df4e6ef2396d

📥 Commits

Reviewing files that changed from the base of the PR and between 9d340e8 and 4ba8c60.

📒 Files selected for processing (5)
  • public/data/music-links.json
  • src/components/Hero.tsx
  • src/components/resources/MusicPacksTab.tsx
  • src/global.css
  • src/pages/ResourcesHub.tsx

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 31, 2026

Greptile Summary

This PR introduces a Music Packs tab to the Resources Hub, adding an embedded YouTube player with a file-browser sidebar, search, and infinite scroll backed by a 694 KB static JSON file of 2,268 music.youtube.com links. The architecture is well-structured — memoized filters, an intersection-observer infinite scroll, and clean tab integration in ResourcesHub.tsx — but there are two P1 issues in MusicPacksTab.tsx that should be addressed before merging:

  • All YouTube links render full iframes immediately. With PAGE_SIZE = 24, up to 24 YouTube player iframes load as users scroll (loading=\"lazy\" only defers until near-viewport, not until click). The thumbnailUrl fallback branch on line 405 was likely intended as a click-to-play facade but is dead codegetEmbedInfo always returns a non-null embedUrl alongside isYoutube: true.
  • <iframe> nested inside <a> is invalid HTML. Interactive elements cannot be descendants of an anchor per spec, breaking screen reader semantics and creating inconsistent cross-browser behavior.

Additional minor points:

  • The 2,268 link count in Hero.tsx is hardcoded and will silently drift when music-links.json is refreshed.
  • The search placeholder reads \"Search cato or sub cat...\"cato appears to be a typo for category.
  • fetch response is not checked for response.ok before parsing JSON.

Confidence Score: 3/5

Not safe to merge as-is; the all-iframes approach will actively degrade performance for most users on the primary new feature path, and the iframe-in-anchor structure breaks accessibility.

Two P1 issues on the primary user path: 24 simultaneous YouTube iframes cause real performance degradation as users scroll, and nesting iframes inside anchors is invalid HTML with real accessibility consequences. Neither is hypothetical — both will manifest for every user who opens the Music Packs tab.

src/components/resources/MusicPacksTab.tsx — specifically the card render block (lines 382–429) needs a click-to-play facade and the anchor-wrapping-iframe structure needs restructuring.

Important Files Changed

Filename Overview
src/components/resources/MusicPacksTab.tsx New component with solid architecture (infinite scroll, memoized filters, intersection observer), but two P1 issues: full YouTube iframes rendered for every card instead of a click-to-play facade (thumbnail fallback is unreachable dead code), and iframes nested inside anchor elements (invalid HTML/accessibility violation).
src/pages/ResourcesHub.tsx Cleanly adds music-packs tab alongside existing tabs; URL param reading and tab routing look correct.
src/components/Hero.tsx Hero section updated to showcase music packs with a preview card; hardcoded link count (2,268) will drift from the JSON if data is ever refreshed.
src/global.css Adds Montserrat font import; no logic changes.
public/data/music-links.json ~694 KB static JSON with 2,268 music.youtube.com links; correctly structured but is a sizable public asset downloaded in full when the tab is first opened.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User opens Music Packs tab"] --> B["useEffect: fetch /data/music-links.json\n(~694 KB)"]
    B -->|success| C["setMusicLinksData\nsetSelectedCategory = first category"]
    B -->|error| D["setMusicLinksData categories empty\nEmpty state shown"]
    C --> E["useMemo: allLinks\nFlattens category→channel→message→link\n(2,268 items)"]
    E --> F["useMemo: filteredLinks\nFilters by selectedCategory + selectedChannel"]
    F --> G["useMemo: displayedLinks\nSlice to visibleCount (PAGE_SIZE=24)"]
    G --> H["Render 24 cards"]
    H -->|isYoutube=true| I["⚠️ Render full YouTube iframe\nloading=lazy"]
    H -->|isYoutube=false| J["Show music icon placeholder\n(thumbnail img branch unreachable)"]
    I --> K["IntersectionObserver on loadMoreRef"]
    K -->|intersecting| L["setVisibleCount +24\nMore iframes load"]
    M["Sidebar search"] --> N["useMemo: filteredCategories\nFilters sidebar only — not the grid"]
    N --> O["User clicks category/channel"]
    O --> P["setSelectedCategory / setSelectedChannel\nsetVisibleCount reset to PAGE_SIZE"]
    P --> F
Loading

Reviews (1): Last reviewed commit: "feat(resources): add music packs tab wit..." | Re-trigger Greptile

Comment on lines +396 to +416
{embedInfo.isYoutube && embedInfo.embedUrl ? (
<iframe
src={embedInfo.embedUrl}
title={item.link}
className="w-full h-full"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
) : embedInfo.thumbnailUrl ? (
<img
src={embedInfo.thumbnailUrl}
alt={item.link}
className="w-full h-full object-cover"
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-muted-foreground">
<IconMusic className="h-10 w-10 opacity-50" />
</div>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 24 simultaneous active YouTube iframes degrade performance

Every card in the grid renders a full <iframe> pointing to https://www.youtube.com/embed/${videoId}. With PAGE_SIZE = 24, as users scroll through the list each YouTube player fully loads (loading="lazy" only defers the load until the iframe nears the viewport — it does not prevent loading on scroll). Once the user has scrolled past the first page, 24 complete YouTube player instances sit in the DOM, each pulling 100–500KB of player JS, API calls, and media.

The thumbnailUrl fallback (line 405) appears to have been intended as a click-to-play pattern, but getEmbedInfo always sets isYoutube: true and embedUrl is always truthy when a video ID is found, so the thumbnail <img> branch is dead code — it can never be reached. Every YouTube link renders an iframe.

The standard fix is a facade pattern: render the thumbnail image until the user clicks, then swap it for the iframe. This also fixes the dead-code branch.

Comment on lines +386 to +427
<motion.a
key={item.id}
href={item.link}
target="_blank"
rel="noopener noreferrer"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
className="group block rounded-lg border border-border bg-card/50 p-3 hover:border-cow-purple/50 transition-colors pixel-corners"
>
<div className="aspect-video rounded-md overflow-hidden border border-border/70 bg-muted/30 mb-3">
{embedInfo.isYoutube && embedInfo.embedUrl ? (
<iframe
src={embedInfo.embedUrl}
title={item.link}
className="w-full h-full"
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
) : embedInfo.thumbnailUrl ? (
<img
src={embedInfo.thumbnailUrl}
alt={item.link}
className="w-full h-full object-cover"
loading="lazy"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-muted-foreground">
<IconMusic className="h-10 w-10 opacity-50" />
</div>
)}
</div>

<div className="flex items-start justify-between gap-3">
<p className="text-sm text-muted-foreground break-all line-clamp-2">{item.link}</p>
<IconExternalLink className="h-4 w-4 text-cow-purple mt-0.5 flex-shrink-0" />
</div>

<div className="mt-2 text-xs text-muted-foreground/80">
{normalizeLabel(item.category)} / {normalizeLabel(item.channel)}
</div>
</motion.a>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Interactive <iframe> nested inside an <a> element

The HTML spec forbids interactive content (such as <iframe>) as a descendant of <a>. Most browsers parse the markup without crashing, but the behavior is inconsistent: screen readers may treat the entire card as a single link (hiding the embedded player from assistive tech), and keyboard navigation becomes ambiguous between the anchor and the iframe's own focus handling.

The fix is to convert motion.a to a motion.div and move the external-link navigation to a dedicated <a> button alongside the URL text at the bottom of the card.

Comment on lines +103 to +107
const loadMusicLinks = async () => {
try {
const response = await fetch('/data/music-links.json');
const data: MusicLinksData = await response.json();
setMusicLinksData(data);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 HTTP error responses are silently swallowed

response.json() is called without first checking response.ok. If the server returns a non-JSON error body (e.g. an HTML 404 or 502 page), the JSON parse will throw, which the outer catch block silences by setting { categories: [] }. The user sees an empty state with no indication of what went wrong.

Suggested change
const loadMusicLinks = async () => {
try {
const response = await fetch('/data/music-links.json');
const data: MusicLinksData = await response.json();
setMusicLinksData(data);
const response = await fetch('/data/music-links.json');
if (!response.ok) {
throw new Error(`Failed to load music links: ${response.status}`);
}
const data: MusicLinksData = await response.json();

Comment on lines +158 to +160
<p className="text-lg font-bold text-cow-purple">2,268</p>
<p className="text-xs text-muted-foreground">Links</p>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded link count will drift from actual data

The 2,268 figure is hardcoded in JSX and must be manually updated whenever music-links.json is refreshed. The JSON already exposes "total_links": 2268 — consider loading and displaying it dynamically, or at least adding a comment so maintainers know to update both files together.

<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search cato or sub cat..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Placeholder text contains a typo

"Search cato or sub cat..."cato appears to be a typo for category.

Suggested change
placeholder="Search cato or sub cat..."
placeholder="Search category or sub cat..."

Add interactive play button to YouTube thumbnails that loads the embed only when clicked, improving initial page performance. The embed state is reset when filters change.
- Add error state to display fetch failures with descriptive messages
- Implement retry functionality via button click
- Validate response data structure to catch malformed JSON
- Extract loading logic into reusable callback with proper cleanup
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/components/resources/MusicPacksTab.tsx (2)

197-203: Consider combining the two reset effects.

These two effects have identical dependencies and could be merged for clarity.

Proposed consolidation
- useEffect(() => {
-   setVisibleCount(PAGE_SIZE);
- }, [selectedCategory, selectedChannel]);
-
- useEffect(() => {
-   setActiveEmbeds(new Set());
- }, [selectedCategory, selectedChannel]);
+ useEffect(() => {
+   setVisibleCount(PAGE_SIZE);
+   setActiveEmbeds(new Set());
+ }, [selectedCategory, selectedChannel]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` around lines 197 - 203, The two
useEffect hooks that both depend on selectedCategory and selectedChannel should
be merged into one effect: inside a single useEffect with [selectedCategory,
selectedChannel] as dependencies, call setVisibleCount(PAGE_SIZE) and
setActiveEmbeds(new Set()) together to reset both pieces of state at once;
reference the existing symbols useEffect, setVisibleCount, PAGE_SIZE,
setActiveEmbeds, selectedCategory and selectedChannel when making the change.

408-415: Improve iframe accessibility with a descriptive title.

Using the raw URL as the title attribute provides poor screen reader experience. Consider a more descriptive title.

Proposed fix
                       <iframe
                         src={embedInfo.embedUrl}
-                        title={item.link}
+                        title="YouTube video player"
                         className="w-full h-full"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` around lines 408 - 415, The
iframe currently uses the raw URL as its title (title={item.link}); update it to
provide a descriptive, accessible title by using a meaningful field such as
embedInfo.title or item.title with a safe fallback (e.g. "Embedded media" plus a
short descriptor or the domain) so screen readers get context; locate the iframe
in MusicPacksTab.tsx and replace the title prop logic to prefer embedInfo.title,
then item.title, then a concise fallback like `Embedded media: ${new
URL(item.link).hostname}`.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/resources/MusicPacksTab.tsx`:
- Line 284: In MusicPacksTab, fix the typo in the search input placeholder by
changing the text value "Search cato or sub cat..." to the correct wording such
as "Search category or subcategory..." (locate the JSX element with
placeholder="Search cato or sub cat..." in the MusicPacksTab component and
update its placeholder string accordingly).
- Around line 74-90: The getEmbedInfo function should append the autoplay query
parameter to the YouTube embed URL so clicking the thumbnail starts playback
immediately; update the embedUrl returned by getEmbedInfo (which uses
extractYoutubeVideoId) to include autoplay=1 (e.g., add ?autoplay=1 or
&autoplay=1 depending on existing params) when isYoutube is true so the iframe
will auto-play on click.
- Around line 103-118: In loadMusicLinks inside the useEffect, check response.ok
before calling response.json(); if !response.ok throw or set an error state
(e.g., setLoadError or reuse setMusicLinksData with an error flag) so failures
(404/500) don't silently parse HTML; on catch setMusicLinksData({ categories: []
}) and setLoadError(error or true) and still call setIsLoading(false) in
finally, then update the component render to show the new error state alongside
the empty-state handling; reference functions/vars: loadMusicLinks,
setMusicLinksData, setIsLoading, setSelectedCategory, setExpandedCategories, and
the fetch('/data/music-links.json') call.

---

Nitpick comments:
In `@src/components/resources/MusicPacksTab.tsx`:
- Around line 197-203: The two useEffect hooks that both depend on
selectedCategory and selectedChannel should be merged into one effect: inside a
single useEffect with [selectedCategory, selectedChannel] as dependencies, call
setVisibleCount(PAGE_SIZE) and setActiveEmbeds(new Set()) together to reset both
pieces of state at once; reference the existing symbols useEffect,
setVisibleCount, PAGE_SIZE, setActiveEmbeds, selectedCategory and
selectedChannel when making the change.
- Around line 408-415: The iframe currently uses the raw URL as its title
(title={item.link}); update it to provide a descriptive, accessible title by
using a meaningful field such as embedInfo.title or item.title with a safe
fallback (e.g. "Embedded media" plus a short descriptor or the domain) so screen
readers get context; locate the iframe in MusicPacksTab.tsx and replace the
title prop logic to prefer embedInfo.title, then item.title, then a concise
fallback like `Embedded media: ${new URL(item.link).hostname}`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6936b85e-b1de-4d20-974d-3d1d8bfd2cb2

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba8c60 and ece42a3.

📒 Files selected for processing (1)
  • src/components/resources/MusicPacksTab.tsx

Comment on lines +74 to +90
const getEmbedInfo = (link: string) => {
const videoId = extractYoutubeVideoId(link);

if (videoId) {
return {
isYoutube: true,
thumbnailUrl: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`,
embedUrl: `https://www.youtube.com/embed/${videoId}`,
};
}

return {
isYoutube: false,
thumbnailUrl: null,
embedUrl: null,
};
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add autoplay=1 to embed URL for click-to-play behavior.

When users click the thumbnail play button, they expect the video to start playing immediately. Without the autoplay parameter, users must click twice—once on the thumbnail and again inside the iframe.

Proposed fix
   if (videoId) {
     return {
       isYoutube: true,
       thumbnailUrl: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`,
-      embedUrl: `https://www.youtube.com/embed/${videoId}`,
+      embedUrl: `https://www.youtube.com/embed/${videoId}?autoplay=1`,
     };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getEmbedInfo = (link: string) => {
const videoId = extractYoutubeVideoId(link);
if (videoId) {
return {
isYoutube: true,
thumbnailUrl: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`,
embedUrl: `https://www.youtube.com/embed/${videoId}`,
};
}
return {
isYoutube: false,
thumbnailUrl: null,
embedUrl: null,
};
};
const getEmbedInfo = (link: string) => {
const videoId = extractYoutubeVideoId(link);
if (videoId) {
return {
isYoutube: true,
thumbnailUrl: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`,
embedUrl: `https://www.youtube.com/embed/${videoId}?autoplay=1`,
};
}
return {
isYoutube: false,
thumbnailUrl: null,
embedUrl: null,
};
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` around lines 74 - 90, The
getEmbedInfo function should append the autoplay query parameter to the YouTube
embed URL so clicking the thumbnail starts playback immediately; update the
embedUrl returned by getEmbedInfo (which uses extractYoutubeVideoId) to include
autoplay=1 (e.g., add ?autoplay=1 or &autoplay=1 depending on existing params)
when isYoutube is true so the iframe will auto-play on click.

<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search cato or sub cat..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in placeholder text.

"cato" should be "category".

Proposed fix
-                  placeholder="Search cato or sub cat..."
+                  placeholder="Search category or channel..."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
placeholder="Search cato or sub cat..."
placeholder="Search category or channel..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/resources/MusicPacksTab.tsx` at line 284, In MusicPacksTab,
fix the typo in the search input placeholder by changing the text value "Search
cato or sub cat..." to the correct wording such as "Search category or
subcategory..." (locate the JSX element with placeholder="Search cato or sub
cat..." in the MusicPacksTab component and update its placeholder string
accordingly).

@creatorcluster creatorcluster merged commit 943560c into creatorcluster:main Mar 31, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants