Skip to content

Commit 35ef4a8

Browse files
committed
fix: correct playlist counts and avatar skeleton
1 parent 83fc64e commit 35ef4a8

6 files changed

Lines changed: 77 additions & 12 deletions

File tree

‎apps/web/src/components/channel-avatar.tsx‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { UserRound } from "lucide-react";
22
import { useState } from "react";
3+
import { Skeleton } from "./skeleton";
34

45
type Props = {
56
src: string;
@@ -37,13 +38,7 @@ export function ChannelAvatar({ src, name, className = "w-8 h-8" }: Props) {
3738
data-avatar-state={state}
3839
aria-busy={loading}
3940
>
40-
{loading && (
41-
<span
42-
className="absolute inset-0 animate-pulse rounded-full bg-fg/10"
43-
data-avatar-skeleton
44-
aria-hidden="true"
45-
/>
46-
)}
41+
{loading && <Skeleton className="absolute inset-0 rounded-full" data-avatar-skeleton />}
4742
{!loading &&
4843
(name.trim() ? (
4944
<span className="text-base leading-none">{getInitial(name)}</span>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { HTMLAttributes } from "react";
2+
3+
export function Skeleton({ className = "", ...props }: HTMLAttributes<HTMLDivElement>) {
4+
return (
5+
<div
6+
{...props}
7+
className={`animate-pulse rounded-md bg-fg/15 ${className}`}
8+
aria-hidden="true"
9+
/>
10+
);
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { PlaylistItem, PlaylistVideoItem } from "../types/user";
2+
3+
export function filterPlaylistSummaries(
4+
playlists: PlaylistItem[],
5+
filter: (videos: PlaylistVideoItem[]) => PlaylistVideoItem[],
6+
): PlaylistItem[] {
7+
return playlists.map((playlist) => {
8+
const videos = playlist.videos ? filter(playlist.videos) : undefined;
9+
return {
10+
...playlist,
11+
videos,
12+
videoCount: playlist.videoCount ?? videos?.length,
13+
};
14+
});
15+
}

‎apps/web/src/routes/playlists.tsx‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useFavoriteStreams } from "../hooks/use-favorite-streams";
1313
import { usePlaylists } from "../hooks/use-playlists";
1414
import { useSavedPlaylists } from "../hooks/use-saved-playlists";
1515
import { useWatchLaterStreams } from "../hooks/use-watch-later-streams";
16+
import { filterPlaylistSummaries } from "../lib/playlist-summary";
1617
import { m } from "../paraglide/messages.js";
1718
import type { SavedPlaylistItem } from "../types/playlist";
1819

@@ -26,11 +27,7 @@ function PlaylistsPage() {
2627
const visibleFavorites = useMemo(() => filter(favorites.videos), [favorites.videos, filter]);
2728
const visibleWatchLater = useMemo(() => filter(watchLater.videos), [filter, watchLater.videos]);
2829
const visiblePlaylists = useMemo(
29-
() =>
30-
playlists.map((playlist) => {
31-
const videos = filter(playlist.videos ?? []);
32-
return { ...playlist, videos, videoCount: videos.length };
33-
}),
30+
() => filterPlaylistSummaries(playlists, filter),
3431
[filter, playlists],
3532
);
3633
const saved = savedPlaylists.items.filter((playlist) => !isPlaylistBlocked(playlist));

‎apps/web/tests/channel-avatar.test.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe("channel avatar", () => {
1111
expect(html).toContain('data-avatar-state="loading"');
1212
expect(html).toContain("data-avatar-skeleton");
1313
expect(html).toContain("animate-pulse");
14+
expect(html).toContain("bg-fg/15");
1415
expect(html).toContain('loading="lazy"');
1516
});
1617

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { filterPlaylistSummaries } from "../src/lib/playlist-summary";
3+
import type { PlaylistItem } from "../src/types/user";
4+
5+
describe("playlist summaries", () => {
6+
test("keeps the server count when the list response omits videos", () => {
7+
const playlist: PlaylistItem = {
8+
id: "playlist-1",
9+
name: "one piece",
10+
description: "",
11+
videoCount: 82,
12+
createdAt: 1,
13+
};
14+
15+
const result = filterPlaylistSummaries([playlist], (videos) => videos);
16+
17+
expect(result[0]?.videoCount).toBe(82);
18+
expect(result[0]?.videos).toBeUndefined();
19+
});
20+
21+
test("falls back to the visible count when the server count is absent", () => {
22+
const playlist: PlaylistItem = {
23+
id: "playlist-1",
24+
name: "one piece",
25+
description: "",
26+
videos: [
27+
{
28+
id: "video-1",
29+
url: "https://www.youtube.com/watch?v=video1",
30+
title: "Video",
31+
thumbnail: "thumbnail",
32+
duration: 1,
33+
position: 0,
34+
watchPosition: 0,
35+
watched: false,
36+
progressUpdatedAt: 0,
37+
},
38+
],
39+
createdAt: 1,
40+
};
41+
42+
const result = filterPlaylistSummaries([playlist], (videos) => videos);
43+
44+
expect(result[0]?.videoCount).toBe(1);
45+
});
46+
});

0 commit comments

Comments
 (0)