From a32f511fced88fc5308e6e5a8485d838bd00f79c Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 13 Apr 2024 06:18:34 -0400 Subject: [PATCH 1/5] feat(artist): add streams page --- next.config.js | 4 - src/pages/artist/{[id].tsx => [id]/index.tsx} | 21 ++- src/pages/artist/[id]/streams.tsx | 150 ++++++++++++++++++ 3 files changed, 165 insertions(+), 10 deletions(-) rename src/pages/artist/{[id].tsx => [id]/index.tsx} (92%) create mode 100644 src/pages/artist/[id]/streams.tsx diff --git a/next.config.js b/next.config.js index afd533fa..d6ab3e53 100755 --- a/next.config.js +++ b/next.config.js @@ -77,10 +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', diff --git a/src/pages/artist/[id].tsx b/src/pages/artist/[id]/index.tsx similarity index 92% rename from src/pages/artist/[id].tsx rename to src/pages/artist/[id]/index.tsx index 4ea563c6..6b14c4e8 100644 --- a/src/pages/artist/[id].tsx +++ b/src/pages/artist/[id]/index.tsx @@ -241,12 +241,21 @@ const Artist: NextPage = ({ artist, origin }) => { description={`Your streams featuring ${artist.name}`} > {({ headerRef }) => ( - event('ARTIST_stream_click')} - /> + <> + event('ARTIST_stream_click')} + /> + {user.hasImported && ( + + + show all + + + )} + )} )} diff --git a/src/pages/artist/[id]/streams.tsx b/src/pages/artist/[id]/streams.tsx new file mode 100644 index 00000000..0f4b8db7 --- /dev/null +++ b/src/pages/artist/[id]/streams.tsx @@ -0,0 +1,150 @@ +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 type { FC } from 'react'; +import { useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; +import { Spinner } from '@/components/Spinner'; +import { RecentStreams } from '@/components/RecentStreams'; +import clsx from 'clsx'; +import { useRouter } from 'next/router'; + +const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( +
+ +
+); + +type Props = SSRProps & { + artist: statsfm.Artist; +}; + +export const getServerSideProps: GetServerSideProps = 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 = ({ artist }) => { + const api = useApi(); + const router = useRouter(); + const { user } = useAuth(); + + const [recentStreams, setRecentStreams] = useState([]); + 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 ( + <> + {`Your streams`} +
+ +
+
+ + + + back to {artist.name} + + +

+ Your streams featuring {artist.name} +

+
+
+
+
+ +
router.push(`/artist/${artist.id}`)} + /> + } + > + {({ headerRef }) => ( + <> + + } + threshold={2000} + useWindow={true} + > + + + {!loadMore && ( +
+ +

No streams to load!

+
+ )} + + )} +
+
+ + ); +}; + +export default ArtistStreamsPage; From 5dfa1aeda37b81931b16c00f59504de307aa7a3b Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 13 Apr 2024 17:43:51 -0400 Subject: [PATCH 2/5] feat(album): add streams page --- next.config.js | 4 - src/pages/album/{[id].tsx => [id]/index.tsx} | 21 ++- src/pages/album/[id]/streams.tsx | 148 +++++++++++++++++++ 3 files changed, 163 insertions(+), 10 deletions(-) rename src/pages/album/{[id].tsx => [id]/index.tsx} (92%) create mode 100644 src/pages/album/[id]/streams.tsx diff --git a/next.config.js b/next.config.js index d6ab3e53..5ddfc425 100755 --- a/next.config.js +++ b/next.config.js @@ -81,10 +81,6 @@ module.exports = withBundleAnalyzer({ source: '/track/:id/:ignore', destination: '/track/:id', }, - { - source: '/album/:id/:ignore', - destination: '/album/:id', - }, { source: '/:id/artists', destination: '/user/:id/artists', diff --git a/src/pages/album/[id].tsx b/src/pages/album/[id]/index.tsx similarity index 92% rename from src/pages/album/[id].tsx rename to src/pages/album/[id]/index.tsx index 2acd3bfc..f52ed8de 100644 --- a/src/pages/album/[id].tsx +++ b/src/pages/album/[id]/index.tsx @@ -244,12 +244,21 @@ const Album: NextPage = ({ album, tracks }) => { {user && (
{({ headerRef }) => ( - event('ALBUM_stream_track_click')} - /> + <> + event('ALBUM_stream_track_click')} + /> + {user.hasImported && ( + + + show all + + + )} + )}
)} diff --git a/src/pages/album/[id]/streams.tsx b/src/pages/album/[id]/streams.tsx new file mode 100644 index 00000000..1406a53f --- /dev/null +++ b/src/pages/album/[id]/streams.tsx @@ -0,0 +1,148 @@ +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 type { FC } from 'react'; +import { useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; +import { Spinner } from '@/components/Spinner'; +import { RecentStreams } from '@/components/RecentStreams'; +import clsx from 'clsx'; +import { useRouter } from 'next/router'; + +const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( +
+ +
+); + +type Props = SSRProps & { + album: statsfm.Album; +}; + +export const getServerSideProps: GetServerSideProps = 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 = ({ album }) => { + const api = useApi(); + const router = useRouter(); + const { user } = useAuth(); + + const [recentStreams, setRecentStreams] = useState([]); + 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 ( + <> + {`Your streams`} +
+ +
+
+ + + + back to {album.name} + + +

+ Your streams +

+
+
+
+
+ +
router.push(`/album/${album.id}`)} /> + } + > + {({ headerRef }) => ( + <> + + } + threshold={2000} + useWindow={true} + > + + + {!loadMore && ( +
+ +

No streams to load!

+
+ )} + + )} +
+
+ + ); +}; + +export default AlbumStreamsPage; From 92ea09efdf2937fcdbba1834fde0da37a94353fd Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 13 Apr 2024 18:09:09 -0400 Subject: [PATCH 3/5] feat(track): add streams page --- next.config.js | 4 - src/pages/track/{[id].tsx => [id]/index.tsx} | 22 ++- src/pages/track/[id]/streams.tsx | 148 +++++++++++++++++++ 3 files changed, 164 insertions(+), 10 deletions(-) rename src/pages/track/{[id].tsx => [id]/index.tsx} (95%) create mode 100644 src/pages/track/[id]/streams.tsx diff --git a/next.config.js b/next.config.js index 5ddfc425..f174f646 100755 --- a/next.config.js +++ b/next.config.js @@ -77,10 +77,6 @@ module.exports = withBundleAnalyzer({ source: '/:id/compare', destination: '/user/:id/compare', }, - { - source: '/track/:id/:ignore', - destination: '/track/:id', - }, { source: '/:id/artists', destination: '/user/:id/artists', diff --git a/src/pages/track/[id].tsx b/src/pages/track/[id]/index.tsx similarity index 95% rename from src/pages/track/[id].tsx rename to src/pages/track/[id]/index.tsx index 50a0de50..c14e48b8 100644 --- a/src/pages/track/[id].tsx +++ b/src/pages/track/[id]/index.tsx @@ -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, @@ -436,12 +437,21 @@ const Track: NextPage = ({ track }) => { description="Your recently played tracks" > {({ headerRef }) => ( - + <> + + {user.hasImported && ( + + + show all + + + )} + )} )} diff --git a/src/pages/track/[id]/streams.tsx b/src/pages/track/[id]/streams.tsx new file mode 100644 index 00000000..4506e308 --- /dev/null +++ b/src/pages/track/[id]/streams.tsx @@ -0,0 +1,148 @@ +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 type { FC } from 'react'; +import { useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroller'; +import { Spinner } from '@/components/Spinner'; +import { RecentStreams } from '@/components/RecentStreams'; +import clsx from 'clsx'; +import { useRouter } from 'next/router'; + +const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( +
+ +
+); + +type Props = SSRProps & { + track: statsfm.Track; +}; + +export const getServerSideProps: GetServerSideProps = 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 track; + try { + track = await api.tracks.get(parseInt(id, 10)); + } catch (e) { + return { notFound: true }; + } + + const user = await fetchUser(ctx); + + return { + props: { + user, + track, + }, + }; +}; + +const TrackStreamsPage: NextPage = ({ track }) => { + const api = useApi(); + const router = useRouter(); + const { user } = useAuth(); + + const [recentStreams, setRecentStreams] = useState([]); + const [loadMore, setLoadMore] = useState(true); + + const callbackRef = async () => { + const lastEndTime = recentStreams[recentStreams.length - 1] + ?.endTime as any as string; + + const streams = await api.users.trackStreams(user!.id, track.id, { + limit: 200, + before: new Date(lastEndTime).getTime() || new Date().getTime(), + }); + + if (streams.length === 0) setLoadMore(false); + setRecentStreams([...(recentStreams || []), ...streams.slice(1)]); + }; + + return ( + <> + {`Your streams`} +
+ +
+
+ + + + back to {track.name} + + +

+ Your streams +

+
+
+
+
+ +
router.push(`/track/${track.id}`)} /> + } + > + {({ headerRef }) => ( + <> + + } + threshold={2000} + useWindow={true} + > + + + {!loadMore && ( +
+ +

No streams to load!

+
+ )} + + )} +
+
+ + ); +}; + +export default TrackStreamsPage; From cc7a5bd604973dfcf16b980cd0d045ddec48a13a Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 13 Apr 2024 18:14:07 -0400 Subject: [PATCH 4/5] chore: extract ToolbarBackLink --- src/components/Section/ToolbarBackLink.tsx | 27 ++++++++++++++++++++++ src/pages/album/[id]/streams.tsx | 25 ++------------------ src/pages/artist/[id]/streams.tsx | 27 ++-------------------- src/pages/track/[id]/streams.tsx | 25 ++------------------ src/pages/user/[id]/streams.tsx | 27 +++------------------- 5 files changed, 36 insertions(+), 95 deletions(-) create mode 100644 src/components/Section/ToolbarBackLink.tsx diff --git a/src/components/Section/ToolbarBackLink.tsx b/src/components/Section/ToolbarBackLink.tsx new file mode 100644 index 00000000..f91a13cf --- /dev/null +++ b/src/components/Section/ToolbarBackLink.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx'; +import { useRouter } from 'next/router'; +import { 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 ( +
+ +
+ ); +}; diff --git a/src/pages/album/[id]/streams.tsx b/src/pages/album/[id]/streams.tsx index 1406a53f..4f1720ca 100644 --- a/src/pages/album/[id]/streams.tsx +++ b/src/pages/album/[id]/streams.tsx @@ -8,29 +8,11 @@ import type * as statsfm from '@/utils/statsfm'; import { Title } from '@/components/Title'; import { Section } from '@/components/Section/Section'; import { useApi, useAuth } from '@/hooks'; -import type { FC } from 'react'; import { useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; import { Spinner } from '@/components/Spinner'; import { RecentStreams } from '@/components/RecentStreams'; -import clsx from 'clsx'; -import { useRouter } from 'next/router'; - -const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( -
- -
-); +import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink'; type Props = SSRProps & { album: statsfm.Album; @@ -64,7 +46,6 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { const AlbumStreamsPage: NextPage = ({ album }) => { const api = useApi(); - const router = useRouter(); const { user } = useAuth(); const [recentStreams, setRecentStreams] = useState([]); @@ -107,9 +88,7 @@ const AlbumStreamsPage: NextPage = ({ album }) => {
router.push(`/album/${album.id}`)} /> - } + toolbar={} > {({ headerRef }) => ( <> diff --git a/src/pages/artist/[id]/streams.tsx b/src/pages/artist/[id]/streams.tsx index 0f4b8db7..05c7d8dd 100644 --- a/src/pages/artist/[id]/streams.tsx +++ b/src/pages/artist/[id]/streams.tsx @@ -8,29 +8,11 @@ import type * as statsfm from '@/utils/statsfm'; import { Title } from '@/components/Title'; import { Section } from '@/components/Section/Section'; import { useApi, useAuth } from '@/hooks'; -import type { FC } from 'react'; import { useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; import { Spinner } from '@/components/Spinner'; import { RecentStreams } from '@/components/RecentStreams'; -import clsx from 'clsx'; -import { useRouter } from 'next/router'; - -const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( -
- -
-); +import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink'; type Props = SSRProps & { artist: statsfm.Artist; @@ -64,7 +46,6 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { const ArtistStreamsPage: NextPage = ({ artist }) => { const api = useApi(); - const router = useRouter(); const { user } = useAuth(); const [recentStreams, setRecentStreams] = useState([]); @@ -107,11 +88,7 @@ const ArtistStreamsPage: NextPage = ({ artist }) => {
router.push(`/artist/${artist.id}`)} - /> - } + toolbar={} > {({ headerRef }) => ( <> diff --git a/src/pages/track/[id]/streams.tsx b/src/pages/track/[id]/streams.tsx index 4506e308..7db60241 100644 --- a/src/pages/track/[id]/streams.tsx +++ b/src/pages/track/[id]/streams.tsx @@ -8,29 +8,11 @@ import type * as statsfm from '@/utils/statsfm'; import { Title } from '@/components/Title'; import { Section } from '@/components/Section/Section'; import { useApi, useAuth } from '@/hooks'; -import type { FC } from 'react'; import { useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; import { Spinner } from '@/components/Spinner'; import { RecentStreams } from '@/components/RecentStreams'; -import clsx from 'clsx'; -import { useRouter } from 'next/router'; - -const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( -
- -
-); +import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink'; type Props = SSRProps & { track: statsfm.Track; @@ -64,7 +46,6 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { const TrackStreamsPage: NextPage = ({ track }) => { const api = useApi(); - const router = useRouter(); const { user } = useAuth(); const [recentStreams, setRecentStreams] = useState([]); @@ -107,9 +88,7 @@ const TrackStreamsPage: NextPage = ({ track }) => {
router.push(`/track/${track.id}`)} /> - } + toolbar={} > {({ headerRef }) => ( <> diff --git a/src/pages/user/[id]/streams.tsx b/src/pages/user/[id]/streams.tsx index da2edb73..f871986a 100644 --- a/src/pages/user/[id]/streams.tsx +++ b/src/pages/user/[id]/streams.tsx @@ -8,14 +8,12 @@ import type * as statsfm from '@/utils/statsfm'; import { Title } from '@/components/Title'; import { Section } from '@/components/Section/Section'; import { useApi } from '@/hooks'; -import type { FC } from 'react'; import { useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; import { Spinner } from '@/components/Spinner'; import { RecentStreams } from '@/components/RecentStreams'; -import clsx from 'clsx'; -import { useRouter } from 'next/router'; import formatter from '@/utils/formatter'; +import { ToolbarBackLink } from '@/components/Section/ToolbarBackLink'; const usePrivacyScope = ( scope: keyof statsfm.UserPrivacySettings, @@ -24,22 +22,6 @@ const usePrivacyScope = ( return user.privacySettings && user.privacySettings[scope]; }; -const Toolbar: FC<{ closeCallback: () => void }> = ({ closeCallback }) => ( -
- -
-); - type Props = SSRProps & { userProfile: statsfm.UserPublic; }; @@ -68,7 +50,6 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => { const StreamsPage: NextPage = ({ userProfile }) => { const api = useApi(); const scopePrivate = usePrivacyScope('streams', userProfile); - const router = useRouter(); const [recentStreams, setRecentStreams] = useState([]); const [loadMore, setLoadMore] = useState(true); @@ -117,10 +98,8 @@ const StreamsPage: NextPage = ({ userProfile }) => { headerStyle="!flex-row-reverse -mt-24 z-30 relative" title={`back to ${userProfile.displayName}`} toolbar={ - - router.push(`/${userProfile.customId || userProfile.id}`) - } + } > From c8a695f2571d484ba25c42bd94589cef7317cb24 Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 13 Apr 2024 19:28:27 -0400 Subject: [PATCH 5/5] chore: fix linting error --- src/components/Section/ToolbarBackLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Section/ToolbarBackLink.tsx b/src/components/Section/ToolbarBackLink.tsx index f91a13cf..acb5c527 100644 --- a/src/components/Section/ToolbarBackLink.tsx +++ b/src/components/Section/ToolbarBackLink.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import { useRouter } from 'next/router'; -import { FC } from 'react'; +import type { FC } from 'react'; import { MdChevronLeft } from 'react-icons/md'; export const ToolbarBackLink: FC<{ path: string }> = (path) => {