diff --git a/package-lock.json b/package-lock.json index 02fbfc4..cdb1641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,18 +16,22 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", + "@tanstack/react-query": "^5.51.23", "axios": "^1.7.3", "query-string": "^9.1.0", "react": "^18.3.1", + "react-country-flag": "^3.1.0", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.26.0" + "react-router-dom": "^6.26.0", + "zustand": "^4.5.5" }, "devDependencies": { "@commitlint/cli": "^19.4.0", "@commitlint/config-conventional": "^19.2.2", "@eslint/js": "^9.8.0", "@tailwindcss/typography": "^0.5.14", + "@tanstack/eslint-plugin-query": "^5.51.15", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", @@ -2113,6 +2117,191 @@ "node": ">=4" } }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.51.15", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.51.15.tgz", + "integrity": "sha512-btX03EOGvNxTGJDqHMmQwfSt/hp93Z8I4FNBijoyEdDnjGi4jVjpGP7nEi9LaMnHFsylucptVGb6GQngWs07bA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "8.0.0-alpha.30" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8 || ^9" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/scope-manager": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.30.tgz", + "integrity": "sha512-FGW/iPWGyPFamAVZ60oCAthMqQrqafUGebF8UKuq/ha+e9SVG6YhJoRzurlQXOVf8dHfOhJ0ADMXyFnMc53clg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.30" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/types": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.30.tgz", + "integrity": "sha512-4WzLlw27SO9pK9UFj/Hu7WGo8WveT0SEiIpFVsV2WwtQmLps6kouwtVCB8GJPZKJyurhZhcqCoQVQFmpv441Vg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.30.tgz", + "integrity": "sha512-WSXbc9ZcXI+7yC+6q95u77i8FXz6HOLsw3ST+vMUlFy1lFbXyFL/3e6HDKQCm2Clt0krnoCPiTGvIn+GkYPn4Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.30", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/utils": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.30.tgz", + "integrity": "sha512-rfhqfLqFyXhHNDwMnHiVGxl/Z2q/3guQ1jLlGQ0hi9Rb7inmwz42crM+NnLPR+2vEnwyw1P/g7fnQgQ3qvFx4g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.0.0-alpha.30", + "@typescript-eslint/types": "8.0.0-alpha.30", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.30" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.0.0-alpha.30", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.30.tgz", + "integrity": "sha512-XZuNurZxBqmr6ZIRIwWFq7j5RZd6ZlkId/HZEWyfciK+CWoyOxSF9Pv2VXH9Rlu2ZG2PfbhLz2Veszl4Pfn7yA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.30", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tanstack/eslint-plugin-query/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz", + "integrity": "sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.51.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.23.tgz", + "integrity": "sha512-CfJCfX45nnVIZjQBRYYtvVMIsGgWLKLYC4xcUiYEey671n1alvTZoCBaU9B85O8mF/tx9LPyrI04A6Bs2THv4A==", + "dependencies": { + "@tanstack/query-core": "5.51.21" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -5852,6 +6041,17 @@ "node": ">=0.10.0" } }, + "node_modules/react-country-flag": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-country-flag/-/react-country-flag-3.1.0.tgz", + "integrity": "sha512-JWQFw1efdv9sTC+TGQvTKXQg1NKbDU2mBiAiRWcKM9F1sK+/zjhP2yGmm8YDddWyZdXVkR8Md47rPMJmo4YO5g==", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -6749,6 +6949,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -7243,6 +7451,33 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 6e9cee0..01e09eb 100644 --- a/package.json +++ b/package.json @@ -22,18 +22,22 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.16.7", + "@tanstack/react-query": "^5.51.23", "axios": "^1.7.3", "query-string": "^9.1.0", "react": "^18.3.1", + "react-country-flag": "^3.1.0", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.26.0" + "react-router-dom": "^6.26.0", + "zustand": "^4.5.5" }, "devDependencies": { "@commitlint/cli": "^19.4.0", "@commitlint/config-conventional": "^19.2.2", "@eslint/js": "^9.8.0", "@tailwindcss/typography": "^0.5.14", + "@tanstack/eslint-plugin-query": "^5.51.15", "@testing-library/jest-dom": "^6.4.8", "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", diff --git a/src/App.tsx b/src/App.tsx index 99ab799..77008aa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,11 +2,15 @@ import "./App.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./routes"; import { AuthProvider } from "./context/AuthProvider.tsx"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; function App() { + const queryClient = new QueryClient(); return ( - - - + + + + + ); } diff --git a/src/api/auth/hooks/useUserQuery.ts b/src/api/auth/hooks/useUserQuery.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/auth/service/auth.service.ts b/src/api/auth/service/auth.service.ts index 1428941..9e31bfc 100644 --- a/src/api/auth/service/auth.service.ts +++ b/src/api/auth/service/auth.service.ts @@ -1,4 +1,7 @@ -import { TokenScopeResponse } from "../../../data-objects/interface"; +import { + TokenResponse, + TokenScopeResponse, +} from "../../../data-objects/interface"; const authorizationEndpoint = "https://accounts.spotify.com/authorize"; const clientId = import.meta.env.VITE_CLIENT_ID; @@ -73,9 +76,10 @@ export async function getToken(code: string): Promise { return response; } -export const getRefreshToken = async () => { +export const getRefreshToken = async ( + refreshToken: string, +): Promise => { // refresh token that has been previously stored - const refreshToken = localStorage.getItem("refresh_token"); const url = "https://accounts.spotify.com/api/token"; const payload = { @@ -92,8 +96,5 @@ export const getRefreshToken = async () => { const body = await fetch(url, payload); const response = await body.json(); - localStorage.setItem("access_token", response.accessToken); - if (response.refreshToken) { - localStorage.setItem("refresh_token", response.refreshToken); - } + return response; }; diff --git a/src/api/user/hooks/useProfileQuery.ts b/src/api/user/hooks/useProfileQuery.ts new file mode 100644 index 0000000..d057b43 --- /dev/null +++ b/src/api/user/hooks/useProfileQuery.ts @@ -0,0 +1,10 @@ +import { useQuery } from "@tanstack/react-query"; +import profileQueryKey from "./user-query-keys.ts"; +import { getUserData } from "../service/user.service.ts"; + +export const useProfileQuery = (token: string) => { + return useQuery({ + queryKey: profileQueryKey.profile, + queryFn: () => getUserData(token), + }); +}; diff --git a/src/api/user/hooks/user-query-keys.ts b/src/api/user/hooks/user-query-keys.ts new file mode 100644 index 0000000..e0d2661 --- /dev/null +++ b/src/api/user/hooks/user-query-keys.ts @@ -0,0 +1,5 @@ +const profileQueryKey = { + profile: ["profile"], +}; + +export default profileQueryKey; diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..2cdec80 --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1 @@ +export * from "./hooks/useProfileQuery"; diff --git a/src/api/user/service/user.service.ts b/src/api/user/service/user.service.ts new file mode 100644 index 0000000..9ca34c4 --- /dev/null +++ b/src/api/user/service/user.service.ts @@ -0,0 +1,12 @@ +import { ProfileInterface } from "../../../data-objects/interface"; + +export async function getUserData(token: string): Promise { + const response = await fetch("https://api.spotify.com/v1/me", { + method: "GET", + headers: { + Authorization: "Bearer " + token, + }, + }); + + return await response.json(); +} diff --git a/src/axios/createBaseAxiosInstance.ts b/src/axios/createBaseAxiosInstance.ts deleted file mode 100644 index 2b12731..0000000 --- a/src/axios/createBaseAxiosInstance.ts +++ /dev/null @@ -1,15 +0,0 @@ -import axios, { CreateAxiosDefaults } from "axios"; - -export const createBaseAxiosInstance = ( - configOverrides: CreateAxiosDefaults = {}, -) => { - const baseURL = import.meta.env.VITE_TMDB_API_URL; - return axios.create({ - baseURL, - ...configOverrides, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("accessToken")}`, - }, - }); -}; diff --git a/src/axios/index.ts b/src/axios/index.ts deleted file mode 100644 index 969a1e6..0000000 --- a/src/axios/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createBaseAxiosInstance } from './createBaseAxiosInstance'; -import Qs from 'query-string'; - -export const axios = createBaseAxiosInstance({ - // Add params serializer to convert objects to query string - // This is useful when you want to pass an object as a query parameter - paramsSerializer: function (params) { - return Qs.stringify(params); - }, -}); \ No newline at end of file diff --git a/src/components/ExpandContent.tsx b/src/components/ExpandContent.tsx index 76ea787..a053374 100644 --- a/src/components/ExpandContent.tsx +++ b/src/components/ExpandContent.tsx @@ -1,6 +1,9 @@ const ExpandContent = () => { return ( -
+

Card title!

If a dog chews shoes whose shoes does he choose?

diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 592a4b7..026fb0e 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -4,7 +4,10 @@ import MainControls from "./MainControls.tsx"; const Footer = () => { return ( -