From fd95e24f0245cb65f09e4b8702b76263c46a199c Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 11:55:46 +0300 Subject: [PATCH 01/10] sidebar blank space handled --- .../UI/TrackListSection/TrackListSection.tsx | 5 -- .../discover/sidebar/DiscoverSideBar.tsx | 66 +++++++------------ 2 files changed, 25 insertions(+), 46 deletions(-) diff --git a/src/components/UI/TrackListSection/TrackListSection.tsx b/src/components/UI/TrackListSection/TrackListSection.tsx index bc5d8b57..c4640442 100644 --- a/src/components/UI/TrackListSection/TrackListSection.tsx +++ b/src/components/UI/TrackListSection/TrackListSection.tsx @@ -27,11 +27,6 @@ const TrackListSection = ({ }: TrackListSectionProps) => { const navigate = useNavigate(); - // ✅ Early return if no children - if (!children) { - return null; - } - return (
{/* Header */} diff --git a/src/components/discover/sidebar/DiscoverSideBar.tsx b/src/components/discover/sidebar/DiscoverSideBar.tsx index ac3848f5..0f58ab53 100644 --- a/src/components/discover/sidebar/DiscoverSideBar.tsx +++ b/src/components/discover/sidebar/DiscoverSideBar.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useLayoutEffect, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import ArtistToolsCard from "./ArtistToolsCard"; import TrackItem from "@/components/UI/TrackItem"; import TrackListSection from "@/components/UI/TrackListSection/TrackListSection"; @@ -19,26 +19,6 @@ import type { Track } from "@/types/track"; const DiscoverSidebar = () => { const asideRef = useRef(null); - useLayoutEffect(() => { - const aside = asideRef.current; - if (!aside) return; - - const update = () => { - aside.style.top = `${window.innerHeight - aside.offsetHeight}px`; - }; - - update(); - - const ro = new ResizeObserver(update); - ro.observe(aside); - window.addEventListener("resize", update, { passive: true }); - - return () => { - ro.disconnect(); - window.removeEventListener("resize", update); - }; - }, []); - const [suggestedArtists, setSuggestedArtists] = useState< ReturnType[] >([]); @@ -101,7 +81,7 @@ const DiscoverSidebar = () => { From 9208e55df6a5f6ebefd676ca519c516375753537 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 11:56:02 +0300 Subject: [PATCH 02/10] renectly played local storage error --- src/stores/history.store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/history.store.ts b/src/stores/history.store.ts index 0bd089a8..3d868dd9 100644 --- a/src/stores/history.store.ts +++ b/src/stores/history.store.ts @@ -1,6 +1,7 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { writeListeningHistory } from "@/services/api/discover.service"; +import { createUserScopedStorage } from "@/stores/userScopedStorage"; import type { Track } from "@/types/track"; import type { Station } from "@/types/station"; import type { PersonalMix } from "@/services/api/discover.service"; @@ -174,6 +175,6 @@ export const useHistoryStore = create()( .entries.filter((e) => e.type === "station") .map((e) => (e as { type: "station"; item: Station; playedAt: string }).item), }), - { name: "rythmify-history", version: 1 }, + { name: "rythmify-history", version: 1, storage: createUserScopedStorage("history") }, ), ); From 02c7ec3a523ada3c85488e24dbb064f2e560f64d Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 12:21:24 +0300 Subject: [PATCH 03/10] default image in almost everything --- src/components/UI/StationCard/StationCard.tsx | 43 +++++++------------ .../[trackSlug]/components/TrackSidebar.tsx | 5 ++- src/pages/home/LandingPage.tsx | 17 +++----- src/pages/you/stations/StationSlugPage.tsx | 2 +- 4 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/components/UI/StationCard/StationCard.tsx b/src/components/UI/StationCard/StationCard.tsx index 1ab9fd79..c188968e 100644 --- a/src/components/UI/StationCard/StationCard.tsx +++ b/src/components/UI/StationCard/StationCard.tsx @@ -8,6 +8,7 @@ import { useNavigate } from "react-router-dom"; import CardOverlay, { AddToPlaylistIcon, } from "@/components/UI/CardOverlay/CardOverlay"; +import CoverImage from "@/components/UI/CoverImage"; import AddToPlaylistModal from "@/components/playlist/AddToPlaylistModal"; import { getStationTracks } from "@/services/api/playlist/playlist.service"; // ─── Color Schemes ──────────────────────────────────────── @@ -147,41 +148,29 @@ export default function StationCard({ {/* Three artist circles */} {/* Circle 1 — top-left, small */}
- {artists[0]?.avatarUrl ? ( - {artists[0].displayName - ) : ( -
- )} +
{/* Circle 2 — center, largest */}
- {artists[1]?.avatarUrl ? ( - {artists[1].displayName - ) : ( -
- )} +
{/* Circle 3 — bottom-right, small */}
- {artists[2]?.avatarUrl ? ( - {artists[2].displayName - ) : ( -
- )} +
{/* SoundCloud logo — top-right */} diff --git a/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx b/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx index a2c90926..fa627291 100644 --- a/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx +++ b/src/pages/[username]/[trackSlug]/components/TrackSidebar.tsx @@ -4,6 +4,7 @@ import * as Tooltip from "@radix-ui/react-tooltip"; import type { Track } from "../../../../types/track"; import type { MockUser } from "../../../../services/mocks/users"; import FollowButton from "@/components/UI/FollowButton"; +import CoverImage from "@/components/UI/CoverImage"; interface TrackSidebarProps { track: Track; @@ -71,8 +72,8 @@ export default function TrackSidebar({ track, featuredArtists, relatedTracks = [ {relatedTracks.slice(0, 3).map((t) => (
- {t.title} diff --git a/src/pages/home/LandingPage.tsx b/src/pages/home/LandingPage.tsx index e53e9587..2ba138a2 100644 --- a/src/pages/home/LandingPage.tsx +++ b/src/pages/home/LandingPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from "react"; import { Link, useNavigate } from "react-router-dom"; import type { Track } from "@/types/track"; +import CoverImage from "@/components/UI/CoverImage"; import GuestPageFooter from "@/components/Upload/GuestPageFooter"; // ── Types ────────────────────────────────────────────────────────────── interface SlideData { @@ -62,17 +63,11 @@ const TrendingTrackCard = ({ track }: { track: Track }) => ( className="group block" >
- {track.coverUrl ? ( - {track.title} - ) : ( -
- {/* Placeholder */} -
- )} + {/* Hover overlay with action icons */}
diff --git a/src/pages/you/stations/StationSlugPage.tsx b/src/pages/you/stations/StationSlugPage.tsx index 99a3bf9c..f0ef9657 100644 --- a/src/pages/you/stations/StationSlugPage.tsx +++ b/src/pages/you/stations/StationSlugPage.tsx @@ -169,7 +169,7 @@ export default function StationSlugPage() { return (
Date: Sat, 2 May 2026 13:28:54 +0300 Subject: [PATCH 04/10] playlist message , playlist name bug ashan khater sajaa --- src/services/api/search/searchMappers.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/api/search/searchMappers.ts b/src/services/api/search/searchMappers.ts index 40729632..ab419875 100644 --- a/src/services/api/search/searchMappers.ts +++ b/src/services/api/search/searchMappers.ts @@ -75,9 +75,10 @@ export function mapTrack(t: any): Track { // created_at, score, preview_tracks: Track[] } export function mapPlaylist(pl: any): Playlist { + const id = pl.id ?? pl.playlist_id ?? ""; return { - id: pl.id, - title: pl.title ?? "Untitled Playlist", + id, + title: pl.title ?? pl.name ?? "Untitled Playlist", creatorName: pl.owner?.display_name ?? pl.creatorName ?? "", creatorUsername: pl.owner?.username ?? pl.owner_username ?? pl.creatorUsername ?? pl.username ?? "unknown", coverUrl: pl.cover_image ?? pl.coverUrl ?? "", @@ -85,7 +86,7 @@ export function mapPlaylist(pl: any): Playlist { trackCount: pl.track_count ?? pl.trackCount ?? 0, likeCount: pl.like_count ?? pl.likeCount ?? 0, repostCount: pl.repost_count ?? pl.repostCount ?? 0, - playlistSlug: pl.playlist_slug ?? pl.playlistSlug ?? String(pl.id), + playlistSlug: pl.playlist_slug ?? pl.slug ?? pl.playlistSlug ?? String(id), isPrivate: pl.is_private ?? pl.isPrivate ?? false, tracks: (pl.preview_tracks ?? pl.tracks ?? []).map(mapTrack), }; From 2cdd47509e7454fdf1d0bab4a1601e6caa9916b2 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 13:29:14 +0300 Subject: [PATCH 05/10] tracks name bug in track tab in profile ashan khater al mo3eed --- src/pages/[username]/tracks/TracksPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[username]/tracks/TracksPage.tsx b/src/pages/[username]/tracks/TracksPage.tsx index a796c741..7c6d8f75 100644 --- a/src/pages/[username]/tracks/TracksPage.tsx +++ b/src/pages/[username]/tracks/TracksPage.tsx @@ -120,7 +120,7 @@ export default function TracksPage() { tracks.map((track) => ( navigator.clipboard.writeText( `${window.location.origin}/${track.artistUsername}/${track.id}`, From 80ba649b85a0fc9eef189e2576ae7700f017ee3b Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 13:49:44 +0300 Subject: [PATCH 06/10] going to tracks page from liked tracks in sidebars --- src/components/Profile/ProfileSideBar/ProfileSideBar.tsx | 2 ++ src/components/UI/TrackItem.tsx | 4 ++-- src/pages/[username]/UsernamePage.tsx | 2 ++ src/services/api/discover.mapper.ts | 7 ++++--- src/services/api/discover.service.ts | 4 +++- src/services/user.service.ts | 2 ++ 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/Profile/ProfileSideBar/ProfileSideBar.tsx b/src/components/Profile/ProfileSideBar/ProfileSideBar.tsx index 878a40eb..ddefbe79 100644 --- a/src/components/Profile/ProfileSideBar/ProfileSideBar.tsx +++ b/src/components/Profile/ProfileSideBar/ProfileSideBar.tsx @@ -33,6 +33,8 @@ interface LikedTrack { comments?: number; audioUrl?: string; artistId?: string; + artistUsername?: string; + trackSlug?: string; } interface ProfileSideBarProps { diff --git a/src/components/UI/TrackItem.tsx b/src/components/UI/TrackItem.tsx index 761f2f6f..1abbb513 100644 --- a/src/components/UI/TrackItem.tsx +++ b/src/components/UI/TrackItem.tsx @@ -81,7 +81,7 @@ const TrackItem: React.FC = ({ const finalArtistSlug = artistUsername || (artist ?? "").toLowerCase().replace(/\s+/g, "-"); - const trackPath = `/discover/personalised/${trackSlug ?? ""}:${id}`; + const trackPath = `/${finalArtistSlug}/${trackSlug ?? id}`; const trackForActions: Track = { id, title, @@ -183,7 +183,7 @@ const TrackItem: React.FC = ({ className="w-12 h-12 cursor-pointer flex-shrink-0 bg-border rounded overflow-hidden relative" onMouseEnter={() => setCoverHovered(true)} onMouseLeave={() => setCoverHovered(false)} - onClick={() => navigate(`/${finalArtistSlug}/${id}`)} + onClick={() => navigate(`/${finalArtistSlug}/${trackSlug ?? id}`)} > Date: Sat, 2 May 2026 13:57:54 +0300 Subject: [PATCH 07/10] cursor stays in message box ashan khater al mo3eed bardo --- src/components/MessagingComponents/MessageBox.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MessagingComponents/MessageBox.tsx b/src/components/MessagingComponents/MessageBox.tsx index 71fc2dd9..6a9dd62b 100644 --- a/src/components/MessagingComponents/MessageBox.tsx +++ b/src/components/MessagingComponents/MessageBox.tsx @@ -167,6 +167,7 @@ export function MessageBox({ data-test="message-input" value={value} onChange={handleChange} + autoFocus rows={4} className={`w-full resize-y bg-[#2a2a2a] border text-white text-sm px-3 py-2 rounded focus:outline-none caret-[#f50] ${ hasError ? "border-red-500" : "border-[#444] focus:border-[#666]" From 67a9457650c06fee2d5dfef26c5ee93ab4c17961 Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 14:28:53 +0300 Subject: [PATCH 08/10] shuffling all the songs msh 5 bas inshallah bashmonds mohamed maytl3sh bugs tanya --- src/stores/player.store.ts | 69 ++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/stores/player.store.ts b/src/stores/player.store.ts index 1db2ce97..8d1f63f8 100644 --- a/src/stores/player.store.ts +++ b/src/stores/player.store.ts @@ -18,6 +18,15 @@ function debouncedSave(userId: string, state: PlayerState) { }, 2000); } +function buildShuffledOrder(queueLength: number, currentIndex: number): number[] { + const others = Array.from({ length: queueLength }, (_, i) => i).filter(i => i !== currentIndex); + for (let i = others.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [others[i], others[j]] = [others[j], others[i]]; + } + return [currentIndex, ...others]; +} + // Dynamic import for audioService to avoid circular dependencies let _seekAudio: ((time: number) => void) | null = null; function getSeekAudio() { @@ -39,6 +48,8 @@ interface PlayerState { volume: number; isMuted: boolean; isShuffle: boolean; + shuffledIndices: number[]; + shuffledPos: number; repeatMode: "none" | "one" | "all"; isLiked: boolean; isAutoplay: boolean; @@ -85,6 +96,8 @@ export const usePlayerStore = create()( volume: 1, isMuted: false, isShuffle: false, + shuffledIndices: [], + shuffledPos: 0, repeatMode: "none", isLiked: false, isAutoplay: true, @@ -94,33 +107,38 @@ export const usePlayerStore = create()( const newQueue = queue ?? get().queue; const index = newQueue.findIndex((t) => t.id === track.id); const isSameTrack = get().currentTrack?.id === track.id; + const { isShuffle } = get(); if (isSameTrack) { + const newIndex = index >= 0 ? index : get().queueIndex; set({ currentTrack: { ...(get().currentTrack ?? track), ...track, }, queue: newQueue, - queueIndex: index >= 0 ? index : get().queueIndex, + queueIndex: newIndex, isPlaying: true, currentTime: startTime ?? get().currentTime, activeSourceId: activeSourceId ?? get().activeSourceId, + ...(isShuffle && { shuffledIndices: buildShuffledOrder(newQueue.length, newIndex), shuffledPos: 1 }), }); return; } const isFromNullState = get().currentTrack === null; const nextTime = startTime ?? (isFromNullState ? get().currentTime : 0); + const newIndex = index >= 0 ? index : 0; set({ currentTrack: track, queue: newQueue, - queueIndex: index >= 0 ? index : 0, + queueIndex: newIndex, isPlaying: true, currentTime: nextTime, isLiked: false, activeSourceId: activeSourceId ?? null, + ...(isShuffle && { shuffledIndices: buildShuffledOrder(newQueue.length, newIndex), shuffledPos: 1 }), }); }, @@ -161,11 +179,13 @@ export const usePlayerStore = create()( commentCount: q.commentCount || 0, } as Track)); - const qIndex = mappedQueue.findIndex(t => String(t.id) === String(fallbackTrack.id)); - + const qIndex = Math.max(0, mappedQueue.findIndex(t => String(t.id) === String(fallbackTrack.id))); + const { isShuffle } = get(); + set({ queue: mappedQueue, - queueIndex: Math.max(0, qIndex) + queueIndex: qIndex, + ...(isShuffle && { shuffledIndices: buildShuffledOrder(mappedQueue.length, qIndex), shuffledPos: 1 }), }); } } catch (e) { @@ -178,9 +198,9 @@ export const usePlayerStore = create()( togglePlay: () => set((s) => ({ isPlaying: !s.isPlaying })), next: () => { - const { queue, queueIndex, isShuffle, isAutoplay, currentTrack } = get(); + const { queue, queueIndex, isShuffle, shuffledIndices, shuffledPos, isAutoplay, currentTrack } = get(); if (!queue.length) return; - + if (queue.length === 1 && queueIndex >= queue.length - 1 && isAutoplay && currentTrack) { import("../services/track.service").then(async (m) => { try { @@ -188,7 +208,7 @@ export const usePlayerStore = create()( if (tracks && tracks.length > 0) { const filtered = tracks.filter((t: Track) => !queue.some(q => q.id === t.id)); if (filtered.length > 0) { - set((s) => ({ + set((s) => ({ queue: [...s.queue, ...filtered], currentTrack: filtered[0], queueIndex: s.queueIndex + 1, @@ -201,18 +221,33 @@ export const usePlayerStore = create()( } catch (err) { console.error("Autoplay failed", err); } - const nextIndex = isShuffle ? Math.floor(Math.random() * queue.length) : (queueIndex + 1) % queue.length; + const nextIndex = (queueIndex + 1) % queue.length; set({ currentTrack: queue[nextIndex], queueIndex: nextIndex, isPlaying: true, currentTime: 0 }); }); return; } - let nextIndex: number; if (isShuffle) { - nextIndex = Math.floor(Math.random() * queue.length); - } else { - nextIndex = (queueIndex + 1) % queue.length; + let indices = shuffledIndices; + let pos = shuffledPos; + if (pos >= indices.length) { + // All tracks played — re-shuffle for next cycle + indices = buildShuffledOrder(queue.length, queueIndex); + pos = 0; + } + const nextIndex = indices[pos]; + set({ + currentTrack: queue[nextIndex], + queueIndex: nextIndex, + isPlaying: true, + currentTime: 0, + shuffledIndices: indices, + shuffledPos: pos + 1, + }); + return; } + + const nextIndex = (queueIndex + 1) % queue.length; set({ currentTrack: queue[nextIndex], queueIndex: nextIndex, @@ -254,7 +289,13 @@ export const usePlayerStore = create()( setDuration: (duration) => set({ duration }), setVolume: (volume) => set({ volume, isMuted: volume === 0 }), toggleMute: () => set((s) => ({ isMuted: !s.isMuted })), - toggleShuffle: () => set((s) => ({ isShuffle: !s.isShuffle })), + toggleShuffle: () => set((s) => { + if (s.isShuffle) { + return { isShuffle: false, shuffledIndices: [], shuffledPos: 0 }; + } + const indices = buildShuffledOrder(s.queue.length, s.queueIndex); + return { isShuffle: true, shuffledIndices: indices, shuffledPos: 1 }; + }), toggleRepeat: () => set((s) => { const modes: ("none" | "all" | "one")[] = ["none", "all", "one"]; From 2fee6f5826468e185413fb03aca37769ac167a7b Mon Sep 17 00:00:00 2001 From: nourabosaif Date: Sat, 2 May 2026 15:28:46 +0300 Subject: [PATCH 09/10] showing comment when passing through the waveform and then disppears --- .../[username]/[trackSlug]/components/TrackHero.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/[username]/[trackSlug]/components/TrackHero.tsx b/src/pages/[username]/[trackSlug]/components/TrackHero.tsx index 9a7a5676..26853893 100644 --- a/src/pages/[username]/[trackSlug]/components/TrackHero.tsx +++ b/src/pages/[username]/[trackSlug]/components/TrackHero.tsx @@ -39,16 +39,18 @@ export default function TrackHero({ const currentSec = Math.floor(currentTime); if (currentSec !== lastSecondRef.current) { lastSecondRef.current = currentSec; - const commentAtThisTime = comments.find(c => Math.floor(c.track_timestamp) === currentSec); if (commentAtThisTime) { setActiveComment(commentAtThisTime); setShowFloating(true); - const timer = setTimeout(() => setShowFloating(false), 3000); - return () => clearTimeout(timer); + return; // skip hide check so the new comment isn't immediately dismissed } } - }, [currentTime, comments]); + // Hide once the playhead has passed the comment + if (activeComment && currentTime > activeComment.track_timestamp + 1) { + setShowFloating(false); + } + }, [currentTime, comments, activeComment]); return (
Date: Sat, 2 May 2026 16:02:04 +0300 Subject: [PATCH 10/10] history stor error --- src/stores/history.store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/history.store.ts b/src/stores/history.store.ts index 3d868dd9..24db0cb7 100644 --- a/src/stores/history.store.ts +++ b/src/stores/history.store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import { persist } from "zustand/middleware"; +import { persist, createJSONStorage } from "zustand/middleware"; import { writeListeningHistory } from "@/services/api/discover.service"; import { createUserScopedStorage } from "@/stores/userScopedStorage"; import type { Track } from "@/types/track"; @@ -175,6 +175,6 @@ export const useHistoryStore = create()( .entries.filter((e) => e.type === "station") .map((e) => (e as { type: "station"; item: Station; playedAt: string }).item), }), - { name: "rythmify-history", version: 1, storage: createUserScopedStorage("history") }, + { name: "rythmify-history", version: 1, storage: createJSONStorage(() => createUserScopedStorage("history")) }, ), );