From 230285b91b23adc671e7dc86e284ec6a1f9cd5e3 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 21:08:36 +0300 Subject: [PATCH 1/7] landing page buttons checking all redirects to valid pages --- src/pages/home/LandingPage.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/LandingPage.tsx b/src/pages/home/LandingPage.tsx index 2ba138a2..a899f517 100644 --- a/src/pages/home/LandingPage.tsx +++ b/src/pages/home/LandingPage.tsx @@ -182,7 +182,7 @@ const LandingPage = () => { {/* ── Embedded Navbar ──────────────────────────────────────── */}
- {/* TODO: Replace with Rhythmify logo SVG */} + RHYTHMIFY @@ -203,7 +203,7 @@ const LandingPage = () => { Create account For Artists @@ -224,7 +224,7 @@ const LandingPage = () => { cta.variant === "primary" ? ( {cta.label} @@ -232,7 +232,7 @@ const LandingPage = () => { ) : ( {cta.label} @@ -374,7 +374,7 @@ const LandingPage = () => { your audience. What are you waiting for?

Find out more From 853d233e212b129e504733a6eec6fe0a87572785 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 21:09:02 +0300 Subject: [PATCH 2/7] replace welcome back with sensable title different logic --- src/pages/signin/PasswordLogin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/signin/PasswordLogin.tsx b/src/pages/signin/PasswordLogin.tsx index c6c2c85b..411167d3 100644 --- a/src/pages/signin/PasswordLogin.tsx +++ b/src/pages/signin/PasswordLogin.tsx @@ -39,7 +39,7 @@ export default function PasswordLogin({ email, onBack, onContinue, onForgotPassw > -

Welcome back!

+

Sign in or create an account

From eb180a33e30176db2a991d130d98b3ce82614a0e Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 21:18:27 +0300 Subject: [PATCH 3/7] container for all sets pages --- src/pages/you/albums/AlbumsForYouSlugPage.tsx | 2 +- src/pages/you/sets/TrendingByGenreSlugPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/you/albums/AlbumsForYouSlugPage.tsx b/src/pages/you/albums/AlbumsForYouSlugPage.tsx index a54dba3c..bdd74c02 100644 --- a/src/pages/you/albums/AlbumsForYouSlugPage.tsx +++ b/src/pages/you/albums/AlbumsForYouSlugPage.tsx @@ -281,7 +281,7 @@ function AlbumsForYouSlugPage() { return (
Date: Sun, 17 May 2026 21:33:40 +0300 Subject: [PATCH 4/7] sidebar purple badge --- src/components/discover/sidebar/ArtistToolsCard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/discover/sidebar/ArtistToolsCard.tsx b/src/components/discover/sidebar/ArtistToolsCard.tsx index 1c8d91ff..93dbf3fa 100644 --- a/src/components/discover/sidebar/ArtistToolsCard.tsx +++ b/src/components/discover/sidebar/ArtistToolsCard.tsx @@ -170,9 +170,9 @@ const styles = { `, ctaButton: ` w-full - bg-purple-700 hover:bg-purple-600 + bg-[#322C5B] text-white text-xs my-2 - rounded-md py-2 px-3 + rounded-sm py-4 px-4 flex items-center gap-2 transition-colors duration-200 `, @@ -303,7 +303,7 @@ const ArtistToolsCard = () => { data-test="button-artist-tools-cta" onClick={() => navigate("/premium")} > - + Unlock Artist tools {monthlyPrice !== null From 078111532d000470acc19d4a8943304828265462 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 23:03:37 +0300 Subject: [PATCH 5/7] track username bug --- src/components/UI/TrackItem.tsx | 35 +++++++++++++++- src/components/player/StickyPlayer.tsx | 41 +++++++++++++++++-- .../[trackSlug]/components/TrackHero.tsx | 36 +++++++++++++++- .../[trackSlug]/components/TrackSidebar.tsx | 36 +++++++++++++++- 4 files changed, 138 insertions(+), 10 deletions(-) diff --git a/src/components/UI/TrackItem.tsx b/src/components/UI/TrackItem.tsx index 8df898b1..7c66ea06 100644 --- a/src/components/UI/TrackItem.tsx +++ b/src/components/UI/TrackItem.tsx @@ -10,6 +10,7 @@ import SharePopup from "@/pages/[username]/[trackSlug]/components/SharePopup"; import AddToPlaylistModal from "@/components/playlist/AddToPlaylistModal"; import type { Track } from "@/types/track"; import CoverImage from "@/components/UI/CoverImage"; +import { getUsernameFromId } from "@/services/user.service"; interface TrackItemProps { id: string; @@ -95,8 +96,38 @@ const TrackItem: React.FC = ({ } }, [storeHasRepostInfo, storeReposted, reposted]); - const finalArtistSlug = - artistUsername || (artist ?? "").toLowerCase().replace(/\s+/g, "-"); + const [finalArtistSlug, setFinalArtistSlug] = useState( + artistUsername || (artist ?? "").toLowerCase().replace(/\s+/g, "-") + ); + + useEffect(() => { + let cancelled = false; + const fallbackSlug = artistUsername || (artist ?? "").toLowerCase().replace(/\s+/g, "-"); + setFinalArtistSlug(fallbackSlug); + + if (!artistId?.trim()) { + return () => { + cancelled = true; + }; + } + + (async () => { + try { + const username = await getUsernameFromId(artistId.trim()); + if (!cancelled) { + setFinalArtistSlug(username || fallbackSlug); + } + } catch { + if (!cancelled) { + setFinalArtistSlug(fallbackSlug); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [artistId, artistUsername, artist]); const trackPath = `/${finalArtistSlug}/${trackSlug ?? id}`; const trackForActions: Track = { diff --git a/src/components/player/StickyPlayer.tsx b/src/components/player/StickyPlayer.tsx index afb0900f..8f7b807e 100644 --- a/src/components/player/StickyPlayer.tsx +++ b/src/components/player/StickyPlayer.tsx @@ -1,7 +1,7 @@ // Import audioService so its module-level code runs once and sets up everything (NOT FINISHED) import "../../services/audioService"; import { seekAudio } from "../../services/audioService"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import { usePlayerStore } from "../../stores/player.store"; import { useLikesStore } from "../../stores/likes.store"; @@ -14,6 +14,7 @@ import { FaHeart, FaUserCheck, FaUserPlus } from "react-icons/fa"; import { MdQueueMusic } from "react-icons/md"; import FollowButton from "../UI/FollowButton"; import CoverImage from "@/components/UI/CoverImage"; +import { getUsernameFromId } from "@/services/user.service"; export default function StickyPlayer() { const { @@ -30,6 +31,38 @@ export default function StickyPlayer() { const { user } = useAuthStore(); const [queueOpen, setQueueOpen] = useState(false); + const [artistUsername, setArtistUsername] = useState(""); + + useEffect(() => { + if (!currentTrack) return; + let cancelled = false; + const fallbackUsername = (currentTrack.artistUsername ?? "unknown").trim() || "unknown"; + + setArtistUsername(fallbackUsername); + + if (!currentTrack.artistId?.trim()) { + return () => { + cancelled = true; + }; + } + + (async () => { + try { + const username = await getUsernameFromId(currentTrack.artistId?.trim() ?? "1"); + if (!cancelled) { + setArtistUsername(username || fallbackUsername); + } + } catch { + if (!cancelled) { + setArtistUsername(fallbackUsername); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [currentTrack?.artistId, currentTrack?.artistUsername]); const isLiked = currentTrack ? isTrackLiked(currentTrack.id) : false; const isFollowing = currentTrack @@ -69,7 +102,7 @@ export default function StickyPlayer() { {/* 7. Artwork + Track Info — far right */}
{currentTrack.title} diff --git a/src/pages/[username]/[trackSlug]/components/TrackHero.tsx b/src/pages/[username]/[trackSlug]/components/TrackHero.tsx index 26853893..1e306a4e 100644 --- a/src/pages/[username]/[trackSlug]/components/TrackHero.tsx +++ b/src/pages/[username]/[trackSlug]/components/TrackHero.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { FaPlay, FaPause, FaLock } from "react-icons/fa"; import TrackWaveform, { type TrackWaveformHandle } from "./TrackWaveform"; import type { Track } from "../../../../types/track"; - +import { getUsernameFromId } from "../../../../services/user.service"; import type { Comment } from "../../../../types/comment"; interface TrackHeroProps { @@ -28,6 +28,38 @@ export default function TrackHero({ const [activeComment, setActiveComment] = useState(null); const [showFloating, setShowFloating] = useState(false); const lastSecondRef = useRef(-1); + const [artistUsername, setArtistUsername] = useState(""); + + useEffect(() => { + let cancelled = false; + if (!track) return; + const fallbackUsername = (track.artistUsername ?? "unknown").trim() || "unknown"; + + setArtistUsername(fallbackUsername); + + if (!track.artistId?.trim()) { + return () => { + cancelled = true; + }; + } + + (async () => { + try { + const username = await getUsernameFromId(track.artistId?.trim() ?? "1"); + if (!cancelled) { + setArtistUsername(username || fallbackUsername); + } + } catch { + if (!cancelled) { + setArtistUsername(fallbackUsername); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [track?.artistId, track?.artistUsername]); const handlePlayPause = (startTime?: number) => { waveformRef.current?.playPause(); @@ -110,7 +142,7 @@ export default function TrackHero({
{track.artistName} diff --git a/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx b/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx index f14b0239..3981c619 100644 --- a/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx +++ b/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx @@ -1,8 +1,9 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Link, useParams } from "react-router-dom"; import * as Tooltip from "@radix-ui/react-tooltip"; import type { Track } from "../../../../types/track"; import type { MockUser } from "../../../../services/mocks/users"; +import { getUsernameFromId } from "../../../../services/user.service"; import FollowButton from "@/components/UI/FollowButton"; import CoverImage from "@/components/UI/CoverImage"; @@ -14,7 +15,38 @@ interface TrackSidebarProps { export default function TrackSidebar({ track, featuredArtists, relatedTracks = [] }: TrackSidebarProps) { const { username: urlUsername } = useParams<{ username: string }>(); - const artistUsername = track.artistUsername || urlUsername || "unknown"; + const [artistUsername, setArtistUsername] = useState(track.artistUsername || urlUsername || "unknown"); + + useEffect(() => { + let cancelled = false; + if (!track) return; + const fallbackUsername = (track.artistUsername ?? "unknown").trim() || "unknown"; + + setArtistUsername(fallbackUsername); + + if (!track.artistId?.trim()) { + return () => { + cancelled = true; + }; + } + + (async () => { + try { + const username = await getUsernameFromId(track.artistId?.trim() ?? "1"); + if (!cancelled) { + setArtistUsername(username || fallbackUsername); + } + } catch { + if (!cancelled) { + setArtistUsername(fallbackUsername); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [track?.artistId, track?.artistUsername]); return ( From 471b15412ca1bf0a5365e6ee4e4b4a134e29b29f Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 23:03:55 +0300 Subject: [PATCH 6/7] refixed all bugs in library --- src/components/UI/MixCard/MixCard.tsx | 14 +- .../UI/PlaylistCard/PlaylistCard.tsx | 54 ++++- src/components/library/useLibraryData.ts | 97 +++++--- src/pages/you/library/LibraryPage.tsx | 14 +- src/pages/you/sets/SetsPage.tsx | 212 +++++++++++------- 5 files changed, 259 insertions(+), 132 deletions(-) diff --git a/src/components/UI/MixCard/MixCard.tsx b/src/components/UI/MixCard/MixCard.tsx index 38d6e398..3069d794 100644 --- a/src/components/UI/MixCard/MixCard.tsx +++ b/src/components/UI/MixCard/MixCard.tsx @@ -154,12 +154,14 @@ export default function MixCard({
{/* Subtitle */} -

- {mix.track_count} tracks -

+ {mix.track_count > 0 && ( +

+ {mix.track_count} tracks +

+ )} {showPlaylistModal && ( void; isLikedOverride?: boolean; + hideOwner?: boolean; }; interface PlaylistCardProps { @@ -46,6 +48,40 @@ export default function PlaylistCard({ // Local State const [showPlaylistModal, setShowPlaylistModal] = useState(false); + const [dynamicUsername, setDynamicUsername] = useState(""); + const [dynamicDisplayName, setDynamicDisplayName] = useState(""); + + useEffect(() => { + let cancelled = false; + const fallbackDisplayName = item.ownerDisplayName ?? item.ownerUsername ?? item.owner; + const fallbackUsername = item.ownerUsername ?? item.ownerDisplayName ?? item.owner; + + if (!UUID_RE.test(item.owner) && !UUID_RE.test(item.ownerUsername ?? "")) { + setDynamicDisplayName(fallbackDisplayName); + setDynamicUsername(fallbackUsername); + return; + } + + const idToFetch = UUID_RE.test(item.owner) ? item.owner : (item.ownerUsername ?? item.owner); + setDynamicDisplayName(fallbackDisplayName); + setDynamicUsername(fallbackUsername); + + (async () => { + try { + const fetchedUser = await getUserById(idToFetch); + if (!cancelled) { + setDynamicDisplayName(fetchedUser.display_name || fallbackDisplayName); + setDynamicUsername(fetchedUser.username || fallbackUsername); + } + } catch { + // Fallbacks already set + } + })(); + + return () => { + cancelled = true; + }; + }, [item.ownerDisplayName, item.ownerUsername, item.owner]); // Derived State const liked = @@ -58,16 +94,14 @@ export default function PlaylistCard({ (currentTrack as any)?.context?.playlist_id === item.id; const ownerDisplay = - item.ownerDisplayName ?? - (UUID_RE.test(item.owner) - ? (user?.displayName ?? user?.username ?? item.owner) - : item.ownerUsername ?? item.owner); - + !UUID_RE.test(dynamicDisplayName) + ? dynamicDisplayName + : (user?.displayName ?? user?.username ?? dynamicDisplayName); // SoundCloud navigation format: /[username]/sets/[slug] const playlistPath = item.isAlbumView - ? `/${item.ownerUsername || item.owner}/album/${item.slug || item.id}` - : `/${item.ownerUsername || item.owner}/sets/${item.slug || item.id}`; + ? `/${dynamicUsername}/album/${item.slug || item.id}` + : `/${dynamicUsername}/sets/${item.slug || item.id}`; const handlePlayClick = (e: React.MouseEvent) => { e.stopPropagation(); @@ -141,7 +175,9 @@ export default function PlaylistCard({ )} {item.title}

-

{ownerDisplay}

+ {!item.hideOwner && ( +

{ownerDisplay}

+ )}
{showPlaylistModal && ( diff --git a/src/components/library/useLibraryData.ts b/src/components/library/useLibraryData.ts index 05261e51..ccbd6095 100644 --- a/src/components/library/useLibraryData.ts +++ b/src/components/library/useLibraryData.ts @@ -46,18 +46,30 @@ export function useLibraryData() { }, []); useEffect(() => { + let cancelled = false; getMyFollowing() .then((items) => Promise.all( - items.map((f) => - getUserById(f.id) - .then((profile) => mapFollowingToUser({ ...f, followers_count: profile.followers_count })) - .catch(() => mapFollowingToUser(f)), - ), - ), + items.map(async (f) => { + try { + const profile = await getUserById(f.id); + return mapFollowingToUser({ ...f, ...profile }); + } catch { + return mapFollowingToUser(f); + } + }) + ) ) - .then(setFollowingUsers) - .catch(() => setFollowingUsers([])); + .then((users) => { + if (!cancelled) setFollowingUsers(users); + }) + .catch(() => { + if (!cancelled) setFollowingUsers([]); + }); + + return () => { + cancelled = true; + }; }, []); useEffect(() => { @@ -65,7 +77,7 @@ export function useLibraryData() { getMyPlaylistsLib() .then((items) => setPlaylists( - items.map((p) => mapPlaylistToCard(p as unknown as Playlist, displayName || p.owner_user_id)), + items.map((p) => mapPlaylistToCard(p as unknown as Playlist, p.owner_user_id === user?.id ? displayName : p.owner_user_id)), ), ) .catch(() => setPlaylists([])); @@ -115,15 +127,57 @@ export function useLibraryData() { .map((e) => e.item as Track); const visiblePlaylists: PlaylistCardData[] = (() => { - const nonAlbumLiked = likedPlaylists.filter((p) => !p.isAlbumView); - if (playlistFilter === "Created") return playlists; - if (playlistFilter === "Liked") return nonAlbumLiked; + const mixIds = new Set(likedMixes.map((m) => m.mix_id ?? m.id)); + const isExcluded = (id: string) => mixIds.has(id); + + const radioMap = new Map(likedRadioTracks.map((r) => [r.playlistId, r])); + + const enhancePlaylistCard = (p: PlaylistCardData): PlaylistCardData => { + const r = radioMap.get(p.id); + if (r) { + return { + ...p, + title: p.title.replace(/\s+Radio$/i, ""), + hideOwner: true, + }; + } + return p; + }; + + const nonAlbumLiked = likedPlaylists.filter((p) => !p.isAlbumView && !isExcluded(p.id)).map(enhancePlaylistCard); + const nonAlbumCreated = playlists.filter((p) => !p.isAlbumView && !isExcluded(p.id)).map(enhancePlaylistCard); + const seen = new Set(); - return [...playlists, ...nonAlbumLiked].filter((p) => { + const basePlaylists = [...nonAlbumCreated, ...nonAlbumLiked].filter((p) => { + if (!p.title || p.title.trim() === "") return false; if (seen.has(p.id)) return false; seen.add(p.id); return true; }); + + const radioPlaylists: PlaylistCardData[] = likedRadioTracks + .map((r) => ({ + id: r.playlistId, + title: r.title.replace(/\s+Radio$/i, ""), + owner: user?.id ?? "", + coverUrl: r.coverImage, + isPrivate: false, + isLiked: true, + hideOwner: true, + })) + .filter((p) => { + if (seen.has(p.id)) return false; + seen.add(p.id); + return true; + }); + + if (playlistFilter === "Created") return nonAlbumCreated; + if (playlistFilter === "Liked") { + const likedWithTitles = nonAlbumLiked.filter((p) => p.title && p.title.trim() !== ""); + return [...likedWithTitles, ...radioPlaylists]; + } + + return [...basePlaylists, ...radioPlaylists]; })(); const displayedAlbums = (() => { @@ -136,23 +190,10 @@ export function useLibraryData() { seen.add(p.playlist_id); return true; }) - .map((p) => mapAlbumToCard(p, displayName || p.owner_user_id)); + .map((p) => mapAlbumToCard(p, p.owner_user_id === user?.id ? displayName : p.owner_user_id)); })(); - const displayedFollowing = (() => { - const followingIdSet = new Set(user?.following_ids ?? []); - const filtered = followingUsers.filter((u) => followingIdSet.has(u.username)); - const seenUsernames = new Set(filtered.map((u) => u.username)); - const synthetic: User[] = (user?.following_ids ?? []) - .filter((username) => !seenUsernames.has(username)) - .map((username, i) => ({ - id: String(-(i + 1)), - username, - displayName: username, - followers: 0, - })); - return [...filtered, ...synthetic]; - })(); + const displayedFollowing = followingUsers; return { recentEntries, diff --git a/src/pages/you/library/LibraryPage.tsx b/src/pages/you/library/LibraryPage.tsx index 5a0fe83d..c2d0a7b5 100644 --- a/src/pages/you/library/LibraryPage.tsx +++ b/src/pages/you/library/LibraryPage.tsx @@ -85,19 +85,7 @@ export default function LibraryPage() { /> - {likedRadioTracks.length > 0 && ( - - {likedRadioTracks.map((item) => ( - - ))} - - )} + {/* Playlists */} extends { daily_mix: infer D } ? D : never : never) { +function getMadeForYouCoverUrl( + mix: HomeData["made_for_you"] extends infer T + ? NonNullable extends { daily_mix: infer D } + ? D + : never + : never, +) { return mix.cover_url ?? mix.preview_track.cover_image ?? ""; } @@ -90,7 +96,8 @@ export default function SetsPage() { ]); setHomeData(home); seedFromHomeData(home); - const isSetPlaylist = (p: Playlist) => !p.is_album_view && p.subtype !== "album"; + const isSetPlaylist = (p: Playlist) => + !p.is_album_view && p.subtype !== "album"; setCreatedPlaylists(created.data.items.filter(isSetPlaylist)); setLikedPlaylists(liked.data.items.filter(isSetPlaylist)); } catch (err) { @@ -116,38 +123,18 @@ export default function SetsPage() { }, [fetchAll]); // Filter and Search Logic - const visiblePlaylists = useMemo(() => { - const match = (name: string) => - name.toLowerCase().includes(filterText.toLowerCase()); - - let list: Playlist[] = []; - if (activeFilter === "Created") { - list = createdPlaylists; - } else if (activeFilter === "Liked") { - list = likedPlaylists; - } else { - // "All" - Merge and remove duplicates by ID - const merged = [...createdPlaylists, ...likedPlaylists]; - const seen = new Set(); - list = merged.filter((p) => { - if (seen.has(p.playlist_id)) return false; - seen.add(p.playlist_id); - return true; - }); - } - return list.filter((p) => match(p.name)); - }, [activeFilter, filterText, createdPlaylists, likedPlaylists]); // Helper to map Playlist API data to PlaylistCard props - const mapToCardData = (p: Playlist): PlaylistCardData => ({ + const mapToCardData = (p: Playlist | any): PlaylistCardData => ({ id: p.playlist_id, title: p.name, owner: p.owner_user_id, - ownerUsername: - user && p.owner_user_id === user.id ? user.username : undefined, + ownerDisplayName: p.ownerDisplayName, + ownerUsername: p.ownerUsername || (user && p.owner_user_id === user.id ? user.username : undefined), coverUrl: p.cover_image || null, isPrivate: !p.is_public, - isLiked: likedPlaylists.some((lp) => lp.playlist_id === p.playlist_id), + isLiked: p.isLikedOverride ?? likedPlaylists.some((lp) => lp.playlist_id === p.playlist_id), + hideOwner: p.hideOwner, }); const madeForYouItems: MadeForYouItem[] = useMemo(() => { @@ -164,7 +151,9 @@ export default function SetsPage() { madeKind: "daily", badgeWords: ["DAILY", "DROPS"], badgeBg: "#1a237e", - previewTrack: mapDiscoveryTrack(homeData.made_for_you.daily_mix.preview_track), + previewTrack: mapDiscoveryTrack( + homeData.made_for_you.daily_mix.preview_track, + ), }); } @@ -177,7 +166,9 @@ export default function SetsPage() { madeKind: "weekly", badgeWords: ["WEEKLY", "WAVE"], badgeBg: "#1b5e20", - previewTrack: mapDiscoveryTrack(homeData.made_for_you.weekly_mix.preview_track), + previewTrack: mapDiscoveryTrack( + homeData.made_for_you.weekly_mix.preview_track, + ), }); } @@ -211,23 +202,92 @@ export default function SetsPage() { return [...mixedForYou, ...madeForYouItems]; }, [homeData, madeForYouItems]); + const visiblePlaylists = useMemo(() => { + const match = (name: string) => + name.toLowerCase().includes(filterText.toLowerCase()); + + const mixIds = new Set(mixItems.map((m) => m.id)); + const isExcluded = (id: string) => mixIds.has(id); + + const radioMap = new Map(likedRadioTracks.map((r) => [r.playlistId, r])); + + const enhanceWithRadio = (p: Playlist): Playlist | any => { + const r = radioMap.get(p.playlist_id); + if (r) { + return { + ...p, + name: p.name.replace(/\s+Radio$/i, ""), + cover_image: r.coverImage ?? p.cover_image, + isLikedOverride: true, + hideOwner: true, + }; + } + return p; + }; + + let list: Playlist[] = []; + if (activeFilter === "Created") { + list = createdPlaylists.filter( + (p) => !isExcluded(p.playlist_id) && p.name && p.name.trim() !== "", + ).map(enhanceWithRadio); + } else if (activeFilter === "Liked") { + list = likedPlaylists.filter( + (p) => !isExcluded(p.playlist_id) && p.name && p.name.trim() !== "", + ).map(enhanceWithRadio); + } else { + // "All" - Merge and remove duplicates by ID + const merged = [...createdPlaylists, ...likedPlaylists]; + const seen = new Set(); + list = merged.filter((p) => { + if (!p.name || p.name.trim() === "") return false; + if (isExcluded(p.playlist_id)) return false; + if (seen.has(p.playlist_id)) return false; + seen.add(p.playlist_id); + return true; + }).map(enhanceWithRadio); + } + + const filteredList = list.filter((p) => match(p.name)); + + const radioPlaylists: any[] = likedRadioTracks.map((r) => ({ + playlist_id: r.playlistId, + name: r.title.replace(/\s+Radio$/i, ""), + owner_user_id: user?.id ?? "", + is_public: true, + cover_image: r.coverImage, + subtype: "playlist", + track_count: 0, + like_count: 1, + created_at: new Date().toISOString(), + is_album_view: false, + isLikedOverride: true, + hideOwner: true, + })); + + // Only add radioPlaylists that are not already in the main list + const existingIds = new Set(filteredList.map(p => p.playlist_id)); + const filteredRadio = radioPlaylists.filter((p) => match(p.name) && !existingIds.has(p.playlist_id)); + + if (activeFilter === "Created") return filteredList; + return [...filteredList, ...filteredRadio]; + }, [ + activeFilter, + filterText, + createdPlaylists, + likedPlaylists, + mixItems, + likedRadioTracks, + user, + ]); + const visibleMixItems = useMemo(() => { if (activeFilter === "Created") return []; - return mixItems.filter((mix) => mix.title.toLowerCase().includes(filterText.toLowerCase())); + return mixItems.filter((mix) => + mix.title.toLowerCase().includes(filterText.toLowerCase()), + ); }, [activeFilter, filterText, mixItems]); - const visibleRadioItems = useMemo(() => { - if (activeFilter === "Created") return []; - const query = filterText.toLowerCase(); - return likedRadioTracks.filter((item) => { - if (!query) return true; - return ( - item.track.title.toLowerCase().includes(query) || - item.title.toLowerCase().includes(query) || - item.description.toLowerCase().includes(query) - ); - }); - }, [activeFilter, filterText, likedRadioTracks]); + const isMadeForYouItem = (item: SetsCarouselItem): item is MadeForYouItem => item.madeKind === "daily" || item.madeKind === "weekly"; @@ -257,7 +317,6 @@ export default function SetsPage() { const hasVisibleContent = visiblePlaylists.length > 0 || - visibleRadioItems.length > 0 || visibleMixItems.length > 0 || genreItems.length > 0; @@ -301,43 +360,41 @@ export default function SetsPage() { ) : ( ), )} - {visibleRadioItems.map((item) => ( - - ))} + {genreItems.map((genre) => ( ) : ( -
+

{filterText ? "No playlists match your search." From 42b402d162b5a3f2e17a1afa45d0ba82258b0dee Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sun, 17 May 2026 23:35:51 +0300 Subject: [PATCH 7/7] type error --- src/components/library/useLibraryData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/library/useLibraryData.ts b/src/components/library/useLibraryData.ts index ccbd6095..fa6e5a2f 100644 --- a/src/components/library/useLibraryData.ts +++ b/src/components/library/useLibraryData.ts @@ -53,7 +53,7 @@ export function useLibraryData() { items.map(async (f) => { try { const profile = await getUserById(f.id); - return mapFollowingToUser({ ...f, ...profile }); + return mapFollowingToUser({ ...f, ...profile, username: profile.username || f.username }); } catch { return mapFollowingToUser(f); }