Skip to content

Commit

Permalink
Merge pull request #54 from Noctember/feat/streams-page
Browse files Browse the repository at this point in the history
feat: add streams page to artist, album and track
  • Loading branch information
stijnvdkolk authored Apr 14, 2024
2 parents 8ef1f4e + c8a695f commit 8a8661e
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 54 deletions.
12 changes: 0 additions & 12 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,6 @@ module.exports = withBundleAnalyzer({
source: '/:id/compare',
destination: '/user/:id/compare',
},
{
source: '/artist/:id/:ignore',
destination: '/artist/:id',
},
{
source: '/track/:id/:ignore',
destination: '/track/:id',
},
{
source: '/album/:id/:ignore',
destination: '/album/:id',
},
{
source: '/:id/artists',
destination: '/user/:id/artists',
Expand Down
27 changes: 27 additions & 0 deletions src/components/Section/ToolbarBackLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import clsx from 'clsx';
import { useRouter } from 'next/router';
import type { FC } from 'react';
import { MdChevronLeft } from 'react-icons/md';

export const ToolbarBackLink: FC<{ path: string }> = (path) => {
const router = useRouter();
const closeCallback = () => {
router.push(path.path);
};

return (
<div>
<button
aria-label="go back"
className={clsx(
'mr-4 rounded-full bg-foreground p-2 ring-neutral-500 transition-all',
// moved from the global css file
'focus-within:ring-2 focus:outline-none focus:ring-2 hover:ring-2',
)}
onClick={() => closeCallback()}
>
<MdChevronLeft className="fill-white" />
</button>
</div>
);
};
21 changes: 15 additions & 6 deletions src/pages/album/[id].tsx → src/pages/album/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,21 @@ const Album: NextPage<Props> = ({ album, tracks }) => {
{user && (
<Section title="Your streams">
{({ headerRef }) => (
<RecentStreams
headerRef={headerRef}
streams={streams ?? []}
loading={streams === null}
onItemClick={() => event('ALBUM_stream_track_click')}
/>
<>
<RecentStreams
headerRef={headerRef}
streams={streams ?? []}
loading={streams === null}
onItemClick={() => event('ALBUM_stream_track_click')}
/>
{user.hasImported && (
<Link legacyBehavior href={`/album/${album.id}/streams`}>
<a className="my-3 font-bold uppercase text-text-grey transition-colors hover:text-white">
show all
</a>
</Link>
)}
</>
)}
</Section>
)}
Expand Down
127 changes: 127 additions & 0 deletions src/pages/album/[id]/streams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Container } from '@/components/Container';
import type { SSRProps } from '@/utils/ssrUtils';
import { getApiInstance, fetchUser } from '@/utils/ssrUtils';
import type { GetServerSideProps, NextPage } from 'next';
import Link from 'next/link';
import { MdChevronLeft, MdDiscFull } from 'react-icons/md';
import type * as statsfm from '@/utils/statsfm';
import { Title } from '@/components/Title';
import { Section } from '@/components/Section/Section';
import { useApi, useAuth } from '@/hooks';
import { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import { Spinner } from '@/components/Spinner';
import { RecentStreams } from '@/components/RecentStreams';
import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink';

type Props = SSRProps & {
album: statsfm.Album;
};

export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
const { identityToken } = ctx.req.cookies;
const api = getApiInstance(identityToken);
const id = ctx.params?.id?.toString();

if (!id) {
throw new Error('no param id recieved');
}

let album;
try {
album = await api.albums.get(parseInt(id, 10));
} catch (e) {
return { notFound: true };
}

const user = await fetchUser(ctx);

return {
props: {
user,
album,
},
};
};

const AlbumStreamsPage: NextPage<Props> = ({ album }) => {
const api = useApi();
const { user } = useAuth();

const [recentStreams, setRecentStreams] = useState<statsfm.Stream[]>([]);
const [loadMore, setLoadMore] = useState(true);

const callbackRef = async () => {
const lastEndTime = recentStreams[recentStreams.length - 1]
?.endTime as any as string;

const streams = await api.users.albumStreams(user!.id, album.id, {
limit: 200,
before: new Date(lastEndTime).getTime() || new Date().getTime(),
});

if (streams.length === 0) setLoadMore(false);
setRecentStreams([...(recentStreams || []), ...streams.slice(1)]);
};

return (
<>
<Title>{`Your streams`}</Title>
<div className="relative z-[31] bg-foreground pt-20">
<Container>
<section className="flex flex-col items-center gap-5 pb-10 pt-24 md:flex-row md:items-start">
<div className="flex w-full flex-col justify-end">
<Link legacyBehavior href={`/album/${album.id}`}>
<a className="-mb-3 flex items-center text-lg text-white">
<MdChevronLeft className="-mr-1 block h-12 w-6 text-white" />
back to {album.name}
</a>
</Link>
<h1 className="text-4xl font-extrabold sm:text-5xl md:text-left">
Your streams
</h1>
</div>
</section>
</Container>
</div>
<Container>
<Section
headerStyle="!flex-row-reverse -mt-24 z-30 relative"
title={`back to ${album.name}`}
toolbar={<ToolbarBackLink path={`/album/${album.id}`} />}
>
{({ headerRef }) => (
<>
<InfiniteScroll
loadMore={callbackRef}
hasMore={loadMore}
loader={
<Spinner
key="spinner"
className="!mx-auto my-10 fill-primary !text-foreground"
/>
}
threshold={2000}
useWindow={true}
>
<RecentStreams
loading={null}
headerRef={headerRef}
streams={recentStreams}
/>
</InfiniteScroll>
{!loadMore && (
<div className="grid w-full place-items-center py-20">
<MdDiscFull />
<p className="m-0 text-text-grey">No streams to load!</p>
</div>
)}
</>
)}
</Section>
</Container>
</>
);
};

export default AlbumStreamsPage;
21 changes: 15 additions & 6 deletions src/pages/artist/[id].tsx → src/pages/artist/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,21 @@ const Artist: NextPage<Props> = ({ artist, origin }) => {
description={`Your streams featuring ${artist.name}`}
>
{({ headerRef }) => (
<RecentStreams
headerRef={headerRef}
streams={streams ?? []}
loading={streams == null}
onItemClick={() => event('ARTIST_stream_click')}
/>
<>
<RecentStreams
headerRef={headerRef}
streams={streams ?? []}
loading={streams == null}
onItemClick={() => event('ARTIST_stream_click')}
/>
{user.hasImported && (
<Link legacyBehavior href={`/artist/${artist.id}/streams`}>
<a className="my-3 font-bold uppercase text-text-grey transition-colors hover:text-white">
show all
</a>
</Link>
)}
</>
)}
</Section>
)}
Expand Down
127 changes: 127 additions & 0 deletions src/pages/artist/[id]/streams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Container } from '@/components/Container';
import type { SSRProps } from '@/utils/ssrUtils';
import { getApiInstance, fetchUser } from '@/utils/ssrUtils';
import type { GetServerSideProps, NextPage } from 'next';
import Link from 'next/link';
import { MdChevronLeft, MdDiscFull } from 'react-icons/md';
import type * as statsfm from '@/utils/statsfm';
import { Title } from '@/components/Title';
import { Section } from '@/components/Section/Section';
import { useApi, useAuth } from '@/hooks';
import { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import { Spinner } from '@/components/Spinner';
import { RecentStreams } from '@/components/RecentStreams';
import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink';

type Props = SSRProps & {
artist: statsfm.Artist;
};

export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
const { identityToken } = ctx.req.cookies;
const api = getApiInstance(identityToken);
const id = ctx.params?.id?.toString();

if (!id) {
throw new Error('no param id recieved');
}

let artist;
try {
artist = await api.artists.get(parseInt(id, 10));
} catch (e) {
return { notFound: true };
}

const user = await fetchUser(ctx);

return {
props: {
user,
artist,
},
};
};

