diff --git a/apps/nextjs-app/package.json b/apps/nextjs-app/package.json index 06b5b876..ea64edf2 100644 --- a/apps/nextjs-app/package.json +++ b/apps/nextjs-app/package.json @@ -29,7 +29,6 @@ "@radix-ui/react-switch": "^1.0.3", "@tanstack/react-query": "^5.32.0", "@tanstack/react-query-devtools": "^5.32.0", - "axios": "^1.6.8", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "dayjs": "^1.11.11", diff --git a/apps/nextjs-app/src/features/comments/api/get-comments.ts b/apps/nextjs-app/src/features/comments/api/get-comments.ts index b1c76479..c53c00fd 100644 --- a/apps/nextjs-app/src/features/comments/api/get-comments.ts +++ b/apps/nextjs-app/src/features/comments/api/get-comments.ts @@ -1,6 +1,6 @@ import { infiniteQueryOptions, useInfiniteQuery } from '@tanstack/react-query'; -import { api, attachCookie } from '@/lib/api-client'; +import { api } from '@/lib/api-client'; import { QueryConfig } from '@/lib/react-query'; import { Comment, Meta } from '@/types/api'; @@ -18,7 +18,7 @@ export const getComments = ({ discussionId, page, }, - headers: attachCookie(cookie).headers, + cookie, }); }; diff --git a/apps/nextjs-app/src/features/discussions/api/get-discussion.ts b/apps/nextjs-app/src/features/discussions/api/get-discussion.ts index ccd101e7..63fb9195 100644 --- a/apps/nextjs-app/src/features/discussions/api/get-discussion.ts +++ b/apps/nextjs-app/src/features/discussions/api/get-discussion.ts @@ -1,6 +1,6 @@ import { useQuery, queryOptions } from '@tanstack/react-query'; -import { api, attachCookie } from '@/lib/api-client'; +import { api } from '@/lib/api-client'; import { QueryConfig } from '@/lib/react-query'; import { Discussion } from '@/types/api'; @@ -12,7 +12,7 @@ export const getDiscussion = ({ cookie?: string; }): Promise<{ data: Discussion }> => { return api.get(`/discussions/${discussionId}`, { - headers: attachCookie(cookie).headers, + cookie, }); }; diff --git a/apps/nextjs-app/src/features/discussions/api/get-discussions.ts b/apps/nextjs-app/src/features/discussions/api/get-discussions.ts index b93e785d..f37dd4e3 100644 --- a/apps/nextjs-app/src/features/discussions/api/get-discussions.ts +++ b/apps/nextjs-app/src/features/discussions/api/get-discussions.ts @@ -1,6 +1,6 @@ import { queryOptions, useQuery } from '@tanstack/react-query'; -import { api, attachCookie } from '@/lib/api-client'; +import { api } from '@/lib/api-client'; import { QueryConfig } from '@/lib/react-query'; import { Discussion, Meta } from '@/types/api'; @@ -14,7 +14,7 @@ export const getDiscussions = ( params: { page, }, - headers: attachCookie(cookie).headers, + cookie, }); }; diff --git a/apps/nextjs-app/src/lib/api-client.ts b/apps/nextjs-app/src/lib/api-client.ts index ff44bdc4..797d2ad0 100644 --- a/apps/nextjs-app/src/lib/api-client.ts +++ b/apps/nextjs-app/src/lib/api-client.ts @@ -1,48 +1,91 @@ -import Axios, { InternalAxiosRequestConfig } from 'axios'; - import { useNotifications } from '@/components/ui/notifications'; import { env } from '@/config/env'; -function authRequestInterceptor(config: InternalAxiosRequestConfig) { - if (config.headers) { - config.headers.Accept = 'application/json'; - } +type RequestOptions = { + method?: string; + headers?: Record; + body?: any; + cookie?: string; + params?: Record; + cache?: RequestCache; + next?: NextFetchRequestConfig; +}; - config.withCredentials = true; - return config; +function buildUrlWithParams( + url: string, + params?: RequestOptions['params'], +): string { + if (!params) return url; + const filteredParams = Object.fromEntries( + Object.entries(params).filter( + ([, value]) => value !== undefined && value !== null, + ), + ); + if (Object.keys(filteredParams).length === 0) return url; + const queryString = new URLSearchParams( + filteredParams as Record, + ).toString(); + return `${url}?${queryString}`; } -export const api = Axios.create({ - baseURL: env.API_URL, -}); - -api.interceptors.request.use(authRequestInterceptor); -api.interceptors.response.use( - (response) => { - return response.data; - }, - (error) => { - const message = error.response?.data?.message || error.message; - useNotifications.getState().addNotification({ - type: 'error', - title: 'Error', - message, - }); +async function fetchApi( + url: string, + options: RequestOptions = {}, +): Promise { + const { + method = 'GET', + headers = {}, + body, + cookie, + params, + cache = 'no-store', + next, + } = options; + const fullUrl = buildUrlWithParams(`${env.API_URL}${url}`, params); - return Promise.reject(error); - }, -); - -// if the endpoint requires the visiting user to be authenticated, -// attaching cookies is required for requests made on the server side -export const attachCookie = ( - cookie?: string, - headers?: Record, -) => { - return { + const response = await fetch(fullUrl, { + method, headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', ...headers, ...(cookie ? { Cookie: cookie } : {}), }, - }; + body: body ? JSON.stringify(body) : undefined, + credentials: 'include', + cache, + next, + }); + + if (!response.ok) { + const message = (await response.json()).message || response.statusText; + if (typeof window !== 'undefined') { + useNotifications.getState().addNotification({ + type: 'error', + title: 'Error', + message, + }); + } + throw new Error(message); + } + + return response.json(); +} + +export const api = { + get(url: string, options?: RequestOptions): Promise { + return fetchApi(url, { ...options, method: 'GET' }); + }, + post(url: string, body?: any, options?: RequestOptions): Promise { + return fetchApi(url, { ...options, method: 'POST', body }); + }, + put(url: string, body?: any, options?: RequestOptions): Promise { + return fetchApi(url, { ...options, method: 'PUT', body }); + }, + patch(url: string, body?: any, options?: RequestOptions): Promise { + return fetchApi(url, { ...options, method: 'PATCH', body }); + }, + delete(url: string, options?: RequestOptions): Promise { + return fetchApi(url, { ...options, method: 'DELETE' }); + }, }; diff --git a/apps/nextjs-app/src/lib/auth.tsx b/apps/nextjs-app/src/lib/auth.tsx index aaadba46..1d0ffa0f 100644 --- a/apps/nextjs-app/src/lib/auth.tsx +++ b/apps/nextjs-app/src/lib/auth.tsx @@ -13,7 +13,7 @@ import { api } from './api-client'; // these are not part of features as this is a module shared across features const getUser = async (): Promise => { - const response = await api.get('/auth/me'); + const response = (await api.get('/auth/me')) as { data: User }; return response.data; }; diff --git a/apps/nextjs-app/yarn.lock b/apps/nextjs-app/yarn.lock index e4bc6abd..f7432784 100644 --- a/apps/nextjs-app/yarn.lock +++ b/apps/nextjs-app/yarn.lock @@ -4066,15 +4066,6 @@ axe-core@^4.2.0, axe-core@^4.9.1: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== -axios@^1.6.8: - version "1.7.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" - integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - axobject-query@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" @@ -6385,7 +6376,7 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.244.0.tgz#dc75ef468959ca72ad5fd89a6a9b0503c141ea8a" integrity sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA== -follow-redirects@^1.14.0, follow-redirects@^1.15.6: +follow-redirects@^1.14.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==