From f790b072c956f338f67c7a23400d2d4209547a3b Mon Sep 17 00:00:00 2001 From: Maria Torrente <64274966+mariatorrentedev@users.noreply.github.com> Date: Sat, 20 Apr 2024 15:43:56 -0400 Subject: [PATCH] Rebase (#22) * Some clean up, added Spotify icon for offline/loading state * Minor clean up in Mobile view * Updating about text. * Fixing max width for the Tab Panel * P-18: fixed bug when type is not track * P-20: Add real time spotify streaming progress bar. (#21) --- package-lock.json | 124 +++++++++++++++++++++++---- package.json | 3 +- src/App.tsx | 2 +- src/components/Navigation.tsx | 34 +++++--- src/components/SpotifyNowPlaying.tsx | 49 ++++++----- src/components/utils.ts | 15 ++++ src/main.tsx | 13 ++- src/services/queries/access-token.ts | 13 +++ src/services/queries/index.ts | 2 + src/services/queries/now-playing.ts | 35 ++++++++ src/services/spotify.ts | 34 ++++---- src/types/spotify.ts | 7 +- 12 files changed, 247 insertions(+), 84 deletions(-) create mode 100644 src/components/utils.ts create mode 100644 src/services/queries/access-token.ts create mode 100644 src/services/queries/index.ts create mode 100644 src/services/queries/now-playing.ts diff --git a/package-lock.json b/package-lock.json index 5b6a3e9..64461ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "buffer": "^6.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-intersection-observer": "^9.8.2" + "react-intersection-observer": "^9.8.2", + "react-query": "^3.39.3" }, "devDependencies": { "@types/react": "^18.2.66", @@ -2098,8 +2099,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -2120,6 +2120,14 @@ } ] }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2141,6 +2149,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browserslist": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", @@ -2272,8 +2295,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/convert-source-map": { "version": "2.0.0", @@ -2346,6 +2368,11 @@ "node": ">=0.4.0" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2886,8 +2913,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2924,7 +2950,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2956,7 +2981,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2966,7 +2990,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3097,7 +3120,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3106,8 +3128,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -3170,6 +3191,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3302,6 +3328,15 @@ "yallist": "^3.0.2" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3324,6 +3359,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3364,6 +3404,14 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -3402,11 +3450,15 @@ "node": ">=0.10.0" } }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3499,7 +3551,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3671,6 +3722,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3701,6 +3777,11 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3739,7 +3820,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -4021,6 +4101,15 @@ "node": ">=14.17" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -4133,8 +4222,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/yallist": { "version": "3.1.1", diff --git a/package.json b/package.json index 61cc32b..3e82043 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "buffer": "^6.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-intersection-observer": "^9.8.2" + "react-intersection-observer": "^9.8.2", + "react-query": "^3.39.3" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/src/App.tsx b/src/App.tsx index e0d3c95..09bc04a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ export default function App() { textAlign="left" margin="0 auto" alignItems="center" - padding={["5rem 2rem", "9rem 2rem"]} + padding={["2rem", "5rem 2rem"]} spacing={2} > diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 7891f2a..fc33873 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Box, Tab, Tabs } from "@mui/material"; +import { Box, Tab, Tabs, Typography } from "@mui/material"; import { styled } from "@mui/system"; import { About } from "./"; @@ -78,6 +78,11 @@ function a11yProps(index: number) { }; } +type TabsContent = { + label: string; + component: React.ReactNode; +}; + export default function Navigation() { const [value, setValue] = React.useState(0); @@ -85,29 +90,30 @@ export default function Navigation() { setValue(newValue); }; + const tabContents: TabsContent[] = [ + { label: "About", component: }, + { label: "Projects", component: Projects }, + ]; + return ( handleChange(newValue)} aria-label="Vertical tabs example" > - - - + {tabContents.map((tab, index) => ( + + ))} - - - - - Projects - - - Contact - + {tabContents.map((tab, index) => ( + + {tab.component} + + ))} ); } diff --git a/src/components/SpotifyNowPlaying.tsx b/src/components/SpotifyNowPlaying.tsx index 7d543e9..459827e 100644 --- a/src/components/SpotifyNowPlaying.tsx +++ b/src/components/SpotifyNowPlaying.tsx @@ -1,22 +1,18 @@ import * as React from "react"; -import type { GetNowPlayingResponse } from "../services/spotify"; import { Box, Card, CardContent, CardMedia, Icon, + LinearProgress, Stack, Typography, } from "@mui/material"; -import { getNowPlaying } from "../services/spotify"; +import { useAccessToken, useNowPlaying } from "../services/queries"; import SpotifyIcon from "../assets/spotify.svg"; -import { - CLIENT_ID as clientId, - CLIENT_SECRET as clientSecret, - REFRESH_TOKEN as refreshToken, -} from "../constants"; import { styled } from "@mui/system"; +import { getProgressPercentage } from "./utils"; const StyledTypography = styled(Typography)(({ theme }) => ({ maxWidth: 190, @@ -42,24 +38,27 @@ function SpotifyStatusMessage({ message }: { message: string }) { } export default function SpotifyNowPlaying() { - const [loading, setLoading] = React.useState(true); - const [nowPlayingData, setNowPlayingData] = - React.useState(null); + const { isLoading: isAccessTokenLoading } = useAccessToken(); + const { data: nowPlayingData, isLoading, refetch } = useNowPlaying(); React.useEffect(() => { - getNowPlaying({ clientId, clientSecret, refreshToken }) - .then((response) => { - setNowPlayingData(response); - setLoading(false); - }) - .catch((error) => { - console.error("Error fetching now playing data:", error); - }); + // For the case when the spotify user stop and restart streaming, there is not refresh + /** + * For the case when the spotify user stop and restart the satreaming, + * there is no refresh but we want to do a hard refresh of the query to + * get the latest state, although we don't want to do it if is currently + * playing because the query interval is in charge of that. + * + */ + if (!isAccessTokenLoading && !nowPlayingData?.is_playing) { + refetch(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( <> - {loading ? ( + {isLoading ? ( ) : nowPlayingData && nowPlayingData?.is_playing && @@ -71,7 +70,7 @@ export default function SpotifyNowPlaying() { - + {nowPlayingData.item.name} {nowPlayingData.item?.artists .map((_artist) => _artist.name) .join(",")} + diff --git a/src/components/utils.ts b/src/components/utils.ts new file mode 100644 index 0000000..dd0be69 --- /dev/null +++ b/src/components/utils.ts @@ -0,0 +1,15 @@ +export function getProgressPercentage( + currentPosition: number, + totalDuration: number +) { + // Ensure totalDuration is a positive number + if (totalDuration <= 0) { + return 0; + } + + // Calculate the progress percentage + const progressPercentage = (currentPosition / totalDuration) * 100; + + // Ensure progress percentage is within [0, 100] range + return Math.min(100, Math.max(0, progressPercentage)); +} diff --git a/src/main.tsx b/src/main.tsx index 1c441f4..10b060d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,12 +4,17 @@ import App from "./App.tsx"; import { theme } from "./theme"; import ThemeProvider from "@mui/material/styles/ThemeProvider"; import CssBaseline from "@mui/material/CssBaseline"; +import { QueryClient, QueryClientProvider } from "react-query"; + +const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById("root")!).render( - - - - + + + + + + ); diff --git a/src/services/queries/access-token.ts b/src/services/queries/access-token.ts new file mode 100644 index 0000000..635a900 --- /dev/null +++ b/src/services/queries/access-token.ts @@ -0,0 +1,13 @@ +import type { QueryObserverOptions } from "react-query"; +import type { GetAccessTokenResponse } from "../spotify"; +import { useQuery } from "react-query"; +import { getAccessToken } from "../spotify"; + +export const useAccessToken = ( + options?: QueryObserverOptions +) => { + return useQuery("accessToken", getAccessToken, { + ...options, + staleTime: Infinity, + }); +}; diff --git a/src/services/queries/index.ts b/src/services/queries/index.ts new file mode 100644 index 0000000..6df7911 --- /dev/null +++ b/src/services/queries/index.ts @@ -0,0 +1,2 @@ +export { useNowPlaying } from "./now-playing"; +export { useAccessToken } from "./access-token"; diff --git a/src/services/queries/now-playing.ts b/src/services/queries/now-playing.ts new file mode 100644 index 0000000..349f900 --- /dev/null +++ b/src/services/queries/now-playing.ts @@ -0,0 +1,35 @@ +import type { QueryObserverOptions } from "react-query"; +import type { GetNowPlayingResponse } from "../spotify"; +import { useQuery, useQueryClient } from "react-query"; +import { getNowPlaying } from "../spotify"; +import { useAccessToken } from "."; + +export const useNowPlaying = ( + options?: QueryObserverOptions +) => { + const { data: accessTokenData, isLoading: accessTokenLoading } = + useAccessToken(); + /** + * TODO: Investigate WebSocket or polling option, anything that will prevent + * fetching the data every 5 seconds. + * + * Retrieve previous nowPlayingData from cache. + */ + const queryClient = useQueryClient(); + const previousNowPlayingData = + queryClient.getQueryData("nowPlaying"); + + return useQuery( + "nowPlaying", + () => getNowPlaying(accessTokenData?.access_token), + { + ...options, + refetchInterval: + previousNowPlayingData && previousNowPlayingData.is_playing + ? 5000 // 5 seconds for now, no like. + : false, + enabled: !!accessTokenData && !accessTokenLoading, + staleTime: Infinity, + } + ); +}; diff --git a/src/services/spotify.ts b/src/services/spotify.ts index 430e8bd..8779d7b 100644 --- a/src/services/spotify.ts +++ b/src/services/spotify.ts @@ -1,9 +1,14 @@ -import type { NowPlayingItem, AuthProps } from "../types/spotify"; +import type { NowPlayingItem } from "../types/spotify"; import axios from "axios"; import { Buffer } from "buffer"; import { TOKEN_ENDPOINT, NOW_PLAYING_ENDPOINT } from "../constants"; +import { + CLIENT_ID as clientId, + CLIENT_SECRET as clientSecret, + REFRESH_TOKEN as refreshToken, +} from "../constants"; -type GetAccessTokenResponse = { +export type GetAccessTokenResponse = { access_token: string; token_type: string; expires_in: number; @@ -11,11 +16,7 @@ type GetAccessTokenResponse = { scope: string; }; -const getAccessToken = async ({ - clientId, - clientSecret, - refreshToken, -}: AuthProps) => { +export const getAccessToken = async () => { const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64"); const bodyParams = new URLSearchParams({ @@ -44,26 +45,21 @@ const getAccessToken = async ({ export type GetNowPlayingResponse = { is_playing: boolean; - currently_playing_type: string; + currently_playing_type: "track" | "episode"; item: NowPlayingItem; + progress_ms: number; }; -export const getNowPlaying = async ({ - clientId, - clientSecret, - refreshToken, -}: AuthProps) => { +export const getNowPlaying = async (accessToken?: string) => { try { - const { access_token } = await getAccessToken({ - clientId, - clientSecret, - refreshToken, - }); + if (!accessToken) { + throw "No Access Token available."; + } const response = await axios.get( NOW_PLAYING_ENDPOINT, { headers: { - Authorization: `Bearer ${access_token}`, + Authorization: `Bearer ${accessToken}`, }, } ); diff --git a/src/types/spotify.ts b/src/types/spotify.ts index ff10119..17a9220 100644 --- a/src/types/spotify.ts +++ b/src/types/spotify.ts @@ -1,9 +1,3 @@ -export type AuthProps = { - clientId: string; - clientSecret: string; - refreshToken: string; -}; - type ExternalUrls = { spotify: string; }; @@ -34,4 +28,5 @@ export type NowPlayingItem = { album: Album; artists: Artist[]; external_urls: ExternalUrls; + duration_ms: number; };