const ArtistStreamsPage: NextPage<Props> = ({ artist }) => {
const api = useApi();
const { user } = useAuth();

const [recentStreams, setRecentStreams] = useState<statsfm.Stream[]>([]);
const [loadMore, setLoadMore] = useState(true);

const callbackRef = async () => {
const lastEndTime = recentStreams[recentStreams.length - 1]
?.endTime as any as string;

const streams = await api.users.artistStreams(user!.id, artist.id, {
limit: 200,
before: new Date(lastEndTime).getTime() || new Date().getTime(),
});

if (streams.length === 0) setLoadMore(false);
setRecentStreams([...(recentStreams || []), ...streams.slice(1)]);
};

return (
<>
<Title>{`Your streams`}</Title>
<div className="relative z-[31] bg-foreground pt-20">
<Container>
<section className="flex flex-col items-center gap-5 pb-10 pt-24 md:flex-row md:items-start">
<div className="flex w-full flex-col justify-end">
<Link legacyBehavior href={`/artist/${artist.id}`}>
<a className="-mb-3 flex items-center text-lg text-white">
<MdChevronLeft className="-mr-1 block h-12 w-6 text-white" />
back to {artist.name}
</a>
</Link>
<h1 className="text-4xl font-extrabold sm:text-5xl md:text-left">
Your streams featuring {artist.name}
</h1>
</div>
</section>
</Container>
</div>
<Container>
<Section
headerStyle="!flex-row-reverse -mt-24 z-30 relative"
title={`back to ${artist.name}`}
toolbar={<ToolbarBackLink path={`/artist/${artist.id}`} />}
>
{({ headerRef }) => (
<>
<InfiniteScroll
loadMore={callbackRef}
hasMore={loadMore}
loader={
<Spinner
key="spinner"
className="!mx-auto my-10 fill-primary !text-foreground"
/>
}
threshold={2000}
useWindow={true}
>
<RecentStreams
loading={null}
headerRef={headerRef}
streams={recentStreams}
/>
</InfiniteScroll>
{!loadMore && (
<div className="grid w-full place-items-center py-20">
<MdDiscFull />
<p className="m-0 text-text-grey">No streams to load!</p>
</div>
)}
</>
)}
</Section>
</Container>
</>
);
};

export default ArtistStreamsPage;
22 changes: 16 additions & 6 deletions src/pages/track/[id].tsx → src/pages/track/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { Radar } from 'react-chartjs-2';
import { AppleMusicLink, SpotifyLink } from '@/components/SocialLink';
import { TopListeners } from '@/components/TopListeners';
import { MdHearingDisabled } from 'react-icons/md';
import Link from 'next/link';

const AudioFeaturesRadarChart = ({
acousticness,
Expand Down Expand Up @@ -436,12 +437,21 @@ const Track: NextPage<Props> = ({ track }) => {
description="Your recently played tracks"
>
{({ headerRef }) => (
<RecentStreams
headerRef={headerRef}
streams={recentStreams ?? []}
loading={recentStreams === null}
track={track}
/>
<>
<RecentStreams
headerRef={headerRef}
streams={recentStreams ?? []}
loading={recentStreams === null}
track={track}
/>
{user.hasImported && (
<Link legacyBehavior href={`/track/${track.id}/streams`}>
<a className="my-3 font-bold uppercase text-text-grey transition-colors hover:text-white">
show all
</a>
</Link>
)}
</>
)}
</Section>
)}
Expand Down
Loading

0 comments on commit 8a8661e

Please sign in to comment.