Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/MessagingComponents/MessageBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export function MessageBox({
data-test="message-input"
value={value}
onChange={handleChange}
autoFocus
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
Expand Down
2 changes: 2 additions & 0 deletions src/components/Profile/ProfileSideBar/ProfileSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ interface LikedTrack {
comments?: number;
audioUrl?: string;
artistId?: string;
artistUsername?: string;
trackSlug?: string;
}

interface ProfileSideBarProps {
Expand Down
43 changes: 16 additions & 27 deletions src/components/UI/StationCard/StationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ────────────────────────────────────────
Expand Down Expand Up @@ -147,41 +148,29 @@ export default function StationCard({
{/* Three artist circles */}
{/* Circle 1 — top-left, small */}
<div className="absolute top-[6%] left-[4%] w-[30%] aspect-square rounded-full overflow-hidden border-[2px] border-white/20 z-10">
{artists[0]?.avatarUrl ? (
<img
src={artists[0].avatarUrl}
alt={artists[0].displayName ?? ""}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-[#2a2a3a]" />
)}
<CoverImage
src={artists[0]?.avatarUrl}
alt={artists[0]?.displayName ?? ""}
className="w-full h-full object-cover"
/>
</div>

{/* Circle 2 — center, largest */}
<div className="absolute top-[22%] left-[24%] w-[50%] aspect-square rounded-full overflow-hidden border-[2px] border-white/25 z-20">
{artists[1]?.avatarUrl ? (
<img
src={artists[1].avatarUrl}
alt={artists[1].displayName ?? ""}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-[#2a2a3a]" />
)}
<CoverImage
src={artists[1]?.avatarUrl}
alt={artists[1]?.displayName ?? ""}
className="w-full h-full object-cover"
/>
</div>

{/* Circle 3 — bottom-right, small */}
<div className="absolute bottom-[14%] right-[4%] w-[30%] aspect-square rounded-full overflow-hidden border-[2px] border-white/20 z-10">
{artists[2]?.avatarUrl ? (
<img
src={artists[2].avatarUrl}
alt={artists[2].displayName ?? ""}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-[#2a2a3a]" />
)}
<CoverImage
src={artists[2]?.avatarUrl}
alt={artists[2]?.displayName ?? ""}
className="w-full h-full object-cover"
/>
</div>

{/* SoundCloud logo — top-right */}
Expand Down
4 changes: 2 additions & 2 deletions src/components/UI/TrackItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const TrackItem: React.FC<TrackItemProps> = ({
const finalArtistSlug =
artistUsername || (artist ?? "").toLowerCase().replace(/\s+/g, "-");

const trackPath = `/discover/personalised/${trackSlug ?? ""}:${id}`;
const trackPath = `/${finalArtistSlug}/${trackSlug ?? id}`;
const trackForActions: Track = {
id,
title,
Expand Down Expand Up @@ -183,7 +183,7 @@ const TrackItem: React.FC<TrackItemProps> = ({
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}`)}
>
<CoverImage
src={coverUrl}
Expand Down
5 changes: 0 additions & 5 deletions src/components/UI/TrackListSection/TrackListSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ const TrackListSection = ({
}: TrackListSectionProps) => {
const navigate = useNavigate();

// ✅ Early return if no children
if (!children) {
return null;
}

return (
<div className={styles.container} data-test="track-list-section">
{/* Header */}
Expand Down
66 changes: 25 additions & 41 deletions src/components/discover/sidebar/DiscoverSideBar.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,26 +19,6 @@ import type { Track } from "@/types/track";
const DiscoverSidebar = () => {
const asideRef = useRef<HTMLElement>(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<typeof mapSuggestedArtistToArtistCard>[]
>([]);
Expand Down Expand Up @@ -101,7 +81,7 @@ const DiscoverSidebar = () => {
<aside
ref={asideRef}
data-test="discover-sidebar"
className="flex flex-col gap-6 w-full sticky"
className="flex flex-col gap-6 w-full sticky top-0"
>
<ArtistToolsCard />

Expand All @@ -114,31 +94,35 @@ const DiscoverSidebar = () => {
/>
)}

{likedTracks.length > 0 && (
<TrackListSection
title={`${likedTracks.length} LIKES`}
viewAllLink="/you/likes"
>
{likedTracks.slice(0, 3).map((track) => (
<TrackListSection
title={likedTracks.length > 0 ? `${likedTracks.length} LIKES` : "LIKES"}
viewAllLink="/you/likes"
>
{likedTracks.length > 0 ? (
likedTracks.slice(0, 3).map((track) => (
<TrackItem key={String(track.id)} {...toItem(track)} />
))}
</TrackListSection>
)}

{historyTracks.length > 0 && (
<TrackListSection
title="LISTENING HISTORY"
viewAllLink="/you/history"
>
{historyTracks.map((track) => (
))
) : (
<p className="text-text-secondary text-xs">No liked tracks yet</p>
)}
</TrackListSection>

<TrackListSection
title="LISTENING HISTORY"
viewAllLink="/you/history"
>
{historyTracks.length > 0 ? (
historyTracks.map((track) => (
<TrackItem
key={String(track.id)}
{...toItem(track)}
initialLiked={false}
/>
))}
</TrackListSection>
)}
))
) : (
<p className="text-text-secondary text-xs">Start listening now</p>
)}
</TrackListSection>

<GoMobileSection />
</aside>
Expand Down
2 changes: 2 additions & 0 deletions src/pages/[username]/UsernamePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ export default function UsernamePage() {
plays: t.play_count,
likes: t.like_count,
artistId: t.user_id,
artistUsername: t.artist_username ?? "",
trackSlug: t.track_slug ?? t.id,
}),
);

Expand Down
10 changes: 6 additions & 4 deletions src/pages/[username]/[trackSlug]/components/TrackHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,8 +72,8 @@ export default function TrackSidebar({ track, featuredArtists, relatedTracks = [
{relatedTracks.slice(0, 3).map((t) => (
<div key={t.id} className="flex gap-3 group cursor-pointer group">
<div className="relative w-12 h-12 shrink-0">
<img
src={t.coverUrl || `https://picsum.photos/seed/${t.id}/80/80`}
<CoverImage
src={t.coverUrl}
alt={t.title}
className="w-full h-full object-cover rounded shadow-sm group-hover:opacity-80 transition-opacity"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/[username]/tracks/TracksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default function TracksPage() {
tracks.map((track) => (
<TrackCard
key={track.id}
track={track}
track={{ ...track, artistUsername: track.artistUsername || username || "" }}
onCopyLink={() =>
navigator.clipboard.writeText(
`${window.location.origin}/${track.artistUsername}/${track.id}`,
Expand Down
17 changes: 6 additions & 11 deletions src/pages/home/LandingPage.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -62,17 +63,11 @@ const TrendingTrackCard = ({ track }: { track: Track }) => (
className="group block"
>
<div className="aspect-square rounded bg-[#333] mb-2 overflow-hidden relative">
{track.coverUrl ? (
<img
src={track.coverUrl}
alt={track.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center text-xs text-[#666]">
{/* Placeholder */}
</div>
)}
<CoverImage
src={track.coverUrl}
alt={track.title}
className="w-full h-full object-cover"
/>
{/* Hover overlay with action icons */}
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-end justify-center pb-3 gap-3">
<button aria-label="Like" className="text-white/80 hover:text-white text-sm">♥</button>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/you/stations/StationSlugPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export default function StationSlugPage() {
return (
<div
data-test="station-slug-page"
className="flex-1 w-full bg-bg min-h-screen"
className="container px-4 md:px-8 lg:px-12 xl:px-20 flex-1 w-full bg-bg min-h-screen"
>
<div data-test="station-slug-hero">
<PlaylistHero
Expand Down
7 changes: 4 additions & 3 deletions src/services/api/discover.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ export function mapTrackSummaryToTrack(api: TrackSummary): Track {
return {
id: api.id as unknown as string,
title: api.title,
artistName: "", // user_id only — call getUserById(api.user_id) to get display_name
artistUsername: "",
artistName: api.artist_name ?? "",
artistUsername: api.artist_username ?? "",
coverUrl: api.cover_image ?? "",
genre: api.genre ?? "",
likeCount: api.like_count,
repostCount: 0,
playCount: api.play_count,
commentCount: 0,
duration: api.duration ? formatDuration(api.duration) : "0:00",
postedAt: "", // TrackSummary has no created_at
postedAt: "",
audioUrl: api.stream_url ?? "",
trackSlug: api.track_slug ?? api.id,
waveformData: [],
};
}
Expand Down
4 changes: 3 additions & 1 deletion src/services/api/discover.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,16 @@ export interface HomeData {
artists_to_watch: EmergingArtist[];
}

// Missing artist name — user_id only. needs getUserById()
export interface TrackSummary {
id: string;
title: string;
genre: string | null;
duration: number | null;
cover_image: string | null;
user_id: string;
artist_name?: string;
artist_username?: string;
track_slug?: string;
play_count: number;
like_count: number;
stream_url: string | null;
Expand Down
7 changes: 4 additions & 3 deletions src/services/api/search/searchMappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,18 @@ 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 ?? "",
postedAt: pl.created_at ?? pl.postedAt ?? "",
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),
};
Expand Down
2 changes: 2 additions & 0 deletions src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ export interface TrackSummary {
cover_image: string | null;
user_id: string;
artist_name: string;
artist_username?: string;
track_slug?: string;
play_count: number;
like_count: number;
stream_url: string | null;
Expand Down
5 changes: 3 additions & 2 deletions src/stores/history.store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
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";
import type { Station } from "@/types/station";
import type { PersonalMix } from "@/services/api/discover.service";
Expand Down Expand Up @@ -174,6 +175,6 @@ export const useHistoryStore = create<HistoryStore>()(
.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: createJSONStorage(() => createUserScopedStorage("history")) },
),
);
Loading
Loading