diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 6540589d8..007884694 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -46,11 +46,7 @@ services: context: ./frontend dockerfile: Dockerfile.dev volumes: - - ./frontend/src/pages:/app/src/pages - - ./frontend/src/components:/app/src/components - - ./frontend/src/ee:/app/src/ee - - ./frontend/src/locales:/app/src/locales - - ./frontend/src/styles:/app/src/styles + - ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files - ./frontend/public:/app/public - ./frontend/next-i18next.config.js:/app/next-i18next.config.js env_file: .env diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a240ef89..f5bb934fe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,6 +28,7 @@ "@reduxjs/toolkit": "^1.8.3", "@stripe/react-stripe-js": "^1.10.0", "@stripe/stripe-js": "^1.46.0", + "@tanstack/react-query": "^4.23.0", "add": "^2.0.6", "axios": "^0.27.2", "axios-auth-refresh": "^3.3.3", @@ -6646,6 +6647,41 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/@tanstack/query-core": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.22.4.tgz", + "integrity": "sha512-t79CMwlbBnj+yL82tEcmRN93bL4U3pae2ota4t5NN2z3cIeWw74pzdWrKRwOfTvLcd+b30tC+ciDlfYOKFPGUw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.23.0.tgz", + "integrity": "sha512-cfQsrecZQjYYueiow4WcK8ItokXJnv+b2OrK8Lf5kF7lM9uCo1ilyygFB8wo4MfxchUBVM6Cs8wq4Ed7fouwkA==", + "dependencies": { + "@tanstack/query-core": "4.22.4", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@testing-library/dom": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", @@ -26993,6 +27029,20 @@ "postcss-selector-parser": "6.0.10" } }, + "@tanstack/query-core": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.22.4.tgz", + "integrity": "sha512-t79CMwlbBnj+yL82tEcmRN93bL4U3pae2ota4t5NN2z3cIeWw74pzdWrKRwOfTvLcd+b30tC+ciDlfYOKFPGUw==" + }, + "@tanstack/react-query": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.23.0.tgz", + "integrity": "sha512-cfQsrecZQjYYueiow4WcK8ItokXJnv+b2OrK8Lf5kF7lM9uCo1ilyygFB8wo4MfxchUBVM6Cs8wq4Ed7fouwkA==", + "requires": { + "@tanstack/query-core": "4.22.4", + "use-sync-external-store": "^1.2.0" + } + }, "@testing-library/dom": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index c03c20343..f97edd68f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "@reduxjs/toolkit": "^1.8.3", "@stripe/react-stripe-js": "^1.10.0", "@stripe/stripe-js": "^1.46.0", + "@tanstack/react-query": "^4.23.0", "add": "^2.0.6", "axios": "^0.27.2", "axios-auth-refresh": "^3.3.3", diff --git a/frontend/src/components/utilities/SecurityClient.ts b/frontend/src/components/utilities/SecurityClient.ts index 57e35d6b2..fa1ec0bb0 100644 --- a/frontend/src/components/utilities/SecurityClient.ts +++ b/frontend/src/components/utilities/SecurityClient.ts @@ -1,27 +1,18 @@ -import token from '@app/pages/api/auth/Token'; +import { getAuthToken, setAuthToken } from '@app/reactQuery'; +// depreciated: go for apiRequest module in config/api export default class SecurityClient { - static #token = ''; - static setToken(tokenStr: string) { - this.#token = tokenStr; + setAuthToken(tokenStr); } static async fetchCall(resource: RequestInfo, options?: RequestInit | undefined) { const req = new Request(resource, options); - if (this.#token === '') { - try { - // TODO: This should be moved to a context to do it only once when app loads - // this try catch saves route guard from a stuck state - this.setToken(await token()); - } catch (error) { - console.error('Unauthorized access'); - } - } + const token = getAuthToken(); - if (this.#token) { - req.headers.set('Authorization', `Bearer ${this.#token}`); + if (token) { + req.headers.set('Authorization', `Bearer ${token}`); } return fetch(req); diff --git a/frontend/src/config/request.ts b/frontend/src/config/request.ts new file mode 100644 index 000000000..0e04789b2 --- /dev/null +++ b/frontend/src/config/request.ts @@ -0,0 +1,19 @@ +import axios from 'axios'; + +import { getAuthToken } from '@app/reactQuery'; + +export const apiRequest = axios.create({ + baseURL: '/', + headers: { + 'Content-Type': 'application/json' + } +}); + +apiRequest.interceptors.request.use((config) => { + const token = getAuthToken(); + if (token && config.headers) { + // eslint-disable-next-line no-param-reassign + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); diff --git a/frontend/src/context/AuthContext/AuthContext.tsx b/frontend/src/context/AuthContext/AuthContext.tsx new file mode 100644 index 000000000..c064852bd --- /dev/null +++ b/frontend/src/context/AuthContext/AuthContext.tsx @@ -0,0 +1,49 @@ +import { ReactNode, useEffect } from 'react'; +import { useRouter } from 'next/router'; + +import { publicPaths } from '@app/const'; +import { useToggle } from '@app/hooks'; +import { useGetAuthToken } from '@app/hooks/api'; +import { isLoggedIn } from '@app/reactQuery'; + +type Props = { + children: ReactNode; +}; + +// TODO(akhilmhdh): Using react-simple-animate from hard dom offloading +// smoother dom offloading needs to be done + +// Authentication controller +// Does route checking +// Provide a context for whole app to notify user is authorized or not +export const AuthProvider = ({ children }: Props): JSX.Element => { + const { isLoading } = useGetAuthToken(); + const { pathname, push } = useRouter(); + const [isReady, setIsReady] = useToggle(false); + + useEffect(() => { + // check if loading of auth is done + if (!isLoading) { + // not a public path and not authenticated kick to login page + if (!publicPaths.includes(pathname) && !isLoggedIn()) { + push('/login').then(() => { + setIsReady.on(); + }); + } else { + // else good to go + setIsReady.on(); + } + } + }, [pathname, isLoading]); + + // wait for app to load the auth state + if (isLoading || !isReady) { + return ( +
+