A decentralized music tracking and discovery platform built on AT Protocol 🎵
listenbrainz spotify atproto lastfm musicbrainz scrobbling

Add TopTrack component to profile

Changed files
+156 -93
apps
web
src
pages
+98 -93
apps/web/src/pages/profile/Profile.tsx
··· 30 import Followers from "./followers"; 31 import { activeTabAtom } from "../../atoms/tab"; 32 import Circles from "./circles"; 33 34 const Group = styled.div` 35 display: flex; ··· 37 justify-content: space-between; 38 align-items: flex-start; 39 margin-top: 20px; 40 - margin-bottom: 50px; 41 `; 42 43 const ProfileInfo = styled.div` ··· 180 return ( 181 <Main> 182 <div className="pb-[100px] pt-[75px]"> 183 - <Group> 184 - <ProfileInfo> 185 - <div className="mr-[20px]"> 186 - <Avatar 187 - name={profiles[did]?.displayName} 188 - src={profiles[did]?.avatar} 189 - size="150px" 190 - /> 191 - </div> 192 - <div style={{ marginTop: profiles[did]?.displayName ? 10 : 30 }}> 193 - <HeadingMedium 194 - marginTop="0px" 195 - marginBottom={0} 196 - className="!text-[var(--color-text)]" 197 - > 198 - {profiles[did]?.displayName} 199 - </HeadingMedium> 200 - <LabelLarge> 201 - <a 202 - href={`https://bsky.app/profile/${profiles[did]?.handle}`} 203 - className="no-underline text-[var(--color-primary)]" 204 - target="_blank" 205 > 206 - @{profiles[did]?.handle} 207 - </a> 208 - <span className="text-[var(--color-text-muted)] text-[15px]"> 209 - {" "} 210 - • scrobbling since{" "} 211 - {dayjs(profiles[did]?.createdAt).format("DD MMM YYYY")} 212 - </span> 213 - </LabelLarge> 214 - <div className="flex-1 mt-[30px] mr-[10px]"> 215 - <a 216 - href={`https://pdsls.dev/at/${profiles[did]?.did}`} 217 - target="_blank" 218 - className="no-underline text-[var(--color-text)] bg-[var(--color-default-button)] p-[16px] rounded-[10px] pl-[25px] pr-[25px]" 219 - > 220 - <ExternalLink size={24} style={{ marginRight: 10 }} /> 221 - View on PDSls 222 - </a> 223 </div> 224 - </div> 225 - </ProfileInfo> 226 - {(profile.data?.did !== localStorage.getItem("did") || 227 - !localStorage.getItem("did")) && ( 228 - <> 229 - {!follows.has(profile.data?.did || "") && !isLoading && ( 230 - <Button 231 - shape="pill" 232 - size="compact" 233 - startEnhancer={<IconPlus size={18} />} 234 - onClick={onFollow} 235 - overrides={{ 236 - BaseButton: { 237 - style: { 238 - marginTop: "12px", 239 - minWidth: "120px", 240 - backgroundColor: "#ff2876", 241 - ":hover": { 242 backgroundColor: "#ff2876", 243 - }, 244 - ":focus": { 245 - backgroundColor: "#ff2876", 246 }, 247 }, 248 - }, 249 - }} 250 - > 251 - Follow 252 - </Button> 253 - )} 254 - {follows.has(profile.data?.did || "") && !isLoading && ( 255 - <Button 256 - shape="pill" 257 - size="compact" 258 - startEnhancer={<IconCheck size={18} />} 259 - onClick={onUnfollow} 260 - overrides={{ 261 - BaseButton: { 262 - style: { 263 - marginTop: "12px", 264 - minWidth: "120px", 265 - backgroundColor: "var(--color-default-button)", 266 - color: "var(--color-text)", 267 - ":hover": { 268 backgroundColor: "var(--color-default-button)", 269 - }, 270 - ":focus": { 271 - backgroundColor: "var(--color-default-button)", 272 }, 273 }, 274 - }, 275 - }} 276 - > 277 - Following 278 - </Button> 279 - )} 280 - </> 281 - )} 282 - </Group> 283 284 <Tabs 285 activeKey={activeKey}
··· 30 import Followers from "./followers"; 31 import { activeTabAtom } from "../../atoms/tab"; 32 import Circles from "./circles"; 33 + import TopTrack from "./toptrack"; 34 35 const Group = styled.div` 36 display: flex; ··· 38 justify-content: space-between; 39 align-items: flex-start; 40 margin-top: 20px; 41 `; 42 43 const ProfileInfo = styled.div` ··· 180 return ( 181 <Main> 182 <div className="pb-[100px] pt-[75px]"> 183 + <div className="mb-[50px]"> 184 + <Group> 185 + <ProfileInfo> 186 + <div className="mr-[20px]"> 187 + <Avatar 188 + name={profiles[did]?.displayName} 189 + src={profiles[did]?.avatar} 190 + size="150px" 191 + /> 192 + </div> 193 + <div style={{ marginTop: profiles[did]?.displayName ? 10 : 30 }}> 194 + <HeadingMedium 195 + marginTop="0px" 196 + marginBottom={0} 197 + className="!text-[var(--color-text)]" 198 > 199 + {profiles[did]?.displayName} 200 + </HeadingMedium> 201 + <LabelLarge> 202 + <a 203 + href={`https://bsky.app/profile/${profiles[did]?.handle}`} 204 + className="no-underline text-[var(--color-primary)]" 205 + target="_blank" 206 + > 207 + @{profiles[did]?.handle} 208 + </a> 209 + <span className="text-[var(--color-text-muted)] text-[15px]"> 210 + {" "} 211 + • scrobbling since{" "} 212 + {dayjs(profiles[did]?.createdAt).format("DD MMM YYYY")} 213 + </span> 214 + </LabelLarge> 215 + <div className="flex-1 mt-[30px] mr-[10px]"> 216 + <a 217 + href={`https://pdsls.dev/at/${profiles[did]?.did}`} 218 + target="_blank" 219 + className="no-underline text-[var(--color-text)] bg-[var(--color-default-button)] p-[16px] rounded-[10px] pl-[25px] pr-[25px]" 220 + > 221 + <ExternalLink size={24} style={{ marginRight: 10 }} /> 222 + View on PDSls 223 + </a> 224 + </div> 225 </div> 226 + </ProfileInfo> 227 + {(profile.data?.did !== localStorage.getItem("did") || 228 + !localStorage.getItem("did")) && ( 229 + <> 230 + {!follows.has(profile.data?.did || "") && !isLoading && ( 231 + <Button 232 + shape="pill" 233 + size="compact" 234 + startEnhancer={<IconPlus size={18} />} 235 + onClick={onFollow} 236 + overrides={{ 237 + BaseButton: { 238 + style: { 239 + marginTop: "12px", 240 + minWidth: "120px", 241 backgroundColor: "#ff2876", 242 + ":hover": { 243 + backgroundColor: "#ff2876", 244 + }, 245 + ":focus": { 246 + backgroundColor: "#ff2876", 247 + }, 248 }, 249 }, 250 + }} 251 + > 252 + Follow 253 + </Button> 254 + )} 255 + {follows.has(profile.data?.did || "") && !isLoading && ( 256 + <Button 257 + shape="pill" 258 + size="compact" 259 + startEnhancer={<IconCheck size={18} />} 260 + onClick={onUnfollow} 261 + overrides={{ 262 + BaseButton: { 263 + style: { 264 + marginTop: "12px", 265 + minWidth: "120px", 266 backgroundColor: "var(--color-default-button)", 267 + color: "var(--color-text)", 268 + ":hover": { 269 + backgroundColor: "var(--color-default-button)", 270 + }, 271 + ":focus": { 272 + backgroundColor: "var(--color-default-button)", 273 + }, 274 }, 275 }, 276 + }} 277 + > 278 + Following 279 + </Button> 280 + )} 281 + </> 282 + )} 283 + </Group> 284 + <div className="mt-[20px] flex justify-end"> 285 + <TopTrack /> 286 + </div> 287 + </div> 288 289 <Tabs 290 activeKey={activeKey}
+55
apps/web/src/pages/profile/toptrack/TopTrack.tsx
···
··· 1 + import { Link, useParams } from "@tanstack/react-router"; 2 + import { useTracksQuery } from "../../../hooks/useLibrary"; 3 + import { getLastDays } from "../../../lib/date"; 4 + 5 + function TopTrack() { 6 + const { did } = useParams({ strict: false }); 7 + const { data, isLoading } = useTracksQuery(did!, 0, 1, ...getLastDays(7)); 8 + console.log(">> data", data); 9 + return ( 10 + <> 11 + {!isLoading && data.length > 0 && ( 12 + <div className="flex"> 13 + <div className="flex flex-col items-end pr-[15px]"> 14 + <h4 15 + className="text-[12px] opacity-60 m-[0px]" 16 + style={{ fontWeight: "bold" }} 17 + > 18 + TOP TRACK 19 + </h4> 20 + <Link 21 + to={data[0].uri?.split("at:/")[1]?.replace("app.rocksky.", "")} 22 + className="!text-[var(--color-text)] no-underline hover:underline" 23 + > 24 + <b className="text-[18px] truncate max-w-[500px]"> 25 + {data[0].title} 26 + </b> 27 + </Link> 28 + 29 + <Link 30 + to={data[0].artistUri 31 + ?.split("at:/")[1] 32 + ?.replace("app.rocksky.", "")} 33 + className="text-[var(--color-text)] no-underline hover:underline" 34 + > 35 + <span className="opacity-90 text-[18px] truncate max-w-[500px]"> 36 + {data[0].artist} 37 + </span> 38 + </Link> 39 + </div> 40 + <Link 41 + to={data[0].albumUri?.split("at:/")[1]?.replace("app.rocksky.", "")} 42 + > 43 + <img 44 + src={data[0].albumArt} 45 + alt={"Addicted"} 46 + className="w-[70px] h-[70px]" 47 + /> 48 + </Link> 49 + </div> 50 + )} 51 + </> 52 + ); 53 + } 54 + 55 + export default TopTrack;
+3
apps/web/src/pages/profile/toptrack/index.tsx
···
··· 1 + import TopTrack from "./TopTrack"; 2 + 3 + export default TopTrack;