v3: the dump page's "In collections" list only shows the owner's private badge — every card there said "public", which says nothing on a list of playlists holding the dump. PlaylistCard takes privateBadgeOnly, passed through PlaylistCardList, which hides the visibility badge except on a private playlist of the signed-in user's; the other lists of playlists keep both badges. 5 PlaylistCard tests pass (new: privateBadgeOnly), and tsc, eslint, doc:lint and deno fmt are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,12 @@ export interface PlaylistCardProps {
|
|||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
/** Hides the owner link and the unread dot. */
|
/** Hides the owner link and the unread dot. */
|
||||||
isOwner?: boolean;
|
isOwner?: boolean;
|
||||||
|
/**
|
||||||
|
* Used by dump pages to display "In collections" lists. Prevents the
|
||||||
|
* visibility badge from showing, except to the owner of a private playlist:
|
||||||
|
* public badges on every card there would say nothing.
|
||||||
|
*/
|
||||||
|
privateBadgeOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,12 +43,13 @@ export interface PlaylistCardProps {
|
|||||||
* device. The card is an `<li>`, for a `<ul>`.
|
* device. The card is an `<li>`, for a `<ul>`.
|
||||||
*/
|
*/
|
||||||
export function PlaylistCard(
|
export function PlaylistCard(
|
||||||
{ playlist, onDelete, isOwner }: PlaylistCardProps,
|
{ playlist, onDelete, isOwner, privateBadgeOnly }: PlaylistCardProps,
|
||||||
): React.JSX.Element {
|
): React.JSX.Element {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { mediaToken } = useAuth();
|
const { mediaToken } = useAuth();
|
||||||
const unread = !isOwner && isRecent(playlist.createdAt) &&
|
const unread = !isOwner && isRecent(playlist.createdAt) &&
|
||||||
!isPlaylistVisited(playlist.id);
|
!isPlaylistVisited(playlist.id);
|
||||||
|
const showBadge = !privateBadgeOnly || (!!isOwner && !playlist.isPublic);
|
||||||
|
|
||||||
function handleNavigate() {
|
function handleNavigate() {
|
||||||
markPlaylistVisited(playlist.id);
|
markPlaylistVisited(playlist.id);
|
||||||
@@ -85,15 +92,17 @@ export function PlaylistCard(
|
|||||||
<p className="playlist-card-description">{playlist.description}</p>
|
<p className="playlist-card-description">{playlist.description}</p>
|
||||||
)}
|
)}
|
||||||
<div className="playlist-card-meta">
|
<div className="playlist-card-meta">
|
||||||
<span
|
{showBadge && (
|
||||||
className={`playlist-badge${
|
<span
|
||||||
playlist.isPublic ? "" : " playlist-badge--private"
|
className={`playlist-badge${
|
||||||
}`}
|
playlist.isPublic ? "" : " playlist-badge--private"
|
||||||
>
|
}`}
|
||||||
{playlist.isPublic
|
>
|
||||||
? <Trans>public</Trans>
|
{playlist.isPublic
|
||||||
: <Trans>private</Trans>}
|
? <Trans>public</Trans>
|
||||||
</span>
|
: <Trans>private</Trans>}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{playlist.ownerUsername && !isOwner && (
|
{playlist.ownerUsername && !isOwner && (
|
||||||
<Link
|
<Link
|
||||||
to={`/users/${playlist.ownerUsername}`}
|
to={`/users/${playlist.ownerUsername}`}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export interface PlaylistCardListProps {
|
|||||||
playlists: Playlist[];
|
playlists: Playlist[];
|
||||||
/** Given, each card shows a delete button, which calls it. */
|
/** Given, each card shows a delete button, which calls it. */
|
||||||
onDelete?: (playlist: Playlist) => void;
|
onDelete?: (playlist: Playlist) => void;
|
||||||
|
/** Passed to each {@link PlaylistCard}. */
|
||||||
|
privateBadgeOnly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +22,7 @@ export interface PlaylistCardListProps {
|
|||||||
* its owner's when the playlist is the signed-in user's.
|
* its owner's when the playlist is the signed-in user's.
|
||||||
*/
|
*/
|
||||||
export function PlaylistCardList(
|
export function PlaylistCardList(
|
||||||
{ playlists, onDelete }: PlaylistCardListProps,
|
{ playlists, onDelete, privateBadgeOnly }: PlaylistCardListProps,
|
||||||
): React.JSX.Element {
|
): React.JSX.Element {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
return (
|
return (
|
||||||
@@ -31,6 +33,7 @@ export function PlaylistCardList(
|
|||||||
playlist={p}
|
playlist={p}
|
||||||
isOwner={!!user && user.id === p.userId}
|
isOwner={!!user && user.id === p.userId}
|
||||||
onDelete={onDelete && (() => onDelete(p))}
|
onDelete={onDelete && (() => onDelete(p))}
|
||||||
|
privateBadgeOnly={privateBadgeOnly}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -569,7 +569,7 @@ export function Dump(): React.JSX.Element {
|
|||||||
<h2 className="related-section-title">
|
<h2 className="related-section-title">
|
||||||
<Trans>In collections</Trans>
|
<Trans>In collections</Trans>
|
||||||
</h2>
|
</h2>
|
||||||
<PlaylistCardList playlists={collections} />
|
<PlaylistCardList playlists={collections} privateBadgeOnly />
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,27 @@ describe("PlaylistCard", () => {
|
|||||||
expect(again.container.querySelector(".unread-dot")).toBeNull();
|
expect(again.container.querySelector(".unread-dot")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("privateBadgeOnly shows only the owner's private badge", () => {
|
||||||
|
const cases = [
|
||||||
|
{ isPublic: true, isOwner: true, shown: false },
|
||||||
|
{ isPublic: false, isOwner: false, shown: false },
|
||||||
|
{ isPublic: false, isOwner: true, shown: true },
|
||||||
|
];
|
||||||
|
for (const { isPublic, isOwner, shown } of cases) {
|
||||||
|
const { container, unmount } = renderWithContexts(
|
||||||
|
<ul>
|
||||||
|
<PlaylistCard
|
||||||
|
playlist={makePlaylist({ isPublic })}
|
||||||
|
isOwner={isOwner}
|
||||||
|
privateBadgeOnly
|
||||||
|
/>
|
||||||
|
</ul>,
|
||||||
|
);
|
||||||
|
expect(container.querySelector(".playlist-badge") !== null).toBe(shown);
|
||||||
|
unmount();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("delete doesn't open the playlist", async () => {
|
test("delete doesn't open the playlist", async () => {
|
||||||
const onDelete = vi.fn();
|
const onDelete = vi.fn();
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|||||||
Reference in New Issue
Block a user