diff --git a/app/layout.tsx b/app/layout.tsx
index acc9ecc..75236df 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -2,9 +2,10 @@ import type { Metadata } from 'next';
import './globals.css';
import classNames from 'classnames';
import { pretendard } from '@/styles/fonts';
+import TanstackQueryProvider from '@/providers/TanstackQueryProvider';
export const metadata: Metadata = {
- title: '역사의 고서',
+ title: '부마위키 | 역사의 고서',
description: '우리의 손으로 써내려 나가는 역사의 고서, 부마위키',
};
@@ -15,7 +16,9 @@ export default function RootLayout({
}>) {
return (
-
{children}
+
+ {children}
+
);
}
diff --git a/app/oauth/_entities/api/axios.ts b/app/oauth/_entities/api/axios.ts
new file mode 100644
index 0000000..4eae745
--- /dev/null
+++ b/app/oauth/_entities/api/axios.ts
@@ -0,0 +1,11 @@
+import { http, refreshTokenHeader } from '@/modules/services';
+
+export const requestLogin = async (authCode: string) => {
+ const { data } = await http.post('/auth/oauth/bsm', {}, { headers: { authCode } });
+ return data;
+};
+
+export const requestLogout = async () => {
+ const { data } = await http.delete('/auth/bsm/logout', refreshTokenHeader());
+ return data;
+};
diff --git a/app/oauth/_entities/api/mutation.ts b/app/oauth/_entities/api/mutation.ts
new file mode 100644
index 0000000..6de5ba8
--- /dev/null
+++ b/app/oauth/_entities/api/mutation.ts
@@ -0,0 +1,26 @@
+import { useMutation } from '@tanstack/react-query';
+import { requestLogin, requestLogout } from './axios';
+import { Storage } from '@/modules/services';
+import { Bumawiki } from '@/modules/constants';
+
+export const useLoginMutation = () => {
+ return useMutation({
+ mutationFn: requestLogin,
+ onSuccess: ({ accessToken, refreshToken }) => {
+ Storage.setItem(Bumawiki.token.access, accessToken);
+ Storage.setItem(Bumawiki.token.refresh, refreshToken);
+ window.history.go(-2);
+ },
+ });
+};
+
+export const useLogoutMutation = () => {
+ return useMutation({
+ mutationFn: requestLogout,
+ onSuccess: window.location.reload,
+ onSettled: () => {
+ Storage.delItem(Bumawiki.token.access);
+ Storage.delItem(Bumawiki.token.refresh);
+ },
+ });
+};
diff --git a/app/oauth/_entities/model/index.ts b/app/oauth/_entities/model/index.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/app/oauth/_entities/model/index.ts
@@ -0,0 +1 @@
+export {};
diff --git a/app/oauth/_features/index.ts b/app/oauth/_features/index.ts
new file mode 100644
index 0000000..08101bf
--- /dev/null
+++ b/app/oauth/_features/index.ts
@@ -0,0 +1 @@
+export { AuthenticationPage } from './ui/AuthenticationPage';
diff --git a/app/oauth/_features/lib/withAuthentication.tsx b/app/oauth/_features/lib/withAuthentication.tsx
new file mode 100644
index 0000000..4b4da32
--- /dev/null
+++ b/app/oauth/_features/lib/withAuthentication.tsx
@@ -0,0 +1,24 @@
+import { useLoginMutation } from '../../_entities/api/mutation';
+import { useMount } from '@/hooks/useMount';
+import { useSearchParams } from 'next/navigation';
+import React, { useEffect } from 'react';
+
+export const withAuthentication = (Component: React.ComponentType
) => {
+ const WrappedComponent: React.FC
= (props) => {
+ const { mutate: login } = useLoginMutation();
+ const isMounted = useMount();
+ const authCode = useSearchParams().get('code') || '';
+
+ useEffect(() => {
+ if (isMounted) login(authCode);
+ }, [isMounted, authCode, login]);
+
+ return ;
+ };
+
+ WrappedComponent.displayName = `withAuthentication(${
+ Component.displayName || Component.name || 'Component'
+ })`;
+
+ return WrappedComponent;
+};
diff --git a/app/oauth/_features/ui/AuthenticationPage.tsx b/app/oauth/_features/ui/AuthenticationPage.tsx
new file mode 100644
index 0000000..1ef8688
--- /dev/null
+++ b/app/oauth/_features/ui/AuthenticationPage.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import { MoonLoader } from 'react-spinners';
+import { withAuthentication } from '../lib/withAuthentication';
+
+export const AuthenticationPage = withAuthentication(() => {
+ return (
+
+
+ 로그인 중...
+
+ );
+});
diff --git a/app/oauth/page.tsx b/app/oauth/page.tsx
new file mode 100644
index 0000000..22eab9d
--- /dev/null
+++ b/app/oauth/page.tsx
@@ -0,0 +1,18 @@
+import { Suspense } from 'react';
+import { AuthenticationPage } from './_features';
+import { Metadata, NextPage } from 'next';
+
+export const metadata: Metadata = {
+ title: '부마위키 | 로그인',
+ description: '로그인 후 부마위키 문서에 직접 기여해보세요.',
+};
+
+const Page: NextPage = () => {
+ return (
+
+
+
+ );
+};
+
+export default Page;
diff --git a/next.config.ts b/next.config.ts
index 61ffa7b..f1d2ec5 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -12,7 +12,7 @@ const nextConfig: NextConfig = {
return [
{
source: '/insert-proxy/:path*',
- destination: 'https://buma.wiki/api/path*',
+ destination: 'https://buma.wiki/api/:path*',
basePath: false,
},
];
diff --git a/package.json b/package.json
index 7550284..47beac4 100644
--- a/package.json
+++ b/package.json
@@ -9,10 +9,13 @@
"lint": "next lint"
},
"dependencies": {
+ "@tanstack/react-query": "^5.62.12",
+ "axios": "^1.7.9",
"classnames": "^2.5.1",
"next": "15.1.3",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "react-spinners": "^0.15.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b3ffac4..8129795 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,12 @@ importers:
.:
dependencies:
+ '@tanstack/react-query':
+ specifier: ^5.62.12
+ version: 5.62.12(react@19.0.0)
+ axios:
+ specifier: ^1.7.9
+ version: 1.7.9
classnames:
specifier: ^2.5.1
version: 2.5.1
@@ -20,6 +26,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
+ react-spinners:
+ specifier: ^0.15.0
+ version: 0.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
devDependencies:
'@eslint/eslintrc':
specifier: ^3
@@ -325,6 +334,14 @@ packages:
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@tanstack/query-core@5.62.12':
+ resolution: {integrity: sha512-6igFeBgymHkCxVgaEk+yiLwkMf9haui/EQLmI3o9CatOyDThEoFKe8toLWvWliZC/Jf+h7NwHi/zjfyLArr1ow==}
+
+ '@tanstack/react-query@5.62.12':
+ resolution: {integrity: sha512-yt8p7l5MlHA3QCt6xF1Cu9dw1Anf93yTK+DMDJQ64h/mshAymVAtcwj8TpsyyBrZNWAAZvza/m76bnTSR79ZtQ==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
@@ -473,6 +490,9 @@ packages:
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -481,6 +501,9 @@ packages:
resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==}
engines: {node: '>=4'}
+ axios@1.7.9:
+ resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
+
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -557,6 +580,10 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
@@ -619,6 +646,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
detect-libc@2.0.3:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
@@ -840,6 +871,15 @@ packages:
flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+ follow-redirects@1.15.9:
+ resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -847,6 +887,10 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
+ form-data@4.0.1:
+ resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+ engines: {node: '>= 6'}
+
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -1148,6 +1192,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1346,6 +1398,9 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -1361,6 +1416,12 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ react-spinners@0.15.0:
+ resolution: {integrity: sha512-ZO3/fNB9Qc+kgpG3SfdlMnvTX6LtLmTnOogb3W6sXIaU/kZ1ydEViPfZ06kSOaEsor58C/tzXw2wROGQu3X2pA==}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@@ -1894,6 +1955,13 @@ snapshots:
dependencies:
tslib: 2.8.1
+ '@tanstack/query-core@5.62.12': {}
+
+ '@tanstack/react-query@5.62.12(react@19.0.0)':
+ dependencies:
+ '@tanstack/query-core': 5.62.12
+ react: 19.0.0
+
'@types/estree@1.0.6': {}
'@types/json-schema@7.0.15': {}
@@ -2091,12 +2159,22 @@ snapshots:
ast-types-flow@0.0.8: {}
+ asynckit@0.4.0: {}
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.0.0
axe-core@4.10.2: {}
+ axios@1.7.9:
+ dependencies:
+ follow-redirects: 1.15.9
+ form-data: 4.0.1
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
axobject-query@4.1.0: {}
balanced-match@1.0.2: {}
@@ -2182,6 +2260,10 @@ snapshots:
color-string: 1.9.1
optional: true
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
commander@4.1.1: {}
concat-map@0.0.1: {}
@@ -2238,6 +2320,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ delayed-stream@1.0.0: {}
+
detect-libc@2.0.3:
optional: true
@@ -2609,6 +2693,8 @@ snapshots:
flatted@3.3.2: {}
+ follow-redirects@1.15.9: {}
+
for-each@0.3.3:
dependencies:
is-callable: 1.2.7
@@ -2618,6 +2704,12 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ form-data@4.0.1:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
fsevents@2.3.3:
optional: true
@@ -2930,6 +3022,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -3125,6 +3223,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ proxy-from-env@1.1.0: {}
+
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -3136,6 +3236,11 @@ snapshots:
react-is@16.13.1: {}
+ react-spinners@0.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
react@19.0.0: {}
read-cache@1.0.0:
diff --git a/src/hooks/useMount.ts b/src/hooks/useMount.ts
new file mode 100644
index 0000000..8df0d0b
--- /dev/null
+++ b/src/hooks/useMount.ts
@@ -0,0 +1,11 @@
+import { useEffect, useState } from 'react';
+
+export const useMount = () => {
+ const [isMounted, setIsMounted] = useState(false);
+
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ return isMounted;
+};
diff --git a/src/modules/constants.ts b/src/modules/constants.ts
new file mode 100644
index 0000000..b642561
--- /dev/null
+++ b/src/modules/constants.ts
@@ -0,0 +1,19 @@
+export const Bumawiki = {
+ error: {
+ img_400_1: 'IMG-400-1',
+ docs_404_1: 'DOCS-404-1',
+ docs_404_2: 'DOCS-404-2',
+ docs_403_1: 'DOCS-403-1',
+ docs_403_2: 'DOCS-403-2',
+ common_403_1: 'COMMON-403-1',
+ user_403_1: 'USER-403-1',
+ user_404_1: 'USER-404-1',
+ token_403_1: 'TOKEN-403-1',
+ token_403_2: 'TOKEN-403-2',
+ token_403_3: 'TOKEN-403-3',
+ } as const,
+ token: {
+ access: 'access_token',
+ refresh: 'refresh_token',
+ } as const,
+};
diff --git a/src/modules/services.ts b/src/modules/services.ts
new file mode 100644
index 0000000..adb1fc3
--- /dev/null
+++ b/src/modules/services.ts
@@ -0,0 +1,67 @@
+import axios from 'axios';
+import { Bumawiki } from './constants';
+
+export type LocalStorageKey = 'access_token' | 'refresh_token';
+
+export class Storage {
+ private static isWindowAvailable() {
+ return typeof window !== 'undefined';
+ }
+
+ static getItem(key: LocalStorageKey) {
+ if (this.isWindowAvailable()) return localStorage.getItem(key);
+ }
+
+ static setItem(key: LocalStorageKey, value: string) {
+ if (!this.isWindowAvailable()) return;
+ localStorage.setItem(key, value);
+ }
+
+ static delItem(key: LocalStorageKey) {
+ if (!this.isWindowAvailable) return;
+ localStorage.removeItem(key);
+ }
+
+ static clear() {
+ if (this.isWindowAvailable()) localStorage.clear();
+ }
+}
+
+export const authorizationHeader = () => ({
+ headers: {
+ Authorization: Storage.getItem(Bumawiki.token.access),
+ },
+});
+
+export const refreshTokenHeader = () => ({
+ headers: {
+ RefreshToken: Storage.getItem(Bumawiki.token.refresh),
+ },
+});
+
+export const http = axios.create({
+ baseURL: '/insert-proxy',
+ timeout: 10000,
+});
+
+http.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const request = error.config;
+ const { code } = error.response.data;
+ const isAccessTokenExpiredError = code === Bumawiki.error.token_403_2;
+
+ if (isAccessTokenExpiredError && !request.sent) {
+ request.sent = true;
+ request.headers.Authorization = await refresh();
+ return http(request);
+ }
+ return Promise.reject(error);
+ },
+);
+
+const refresh = async () => {
+ const { data } = await http.put('/auth/refresh/access', {}, refreshTokenHeader());
+ Storage.setItem(Bumawiki.token.access, data.accessToken);
+ return data.accessToken;
+};
diff --git a/src/providers/TanstackQueryProvider.tsx b/src/providers/TanstackQueryProvider.tsx
new file mode 100644
index 0000000..75c1bb4
--- /dev/null
+++ b/src/providers/TanstackQueryProvider.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import React from 'react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+const generateQueryClient = () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ refetchIntervalInBackground: false,
+ },
+ },
+ });
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+const getQueryClient = () => {
+ if (typeof window === 'undefined') return generateQueryClient();
+ if (!browserQueryClient) browserQueryClient = generateQueryClient();
+ return browserQueryClient;
+};
+
+const TanstackQueryProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const queryClient = getQueryClient();
+
+ return {children};
+};
+export default TanstackQueryProvider;