From b835f3b564615a627005f2ddc71607d912a5788c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 00:21:22 +0000 Subject: [PATCH 1/3] Fix coin detail pages 404ing on prod by adding SSR routes The SSR coins route only matched /clubs and /coins exactly, so URLs like /coins/0003HZ had no SSR route handler. This caused them to fall through to the track route (/@handle/@slug) which treated "coins" as a user handle. Add all coin sub-routes (/coins/@ticker, /coins/@ticker/buy, etc.) to the SSR coins route so Vike correctly identifies these as coin pages. https://claude.ai/code/session_017qmCwviCkRf65xdCuoZFXq --- packages/web/src/ssr/coins/+route.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/web/src/ssr/coins/+route.ts b/packages/web/src/ssr/coins/+route.ts index 6f3eb882eda..faa2204ef39 100644 --- a/packages/web/src/ssr/coins/+route.ts +++ b/packages/web/src/ssr/coins/+route.ts @@ -1,3 +1,17 @@ import { makePageRoute } from 'ssr/util' -export default makePageRoute(['/clubs', '/coins'], 'Fan Clubs Page') +export default makePageRoute( + [ + '/clubs', + '/coins', + '/coins/create', + '/coins/sort', + '/coins/@ticker', + '/coins/@ticker/buy', + '/coins/@ticker/redeem', + '/coins/@ticker/redeem/@code', + '/coins/@ticker/exclusive-tracks', + '/coins/@ticker/edit' + ], + 'Fan Clubs Page' +) From 9ac701fecfb39d83b905927019c6282c0a376687 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 00:29:24 +0000 Subject: [PATCH 2/3] Add /clubs/:ticker routes mirroring /coins/:ticker for backwards compat - Add CLUB_DETAIL_PAGE, CLUB_DETAIL_BUY_PAGE, CLUB_REDEEM_PAGE, etc. route constants alongside existing COIN_* routes - Register /clubs/:ticker routes in both mobile and desktop WebPlayer sections - Update SSR coins route to handle all /clubs/* sub-routes - Fix FanClubDetailPageRoute and CoinExclusiveTracksLegacyRedirect to preserve /clubs vs /coins URL prefix when normalizing ticker case - Both /coins/TICKER and /clubs/TICKER now render the same fan club detail page https://claude.ai/code/session_017qmCwviCkRf65xdCuoZFXq --- packages/common/src/utils/route.ts | 21 +++++ packages/web/src/app/web-player/WebPlayer.tsx | 86 ++++++++++++++++++- .../FanClubDetailPage.tsx | 9 +- packages/web/src/ssr/coins/+route.ts | 10 ++- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/packages/common/src/utils/route.ts b/packages/common/src/utils/route.ts index 8de0254b7cf..7cf6d07a3f2 100644 --- a/packages/common/src/utils/route.ts +++ b/packages/common/src/utils/route.ts @@ -77,10 +77,17 @@ export const COIN_DETAIL_BUY_PAGE = '/coins/:ticker/buy' export const COIN_REDEEM_PAGE = '/coins/:ticker/redeem/:code?' export const COIN_EXCLUSIVE_TRACKS_PAGE = '/coins/:ticker/exclusive-tracks' export const EDIT_COIN_DETAILS_PAGE = '/coins/:ticker/edit' +/** Primary club detail route */ +export const CLUB_DETAIL_PAGE = '/clubs/:ticker' +export const CLUB_DETAIL_BUY_PAGE = '/clubs/:ticker/buy' +export const CLUB_REDEEM_PAGE = '/clubs/:ticker/redeem/:code?' +export const CLUB_EXCLUSIVE_TRACKS_PAGE = '/clubs/:ticker/exclusive-tracks' +export const EDIT_CLUB_DETAILS_PAGE = '/clubs/:ticker/edit' export const WALLET_PAGE = '/wallet' export const WALLET_GUIDE_PAGE = '/wallet/guide' export const CASH_PAGE = '/cash' export const COINS_CREATE_PAGE = '/coins/create' +export const CLUBS_CREATE_PAGE = '/clubs/create' /** Legacy explore URL; app redirects to CLUBS_EXPLORE_PAGE. */ export const COINS_EXPLORE_PAGE = '/coins' /** Fan club discovery (primary); same UI as legacy /coins. */ @@ -160,8 +167,11 @@ export const FOLLOWING_USERS_ROUTE = '/following' export const FOLLOWERS_USERS_ROUTE = '/followers' export const LEADERBOARD_USERS_ROUTE = '/leaderboard' export const COIN_DETAIL_MOBILE_WEB_ROUTE = '/coins/:ticker/details' +export const CLUB_DETAIL_MOBILE_WEB_ROUTE = '/clubs/:ticker/details' export const COIN_EXCLUSIVE_TRACKS_MOBILE_ROUTE = '/coins/:ticker/exclusive-tracks/mobile' +export const CLUB_EXCLUSIVE_TRACKS_MOBILE_ROUTE = + '/clubs/:ticker/exclusive-tracks/mobile' export const ACCOUNT_SETTINGS_PAGE = '/settings/account' export const NOTIFICATION_SETTINGS_PAGE = '/settings/notifications' export const ABOUT_SETTINGS_PAGE = '/settings/about' @@ -243,6 +253,7 @@ export const authenticatedRoutes = [ PAYMENTS_PAGE, WITHDRAWALS_PAGE, COINS_CREATE_PAGE, + CLUBS_CREATE_PAGE, WALLET_GUIDE_PAGE, CASH_PAGE ] @@ -297,12 +308,15 @@ export const orderedRoutes = [ AUDIO_PAGE, WALLET_AUDIO_PAGE, COIN_DETAIL_PAGE, + CLUB_DETAIL_PAGE, EDIT_COIN_DETAILS_PAGE, + EDIT_CLUB_DETAILS_PAGE, WALLET_PAGE, CASH_PAGE, COINS_EXPLORE_PAGE, CLUBS_EXPLORE_PAGE, COINS_CREATE_PAGE, + CLUBS_CREATE_PAGE, WALLET_GUIDE_PAGE, REWARDS_PAGE, SETTINGS_PAGE, @@ -354,6 +368,7 @@ export const staticRoutes = new Set([ COINS_EXPLORE_PAGE, CLUBS_EXPLORE_PAGE, COINS_CREATE_PAGE, + CLUBS_CREATE_PAGE, WALLET_AUDIO_PAGE, CASH_PAGE, REWARDS_PAGE, @@ -466,5 +481,11 @@ export const searchPage = (searchOptions: SearchOptions) => { export const coinPage = (ticker: string) => `/coins/${formatTickerForUrl(ticker)}` +export const clubPage = (ticker: string) => + `/clubs/${formatTickerForUrl(ticker)}` + export const coinRedeemPage = (ticker: string, code?: string) => `/coins/${formatTickerForUrl(ticker)}/redeem${code ? `/${code}` : ''}` + +export const clubRedeemPage = (ticker: string, code?: string) => + `/clubs/${formatTickerForUrl(ticker)}/redeem${code ? `/${code}` : ''}` diff --git a/packages/web/src/app/web-player/WebPlayer.tsx b/packages/web/src/app/web-player/WebPlayer.tsx index 42c8c7b7c04..dd94a60b997 100644 --- a/packages/web/src/app/web-player/WebPlayer.tsx +++ b/packages/web/src/app/web-player/WebPlayer.tsx @@ -18,6 +18,14 @@ import { Client, Status } from '@audius/common/models' import { StringKeys } from '@audius/common/services' import { COIN_DETAIL_BUY_PAGE, + CLUB_DETAIL_PAGE, + CLUB_DETAIL_BUY_PAGE, + CLUB_REDEEM_PAGE, + CLUB_EXCLUSIVE_TRACKS_PAGE, + CLUB_EXCLUSIVE_TRACKS_MOBILE_ROUTE, + CLUB_DETAIL_MOBILE_WEB_ROUTE, + EDIT_CLUB_DETAILS_PAGE, + CLUBS_CREATE_PAGE, guestRoutes } from '@audius/common/src/utils/route' import { route } from '@audius/common/utils' @@ -382,10 +390,13 @@ const FanClubDetailPageRoute = ({ const { ticker } = params if (ticker && ticker !== ticker.toUpperCase()) { + // Preserve the current path prefix (/clubs or /coins) + const isClubsRoute = location.pathname.startsWith('/clubs') + const detailPage = isClubsRoute ? CLUB_DETAIL_PAGE : COIN_DETAIL_PAGE return ( { const params = useParams<{ ticker?: string }>() + const location = useLocation() const ticker = (params.ticker ?? '').toUpperCase() - return + const isClubsRoute = location.pathname.startsWith('/clubs') + const detailPage = isClubsRoute ? CLUB_DETAIL_PAGE : COIN_DETAIL_PAGE + return } type HomePageRedirectProps = { @@ -924,32 +938,62 @@ const WebPlayer = (props: WebPlayerProps) => { path='/coins/sort' element={} /> + } + /> } /> + } /> } /> + + } + /> } /> + + } + /> } /> + } /> } /> + } + /> } /> + } + /> } /> + } + /> } /> } /> } /> @@ -1166,6 +1210,10 @@ const WebPlayer = (props: WebPlayerProps) => { path={COIN_DETAIL_MOBILE_WEB_ROUTE} element={} /> + } + /> } /> ) : ( @@ -1194,6 +1242,10 @@ const WebPlayer = (props: WebPlayerProps) => { path={COIN_DETAIL_MOBILE_WEB_ROUTE} element={} /> + } + /> } @@ -1328,31 +1380,57 @@ const WebPlayer = (props: WebPlayerProps) => { element={} /> } /> + } /> } /> + + } + /> } /> + + } + /> } /> + } /> } /> + } + /> } /> + } + /> } /> + } + /> } /> } /> } /> @@ -1499,6 +1577,10 @@ const WebPlayer = (props: WebPlayerProps) => { path={COIN_DETAIL_MOBILE_WEB_ROUTE} element={} /> + } + /> } diff --git a/packages/web/src/pages/fan-club-detail-page/FanClubDetailPage.tsx b/packages/web/src/pages/fan-club-detail-page/FanClubDetailPage.tsx index 6b71c210064..5dbb9d6b64c 100644 --- a/packages/web/src/pages/fan-club-detail-page/FanClubDetailPage.tsx +++ b/packages/web/src/pages/fan-club-detail-page/FanClubDetailPage.tsx @@ -4,10 +4,10 @@ import { useUser } from '@audius/common/api' import { coinDetailsMessages } from '@audius/common/messages' -import { coinPage } from '@audius/common/src/utils/route' +import { coinPage, clubPage } from '@audius/common/src/utils/route' import { formatTickerForUrl, route } from '@audius/common/utils' import { Flex, LoadingSpinner } from '@audius/harmony' -import { Navigate, useParams } from 'react-router' +import { Navigate, useLocation, useParams } from 'react-router' import { Header } from 'components/header/desktop/Header' import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' @@ -87,9 +87,11 @@ const MobileFanClubDetailPageContent = ({ export const FanClubDetailPage = () => { const { ticker } = useParams<{ ticker?: string }>() + const location = useLocation() const isMobile = useIsMobile() const { data: currentUserId } = useCurrentUserId() const formattedTicker = formatTickerForUrl(ticker ?? '') + const isClubsRoute = location.pathname.startsWith('/clubs') const { data: coin, @@ -107,7 +109,8 @@ export const FanClubDetailPage = () => { } if (ticker !== formattedTicker) { - return + const detailPage = isClubsRoute ? clubPage : coinPage + return } if (isError || (isSuccess && !coin)) { diff --git a/packages/web/src/ssr/coins/+route.ts b/packages/web/src/ssr/coins/+route.ts index faa2204ef39..dfcc914c195 100644 --- a/packages/web/src/ssr/coins/+route.ts +++ b/packages/web/src/ssr/coins/+route.ts @@ -11,7 +11,15 @@ export default makePageRoute( '/coins/@ticker/redeem', '/coins/@ticker/redeem/@code', '/coins/@ticker/exclusive-tracks', - '/coins/@ticker/edit' + '/coins/@ticker/edit', + '/clubs/create', + '/clubs/sort', + '/clubs/@ticker', + '/clubs/@ticker/buy', + '/clubs/@ticker/redeem', + '/clubs/@ticker/redeem/@code', + '/clubs/@ticker/exclusive-tracks', + '/clubs/@ticker/edit' ], 'Fan Clubs Page' ) From ee7d6bdcaf7f40139c104446bf8b4ca253a974e6 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Thu, 2 Apr 2026 17:36:10 -0700 Subject: [PATCH 3/3] Fix some lint --- .../src/api/tan-query/comments/useDeleteTextPost.ts | 9 ++------- .../coin-details-screen/components/FanClubTab.tsx | 4 +--- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/common/src/api/tan-query/comments/useDeleteTextPost.ts b/packages/common/src/api/tan-query/comments/useDeleteTextPost.ts index c7cc968f3f1..f0e6faef09d 100644 --- a/packages/common/src/api/tan-query/comments/useDeleteTextPost.ts +++ b/packages/common/src/api/tan-query/comments/useDeleteTextPost.ts @@ -39,10 +39,7 @@ export const useDeleteTextPost = () => { newState.pages = newState.pages.map((page: FanClubFeedItem[]) => page.filter( (item) => - !( - item.itemType === 'text_post' && - item.commentId === commentId - ) + !(item.itemType === 'text_post' && item.commentId === commentId) ) ) } @@ -69,9 +66,7 @@ export const useDeleteTextPost = () => { name: 'Comments', feature: Feature.Comments }) - dispatch( - toast({ content: 'There was an error deleting the post.' }) - ) + dispatch(toast({ content: 'There was an error deleting the post.' })) // Reset to server state queryClient.invalidateQueries({ queryKey: getFanClubFeedQueryKey({ diff --git a/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx b/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx index 244f687b36b..2d146c1c579 100644 --- a/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx +++ b/packages/mobile/src/screens/coin-details-screen/components/FanClubTab.tsx @@ -377,9 +377,7 @@ export const FanClubTab = ({ mint, onSwitchToCoinTab }: FanClubTabProps) => { ) : null} - {membershipKnown ? ( - - ) : null} + {membershipKnown ? : null} ) }