Skip to content

Commit

Permalink
[C-4071] Set up the collection page for SSR (#7963)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle-Shanks authored Mar 27, 2024
1 parent 370d127 commit 46cb255
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 102 deletions.
1 change: 1 addition & 0 deletions packages/common/src/models/SsrPageProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { full as FullSdk } from '@audius/sdk'
export type SsrPageProps = {
track?: FullSdk.TrackFull
user?: FullSdk.UserFull
collection?: FullSdk.PlaylistFull
error?: { isErrorPageOpen: boolean }
}
57 changes: 52 additions & 5 deletions packages/common/src/store/cache/collections/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import snakecaseKeys from 'snakecase-keys'

import { makePlaylist } from '~/services/audius-api-client/ResponseAdapter'
import { initialCacheState } from '~/store/cache/reducer'
import { makeUid } from '~/utils'

import { Collection, ID, Kind, PlaylistTrackId } from '../../../models'
import {
Collection,
ID,
Kind,
PlaylistTrackId,
SsrPageProps
} from '../../../models'
import {
AddEntriesAction,
AddSuccededAction,
Expand Down Expand Up @@ -89,9 +98,47 @@ const actionsMap = {
}
}

const reducer = (state = initialState, action: any) => {
const matchingReduceFunction = actionsMap[action.type]
if (!matchingReduceFunction) return state
return matchingReduceFunction(state, action)
const buildInitialState = (ssrPageProps?: SsrPageProps) => {
// If we have preloaded data from the server, populate the initial
// cache state with it
if (ssrPageProps?.collection) {
// @ts-ignore
const collection = makePlaylist(snakecaseKeys(ssrPageProps.collection))
if (!collection) return initialState

const id = collection.playlist_id
const uid = makeUid(Kind.COLLECTIONS, id)

return {
...initialState,
entries: {
[id]: {
metadata: collection,
_timestamp: Date.now()
}
},
uids: {
[uid]: collection.playlist_id
},
statuses: {
[id]: 'SUCCESS'
}
}
}
return initialState
}

const reducer =
(ssrPageProps?: SsrPageProps) =>
(state: CollectionsCacheState, action: any) => {
if (!state) {
// @ts-ignore
state = buildInitialState(ssrPageProps)
}

const matchingReduceFunction = actionsMap[action.type]
if (!matchingReduceFunction) return state
return matchingReduceFunction(state, action)
}

export default reducer
9 changes: 6 additions & 3 deletions packages/common/src/store/cache/users/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ const buildInitialState = (ssrPageProps?: SsrPageProps) => {
// If we have preloaded data from the server, populate the initial
// cache state with it
if (ssrPageProps) {
const apiUser = ssrPageProps.user ?? ssrPageProps.track?.user ?? null
const apiUser =
ssrPageProps.user ??
ssrPageProps.track?.user ??
ssrPageProps.collection?.user ??
null

// @ts-ignore
const user = apiUser ? makeUser(snakecaseKeys(apiUser)) : null
Expand All @@ -76,7 +80,7 @@ const buildInitialState = (ssrPageProps?: SsrPageProps) => {
const id = user.user_id
const uid = makeUid(Kind.USERS, id)

const initialCacheState = {
return {
...initialState,
entries: {
[id]: {
Expand All @@ -91,7 +95,6 @@ const buildInitialState = (ssrPageProps?: SsrPageProps) => {
[id]: 'SUCCESS'
}
}
return initialCacheState
}
return initialState
}
Expand Down
58 changes: 46 additions & 12 deletions packages/common/src/store/pages/collection/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import snakecaseKeys from 'snakecase-keys'

import { Kind, SsrPageProps } from '~/models'
import { Collection } from '~/models/Collection'
import { makePlaylist } from '~/services/audius-api-client/ResponseAdapter'
import tracksReducer, {
initialState as initialLineupState
} from '~/store/pages/collection/lineup/reducer'
import { makeUid } from '~/utils'

import { Status } from '../../../models/Status'
import { LineupActions, asLineup } from '../../../store/lineup/reducer'
Expand Down Expand Up @@ -88,18 +93,47 @@ const actionsMap = {

const tracksLineupReducer = asLineup(tracksPrefix, tracksReducer)

const reducer = (
state = initialState,
action: CollectionPageAction | LineupActions<Collection>
) => {
const updatedTracks = tracksLineupReducer(
state.tracks,
action as LineupActions<Collection>
)
if (updatedTracks !== state.tracks) return { ...state, tracks: updatedTracks }
const matchingReduceFunction = actionsMap[action.type]
if (!matchingReduceFunction) return state
return matchingReduceFunction(state, action as CollectionPageAction)
const buildInitialState = (ssrPageProps?: SsrPageProps) => {
// If we have preloaded data from the server, populate the initial
// page state with it
if (ssrPageProps?.collection) {
// @ts-ignore
const collection = makePlaylist(snakecaseKeys(ssrPageProps.collection))
if (!collection) return initialState

return {
...initialState,
collectionId: collection.playlist_id,
collectionUid: makeUid(Kind.COLLECTIONS, collection.playlist_id),
userUid: makeUid(Kind.USERS, collection.user.user_id),
status: Status.SUCCESS,
collectionPermalink: collection.permalink
}
}
return initialState
}

const reducer =
(ssrPageProps?: SsrPageProps) =>
(
state: CollectionsPageState,
action: CollectionPageAction | LineupActions<Collection>
) => {
if (!state) {
// @ts-ignore
state = buildInitialState(ssrPageProps)
}

const updatedTracks = tracksLineupReducer(
// @ts-ignore
state.tracks,
action as LineupActions<Collection>
)
if (updatedTracks !== state.tracks)
return { ...state, tracks: updatedTracks }
const matchingReduceFunction = actionsMap[action.type]
if (!matchingReduceFunction) return state
return matchingReduceFunction(state, action as CollectionPageAction)
}

export default reducer
1 change: 0 additions & 1 deletion packages/common/src/store/pages/profile/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,6 @@ const reducer =
}

const tracks = tracksLineupReducer(
// TODO: KJ - Fix this later, weird never type
// @ts-ignore
newEntry.tracks,
action as LineupActions<Track>
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const reducers = (

// Cache
// @ts-ignore
collections: asCache(collectionsReducer, Kind.COLLECTIONS),
collections: asCache(collectionsReducer(ssrPageProps), Kind.COLLECTIONS),
// TODO: Fix type error
// @ts-ignore
tracks: asCache(tracksReducer(ssrPageProps), Kind.TRACKS),
Expand Down Expand Up @@ -239,7 +239,7 @@ export const reducers = (
audioRewards: audioRewardsSlice.reducer,
audioTransactions: audioTransactionsSlice.reducer,
chat: chatReducer,
collection,
collection: collection(ssrPageProps),
deactivateAccount: deactivateAccountReducer,
feed,
explore: explorePageReducer,
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/utils/urlUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const collectionUrlRegex =
export const isCollectionUrl = (url: string) =>
new RegExp(collectionUrlRegex).test(url)
export const getPathFromPlaylistUrl = (url: string) => {
const results = new RegExp(trackUrlRegex).exec(url)
const results = new RegExp(collectionUrlRegex).exec(url)
if (!results) return null
return results[3]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CollectionPageTrackRecord
} from '@audius/common/store'

import { ClientOnly } from 'components/client-only/ClientOnly'
import {
CollectiblesPlaylistTableColumn,
CollectiblesPlaylistTable
Expand Down Expand Up @@ -252,57 +253,59 @@ const CollectionPage = ({
contentClassName={styles.pageContent}
scrollableSearch
>
<Tile
className={styles.bodyWrapper}
size='large'
elevation='mid'
dogEar={isPrivate ? DogEarType.HIDDEN : undefined}
>
<div className={styles.topSectionWrapper}>{topSection}</div>
{!collectionLoading && isEmpty ? (
<EmptyPage
isOwner={isOwner}
isAlbum={isAlbum}
text={customEmptyText}
/>
) : (
<div className={styles.tableWrapper}>
<TableComponent
// @ts-ignore
columns={tracksTableColumns}
wrapperClassName={styles.tracksTableWrapper}
key={playlistName}
loading={isNftPlaylist ? collectionLoading : tracksLoading}
userId={userId}
playing={playing}
playingIndex={playingIndex}
data={dataSource}
onClickRow={onClickRow}
onClickFavorite={onClickSave}
onClickRemove={isOwner ? onClickRemove : undefined}
onClickRepost={onClickRepostTrack}
onReorderTracks={onReorderTracks}
onSortTracks={onSortTracks}
isReorderable={
userId !== null &&
userId === playlistOwnerId &&
allowReordering &&
(!isAlbum || isEditAlbumsEnabled)
}
removeText={`${messages.remove} ${
isAlbum ? messages.type.album : messages.type.playlist
}`}
isAlbumPage={isAlbum}
<ClientOnly>
<Tile
className={styles.bodyWrapper}
size='large'
elevation='mid'
dogEar={isPrivate ? DogEarType.HIDDEN : undefined}
>
<div className={styles.topSectionWrapper}>{topSection}</div>
{!collectionLoading && isEmpty ? (
<EmptyPage
isOwner={isOwner}
isAlbum={isAlbum}
text={customEmptyText}
/>
</div>
)}
</Tile>
{isOwner && (!isAlbum || isEditAlbumsEnabled) && !isNftPlaylist ? (
<>
<Divider variant='default' className={styles.tileDivider} />
<SuggestedCollectionTracks collectionId={playlistId} />
</>
) : null}
) : (
<div className={styles.tableWrapper}>
<TableComponent
// @ts-ignore
columns={tracksTableColumns}
wrapperClassName={styles.tracksTableWrapper}
key={playlistName}
loading={isNftPlaylist ? collectionLoading : tracksLoading}
userId={userId}
playing={playing}
playingIndex={playingIndex}
data={dataSource}
onClickRow={onClickRow}
onClickFavorite={onClickSave}
onClickRemove={isOwner ? onClickRemove : undefined}
onClickRepost={onClickRepostTrack}
onReorderTracks={onReorderTracks}
onSortTracks={onSortTracks}
isReorderable={
userId !== null &&
userId === playlistOwnerId &&
allowReordering &&
(!isAlbum || isEditAlbumsEnabled)
}
removeText={`${messages.remove} ${
isAlbum ? messages.type.album : messages.type.playlist
}`}
isAlbumPage={isAlbum}
/>
</div>
)}
</Tile>
{isOwner && (!isAlbum || isEditAlbumsEnabled) && !isNftPlaylist ? (
<>
<Divider variant='default' className={styles.tileDivider} />
<SuggestedCollectionTracks collectionId={playlistId} />
</>
) : null}
</ClientOnly>
</Page>
)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/web/src/ssr/collection/+Page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Empty page, everything is handled in +onRenderHtml and +onRenderClient
export default function render() {
return null
}
46 changes: 46 additions & 0 deletions packages/web/src/ssr/collection/+onBeforeRender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Maybe } from '@audius/common/utils'
import type { full as FullSdk } from '@audius/sdk'
import type { PageContextServer } from 'vike/types'

import { audiusSdk } from '../sdk'

export type CollectionPageProps = {
collection: Maybe<FullSdk.PlaylistFull>
}

export async function onBeforeRender(pageContext: PageContextServer) {
const { handle, slug } = pageContext.routeParams

try {
// NOTE: This is the playlist api, but works for both albums and playlists
const { data: collections } =
await audiusSdk.full.playlists.getPlaylistByHandleAndSlug({
handle,
slug
})
const collection = collections?.[0]

return {
pageContext: {
pageProps: {
collection
}
}
}
} catch (e) {
console.error(
'Error fetching collection for collection page SSR',
'handle',
handle,
'slug',
slug,
'error',
e
)
return {
pageContext: {
pageProps: {}
}
}
}
}
6 changes: 6 additions & 0 deletions packages/web/src/ssr/collection/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { makePageRoute } from 'ssr/util'

export default makePageRoute(
['/@handle/playlist/@slug', '/@handle/album/@slug'],
'Collection Page'
)
2 changes: 1 addition & 1 deletion packages/web/src/ssr/profile/+route.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { makePageRoute } from 'ssr/util'

export default makePageRoute('/@handle', 'Profile Page')
export default makePageRoute(['/@handle'], 'Profile Page')
Loading

0 comments on commit 46cb255

Please sign in to comment.