A decentralized music tracking and discovery platform built on AT Protocol 🎵

Support multiple artists on Song page

Add artists field to feed and library API responses and song atom.
Index artists by name in the Song component and render song.artist as a
comma-separated list, turning names into links when the artist has a
uri.

+43 -17
+1
apps/web/src/api/feed.ts
··· 28 28 spotifyLink: response.data?.spotifyLink, 29 29 composer: response.data?.composer, 30 30 uri: response.data?.uri, 31 + artists: response.data?.artists, 31 32 }; 32 33 }; 33 34
+1
apps/web/src/api/library.ts
··· 27 27 spotifyLink: response.data?.spotifyLink, 28 28 composer: response.data?.composer, 29 29 uri: response.data?.uri, 30 + artists: response.data?.artists, 30 31 }; 31 32 }; 32 33
+7
apps/web/src/atoms/song.ts
··· 2 2 3 3 export const songAtom = atom<{ 4 4 title: string; 5 + artist: string; 6 + artists: { 7 + id: string; 8 + name: string; 9 + picture?: string; 10 + uri?: string; 11 + }[]; 5 12 albumArtist: string; 6 13 cover: string; 7 14 listeners: number;
+34 -17
apps/web/src/pages/song/Song.tsx
··· 29 29 import Credits from "./Credits"; 30 30 import PopularAlbums from "./PopularAlbums"; 31 31 import PopularTracks from "./PopularTracks"; 32 + import * as R from "ramda"; 32 33 33 34 const Group = styled.div` 34 35 display: flex; ··· 198 199 artistTracksResult.isLoading || 199 200 artistAlbumResult.isLoading; 200 201 202 + const artists = R.indexBy(R.prop("name"), song?.artists || []); 203 + 201 204 return ( 202 205 <Main> 203 206 <div className="pb-[100px] pt-[50px]"> ··· 250 253 <HeadingMedium margin={0} className="!text-[var(--color-text)]"> 251 254 {song?.title} 252 255 </HeadingMedium> 253 - {song?.artistUri && ( 254 - <Link 255 - to={`/${song.artistUri.split("at://")[1].replace("app.rocksky.", "")}`} 256 - > 257 - <LabelLarge 258 - margin={0} 259 - className="!text-[var(--color-text)]" 260 - > 261 - {song?.albumArtist} 262 - </LabelLarge> 263 - </Link> 264 - )} 265 - {!song?.artistUri && ( 266 - <LabelLarge margin={0} className="!text-[var(--color-text)]"> 267 - {song?.albumArtist} 268 - </LabelLarge> 269 - )} 256 + {song?.artist 257 + .split(",") 258 + .map((name) => name.trim()) 259 + .filter((name) => name !== "") 260 + .map((name, index, array) => ( 261 + <span key={index}> 262 + <div className="inline-block"> 263 + {artists[name]?.uri ? ( 264 + <Link 265 + to={`/${artists[name]?.uri?.split("at://")[1].replace("app.rocksky.", "")}`} 266 + > 267 + <LabelLarge 268 + margin={0} 269 + className="!text-[var(--color-text)]" 270 + > 271 + {name} 272 + </LabelLarge> 273 + </Link> 274 + ) : ( 275 + <LabelLarge 276 + margin={0} 277 + className="!text-[var(--color-text)]" 278 + > 279 + {name} 280 + </LabelLarge> 281 + )} 282 + </div> 283 + {index < array.length - 1 && ", "} 284 + </span> 285 + ))} 286 + 270 287 <div className="mt-[20px] flex flex-row"> 271 288 <div 272 289 style={{