Skip to content

Commit 676b241

Browse files
committed
refactor: introduce and integrate query and mutation hooks for music data fetching across various components.
1 parent d262379 commit 676b241

30 files changed

Lines changed: 1598 additions & 1585 deletions

client/src/Context/PlayerContext.jsx

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,42 @@
11
import { useContext, useEffect, useRef } from "react"
22
import { Context } from "./Context"
33
import { usePlayerStore } from "@/stores/playerStore"
4+
import { useUserPlaylistsQuery } from "@/hooks/queries/usePlaylistQueries"
45

5-
/**
6-
* PlayerProvider - Minimal provider that only:
7-
* 1. Manages audio element refs
8-
* 2. Sets up audio event listeners
9-
* 3. Syncs audio with store state
10-
*/
116
export function PlayerProvider({ children }) {
127
const { user, loading } = useContext(Context)
138
const audioRef = useRef(null)
149
const nextAudioRef = useRef(null)
1510
const prevSongIdRef = useRef(null)
1611
const lastUpdateTime = useRef(0)
1712

18-
// Get store actions (stable references)
1913
const setAudioRefs = usePlayerStore((s) => s.setAudioRefs)
2014
const loadAndPlayCurrentSong = usePlayerStore((s) => s.loadAndPlayCurrentSong)
2115
const handleNextSong = usePlayerStore((s) => s.handleNextSong)
2216
const updateTime = usePlayerStore((s) => s.updateTime)
2317
const setDuration = usePlayerStore((s) => s.setDuration)
24-
const getPlaylists = usePlayerStore((s) => s.getPlaylists)
18+
const setUserPlaylist = usePlayerStore((s) => s.setUserPlaylist)
2519
const decrementSongsRemaining = usePlayerStore((s) => s.decrementSongsRemaining)
2620

27-
// Get state that triggers effects
2821
const currentSong = usePlayerStore((s) => s.currentSong)
2922
const volume = usePlayerStore((s) => s.volume)
3023

31-
// Set audio refs on mount
24+
const { data: userPlaylists } = useUserPlaylistsQuery({
25+
enabled: !!user && !loading,
26+
})
27+
28+
useEffect(() => {
29+
if (userPlaylists) {
30+
setUserPlaylist(userPlaylists)
31+
}
32+
}, [userPlaylists, setUserPlaylist])
33+
3234
useEffect(() => {
3335
if (audioRef.current && nextAudioRef.current) {
3436
setAudioRefs(audioRef.current, nextAudioRef.current)
3537
}
3638
}, [setAudioRefs])
3739

38-
// Audio event listeners
3940
useEffect(() => {
4041
const audio = audioRef.current
4142
if (!audio) return
@@ -68,27 +69,19 @@ export function PlayerProvider({ children }) {
6869
}
6970
}, [handleNextSong, volume, updateTime, setDuration])
7071

71-
// Load and play song when currentSong changes
7272
useEffect(() => {
7373
const loadSong = async () => {
7474
prevSongIdRef.current = await loadAndPlayCurrentSong(prevSongIdRef.current)
7575
}
7676
loadSong()
7777
}, [currentSong, loadAndPlayCurrentSong])
7878

79-
// Decrement songs remaining on song change (for sleep timer)
8079
useEffect(() => {
8180
if (currentSong) {
8281
decrementSongsRemaining()
8382
}
8483
}, [currentSong?.id, decrementSongsRemaining])
8584

86-
// Fetch user playlists on login
87-
useEffect(() => {
88-
if (!user || loading) return
89-
getPlaylists()
90-
}, [user, loading, getPlaylists])
91-
9285
return (
9386
<>
9487
{children}

client/src/Pages/Music/AddToPlaylist.jsx

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,87 +4,71 @@ import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from "@/components/u
44
import { Input } from "@/components/ui/input"
55
import { Label } from "@/components/ui/label"
66
import { ScrollArea } from "@/components/ui/scroll-area"
7-
import { usePlayerStore } from "@/stores/playerStore"
8-
import axios from "axios"
97
import { motion } from "framer-motion"
108
import { Check, ListMusic, Loader2, Music, Plus, Search, X } from "lucide-react"
119
import { useState } from "react"
1210
import { toast } from "sonner"
11+
import { useUserPlaylistsQuery } from "@/hooks/queries/usePlaylistQueries"
12+
import {
13+
useCreatePlaylistMutation,
14+
useAddSongToPlaylistMutation,
15+
} from "@/hooks/mutations/usePlaylistMutations"
1316

1417
const AddToPlaylist = ({ dialogOpen, setDialogOpen, song }) => {
15-
// Individual selectors
16-
const userPlaylist = usePlayerStore((s) => s.userPlaylist)
17-
const getPlaylists = usePlayerStore((s) => s.getPlaylists)
18+
const { data: userPlaylist = [] } = useUserPlaylistsQuery()
1819
const [newPlaylistDialog, setNewPlaylistDialog] = useState(false)
1920
const [newPlaylistName, setNewPlaylistName] = useState("")
20-
const [loading, setLoading] = useState(false)
21-
const [addingSong, setAddingSong] = useState(false)
2221
const [searchQuery, setSearchQuery] = useState("")
2322
const [selectedPlaylistId, setSelectedPlaylistId] = useState(null)
2423
const [addingSuccess, setAddingSuccess] = useState(false)
2524

25+
const createMutation = useCreatePlaylistMutation({
26+
onSuccess: () => {
27+
toast.success("Playlist created successfully")
28+
setNewPlaylistDialog(false)
29+
setNewPlaylistName("")
30+
},
31+
onError: (error) => {
32+
toast.error(error.response?.data?.message || "Failed to create playlist")
33+
},
34+
})
35+
36+
const addSongMutation = useAddSongToPlaylistMutation({
37+
onSuccess: () => {
38+
setAddingSuccess(true)
39+
setTimeout(() => {
40+
setDialogOpen(false)
41+
setAddingSuccess(false)
42+
setSelectedPlaylistId(null)
43+
}, 1500)
44+
},
45+
onError: (error) => {
46+
toast.error(error.response?.data?.message || "An error occurred.")
47+
setSelectedPlaylistId(null)
48+
},
49+
})
50+
2651
const filteredPlaylists = userPlaylist?.filter((playlist) =>
2752
playlist.name.toLowerCase().includes(searchQuery.toLowerCase()),
2853
)
2954

30-
const handleCreatePlaylist = async (e) => {
55+
const handleCreatePlaylist = (e) => {
3156
e.preventDefault()
3257
if (!newPlaylistName.trim()) return
33-
34-
setLoading(true)
35-
try {
36-
const response = await axios.post(
37-
`${import.meta.env.VITE_API_URL}/api/playlist/create`,
38-
{ name: newPlaylistName },
39-
{ withCredentials: true },
40-
)
41-
42-
if (response.status === 200) {
43-
toast.success("Playlist created successfully")
44-
await getPlaylists()
45-
setNewPlaylistDialog(false)
46-
setNewPlaylistName("")
47-
}
48-
} catch (error) {
49-
toast.error(error.response?.data?.message || "Failed to create playlist")
50-
} finally {
51-
setLoading(false)
52-
}
58+
createMutation.mutate({ name: newPlaylistName })
5359
}
5460

55-
const handleAddToPlaylist = async (playlistId) => {
61+
const handleAddToPlaylist = (playlistId) => {
5662
if (!playlistId || !song) {
5763
return toast.error("An error occurred. Please try again.")
5864
}
5965

6066
setSelectedPlaylistId(playlistId)
61-
setAddingSong(true)
62-
63-
try {
64-
const response = await axios.post(
65-
`${import.meta.env.VITE_API_URL}/api/playlist/add-song`,
66-
{
67-
playlistId,
68-
songId: song.id,
69-
songData: JSON.stringify(song),
70-
},
71-
{ withCredentials: true },
72-
)
73-
74-
if (response.status === 201) {
75-
setAddingSuccess(true)
76-
setTimeout(() => {
77-
setDialogOpen(false)
78-
setAddingSuccess(false)
79-
setSelectedPlaylistId(null)
80-
}, 1500)
81-
}
82-
} catch (error) {
83-
toast.error(error.response?.data?.message || "An error occurred.")
84-
setSelectedPlaylistId(null)
85-
} finally {
86-
setAddingSong(false)
87-
}
67+
addSongMutation.mutate({
68+
playlistId,
69+
songId: song.id,
70+
songData: song,
71+
})
8872
}
8973

9074
return (
@@ -123,14 +107,14 @@ const AddToPlaylist = ({ dialogOpen, setDialogOpen, song }) => {
123107
variant="outline"
124108
className="w-full flex items-center gap-2 h-10 text-base hover:bg-primary hover:text-primary-foreground transition-all duration-200"
125109
onClick={() => setNewPlaylistDialog(true)}
126-
disabled={loading}
110+
disabled={createMutation.isPending}
127111
>
128112
<Plus className="w-5 h-5" />
129113
Create New Playlist
130114
</Button>
131115

132116
<ScrollArea className="h-[400px] pr-4">
133-
{loading ? (
117+
{createMutation.isPending ? (
134118
<div className="flex items-center h-[300px] justify-center">
135119
<Loader2 className="w-6 h-6 animate-spin" />
136120
</div>
@@ -144,12 +128,12 @@ const AddToPlaylist = ({ dialogOpen, setDialogOpen, song }) => {
144128
transition-all duration-200
145129
${selectedPlaylistId === playlist.id ? "bg-primary/10" : "hover:bg-accent"}
146130
${
147-
addingSong && selectedPlaylistId !== playlist.id
131+
addSongMutation.isPending && selectedPlaylistId !== playlist.id
148132
? "opacity-50 pointer-events-none"
149133
: "cursor-pointer"
150134
}
151135
`}
152-
onClick={() => !addingSong && handleAddToPlaylist(playlist.id)}
136+
onClick={() => !addSongMutation.isPending && handleAddToPlaylist(playlist.id)}
153137
>
154138
<Avatar className="w-12 h-12 rounded-lg">
155139
<AvatarImage src={playlist.image} className="object-cover" />
@@ -210,7 +194,7 @@ const AddToPlaylist = ({ dialogOpen, setDialogOpen, song }) => {
210194
onChange={(e) => setNewPlaylistName(e.target.value)}
211195
placeholder="Enter playlist name"
212196
required
213-
disabled={loading}
197+
disabled={createMutation.isPending}
214198
className="h-12"
215199
/>
216200
</div>
@@ -220,16 +204,16 @@ const AddToPlaylist = ({ dialogOpen, setDialogOpen, song }) => {
220204
variant="outline"
221205
className="flex-1 h-12"
222206
onClick={() => setNewPlaylistDialog(false)}
223-
disabled={loading}
207+
disabled={createMutation.isPending}
224208
>
225209
Cancel
226210
</Button>
227211
<Button
228212
type="submit"
229213
className="flex-1 h-12"
230-
disabled={loading || !newPlaylistName.trim()}
214+
disabled={createMutation.isPending || !newPlaylistName.trim()}
231215
>
232-
{loading ? (
216+
{createMutation.isPending ? (
233217
<span className="flex items-center gap-2">
234218
<Loader2 className="w-4 h-4 animate-spin" />
235219
Creating...

client/src/Pages/Music/Album.jsx

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,20 @@
11
import { usePlayerStore } from "@/stores/playerStore"
2-
import axios from "axios"
3-
import { useEffect, useState } from "react"
4-
import { useLocation } from "react-router-dom"
2+
import { useParams } from "react-router-dom"
53
import { SongCard } from "./Cards"
64
import { ensureHttpsForDownloadUrls, LoadingState, PlaylistActions } from "./Common"
7-
import { useParams } from "react-router-dom"
5+
import { useAlbumQuery } from "@/hooks/queries/useSongQueries"
86

97
const Album = () => {
10-
const location = useLocation()
118
const params = useParams()
12-
const id = location.state || params?.id || null
13-
const [albumData, setAlbumData] = useState(null)
14-
const [loading, setLoading] = useState(true)
9+
const id = params?.id || null
1510

16-
// Individual selectors
1711
const setPlaylist = usePlayerStore((s) => s.setPlaylist)
1812
const playSong = usePlayerStore((s) => s.playSong)
1913

20-
useEffect(() => {
21-
const fetchAlbumData = async () => {
22-
try {
23-
const response = await axios.get(`${import.meta.env.VITE_SONG_URL}/album?id=${id}`)
24-
const data = response.data
25-
setAlbumData(data.data)
26-
setLoading(false)
27-
} catch (error) {
28-
setLoading(false)
29-
console.error("Error fetching playlist data:", error)
30-
}
31-
}
32-
33-
if (id) {
34-
fetchAlbumData()
35-
}
36-
}, [id])
14+
const { data: albumData, isLoading } = useAlbumQuery(id)
3715

38-
if (loading) return <LoadingState />
16+
if (isLoading) return <LoadingState />
17+
if (!albumData) return null
3918

4019
const handlePlayAll = () => {
4120
if (albumData?.songs?.length) {
@@ -62,7 +41,6 @@ const Album = () => {
6241

6342
return (
6443
<div className="flex flex-col gap-10 p-5">
65-
{/** Album Info */}
6644
<div
6745
className={`w-full h-[250px] rounded-2xl bg-cover`}
6846
style={{
@@ -90,7 +68,6 @@ const Album = () => {
9068
disabled={!albumData?.songs?.length}
9169
/>
9270

93-
{/** Album Songs */}
9471
<div className="grid grid-cols-1 sm:grid-cols-4 gap-2">
9572
{albumData.songs.map((song, index) => (
9673
<div key={index}>

0 commit comments

Comments
 (0)