1- import { useCallback , useEffect , useMemo , useState } from 'react'
1+ import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
22
33import {
44 useCurrentAccount ,
@@ -19,6 +19,7 @@ import {
1919 libraryPageTracksLineupActions as tracksActions ,
2020 libraryPageActions as saveActions ,
2121 libraryPageSelectors ,
22+ LibraryCategory ,
2223 LibraryPageTabs ,
2324 queueSelectors ,
2425 tracksSocialActions as socialActions ,
@@ -34,15 +35,29 @@ import { route } from '@audius/common/utils'
3435import { GetUserLibraryTracksSortMethodEnum } from '@audius/sdk'
3536import { debounce } from 'lodash'
3637import { useDispatch , useSelector } from 'react-redux'
38+ import { useLocation , useNavigate , useSearchParams } from 'react-router'
3739
3840import { TrackEvent , make } from 'common/store/analytics/actions'
3941import { push } from 'utils/navigation'
4042
43+ import {
44+ getTabFromPathname ,
45+ getLibraryPath ,
46+ categoryFromFilterParam ,
47+ filterParamFromCategory ,
48+ LIBRARY_FILTER_PARAM ,
49+ LIBRARY_SEARCH_PARAM
50+ } from '../lib/libraryUrl'
51+
4152const { profilePage } = route
4253const { makeGetCurrent } = queueSelectors
4354const { getPlaying, getBuffering } = playerSelectors
44- const { getLibraryTracksLineup, hasReachedEnd, getTracksCategory } =
45- libraryPageSelectors
55+ const {
56+ getLibraryTracksLineup,
57+ hasReachedEnd,
58+ getTracksCategory,
59+ getCategory
60+ } = libraryPageSelectors
4661const { updatedPlaylistViewed } = playlistUpdatesActions
4762
4863const { selectAllPlaylistUpdateIds } = playlistUpdatesSelectors
@@ -77,6 +92,11 @@ type LibraryPageState = {
7792
7893export const useLibraryPage = ( ) => {
7994 const dispatch = useDispatch ( )
95+ const location = useLocation ( )
96+ const navigate = useNavigate ( )
97+ const [ searchParams , setSearchParams ] = useSearchParams ( )
98+ const lastCategoryUrlRef = useRef < string | null > ( null )
99+
80100 const currentTrack = useCurrentTrack ( )
81101 const tracks = useLineupTable ( getLibraryTracksLineup )
82102
@@ -101,16 +121,70 @@ export const useLibraryPage = () => {
101121 }
102122 } )
103123
124+ const urlTab = getTabFromPathname ( location . pathname )
125+ const urlFilter = searchParams . get ( LIBRARY_FILTER_PARAM )
126+ const urlSearch = searchParams . get ( LIBRARY_SEARCH_PARAM ) ?? ''
127+
104128 const [ state , setState ] = useState < LibraryPageState > ( {
105- filterText : '' ,
129+ filterText : urlSearch ,
106130 sortMethod : '' ,
107131 sortDirection : '' ,
108132 initialOrder : null ,
109133 allTracksFetched : false ,
110- currentTab : ProfileTabs . TRACKS ,
134+ currentTab : urlTab ,
111135 shouldReturnToTrackPurchases : false
112136 } )
113137
138+ const selectedCategoryForUrlTab = useSelector (
139+ ( state : Parameters < typeof getCategory > [ 0 ] ) =>
140+ getCategory ( state , { currentTab : urlTab } )
141+ )
142+
143+ // Sync from URL to state and Redux when location changes
144+ useEffect ( ( ) => {
145+ const tab = getTabFromPathname ( location . pathname )
146+ let category = categoryFromFilterParam ( urlFilter )
147+ if (
148+ tab === LibraryPageTabs . PLAYLISTS &&
149+ category === LibraryCategory . Purchase
150+ ) {
151+ category = LibraryCategory . All
152+ }
153+ const search = urlSearch
154+
155+ lastCategoryUrlRef . current = filterParamFromCategory ( category )
156+ setState ( ( prev ) => ( {
157+ ...prev ,
158+ currentTab : tab ,
159+ filterText : search
160+ } ) )
161+ dispatch (
162+ saveActions . setSelectedCategory ( {
163+ currentTab : tab ,
164+ category
165+ } )
166+ )
167+ } , [ location . pathname , urlFilter , urlSearch , dispatch ] )
168+
169+ // When user changes category via menu (Redux updates), sync to URL
170+ useEffect ( ( ) => {
171+ const urlCategoryParam = filterParamFromCategory ( selectedCategoryForUrlTab )
172+ if ( lastCategoryUrlRef . current === urlCategoryParam ) return
173+ lastCategoryUrlRef . current = urlCategoryParam
174+ setSearchParams (
175+ ( prev ) => {
176+ const next = new URLSearchParams ( prev )
177+ if ( urlCategoryParam === 'all' ) {
178+ next . delete ( LIBRARY_FILTER_PARAM )
179+ } else {
180+ next . set ( LIBRARY_FILTER_PARAM , urlCategoryParam )
181+ }
182+ return next
183+ } ,
184+ { replace : true }
185+ )
186+ } , [ selectedCategoryForUrlTab , setSearchParams ] )
187+
114188 const fetchLibraryTracks = useCallback (
115189 (
116190 query ?: string ,
@@ -310,17 +384,42 @@ export const useLibraryPage = () => {
310384 handleFetchSavedTracks ( )
311385 } , [ tracksCategory , handleFetchSavedTracks ] )
312386
387+ const updateSearchParam = useCallback (
388+ ( search : string ) => {
389+ setSearchParams (
390+ ( prev ) => {
391+ const next = new URLSearchParams ( prev )
392+ if ( search . trim ( ) === '' ) {
393+ next . delete ( LIBRARY_SEARCH_PARAM )
394+ } else {
395+ next . set ( LIBRARY_SEARCH_PARAM , search )
396+ }
397+ return next
398+ } ,
399+ { replace : true }
400+ )
401+ } ,
402+ [ setSearchParams ]
403+ )
404+
405+ const debouncedUpdateSearchParam = useMemo (
406+ ( ) => debounce ( updateSearchParam , 300 ) ,
407+ [ updateSearchParam ]
408+ )
409+
313410 const onFilterChange = useCallback (
314411 ( e : any ) => {
412+ const value = e . target . value
315413 const callBack = ! state . allTracksFetched
316414 ? handleFetchSavedTracks
317415 : undefined
318- setState ( ( prev ) => ( { ...prev , filterText : e . target . value } ) )
416+ setState ( ( prev ) => ( { ...prev , filterText : value } ) )
417+ debouncedUpdateSearchParam ( value )
319418 if ( callBack ) {
320419 callBack ( )
321420 }
322421 } ,
323- [ state . allTracksFetched , handleFetchSavedTracks ]
422+ [ state . allTracksFetched , handleFetchSavedTracks , debouncedUpdateSearchParam ]
324423 )
325424
326425 const onSortChange = useCallback (
@@ -554,9 +653,15 @@ export const useLibraryPage = () => {
554653 [ formatMetadata , tracks . entries , state . initialOrder , updateLineupOrder ]
555654 )
556655
557- const onChangeTab = useCallback ( ( tab : LibraryPageTabs ) => {
558- setState ( ( prev ) => ( { ...prev , currentTab : tab } ) )
559- } , [ ] )
656+ const onChangeTab = useCallback (
657+ ( tab : LibraryPageTabs ) => {
658+ setState ( ( prev ) => ( { ...prev , currentTab : tab } ) )
659+ const path = getLibraryPath ( tab )
660+ const search = searchParams . toString ( )
661+ navigate ( search ? `${ path } ?${ search } ` : path )
662+ } ,
663+ [ navigate , searchParams ]
664+ )
560665
561666 const isQueuedValue = isQueued ( )
562667 const playingUid = getPlayingUid ( )
0 commit comments