diff --git a/jest.setup.js b/jest.setup.js
index 1b8dd7e..32e70ba 100644
--- a/jest.setup.js
+++ b/jest.setup.js
@@ -1,4 +1,5 @@
import '@testing-library/jest-dom';
+import axios from 'axios';
// eslint-disable-next-line no-undef
jest.mock('react-i18next', () => ({
@@ -17,3 +18,5 @@ jest.mock('react-i18next', () => ({
return Component;
},
}));
+
+axios.defaults.baseURL = 'http://localhost';
diff --git a/package.json b/package.json
index 2efa826..b55218e 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,8 @@
"prepare": "husky install"
},
"dependencies": {
+ "@tanstack/react-query": "^5.36.2",
+ "@tanstack/react-query-devtools": "^5.36.2",
"axios": "^0.21.2",
"classnames": "^2.2.6",
"framer-motion": "^10.15.0",
@@ -25,6 +27,7 @@
"i18next-xhr-backend": "^3.2.2",
"lodash": "^4.17.21",
"moment": "^2.29.2",
+ "nock": "^13.5.4",
"prop-types": "^15.7.2",
"qs": "^6.9.4",
"react": "^18.2.0",
diff --git a/src/components/sections/account/AcademicInfoSubSection.jsx b/src/components/sections/account/AcademicInfoSubSection.jsx
deleted file mode 100644
index b15d433..0000000
--- a/src/components/sections/account/AcademicInfoSubSection.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { withTranslation } from 'react-i18next';
-
-import { appBoundClassNames as classNames } from '../../../common/boundClassNames';
-
-import userShape from '../../../shapes/model/session/UserShape';
-import { CONTACT } from '../../../common/constants';
-import Attributes from '../../Attributes';
-
-class AcademicInfoSubSection extends Component {
- render() {
- const { t } = this.props;
- const { user } = this.props;
-
- if (user == null) {
- return null;
- }
-
- return (
-
-
{t('ui.title.academicInformation')}
-
d[t('js.property.name')]).join(', '),
- },
- ]}
- />
-
- {t('ui.message.academicInfoCaptionHead')}
-
- {CONTACT}
-
- {t('ui.message.academicInfoCaptionTail')}
-
-
- );
- }
-}
-
-const mapStateToProps = (state) => ({
- user: state.common.user.user,
-});
-
-const mapDispatchToProps = (dispatch) => ({});
-
-AcademicInfoSubSection.propTypes = {
- user: userShape,
-};
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(AcademicInfoSubSection),
-);
diff --git a/src/components/sections/account/AcademicInfoSubSection.tsx b/src/components/sections/account/AcademicInfoSubSection.tsx
new file mode 100644
index 0000000..e6a52ff
--- /dev/null
+++ b/src/components/sections/account/AcademicInfoSubSection.tsx
@@ -0,0 +1,44 @@
+import { useTranslation } from 'react-i18next';
+
+import { appBoundClassNames as classNames } from '@/common/boundClassNames';
+import { CONTACT } from '@/common/constants';
+import Attributes from '@/components/Attributes';
+import { useSessionInfo } from '@/queries/account';
+import { useTranslatedString } from '@/hooks/useTranslatedString';
+
+const AcademicInfoSubSection = () => {
+ const { t } = useTranslation();
+ const translate = useTranslatedString();
+ const { data: user } = useSessionInfo();
+
+ if (!user) {
+ return null;
+ }
+
+ return (
+
+
{t('ui.title.academicInformation')}
+
translate(d, 'name')).join(', '),
+ },
+ ]}
+ />
+
+ {t('ui.message.academicInfoCaptionHead')}
+
+ {CONTACT}
+
+ {t('ui.message.academicInfoCaptionTail')}
+
+
+ );
+};
+
+export default AcademicInfoSubSection;
diff --git a/src/components/sections/account/FavoriteDepartmentsSubSection.jsx b/src/components/sections/account/FavoriteDepartmentsSubSection.jsx
deleted file mode 100644
index b82090e..0000000
--- a/src/components/sections/account/FavoriteDepartmentsSubSection.jsx
+++ /dev/null
@@ -1,174 +0,0 @@
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-import axios from 'axios';
-
-import { appBoundClassNames as classNames } from '../../../common/boundClassNames';
-
-import SearchFilter from '../../SearchFilter';
-
-import { setUser } from '../../../redux/actions/common/user';
-
-import userShape from '../../../shapes/model/session/UserShape';
-
-class FavoriteDepartmentsSubSection extends Component {
- constructor(props) {
- super(props);
-
- this.state = {
- savedSelectedDepartments: new Set([]),
- selectedDepartments: new Set([]),
- allDepartments: [],
- };
- }
-
- componentDidMount() {
- const { user } = this.props;
-
- if (user) {
- this._setUserDepartment();
- }
-
- axios
- .get('/session/department-options', {})
- .then((response) => {
- this.setState({
- allDepartments: response.data.flat(1),
- });
- })
- .catch((error) => {});
- }
-
- componentDidUpdate(prevProps) {
- const { user } = this.props;
-
- if (!prevProps.user && user) {
- this._setUserDepartment();
- }
- }
-
- _setUserDepartment = () => {
- const { user } = this.props;
-
- this.setState({
- savedSelectedDepartments: new Set(user.favorite_departments.map((d) => String(d.id))),
- selectedDepartments: new Set(user.favorite_departments.map((d) => String(d.id))),
- });
- };
-
- updateCheckedValues = (filterName) => (checkedValues) => {
- this.setState({
- [filterName]: checkedValues,
- });
- };
-
- handleSubmit = (e) => {
- const { selectedDepartments } = this.state;
-
- e.preventDefault();
- e.stopPropagation();
-
- axios
- .post(
- '/session/favorite-departments',
- {
- fav_department: Array.from(selectedDepartments).filter((d) => d !== 'ALL'),
- },
- {},
- )
- .then((response) => {
- this.setState({
- savedSelectedDepartments: selectedDepartments,
- });
- this._refetchUser();
- })
- .catch((error) => {});
- };
-
- _refetchUser = () => {
- const { setUserDispatch } = this.props;
-
- axios
- .get('/session/info', {
- metadata: {
- gaCategory: 'User',
- gaVariable: 'GET / Instance',
- },
- })
- .then((response) => {
- setUserDispatch(response.data);
- })
- .catch((error) => {});
- };
-
- render() {
- const { t } = this.props;
- const { user } = this.props;
- const { allDepartments, savedSelectedDepartments, selectedDepartments } = this.state;
-
- if (user == null) {
- return null;
- }
-
- const departmentOptions = allDepartments.map((d) => [
- String(d.id),
- `${d[t('js.property.name')]} (${d.code})`,
- ]);
-
- const hasChange =
- selectedDepartments.size !== savedSelectedDepartments.size ||
- Array.from(selectedDepartments).some((d) => !savedSelectedDepartments.has(d));
-
- const favoriteDepartmentForm =
- allDepartments.length === 0 ? null : (
-
- );
-
- return (
-
-
{t('ui.title.settings')}
- {favoriteDepartmentForm}
-
- );
- }
-}
-
-const mapStateToProps = (state) => ({
- user: state.common.user.user,
-});
-
-const mapDispatchToProps = (dispatch) => ({
- setUserDispatch: (user) => {
- dispatch(setUser(user));
- },
-});
-
-FavoriteDepartmentsSubSection.propTypes = {
- user: userShape,
-
- setUserDispatch: PropTypes.func.isRequired,
-};
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(FavoriteDepartmentsSubSection),
-);
diff --git a/src/components/sections/account/FavoriteDepartmentsSubSection.tsx b/src/components/sections/account/FavoriteDepartmentsSubSection.tsx
new file mode 100644
index 0000000..e54d033
--- /dev/null
+++ b/src/components/sections/account/FavoriteDepartmentsSubSection.tsx
@@ -0,0 +1,77 @@
+import { FormEventHandler, useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { appBoundClassNames as classNames } from '@/common/boundClassNames';
+import SearchFilter from '@/components/SearchFilter';
+import {
+ useDepartmentOptions,
+ useSessionInfo,
+ useUpdateFavoriteDepartments,
+} from '@/queries/account';
+import { useTranslatedString } from '@/hooks/useTranslatedString';
+
+const FavoriteDepartmentsSubSection = () => {
+ const [selectedDepartments, setSelectedDepartments] = useState>(new Set([]));
+
+ const { t } = useTranslation();
+ const translate = useTranslatedString();
+ const { data: user } = useSessionInfo();
+ const { data: departmentOptions } = useDepartmentOptions();
+ const { mutate: updateFavoriteDepartments } = useUpdateFavoriteDepartments();
+
+ useEffect(() => {
+ if (user) {
+ setSelectedDepartments(new Set(user.favorite_departments.map((d) => String(d.id))));
+ }
+ }, [user]);
+
+ const handleSubmit: FormEventHandler = (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ updateFavoriteDepartments({ selectedDepartments });
+ };
+
+ if (!user) {
+ return null;
+ }
+
+ // TODO: Change comparison logic
+ const hasChange =
+ user.favorite_departments.length !== selectedDepartments.size ||
+ user.favorite_departments.some(({ id }) => !selectedDepartments.has(id.toString()));
+
+ return (
+
+
{t('ui.title.settings')}
+ {departmentOptions ? (
+
+ ) : (
+
{t('ui.placeholder.loading')}
+ )}
+
+ );
+};
+
+export default FavoriteDepartmentsSubSection;
diff --git a/src/components/sections/account/MyInfoSubSection.jsx b/src/components/sections/account/MyInfoSubSection.jsx
deleted file mode 100644
index c9ee4d0..0000000
--- a/src/components/sections/account/MyInfoSubSection.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import { withTranslation } from 'react-i18next';
-
-import { appBoundClassNames as classNames } from '../../../common/boundClassNames';
-
-import userShape from '../../../shapes/model/session/UserShape';
-
-import { getFullName } from '../../../common/guideline/components/Header';
-import Attributes from '../../Attributes';
-
-class MyInfoSubSection extends Component {
- render() {
- const { t } = this.props;
- const { user } = this.props;
-
- if (user == null) {
- return null;
- }
-
- return (
-
-
{t('ui.title.myInformation')}
-
-
- {t('ui.message.myInfoCaptionHead')}
-
- SPARCS SSO
-
- {t('ui.message.myInfoCaptionTail')}
-
-
- );
- }
-}
-
-const mapStateToProps = (state) => ({
- user: state.common.user.user,
-});
-
-const mapDispatchToProps = (dispatch) => ({});
-
-MyInfoSubSection.propTypes = {
- user: userShape,
-};
-
-export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(MyInfoSubSection));
diff --git a/src/components/sections/account/MyInfoSubSection.tsx b/src/components/sections/account/MyInfoSubSection.tsx
new file mode 100644
index 0000000..b1ae2bb
--- /dev/null
+++ b/src/components/sections/account/MyInfoSubSection.tsx
@@ -0,0 +1,40 @@
+import { useTranslation } from 'react-i18next';
+
+import { appBoundClassNames as classNames } from '@/common/boundClassNames';
+import { getFullName } from '@/common/guideline/components/Header';
+import Attributes from '@/components/Attributes';
+import { useSessionInfo } from '@/queries/account';
+
+const MyInfoSubSection = () => {
+ const { t } = useTranslation();
+ const { data: user } = useSessionInfo();
+
+ if (!user) {
+ return null;
+ }
+
+ return (
+
+
{t('ui.title.myInformation')}
+
+
+ {t('ui.message.myInfoCaptionHead')}
+
+ SPARCS SSO
+
+ {t('ui.message.myInfoCaptionTail')}
+
+
+ );
+};
+
+export default MyInfoSubSection;
diff --git a/src/components/sections/account/__tests__/AcademicInfoSubSection.test.tsx b/src/components/sections/account/__tests__/AcademicInfoSubSection.test.tsx
new file mode 100644
index 0000000..0c66651
--- /dev/null
+++ b/src/components/sections/account/__tests__/AcademicInfoSubSection.test.tsx
@@ -0,0 +1,16 @@
+import { renderWithQueryClient, sampleUser } from '@/test-utils';
+import AcademicInfoSubSection from '../AcademicInfoSubSection';
+import nock from 'nock';
+
+test('renders AcademicInfoSubSection', async () => {
+ renderWithQueryClient();
+});
+
+test('renders contact mail, student id', async () => {
+ nock('http://localhost').get('/session/info').reply(200, sampleUser);
+ const { findByTestId, findByText } = renderWithQueryClient();
+ const contact = await findByTestId('contact-mail');
+ expect(contact.textContent).toBe('otlplus@sparcs.org');
+ const studentId = await findByText('20210378');
+ expect(studentId).toBeTruthy();
+});
diff --git a/src/components/sections/account/__tests__/FavoriteDepartmentsSubSection.test.tsx b/src/components/sections/account/__tests__/FavoriteDepartmentsSubSection.test.tsx
new file mode 100644
index 0000000..1f06d25
--- /dev/null
+++ b/src/components/sections/account/__tests__/FavoriteDepartmentsSubSection.test.tsx
@@ -0,0 +1,6 @@
+import { renderWithQueryClient } from '@/test-utils';
+import FavoriteDepartmentsSubSection from '../FavoriteDepartmentsSubSection';
+
+test('renders FavoriteDepartmentsSubSection', async () => {
+ renderWithQueryClient();
+});
diff --git a/src/components/sections/account/__tests__/MyInfoSubSection.test.tsx b/src/components/sections/account/__tests__/MyInfoSubSection.test.tsx
new file mode 100644
index 0000000..008e1eb
--- /dev/null
+++ b/src/components/sections/account/__tests__/MyInfoSubSection.test.tsx
@@ -0,0 +1,6 @@
+import { renderWithQueryClient } from '@/test-utils';
+import MyInfoSubSection from '../MyInfoSubSection';
+
+test('renders MyInfoSubSection', async () => {
+ renderWithQueryClient();
+});
diff --git a/src/index.tsx b/src/index.tsx
index 5e6c628..c441f39 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -10,6 +10,8 @@ import { initReactI18next } from 'react-i18next';
import { Provider as ReduxProvider } from 'react-redux';
import { createBrowserRouter, Navigate, RouterProvider } from 'react-router-dom';
import { compose, legacy_createStore as createStore } from 'redux';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import App from '@/App';
import { API_URL, TRACKING_ID } from '@/const';
@@ -126,9 +128,10 @@ ReactGA.initialize([
]);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
-
const reduxStore = createStore(rootReducer, composeEnhancers());
+const queryClient = new QueryClient();
+
const router = createBrowserRouter([
{
path: '/',
@@ -157,7 +160,10 @@ const router = createBrowserRouter([
createRoot(document.getElementById('root')!).render(
-
+
+
+
+
,
);
diff --git a/src/pages/AccountPage.jsx b/src/pages/AccountPage.jsx
deleted file mode 100644
index f008bb9..0000000
--- a/src/pages/AccountPage.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React, { Component } from 'react';
-import { withTranslation } from 'react-i18next';
-
-import { appBoundClassNames as classNames } from '../common/boundClassNames';
-
-import Divider from '../components/Divider';
-import Scroller from '../components/Scroller';
-import MyInfoSubSection from '../components/sections/account/MyInfoSubSection';
-import AcademicInfoSubSection from '../components/sections/account/AcademicInfoSubSection';
-import FavoriteDepartmentsSubSection from '../components/sections/account/FavoriteDepartmentsSubSection';
-import { API_URL } from '../const';
-
-class AccountPage extends Component {
- render() {
- const { t } = this.props;
- return (
-
- );
- }
-}
-
-AccountPage.propTypes = {};
-
-export default withTranslation()(AccountPage);
diff --git a/src/pages/AccountPage.tsx b/src/pages/AccountPage.tsx
new file mode 100644
index 0000000..e1e43cf
--- /dev/null
+++ b/src/pages/AccountPage.tsx
@@ -0,0 +1,38 @@
+import { useTranslation } from 'react-i18next';
+
+import { appBoundClassNames as classNames } from '@/common/boundClassNames';
+import Divider from '@/components/Divider';
+import Scroller from '@/components/Scroller';
+import MyInfoSubSection from '@/components/sections/account/MyInfoSubSection';
+import AcademicInfoSubSection from '@/components/sections/account/AcademicInfoSubSection';
+import FavoriteDepartmentsSubSection from '@/components/sections/account/FavoriteDepartmentsSubSection';
+import { API_URL } from '@/const';
+
+const AccountPage = () => {
+ const { t } = useTranslation();
+ return (
+
+ );
+};
+
+export default AccountPage;
diff --git a/src/queries/account.ts b/src/queries/account.ts
new file mode 100644
index 0000000..823e055
--- /dev/null
+++ b/src/queries/account.ts
@@ -0,0 +1,52 @@
+import User from '@/shapes/model/session/User';
+import Department from '@/shapes/model/subject/Department';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import axios from 'axios';
+
+const QUERY_KEYS = {
+ SESSION_INFO: 'sessionInfo',
+ DEPARTMENT_OPTIONS: 'departmentOptions',
+} as const;
+
+export const useSessionInfo = () => {
+ return useQuery({
+ queryKey: [QUERY_KEYS.SESSION_INFO],
+ staleTime: Infinity,
+ queryFn: async () => {
+ return (
+ await axios.get('/session/info', {
+ metadata: {
+ gaCategory: 'User',
+ gaVariable: 'GET / Instance',
+ },
+ })
+ ).data;
+ },
+ });
+};
+
+export const useDepartmentOptions = () => {
+ // const translate = useTranslatedString();
+ return useQuery({
+ queryKey: [QUERY_KEYS.DEPARTMENT_OPTIONS],
+ staleTime: Infinity,
+ queryFn: async () => {
+ return (await axios.get('/session/department-options')).data.flat(1);
+ },
+ });
+};
+
+export const useUpdateFavoriteDepartments = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: ({ selectedDepartments }: { selectedDepartments: Set }) => {
+ return axios.post('/session/favorite-departments', {
+ fav_department: Array.from(selectedDepartments).filter((d) => d !== 'ALL'),
+ });
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.DEPARTMENT_OPTIONS] });
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SESSION_INFO] });
+ },
+ });
+};
diff --git a/src/shapes/model/session/User.ts b/src/shapes/model/session/User.ts
index 1105d0c..eded726 100644
--- a/src/shapes/model/session/User.ts
+++ b/src/shapes/model/session/User.ts
@@ -11,7 +11,7 @@ export default interface User {
majors: Department[];
department?: Department;
departments: Department[];
- favorite_departments?: Department[];
+ favorite_departments: Department[];
review_writable_lectures: Lecture[];
my_timetable_lectures: Lecture[];
reviews: Review[];
diff --git a/src/test-utils.tsx b/src/test-utils.tsx
index 59f1265..a23cd73 100644
--- a/src/test-utils.tsx
+++ b/src/test-utils.tsx
@@ -1,16 +1,27 @@
-import React, { ReactElement } from 'react';
-import { render, RenderOptions } from '@testing-library/react';
+import { ReactElement, ReactNode } from 'react';
+import { render } from '@testing-library/react';
import Course from '@/shapes/model/subject/Course';
import { SemesterType } from '@/shapes/enum';
import Lecture from '@/shapes/model/subject/Lecture';
import Classtime from '@/shapes/model/subject/Classtime';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import User from '@/shapes/model/session/User';
-const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
- return children;
+const queryClientWrapper = ({ children }: { children: ReactNode }) => {
+ const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+ });
+ return {children};
};
-const customRender = (ui: ReactElement, options?: Omit) =>
- render(ui, { wrapper: AllTheProviders, ...options });
+const customRender = (ui: ReactElement) => render(ui);
+
+export const renderWithQueryClient = (ui: ReactElement) =>
+ render(ui, { wrapper: queryClientWrapper });
export * from '@testing-library/react';
export { customRender as render };
@@ -84,3 +95,17 @@ export const sampleClasstime: Classtime = {
begin: 0,
end: 0,
};
+
+export const sampleUser: User = {
+ id: 0,
+ email: '',
+ student_id: '20210378',
+ firstName: '',
+ lastName: '',
+ majors: [],
+ departments: [],
+ favorite_departments: [],
+ review_writable_lectures: [],
+ my_timetable_lectures: [],
+ reviews: [],
+};
diff --git a/yarn.lock b/yarn.lock
index 8358ef9..7ef244d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -383,14 +383,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
-"@babel/plugin-syntax-jsx@^7.23.3", "@babel/plugin-syntax-jsx@^7.7.2":
- version "7.24.1"
- resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz"
- integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==
- dependencies:
- "@babel/helper-plugin-utils" "^7.24.0"
-
-"@babel/plugin-syntax-jsx@^7.24.1":
+"@babel/plugin-syntax-jsx@^7.23.3", "@babel/plugin-syntax-jsx@^7.24.1", "@babel/plugin-syntax-jsx@^7.7.2":
version "7.24.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10"
integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==
@@ -1715,6 +1708,30 @@
"@swc/core-win32-ia32-msvc" "1.3.71"
"@swc/core-win32-x64-msvc" "1.3.71"
+"@tanstack/query-core@5.36.1":
+ version "5.36.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.36.1.tgz#ae46f935c4752812a56c6815305061a3da82e7b8"
+ integrity sha512-BteWYEPUcucEu3NBcDAgKuI4U25R9aPrHSP6YSf2NvaD2pSlIQTdqOfLRsxH9WdRYg7k0Uom35Uacb6nvbIMJg==
+
+"@tanstack/query-devtools@5.32.1":
+ version "5.32.1"
+ resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.32.1.tgz#2c03f2fbe9162b650e697c469c8618c7a05d593f"
+ integrity sha512-7Xq57Ctopiy/4atpb0uNY5VRuCqRS/1fi/WBCKKX6jHMa6cCgDuV/AQuiwRXcKARbq2OkVAOrW2v4xK9nTbcCA==
+
+"@tanstack/react-query-devtools@^5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.36.2.tgz#d020f6e44cf730af35d349426c96d85257d1a2ca"
+ integrity sha512-bkPQrKmKJOa2dNs6rBB9aef8jCG8XAg8QKIhwN8NI+QaXky86IofnO8YjiF6P1mYquLXbQvK0VZ9DnGV0wH/eA==
+ dependencies:
+ "@tanstack/query-devtools" "5.32.1"
+
+"@tanstack/react-query@^5.36.2":
+ version "5.36.2"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.36.2.tgz#1b7dc4c2fa0e48912335f0a157dd942cfa269326"
+ integrity sha512-bHNa+5dead+j6SA8WVlEOPxcGfteVFgdyFTCFcxBgjnPf0fFpHUc7aNZBCnvmPXqy/BeQa9zTuU9ectb7i8ZXA==
+ dependencies:
+ "@tanstack/query-core" "5.36.1"
+
"@testing-library/dom@^9.0.0":
version "9.3.4"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz"
@@ -4904,6 +4921,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+json-stringify-safe@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+ integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
+
json2mq@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz"
@@ -5448,6 +5470,15 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
+nock@^13.5.4:
+ version "13.5.4"
+ resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479"
+ integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==
+ dependencies:
+ debug "^4.1.0"
+ json-stringify-safe "^5.0.1"
+ propagate "^2.0.0"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
@@ -5854,6 +5885,11 @@ prop-types@^15.0.0, prop-types@^15.7.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
+propagate@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45"
+ integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==
+
property-information@^6.0.0:
version "6.2.0"
resolved "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz"