Skip to content

Commit 8251538

Browse files
committed
feat: add playlist support
1 parent 31293cf commit 8251538

29 files changed

+11703
-598
lines changed

api.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { AudioFeature } from "./lib/AudioFeaturesResponse";
2+
import { Item } from "./lib/TracksByPlaylistResponse";
3+
4+
export interface FeaturedItem extends Item {
5+
features: AudioFeature;
6+
}
7+
8+
export interface TracksByPlaylistWithFeaturesResponse {
9+
items: FeaturedItem[];
10+
}

hooks/useErrorsGuardedFetch.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { redirect } from "next/dist/next-server/server/api-utils";
2+
import { useRouter } from "next/router";
3+
import useFetch from "./useFetch";
4+
5+
function useErrorGuardedFetch<ResultData = unknown>(...args) {
6+
const result = useFetch<ResultData>(...args);
7+
const router = useRouter()
8+
9+
if (result.status === "error" && result.error.status === 401) {
10+
router.push("/login");
11+
}
12+
13+
return result;
14+
}
15+
16+
export default useErrorGuardedFetch;

hooks/useFetch.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useEffect, useReducer, useRef } from "react";
2+
import axios, { AxiosRequestConfig } from "axios";
3+
// State & hook output
4+
5+
interface Error {
6+
message: string,
7+
status: number,
8+
}
9+
10+
interface State<T> {
11+
status: "init" | "fetching" | "error" | "fetched";
12+
data?: T;
13+
error?: Error;
14+
}
15+
interface Cache<T> {
16+
[url: string]: T;
17+
}
18+
19+
20+
// discriminated union type
21+
type Action<T> =
22+
| { type: "request" }
23+
| { type: "success"; payload: T }
24+
| { type: "failure"; payload: Error };
25+
function useFetch<T = unknown>(
26+
url?: string,
27+
options?: AxiosRequestConfig
28+
): State<T> {
29+
const cache = useRef<Cache<T>>({});
30+
const cancelRequest = useRef<boolean>(false);
31+
const initialState: State<T> = {
32+
status: "init",
33+
error: undefined,
34+
data: undefined,
35+
};
36+
// Keep state logic separated
37+
const fetchReducer = (state: State<T>, action: Action<T>): State<T> => {
38+
switch (action.type) {
39+
case "request":
40+
return { ...initialState, status: "fetching" };
41+
case "success":
42+
return { ...initialState, status: "fetched", data: action.payload };
43+
case "failure":
44+
return { ...initialState, status: "error", error: action.payload };
45+
default:
46+
return state;
47+
}
48+
};
49+
const [state, dispatch] = useReducer(fetchReducer, initialState);
50+
useEffect(() => {
51+
if (!url) {
52+
return;
53+
}
54+
const fetchData = async () => {
55+
dispatch({ type: "request" });
56+
if (cache.current[url]) {
57+
dispatch({ type: "success", payload: cache.current[url] });
58+
} else {
59+
try {
60+
const response = await axios(url, options);
61+
cache.current[url] = response.data;
62+
if (cancelRequest.current) return;
63+
dispatch({ type: "success", payload: response.data });
64+
} catch (error) {
65+
if (cancelRequest.current) return;
66+
console.log(error.response);
67+
dispatch({ type: "failure", payload: { message: error.message, status: error.response.status }});
68+
}
69+
}
70+
};
71+
fetchData();
72+
return () => {
73+
cancelRequest.current = true;
74+
};
75+
// eslint-disable-next-line react-hooks/exhaustive-deps
76+
}, [url]);
77+
return state;
78+
}
79+
export default useFetch;

lib/AudioFeaturesResponse.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export interface AudioFeaturesResponse {
2+
audio_features: AudioFeature[];
3+
}
4+
5+
export interface NumericAudioFeatures {
6+
danceability: number;
7+
energy: number;
8+
key: number;
9+
loudness: number;
10+
mode: number;
11+
speechiness: number;
12+
acousticness: number;
13+
instrumentalness: number;
14+
liveness: number;
15+
valence: number;
16+
tempo: number;
17+
duration_ms: number;
18+
time_signature: number;
19+
}
20+
21+
export interface AudioFeature extends NumericAudioFeatures {
22+
type: string;
23+
id: string;
24+
uri: string;
25+
track_href: string;
26+
analysis_url: string;
27+
}
28+
29+

lib/MeResponse.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export interface MeResponse {
2+
display_name: string;
3+
external_urls: ExternalUrls;
4+
followers: Followers;
5+
href: string;
6+
id: string;
7+
images: Image[];
8+
type: string;
9+
uri: string;
10+
}
11+
12+
export interface ExternalUrls {
13+
spotify: string;
14+
}
15+
16+
export interface Followers {
17+
href: null;
18+
total: number;
19+
}
20+
21+
export interface Image {
22+
height: null;
23+
url: string;
24+
width: null;
25+
}

lib/PlaylistsResponse.ts

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
export interface PlaylistsResponse {
2+
href: string;
3+
items: Playlist[];
4+
limit: number;
5+
next: string;
6+
offset: number;
7+
previous: null;
8+
total: number;
9+
}
10+
11+
export interface Playlist {
12+
collaborative: boolean;
13+
description: string;
14+
external_urls: ExternalUrls;
15+
href: string;
16+
id: string;
17+
images: Image[];
18+
name: string;
19+
owner: Owner;
20+
primary_color: null;
21+
public: boolean;
22+
snapshot_id: string;
23+
tracks: Tracks;
24+
type: ItemType;
25+
uri: string;
26+
}
27+
28+
export interface ExternalUrls {
29+
spotify: string;
30+
}
31+
32+
export interface Image {
33+
height: number | null;
34+
url: string;
35+
width: number | null;
36+
}
37+
38+
export interface Owner {
39+
display_name: string;
40+
external_urls: ExternalUrls;
41+
href: string;
42+
id: string;
43+
type: OwnerType;
44+
uri: string;
45+
}
46+
47+
export enum OwnerType {
48+
User = "user",
49+
}
50+
51+
export interface Tracks {
52+
href: string;
53+
total: number;
54+
}
55+
56+
export enum ItemType {
57+
Playlist = "playlist",
58+
}

lib/TracksByPlaylistResponse.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export interface TracksByPlaylistResponse {
2+
items: Item[];
3+
}
4+
5+
export interface Item {
6+
track: Track;
7+
}
8+
9+
export interface Track {
10+
album: Album;
11+
id: string;
12+
name: string;
13+
}
14+
15+
export interface Album {
16+
images: Image[];
17+
}
18+
19+
export interface Image {
20+
height: number;
21+
url: string;
22+
width: number;
23+
}

0 commit comments

Comments
 (0)