diff --git a/packages/nextjs/components/ClaimNft.tsx b/packages/nextjs/components/ClaimNft.tsx index 1092c72..cc4980f 100644 --- a/packages/nextjs/components/ClaimNft.tsx +++ b/packages/nextjs/components/ClaimNft.tsx @@ -47,13 +47,13 @@ const ClaimNFT: React.FC = () => { return (
-
+
{tasks.map((task, index) => ( -
-

{task.name}

-

{task.description}

+
+

{task.name}

+

{task.description}

-
diff --git a/packages/nextjs/components/dashboard/DashboardLayout.tsx b/packages/nextjs/components/dashboard/DashboardLayout.tsx index 950dc48..6b36f6d 100644 --- a/packages/nextjs/components/dashboard/DashboardLayout.tsx +++ b/packages/nextjs/components/dashboard/DashboardLayout.tsx @@ -1,5 +1,5 @@ import React from "react"; -import Header from "./Header"; +import Header from "../spotify/Header"; import Sidebar from "./Sidebar"; interface DashboardLayoutProps { @@ -13,7 +13,7 @@ const DashboardLayout: React.FC = ({ children }) => {
-
{children}
+
{children}
); diff --git a/packages/nextjs/components/dashboard/Sidebar.tsx b/packages/nextjs/components/dashboard/Sidebar.tsx index f6fdf74..401445d 100644 --- a/packages/nextjs/components/dashboard/Sidebar.tsx +++ b/packages/nextjs/components/dashboard/Sidebar.tsx @@ -3,25 +3,25 @@ import Link from "next/link"; const Sidebar: React.FC = () => { return ( -
+
Logo -
diff --git a/packages/nextjs/components/spotify/ArtistList.tsx b/packages/nextjs/components/spotify/ArtistList.tsx index 68eb102..fc90594 100644 --- a/packages/nextjs/components/spotify/ArtistList.tsx +++ b/packages/nextjs/components/spotify/ArtistList.tsx @@ -16,7 +16,7 @@ export default function ArtistList({ artists }: IProps) { heading={artist.name} images={artist.images} altTitle={artist.name} - subheading="Artist" + subheading={artist.genres && artist.genres.length > 0 ? artist.genres[0] : "artist"} imageRounded type="artist" /> diff --git a/packages/nextjs/components/spotify/CardItem.tsx b/packages/nextjs/components/spotify/CardItem.tsx index 43d80d7..a0a58ca 100644 --- a/packages/nextjs/components/spotify/CardItem.tsx +++ b/packages/nextjs/components/spotify/CardItem.tsx @@ -1,4 +1,3 @@ -import Image from "next/image"; import Link from "next/link"; import { RiMusic2Fill } from "react-icons/ri"; @@ -23,12 +22,14 @@ export default function CardItem({ }: IProps) { return ( -
+
{images.length > 0 ? ( - {altTitle} ) : (
@@ -36,7 +37,7 @@ export default function CardItem({
)}

{heading}

- {subheading &&
{subheading}
} + {subheading &&
{subheading}
}
); diff --git a/packages/nextjs/components/spotify/CardItemGrid.tsx b/packages/nextjs/components/spotify/CardItemGrid.tsx index 164cc8d..9201b8d 100644 --- a/packages/nextjs/components/spotify/CardItemGrid.tsx +++ b/packages/nextjs/components/spotify/CardItemGrid.tsx @@ -1,3 +1,3 @@ export default function CardItemGrid({ children }: any) { - return
{children}
; + return
{children}
; } diff --git a/packages/nextjs/components/spotify/Header.tsx b/packages/nextjs/components/spotify/Header.tsx index 1f3eb01..504b01b 100644 --- a/packages/nextjs/components/spotify/Header.tsx +++ b/packages/nextjs/components/spotify/Header.tsx @@ -26,7 +26,7 @@ export default function Header() { } return ( -
+
+
+
+ +
+
+ +
+

{artist.name}

+

LOS ANGELES, CALIFORNIA

+
+ + + + + + +
+ {/* {isArtistLiked() && ( +
+

You liked this artists music!

+
+ )} */} +

+ An artist of considerable range, Jenna the name taken by Melbourne-raised, Brooklyn-based Nick Murphy + writes, performs and records all of his own music, giving it a warm, intimate feel with a solid groove + structure. An artist of considerable range. + Show More +

+
+
+ + {/* */} + +
+
+ {artist && ( + <> + {artist.images && artist.images.length > 0 ? ( + {artist.name} + ) : ( +
+ +
+ )} +
+

{artist.name}

+ {artist.followers?.total.toLocaleString()} followers +
+ {artist.genres?.map(genre => ( + {genre} + ))} +
+
+ + )} +
+ +
+ +
+ +
+
+ + {artistAlbums?.items.length > 0 && ( +
+ + +
+ )} + + {artistSingles?.items.length > 0 && ( +
+ + +
+ )} + + {artistAppearsOn?.items.length > 0 && ( +
+ + +
+ )} + + {artistCompilation?.items.length > 0 && ( +
+ + +
+ )} + + {relatedArtists?.artists.length > 0 && ( +
+ + +
+ )} + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const artistId = ctx.params?.artistId; + const artist = await customGet(`https://api.spotify.com/v1/artists/${artistId}`, session); + console.log(artist); + + const artistTracks = await customGet( + `https://api.spotify.com/v1/artists/${artistId}/top-tracks?market=from_token`, + session, + ); + + const artistAlbums = await customGet( + `https://api.spotify.com/v1/artists/${artistId}/albums?include_groups=album`, + session, + ); + + const artistSingles = await customGet( + `https://api.spotify.com/v1/artists/${artistId}/albums?include_groups=single`, + session, + ); + + const artistAppearsOn = await customGet( + `https://api.spotify.com/v1/artists/${artistId}/albums?include_groups=appears_on`, + session, + ); + + const artistCompilation = await customGet( + `https://api.spotify.com/v1/artists/${artistId}/albums?include_groups=compilation`, + session, + ); + + const relatedArtists = await customGet(`https://api.spotify.com/v1/artists/${artistId}/related-artists`, session); + + /* ---- Check if user follows current artist */ + const userFollowsArtist = await customGet( + `https://api.spotify.com/v1/me/following/contains?type=artist&ids=${artistId}`, + session, + ); + + /* ---- Get user liked tracks */ + const userLikedTracks = await customGet(`https://api.spotify.com/v1/me/tracks?market=from_token&limit=50`, session); + // console.log("User Liked Tracks:", userLikedTracks.items); + + return { + props: { + artist, + artistTracks: artistTracks.tracks, + artistAlbums, + artistSingles, + artistAppearsOn, + artistCompilation, + relatedArtists, + userFollowsArtist: userFollowsArtist[0], + userLikedTracks: userLikedTracks.items, + }, + }; +}; diff --git a/packages/nextjs/pages/artist/index.tsx b/packages/nextjs/pages/artist/claim.tsx similarity index 100% rename from packages/nextjs/pages/artist/index.tsx rename to packages/nextjs/pages/artist/claim.tsx diff --git a/packages/nextjs/pages/dashboard/artists.tsx b/packages/nextjs/pages/dashboard/artists.tsx new file mode 100644 index 0000000..fa99fd7 --- /dev/null +++ b/packages/nextjs/pages/dashboard/artists.tsx @@ -0,0 +1,43 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import DashboardLayout from "~~/components/dashboard/DashboardLayout"; +import ArtistList from "~~/components/spotify/ArtistList"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import { MySession } from "~~/types/session"; +import { Artist } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + followedArtists: Artist[]; +} + +export default function FollowedArtists({ followedArtists }: IProps) { + return ( + + + + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const followedArtists = await customGet(`https://api.spotify.com/v1/me/following?type=artist&limit=50`, session); + console.log(followedArtists); + + return { props: { followedArtists: followedArtists.artists.items } }; +}; diff --git a/packages/nextjs/pages/login.tsx b/packages/nextjs/pages/login.tsx index 758706e..cf1003a 100644 --- a/packages/nextjs/pages/login.tsx +++ b/packages/nextjs/pages/login.tsx @@ -2,7 +2,7 @@ import { signIn } from "next-auth/react"; export default function Login() { const handleLogin = () => { - signIn("spotify", { callbackUrl: "http://localhost:3000/dashboard" }); + signIn("spotify", { callbackUrl: "http://localhost:3000/dashboard/artists" }); }; return ( diff --git a/packages/nextjs/pages/search/[query]/albums.tsx b/packages/nextjs/pages/search/[query]/albums.tsx new file mode 100644 index 0000000..9a0b979 --- /dev/null +++ b/packages/nextjs/pages/search/[query]/albums.tsx @@ -0,0 +1,47 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import AlbumList from "~~/components/spotify/AlbumList"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import { MySession } from "~~/types/session"; +import { Album } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + query: string; + searchAlbums: { + albums: { + items: Album[]; + }; + }; +} + +export default function SearchAlbums({ query, searchAlbums }: IProps) { + return ( + + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const query = ctx.params?.query; + const searchAlbums = await customGet( + `https://api.spotify.com/v1/search?q=${query}&market=from_token&type=album&limit=50`, + session, + ); + return { props: { query, searchAlbums } }; +}; diff --git a/packages/nextjs/pages/search/[query]/artists.tsx b/packages/nextjs/pages/search/[query]/artists.tsx new file mode 100644 index 0000000..1223865 --- /dev/null +++ b/packages/nextjs/pages/search/[query]/artists.tsx @@ -0,0 +1,47 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import ArtistList from "~~/components/spotify/ArtistList"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import { MySession } from "~~/types/session"; +import { Artist } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + query: string; + searchArtists: { + artists: { + items: Artist[]; + }; + }; +} + +export default function SearchArtists({ query, searchArtists }: IProps) { + return ( + + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const query = ctx.params?.query; + const searchArtists = await customGet( + `https://api.spotify.com/v1/search?q=${query}&market=from_token&type=artist&limit=50`, + session, + ); + return { props: { query, searchArtists } }; +}; diff --git a/packages/nextjs/pages/search/[query]/index.tsx b/packages/nextjs/pages/search/[query]/index.tsx new file mode 100644 index 0000000..3f3913c --- /dev/null +++ b/packages/nextjs/pages/search/[query]/index.tsx @@ -0,0 +1,138 @@ +import { Fragment } from "react"; +import Link from "next/link"; +import { useSpotify } from "../../../context/SpotifyContext"; +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import DashboardLayout from "~~/components/dashboard/DashboardLayout"; +import AlbumList from "~~/components/spotify/AlbumList"; +import ArtistList from "~~/components/spotify/ArtistList"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import PlaylistList from "~~/components/spotify/PlaylistList"; +import { MySession } from "~~/types/session"; +import { SearchResults, Track } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { fmtMSS } from "~~/utils/beat-bridge/formatDuration"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + query: string; + searchResults: SearchResults; +} + +export default function Search({ query, searchResults }: IProps) { + const { setCurrentTrack } = useSpotify(); + + const playTrack = (track: Track) => { + if (track.preview_url) { + setCurrentTrack(track); + } + }; + + return ( + + + {searchResults && ( + <> +
+ + + + + {searchResults.tracks?.items?.slice(0, 5).map(track => ( +
+
+
+
+ {track.name} +
+ +
+
playTrack(track)} + > + {track.name} +
+ +
+ + {track.artists.map((artist, index) => ( + + + {index !== 0 ? `, ${artist.name}` : artist.name} + + + ))} + +
+
+
+
+ +
+ {fmtMSS(track.duration_ms)} +
+
+ ))} +
+ + {searchResults.artists && searchResults.artists?.items.length > 0 ? ( +
+ + + + +
+ ) : ( + <> + )} + +
+ + + + +
+ +
+ + + + +
+ + )} +
+
+ ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const query = ctx.params?.query; + const searchResults = await customGet( + `https://api.spotify.com/v1/search?q=${query}&market=from_token&type=album,artist,track,playlist&limit=50`, + session, + ); + return { props: { query, searchResults } }; +}; diff --git a/packages/nextjs/pages/search/[query]/playlists.tsx b/packages/nextjs/pages/search/[query]/playlists.tsx new file mode 100644 index 0000000..a0322f1 --- /dev/null +++ b/packages/nextjs/pages/search/[query]/playlists.tsx @@ -0,0 +1,47 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import PlaylistList from "~~/components/spotify/PlaylistList"; +import { MySession } from "~~/types/session"; +import { PlaylistType } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + query: string; + searchPlaylists: { + playlists: { + items: PlaylistType[]; + }; + }; +} + +export default function SearchPlaylists({ query, searchPlaylists }: IProps) { + return ( + + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = ((await getSession(ctx)) as MySession) || null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const query = ctx.params?.query; + const searchPlaylists = await customGet( + `https://api.spotify.com/v1/search?q=${query}&market=from_token&type=playlist&limit=50`, + session, + ); + return { props: { query, searchPlaylists } }; +}; diff --git a/packages/nextjs/pages/search/[query]/tracks.tsx b/packages/nextjs/pages/search/[query]/tracks.tsx new file mode 100644 index 0000000..94f47e7 --- /dev/null +++ b/packages/nextjs/pages/search/[query]/tracks.tsx @@ -0,0 +1,47 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import TracksTable from "~~/components/spotify/TracksTable"; +import { MySession } from "~~/types/session"; +import { Track } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + query: string; + searchTracks: { + tracks: { + items: Track[]; + }; + }; +} + +export default function SearchTracks({ query, searchTracks }: IProps) { + return ( + + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const query = ctx.params?.query; + const searchTracks = await customGet( + `https://api.spotify.com/v1/search?q=${query}&market=from_token&type=track&limit=50`, + session, + ); + return { props: { query, searchTracks } }; +}; diff --git a/packages/nextjs/pages/search/index.tsx b/packages/nextjs/pages/search/index.tsx new file mode 100644 index 0000000..a3eb8be --- /dev/null +++ b/packages/nextjs/pages/search/index.tsx @@ -0,0 +1,59 @@ +import { GetServerSideProps } from "next"; +import { getSession } from "next-auth/react"; +import DashboardLayout from "~~/components/dashboard/DashboardLayout"; +import CardItem from "~~/components/spotify/CardItem"; +import CardItemGrid from "~~/components/spotify/CardItemGrid"; +import Heading from "~~/components/spotify/Heading"; +import Layout from "~~/components/spotify/Layout"; +import { MySession } from "~~/types/session"; +import "~~/types/spotify"; +import { Category } from "~~/types/spotify"; +import { customGet } from "~~/utils/beat-bridge/customGet"; +import { isAuthenticated } from "~~/utils/beat-bridge/isAuthenticated"; + +interface IProps { + categories: { + categories: { + items: Category[]; + }; + }; +} + +export default function Search({ categories }: IProps) { + return ( + + + + + + {categories?.categories.items.map((category: { id: string; name: string; icons: any }) => ( + + ))} + + + + ); +} + +export const getServerSideProps: GetServerSideProps = async ctx => { + const session = (await getSession(ctx)) as MySession | null; + + if (!(await isAuthenticated(session))) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const categories = await customGet("https://api.spotify.com/v1/browse/categories?limit=50&country=IN", session); + return { props: { categories } }; +}; diff --git a/packages/nextjs/types/spotify.ts b/packages/nextjs/types/spotify.ts index 04faea9..4d4dae5 100644 --- a/packages/nextjs/types/spotify.ts +++ b/packages/nextjs/types/spotify.ts @@ -30,7 +30,7 @@ export interface Artist { export interface Track { id: string; name: string; - album: Album; + album?: Album; artists: [Artist]; duration_ms: number; preview_url: string; @@ -71,3 +71,9 @@ export interface SearchResults { items: Track[]; }; } +export interface Category { + id: string; + icons: Image[]; + href: string; + name: string; +} diff --git a/packages/nextjs/utils/beat-bridge/customGet.ts b/packages/nextjs/utils/beat-bridge/customGet.ts new file mode 100644 index 0000000..729b393 --- /dev/null +++ b/packages/nextjs/utils/beat-bridge/customGet.ts @@ -0,0 +1,11 @@ +import { MySession } from "~~/types/session"; + +export const customGet = async (url: string, session: MySession | null) => { + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${session?.user.accessToken || ""}`, + }, + }).then(res => res.json()); + + return res; +};