├── .gitignore ├── postcss.config.js ├── src ├── assets │ ├── index.js │ ├── constants.js │ ├── loader.svg │ ├── macFavicon.svg │ └── macLogo.svg ├── components │ ├── Error.jsx │ ├── Loader.jsx │ ├── PlayPause.jsx │ ├── ArtistCard.jsx │ ├── index.js │ ├── MusicPlayer │ │ ├── Track.jsx │ │ ├── VolumeBar.jsx │ │ ├── Player.jsx │ │ ├── Seekbar.jsx │ │ ├── Controls.jsx │ │ └── index.jsx │ ├── RelatedSongs.jsx │ ├── Searchbar.jsx │ ├── DetailsHeader.jsx │ ├── SongBar.jsx │ ├── SongCard.jsx │ ├── Sidebar.jsx │ └── TopPlay.jsx ├── pages │ ├── index.js │ ├── TopArtists.jsx │ ├── ArtistDetails.jsx │ ├── TopCharts.jsx │ ├── Search.jsx │ ├── AroundYou.jsx │ ├── Discover.jsx │ └── SongDetails.jsx ├── redux │ ├── store.js │ ├── services │ │ └── shazamCore.js │ └── features │ │ └── playerSlice.js ├── index.jsx ├── index.css └── App.jsx ├── vite.config.js ├── index.html ├── package.json ├── tailwind.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/assets/index.js: -------------------------------------------------------------------------------- 1 | import loader from './loader.svg'; 2 | import logo from './macLogo.svg'; 3 | 4 | export { 5 | logo, 6 | loader, 7 | }; 8 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | -------------------------------------------------------------------------------- /src/components/Error.jsx: -------------------------------------------------------------------------------- 1 | const Error = () => ( 2 |
3 |

Something went wrong. Please try again.

4 |
5 | ); 6 | 7 | export default Error; 8 | -------------------------------------------------------------------------------- /src/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import { loader } from '../assets'; 2 | 3 | const Loader = ({ title }) => ( 4 |
5 | loader 6 |

{title || 'Loading...'}

7 |
8 | ); 9 | 10 | export default Loader; 11 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import Discover from './Discover'; 2 | import TopArtists from './TopArtists'; 3 | import ArtistDetails from './ArtistDetails'; 4 | import SongDetails from './SongDetails'; 5 | import Search from './Search'; 6 | import TopCharts from './TopCharts'; 7 | import AroundYou from './AroundYou'; 8 | 9 | export { 10 | Discover, 11 | Search, 12 | TopArtists, 13 | ArtistDetails, 14 | SongDetails, 15 | TopCharts, 16 | AroundYou, 17 | }; 18 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | 3 | import playerReducer from './features/playerSlice'; 4 | import { shazamCoreApi } from './services/shazamCore'; 5 | 6 | export const store = configureStore({ 7 | reducer: { 8 | [shazamCoreApi.reducerPath]: shazamCoreApi.reducer, 9 | player: playerReducer, 10 | }, 11 | middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(shazamCoreApi.middleware), 12 | }); 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Music App Clone 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/PlayPause.jsx: -------------------------------------------------------------------------------- 1 | import { FaPauseCircle, FaPlayCircle } from "react-icons/fa"; 2 | 3 | const PlayPause = ({isPlaying, activeSong, song, handlePause, handlePlay}) => (isPlaying && activeSong?.title === song.title ? ( 4 | 9 | ) : ( 10 | 15 | )); 16 | 17 | export default PlayPause; 18 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | 6 | import './index.css'; 7 | import App from './App'; 8 | import { store } from './redux/store'; 9 | 10 | ReactDOM.createRoot(document.getElementById('root')).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | , 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/ArtistCard.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | 3 | const ArtistCard = ({ track }) => { 4 | const navigate = useNavigate(); 5 | 6 | return ( 7 |
navigate(`/artists/${track?.artists[0].adamid}`)}> 8 | artist 9 |

{track?.subtitle}

10 |
11 | ); 12 | }; 13 | 14 | export default ArtistCard; 15 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Sidebar from './Sidebar'; 2 | import Searchbar from './Searchbar'; 3 | import SongCard from './SongCard'; 4 | import TopPlay from './TopPlay'; 5 | import ArtistCard from './ArtistCard'; 6 | import DetailsHeader from './DetailsHeader'; 7 | import SongBar from './SongBar'; 8 | import RelatedSongs from './RelatedSongs'; 9 | import MusicPlayer from './MusicPlayer'; 10 | import Loader from './Loader'; 11 | import Error from './Error'; 12 | 13 | export { 14 | TopPlay, 15 | Sidebar, 16 | SongCard, 17 | Searchbar, 18 | ArtistCard, 19 | DetailsHeader, 20 | SongBar, 21 | RelatedSongs, 22 | MusicPlayer, 23 | Loader, 24 | Error, 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/MusicPlayer/Track.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Track = ({ isPlaying, isActive, activeSong }) => ( 4 |
5 | 8 |
9 |

10 | {activeSong?.title ? activeSong?.title : 'No active Song'} 11 |

12 |

13 | {activeSong?.subtitle ? activeSong?.subtitle : 'No active Song'} 14 |

15 |
16 |
17 | ); 18 | 19 | export default Track; 20 | -------------------------------------------------------------------------------- /src/components/RelatedSongs.jsx: -------------------------------------------------------------------------------- 1 | import SongBar from "./SongBar"; 2 | 3 | 4 | const RelatedSongs = ({ data, isPlaying, activeSong, handlePauseClick, handlePlayClick, artistId }) => ( 5 |
6 |

Related Songs:

7 | 8 |
9 | {data?.map((song, i) => ( 10 | 20 | ))} 21 |
22 | 23 |
24 | ); 25 | 26 | export default RelatedSongs; 27 | -------------------------------------------------------------------------------- /src/components/MusicPlayer/VolumeBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BsFillVolumeUpFill, BsVolumeDownFill, BsFillVolumeMuteFill } from 'react-icons/bs'; 3 | 4 | const VolumeBar = ({ value, min, max, onChange, setVolume }) => ( 5 |
6 | {value <= 1 && value > 0.5 && setVolume(0)} />} 7 | {value <= 0.5 && value > 0 && setVolume(0)} />} 8 | {value === 0 && setVolume(1)} />} 9 | 18 |
19 | ); 20 | 21 | export default VolumeBar; 22 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Active Navigation Link */ 6 | .active { 7 | @apply text-white; 8 | } 9 | 10 | .active svg { 11 | @apply text-white; 12 | } 13 | 14 | .bar:nth-child(2) { 15 | animation-delay: 0.2s; 16 | } 17 | .bar:nth-child(3) { 18 | animation-delay: 0.4s; 19 | } 20 | .bar:nth-child(4) { 21 | animation-delay: 0.6s; 22 | } 23 | .bar:nth-child(5) { 24 | animation-delay: 0.8s; 25 | } 26 | .bar:nth-child(6) { 27 | animation-delay: 1s; 28 | } 29 | 30 | .smooth-transition { 31 | transition: all 0.3s ease-in-out; 32 | } 33 | 34 | /* Hide scrollbar for Chrome, Safari and Opera */ 35 | .hide-scrollbar::-webkit-scrollbar { 36 | display: none; 37 | } 38 | 39 | /* Hide scrollbar for IE, Edge and Firefox */ 40 | .hide-scrollbar { 41 | -ms-overflow-style: none; /* IE and Edge */ 42 | scrollbar-width: none; /* Firefox */ 43 | } 44 | -------------------------------------------------------------------------------- /src/pages/TopArtists.jsx: -------------------------------------------------------------------------------- 1 | import { ArtistCard, Loader, Error } from "../components"; 2 | import { useGetTopChartsQuery } from "../redux/services/shazamCore"; 3 | 4 | const TopArtists = () => { 5 | const { data, isFetching, error } = useGetTopChartsQuery(); 6 | 7 | if(isFetching) return ; 8 | 9 | if(error) return ; 10 | 11 | return ( 12 |
13 |

14 | Top Artists 15 |

16 | 17 |
18 | {data?.map((track) => ( 19 | 23 | ))} 24 |
25 |
26 | ) 27 | 28 | }; 29 | 30 | export default TopArtists; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elagant-music-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@reduxjs/toolkit": "^1.8.5", 12 | "axios": "^0.27.2", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-icons": "^4.4.0", 16 | "react-redux": "^8.0.2", 17 | "react-router-dom": "^6.3.0", 18 | "swiper": "^8.4.2" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.0.0", 22 | "@types/react-dom": "^18.0.0", 23 | "@vitejs/plugin-react": "^1.3.0", 24 | "autoprefixer": "^10.4.7", 25 | "eslint": "^8.18.0", 26 | "eslint-config-airbnb": "^19.0.4", 27 | "eslint-plugin-import": "^2.26.0", 28 | "eslint-plugin-jsx-a11y": "^6.5.1", 29 | "eslint-plugin-react": "^7.30.0", 30 | "eslint-plugin-react-hooks": "^4.6.0", 31 | "postcss": "^8.4.14", 32 | "tailwindcss": "^3.1.3", 33 | "vite": "^2.9.9" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/MusicPlayer/Player.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/media-has-caption */ 2 | import React, { useRef, useEffect } from 'react'; 3 | 4 | const Player = ({ activeSong, isPlaying, volume, seekTime, onEnded, onTimeUpdate, onLoadedData, repeat }) => { 5 | const ref = useRef(null); 6 | // eslint-disable-next-line no-unused-expressions 7 | if (ref.current) { 8 | if (isPlaying) { 9 | ref.current.play(); 10 | } else { 11 | ref.current.pause(); 12 | } 13 | } 14 | 15 | useEffect(() => { 16 | ref.current.volume = volume; 17 | }, [volume]); 18 | // updates audio element only on seekTime change (and not on each rerender): 19 | useEffect(() => { 20 | ref.current.currentTime = seekTime; 21 | }, [seekTime]); 22 | 23 | return ( 24 |