diff --git a/.env.example b/.env.example
index 5b5d188..137a488 100644
--- a/.env.example
+++ b/.env.example
@@ -14,6 +14,4 @@ MUSICRITIC_PROD_FIREBASE_PROJECT_ID=
MUSICRITIC_PROD_FIREBASE_DATABASE_URL=
MUSICRITIC_PROD_FIREBASE_APP_ID=
MUSICRITIC_PROD_FIREBASE_AUTH_DOMAIN=
-MUSICRITIC_PROD_FIREBASE_API_KEY=
-
-
+MUSICRITIC_PROD_FIREBASE_API_KEY=
\ No newline at end of file
diff --git a/firebase.json b/firebase.json
index 07811e1..9594489 100644
--- a/firebase.json
+++ b/firebase.json
@@ -5,7 +5,7 @@
},
"ui": {
"enabled": true,
- "port": 5000
+ "port": 6000
}
}
}
diff --git a/next-env.d.ts b/next-env.d.ts
index c6643fd..9bc3dd4 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,3 +1,6 @@
///
///
///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/pages/tracks/[id].tsx b/pages/tracks/[id].tsx
new file mode 100644
index 0000000..c8abd00
--- /dev/null
+++ b/pages/tracks/[id].tsx
@@ -0,0 +1,58 @@
+import type {
+ GetStaticPaths,
+ GetStaticProps,
+ GetStaticPropsContext,
+} from 'next'
+import type { FC } from 'react'
+import type { Track } from 'spotify-web-sdk'
+import { useRouter } from 'next/router'
+
+import spotify from '@/node/lib/spotify'
+import PageHeader from '@/components/common/PageHeader'
+import TrackMetadata from '@/components/track/TrackMetadata'
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ paths: [],
+ fallback: true,
+ }
+}
+
+export const getStaticProps: GetStaticProps = async (
+ context: GetStaticPropsContext
+) => {
+ const trackId = (context.params?.id as string) ?? ''
+ const trackInfo = await spotify.getTrack(trackId)
+
+ return {
+ props: { trackInfo: JSON.parse(JSON.stringify(trackInfo.toJSON())) },
+ }
+}
+
+interface Props {
+ trackInfo: Track
+}
+
+const TrackPage: FC = ({ trackInfo }) => {
+ const { isFallback: isLoading } = useRouter()
+
+ return (
+
+
+
+ )
+}
+
+export default TrackPage
diff --git a/react/components/album/AlbumPageHeader/index.tsx b/react/components/album/AlbumPageHeader/index.tsx
index b4dc947..b185e79 100644
--- a/react/components/album/AlbumPageHeader/index.tsx
+++ b/react/components/album/AlbumPageHeader/index.tsx
@@ -6,7 +6,7 @@ import type { ThemeUIStyleObject } from 'theme-ui'
import Skeleton from '@/components/common/Skeleton'
import SkeletonImage from '@/components/common/SkeletonImage'
-import AlbumRatingBadge from './AlbumRatingBadge'
+import AlbumRatingBadge from '../../common/RatingBadge'
import AlbumMetadata from './AlbumMetadata'
const containerStyles: ThemeUIStyleObject = {
diff --git a/react/components/common/PageHeader.tsx b/react/components/common/PageHeader.tsx
new file mode 100644
index 0000000..c6eb2a8
--- /dev/null
+++ b/react/components/common/PageHeader.tsx
@@ -0,0 +1,50 @@
+import { Flex } from '@theme-ui/components'
+import type { ThemeUIStyleObject } from 'theme-ui'
+import type { PropsWithChildren } from 'hoist-non-react-statics/node_modules/@types/react'
+
+import RatingBadge from '@/components/common/RatingBadge'
+import SkeletonImage from '@/components/common/SkeletonImage'
+
+const containerStyles: ThemeUIStyleObject = {
+ flexDirection: ['column', 'column', 'row'],
+ backgroundColor: 'muted.0',
+ width: '100%',
+ padding: [3, 4, 6, 16],
+ justifyContent: 'space-between',
+ alignItems: 'center',
+}
+
+interface Props {
+ imgUrl: string
+ imgAlt: string
+ loading?: boolean
+ rating?: number
+}
+
+const PageHeader = ({
+ imgUrl,
+ imgAlt,
+ rating = 5,
+ loading = false,
+ children,
+}: PropsWithChildren) => {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+ )
+}
+
+export default PageHeader
diff --git a/react/components/album/AlbumPageHeader/AlbumRatingBadge.tsx b/react/components/common/RatingBadge.tsx
similarity index 85%
rename from react/components/album/AlbumPageHeader/AlbumRatingBadge.tsx
rename to react/components/common/RatingBadge.tsx
index 292873c..604cf02 100644
--- a/react/components/album/AlbumPageHeader/AlbumRatingBadge.tsx
+++ b/react/components/common/RatingBadge.tsx
@@ -19,8 +19,8 @@ interface Props {
rating: number
}
-const AlbumRatingBadge: FC = ({ rating }) => (
+const RatingBadge: FC = ({ rating }) => (
{rating}
)
-export default AlbumRatingBadge
+export default RatingBadge
diff --git a/react/components/track/TrackMetadata/index.tsx b/react/components/track/TrackMetadata/index.tsx
new file mode 100644
index 0000000..c4a78d0
--- /dev/null
+++ b/react/components/track/TrackMetadata/index.tsx
@@ -0,0 +1,60 @@
+import { Box, Heading, Link } from '@theme-ui/components'
+import { defineMessages, FormattedDate, FormattedMessage } from 'react-intl'
+
+const messages = defineMessages({
+ byArtists: { id: 'musicritic.album-page.by-artists' },
+ releaseDate: { id: 'musicritic.album-page.release-date' },
+ openOnSpotify: { id: 'musicritic.album-page.open-on-spotify' },
+})
+
+interface Props {
+ name: string
+ artist: string
+ album: string
+ length: string
+ releaseDate: string
+ spotifyUrl: string
+}
+
+const TrackMetadata = ({
+ name,
+ artist,
+ album,
+ length,
+ releaseDate,
+ spotifyUrl,
+}: Props) => {
+ const formattedReleaseDate = (
+
+ )
+
+ return (
+
+ {name}
+
+
+
+ {album}
+
+ {' '}
+ ยท {length}
+
+
+
+
+
+ )
+}
+
+export default TrackMetadata
diff --git a/react/providers/theme.ts b/react/providers/theme.ts
index c8099d9..64e258d 100644
--- a/react/providers/theme.ts
+++ b/react/providers/theme.ts
@@ -73,6 +73,7 @@ export const theme: Theme | Record = {
},
links: {
button: {
+ display: 'inline-block',
border: '1px solid',
borderColor: 'muted.4',
color: 'muted.4',