Skip to content

Commit 972a70b

Browse files
authored
Fix spotify api (#314)
* bump minor deps * fix spotify api * remove duplicate file * remove sonar bug
1 parent 735ec58 commit 972a70b

20 files changed

+571
-407
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api
88

9+
Therefore, many songs don't have preview_url, so they are not playable.
10+
911
A web app that recreates spotify app.
1012

1113
Running demo [here](https://spoty-like.netlify.app/)

package.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@
1616
},
1717
"dependencies": {
1818
"@biomejs/biome": "^1.9.4",
19-
"@reduxjs/toolkit": "^2.3.0",
20-
"@testing-library/react": "^16.0.1",
21-
"@testing-library/user-event": "^14.4.3",
22-
"axios": "^1.7.7",
19+
"@reduxjs/toolkit": "^2.6.0",
20+
"@testing-library/react": "^16.2.0",
21+
"@testing-library/user-event": "^14.6.1",
22+
"axios": "^1.8.1",
2323
"fast-average-color": "^9.4.0",
24-
"qs": "^6.13.0",
24+
"qs": "^6.14.0",
2525
"react": "^18.2.0",
2626
"react-dom": "^18.2.0",
27-
"react-redux": "^9.1.2",
27+
"react-redux": "^9.2.0",
2828
"react-router-dom": "^6.27.0",
29-
"sass": "^1.80.3",
30-
"typescript": "^5.6.3",
31-
"universal-cookie": "^7.2.1"
29+
"sass": "^1.85.1",
30+
"typescript": "^5.8.2",
31+
"universal-cookie": "^7.2.2"
3232
},
3333
"browserslist": {
3434
"production": [
@@ -44,12 +44,12 @@
4444
},
4545
"devDependencies": {
4646
"@testing-library/dom": "^10.4.0",
47-
"@types/qs": "^6.9.16",
47+
"@types/qs": "^6.9.18",
4848
"@types/react": "^18.3.11",
4949
"@types/react-dom": "^18.3.1",
50-
"@vitejs/plugin-react": "^4.3.3",
50+
"@vitejs/plugin-react": "^4.3.4",
5151
"@vitest/coverage-v8": "^2.1.3",
52-
"jsdom": "^25.0.1",
52+
"jsdom": "^26.0.0",
5353
"vite": "^5.4.9",
5454
"vite-plugin-checker": "^0.8.0",
5555
"vite-plugin-svgr": "^4.2.0",

src/API.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import axios from 'axios';
22
import qs from 'qs';
33
import Cookies from 'universal-cookie';
44
import type { PlaylistType } from './types/playlist.interface';
5-
import type { FeaturedPlaylistsResponse } from './types/playlists.interface';
5+
import type { UserPlaylistsType } from './types/playlists.interface';
66

77
const BASE_URL = 'https://api.spotify.com/v1';
88
const cookies = new Cookies();
@@ -29,6 +29,9 @@ const getAuthorizationToken = async (): Promise<void> => {
2929
});
3030
};
3131

32+
/**
33+
* Read bearer token from cookies
34+
*/
3235
const getAuth = async (): Promise<string> => {
3336
let auth: string = cookies.get('auth');
3437
if (auth === undefined) {
@@ -39,17 +42,21 @@ const getAuth = async (): Promise<string> => {
3942
return auth;
4043
};
4144

42-
export const GetFeaturedPlaylists =
43-
async (): Promise<FeaturedPlaylistsResponse> => {
44-
const auth = await getAuth();
45-
return axios
46-
.get(`${BASE_URL}/browse/featured-playlists`, {
47-
headers: {
48-
Authorization: `Bearer ${auth}`,
49-
},
50-
})
51-
.then((response) => response.data);
52-
};
45+
export const GetUserPlaylists = async (
46+
user: string,
47+
): Promise<UserPlaylistsType> => {
48+
const auth = await getAuth();
49+
return axios
50+
.get(`${BASE_URL}/users/${user}/playlists`, {
51+
headers: {
52+
Authorization: `Bearer ${auth}`,
53+
},
54+
})
55+
.then((response) => {
56+
console.log(response.data);
57+
return response.data;
58+
});
59+
};
5360

5461
export const GetPlaylistDetail = async (
5562
playlistID: string,

src/App.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,18 @@ import PlaylistDetail from './pages/PlaylistDetail/PlaylistDetail';
99
import Playlists from './pages/Playlists/Playlists';
1010
import { useAppDispatch, useAppSelector } from './store/hooks';
1111
import {
12-
fetchFeaturedPlaylists,
13-
selectFeaturedPlaylists,
14-
} from './store/reducers/featuredPlaylists.slice';
12+
fetchUserPlaylists,
13+
selectUserPlaylists,
14+
} from './store/reducers/userPlaylists.slice';
1515

1616
const App = () => {
1717
const cookies = new Cookies();
1818
const dispatch = useAppDispatch();
19-
const { playlists, message, loading, error } = useAppSelector(
20-
selectFeaturedPlaylists,
21-
);
19+
const user = 'smedjan';
20+
const { playlists, loading, error } = useAppSelector(selectUserPlaylists);
2221

2322
useEffect(() => {
24-
void dispatch(fetchFeaturedPlaylists());
23+
dispatch(fetchUserPlaylists(user));
2524
}, [dispatch]);
2625

2726
return (
@@ -45,7 +44,10 @@ const App = () => {
4544
<Route
4645
path="/"
4746
element={
48-
<Playlists playlists={playlists} message={message} />
47+
<Playlists
48+
playlists={playlists}
49+
message={`${user}'s playlists`}
50+
/>
4951
}
5052
/>
5153
<Route path="/playlist/:id" element={<PlaylistDetail />} />

src/components/Player/Player.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ const Player = () => {
7676
<footer className={styles.Player}>
7777
<div className={styles.Song}>
7878
<div className={styles.Img}>
79-
<img src={song.track.album.images[0].url} alt="song" />
79+
{song.track.album.images?.[0]?.url && (
80+
<img src={song.track.album.images[0].url} alt="song" />
81+
)}
8082
</div>
8183
<div className={styles.Infos}>
8284
<div className={styles.Name}>{song.track.name}</div>

src/components/SideBar/SideBar.spec.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('SideBar', () => {
88
test('should render playlists', async () => {
99
render(
1010
<BrowserRouter>
11-
<SideBar playlists={mockPlaylists} />
11+
<SideBar playlists={mockPlaylists.items} />
1212
</BrowserRouter>,
1313
);
1414

src/components/SideBar/SideBar.tsx

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { Link } from 'react-router-dom';
22
import Logo from '../../assets/logo.svg?react';
3-
import type {
4-
PlaylistTrackDetails,
5-
PlaylistsType,
6-
} from '../../types/playlists.interface';
3+
import type { PlaylistTrackDetails } from '../../types/playlists.interface';
74
import styles from './SideBar.module.scss';
85

9-
const SideBar = ({ playlists }: { playlists: PlaylistsType }) => (
6+
const SideBar = ({ playlists }: { playlists: PlaylistTrackDetails[] }) => (
107
<div className={styles.SideBar}>
118
<Link
129
style={{ textDecoration: 'none', color: 'white' }}
@@ -19,7 +16,7 @@ const SideBar = ({ playlists }: { playlists: PlaylistsType }) => (
1916
<h1 className={styles.Title}>Playlists</h1>
2017
<hr className={styles.Separator} />
2118
<nav className={styles.List}>
22-
{playlists.items?.map((item) => (
19+
{playlists.map((item) => (
2320
<ListItem playlist={item} key={item.id} />
2421
))}
2522
</nav>

src/pages/PlaylistDetail/PlaylistDetail.module.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@
4848

4949
.Title {
5050
padding: 0.08em 0px;
51-
font-size: 96px;
51+
font-size: clamp(2rem, 6vw, 8rem);
5252
font-weight: 700;
5353
margin: 0;
5454
}
5555

5656
.Text_Light::before {
57-
content: '';
57+
content: "";
5858
margin: 0px 4px;
5959
}
6060

src/pages/PlaylistDetail/PlaylistDetail.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ const PlaylistDetail = () => {
122122
(item, index) =>
123123
item.track && (
124124
<SongItem
125-
key={item.track.id}
125+
key={item.track.id + index.toString()}
126126
song={item}
127127
index={index}
128128
current={item.track.id === song?.track?.id}

src/pages/PlaylistDetail/SongItem/SongItem.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const SongItem = ({ song, index, songClicked, current }: SongItemPros) =>
1616
<div
1717
className={[
1818
styles.Item,
19-
song.track.preview_url !== '' ? styles.Enabled : styles.Disabled,
19+
song.track.preview_url !== null ? styles.Enabled : styles.Disabled,
2020
].join(' ')}
2121
// biome-ignore lint/a11y/useSemanticElements: clickable div is fine
2222
role="button"
@@ -34,7 +34,9 @@ const SongItem = ({ song, index, songClicked, current }: SongItemPros) =>
3434
</div>
3535

3636
<div className={styles.Title}>
37-
<img src={song.track.album.images[0].url} alt="cover img" />
37+
{song.track.album.images?.[0]?.url && (
38+
<img src={song.track.album.images[0].url} alt="cover img" />
39+
)}
3840
<div className={styles.NameContainer}>
3941
<div className={styles.Name}>
4042
<span className={current ? 'playing' : ''}>{song.track.name}</span>
@@ -56,8 +58,8 @@ const SongItem = ({ song, index, songClicked, current }: SongItemPros) =>
5658
{msToMinutesAndSeconds(song.track.duration_ms)}
5759
{/* should be in another column */}
5860
{/* <button type="button" className={styles.More} tabIndex={-1}>
59-
...
60-
</button> */}
61+
...
62+
</button> */}
6163
</div>
6264
</div>
6365
);

src/pages/Playlists/PlaylistItem/PlaylistItem.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import styles from './PlaylistItem.module.scss';
66
const PlaylistItem = ({ playlist }: { playlist: PlaylistTrackDetails }) => (
77
<Link to={`/playlist/${playlist.id}`} className={styles.LinkPlaylist}>
88
<div className={styles.imgContainer}>
9-
<img src={playlist.images[0].url} alt="Tokyo" />
9+
{playlist.images?.[0].url && <img src={playlist.images[0].url} alt="" />}
1010
<div className={styles.PlayContainer}>
1111
<button type="button" className={styles.PlayButton} title="Play">
1212
<Play />

src/pages/Playlists/Playlists.spec.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import Playlists from './Playlists';
66

77
describe('Playlists', () => {
88
test('should render playlists', async () => {
9+
const message = 'mock message';
910
render(
1011
<BrowserRouter>
11-
<Playlists playlists={mockPlaylists} message="mock message" />
12+
<Playlists playlists={mockPlaylists.items} message={message} />
1213
</BrowserRouter>,
1314
);
1415

15-
expect((await screen.findByRole('heading')).textContent).toBe(
16-
'Playlists - mock message',
17-
);
16+
expect((await screen.findByRole('heading')).textContent).toBe(message);
1817
expect(screen.getByText('Hits du Moment')).toBeTruthy();
1918
});
2019
});

src/pages/Playlists/Playlists.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PlaylistsType } from '../../types/playlists.interface';
1+
import type { PlaylistTrackDetails } from '../../types/playlists.interface';
22
import PlaylistItem from './PlaylistItem/PlaylistItem';
33
import styles from './Playlists.module.scss';
44

@@ -7,12 +7,12 @@ const Playlists = ({
77
playlists,
88
}: {
99
message: string;
10-
playlists: PlaylistsType;
10+
playlists: PlaylistTrackDetails[];
1111
}) => (
1212
<div className={styles.Playlists}>
13-
<h1 className={styles.Title}>Playlists - {message}</h1>
13+
<h1 className={styles.Title}>{message}</h1>
1414
<div className={styles.Container}>
15-
{playlists.items.map((item) => (
15+
{playlists.map((item) => (
1616
<PlaylistItem key={item.id} playlist={item} />
1717
))}
1818
</div>

src/store/reducers/featuredPlaylists.slice.ts

-58
This file was deleted.

0 commit comments

Comments
 (0)