sforkowany z mirror/soapbox
Porównaj commity
3 Commity
Autor | SHA1 | Data |
---|---|---|
Alex Gleason | b09c2b246e | |
Justin | f6098c7587 | |
Justin | 82bab3bc67 |
|
@ -1,58 +0,0 @@
|
|||
import { __stub } from 'soapbox/api';
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { fetchCarouselAvatars } from '../carousels';
|
||||
|
||||
describe('fetchCarouselAvatars()', () => {
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore(rootState);
|
||||
});
|
||||
|
||||
describe('with a successful API request', () => {
|
||||
let avatars: Record<string, any>[];
|
||||
|
||||
beforeEach(() => {
|
||||
avatars = [
|
||||
{ account_id: '1', acct: 'jl', account_avatar: 'https://example.com/some.jpg' },
|
||||
];
|
||||
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/truth/carousels/avatars').reply(200, avatars);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch the users from the API', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'CAROUSEL_AVATAR_REQUEST' },
|
||||
{ type: 'CAROUSEL_AVATAR_SUCCESS', payload: avatars },
|
||||
];
|
||||
|
||||
await store.dispatch(fetchCarouselAvatars());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an unsuccessful API request', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/truth/carousels/avatars').networkError();
|
||||
});
|
||||
});
|
||||
|
||||
it('should dispatch failed action', async() => {
|
||||
const expectedActions = [
|
||||
{ type: 'CAROUSEL_AVATAR_REQUEST' },
|
||||
{ type: 'CAROUSEL_AVATAR_FAIL' },
|
||||
];
|
||||
|
||||
await store.dispatch(fetchCarouselAvatars());
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
import { AxiosResponse } from 'axios';
|
||||
|
||||
import { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
const CAROUSEL_AVATAR_REQUEST = 'CAROUSEL_AVATAR_REQUEST';
|
||||
const CAROUSEL_AVATAR_SUCCESS = 'CAROUSEL_AVATAR_SUCCESS';
|
||||
const CAROUSEL_AVATAR_FAIL = 'CAROUSEL_AVATAR_FAIL';
|
||||
|
||||
const fetchCarouselAvatars = () => (dispatch: AppDispatch, getState: () => RootState) => {
|
||||
dispatch({ type: CAROUSEL_AVATAR_REQUEST });
|
||||
|
||||
return api(getState)
|
||||
.get('/api/v1/truth/carousels/avatars')
|
||||
.then((response: AxiosResponse) => dispatch({ type: CAROUSEL_AVATAR_SUCCESS, payload: response.data }))
|
||||
.catch(() => dispatch({ type: CAROUSEL_AVATAR_FAIL }));
|
||||
};
|
||||
|
||||
export {
|
||||
CAROUSEL_AVATAR_REQUEST,
|
||||
CAROUSEL_AVATAR_SUCCESS,
|
||||
CAROUSEL_AVATAR_FAIL,
|
||||
fetchCarouselAvatars,
|
||||
};
|
|
@ -2,9 +2,9 @@ import classNames from 'classnames';
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { fetchCarouselAvatars } from 'soapbox/actions/carousels';
|
||||
import { replaceHomeTimeline } from 'soapbox/actions/timelines';
|
||||
import { useAppDispatch, useAppSelector, useDimensions, useFeatures } from 'soapbox/hooks';
|
||||
import { useGetCarouselAvatarsQuery } from 'soapbox/queries/rtk-client';
|
||||
|
||||
import { Card, HStack, Icon, Stack, Text } from '../../components/ui';
|
||||
import PlaceholderAvatar from '../placeholder/components/placeholder_avatar';
|
||||
|
@ -15,10 +15,10 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
|||
const selectedAccountId = useAppSelector(state => state.timelines.get('home')?.feedAccountId);
|
||||
const isSelected = avatar.account_id === selectedAccountId;
|
||||
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const [isFetching, setLoading] = useState<boolean>(false);
|
||||
|
||||
const handleClick = () => {
|
||||
if (isLoading) {
|
||||
if (isFetching) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div aria-disabled={isLoading} onClick={handleClick} className='cursor-pointer' role='filter-feed-by-user'>
|
||||
<div aria-disabled={isFetching} onClick={handleClick} className='cursor-pointer' role='filter-feed-by-user'>
|
||||
<Stack className='w-16 h-auto' space={3}>
|
||||
<div className='block mx-auto relative w-14 h-14 rounded-full'>
|
||||
{isSelected && (
|
||||
|
@ -59,19 +59,17 @@ const CarouselItem = ({ avatar }: { avatar: any }) => {
|
|||
};
|
||||
|
||||
const FeedCarousel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const features = useFeatures();
|
||||
|
||||
const { data: avatars, isFetching, isError } = useGetCarouselAvatarsQuery(null);
|
||||
|
||||
const [cardRef, setCardRef, { width }] = useDimensions();
|
||||
|
||||
const [pageSize, setPageSize] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
const avatars = useAppSelector((state) => state.carousels.avatars);
|
||||
const isLoading = useAppSelector((state) => state.carousels.isLoading);
|
||||
const hasError = useAppSelector((state) => state.carousels.error);
|
||||
const numberOfPages = Math.ceil(avatars.length / pageSize);
|
||||
const widthPerAvatar = (cardRef?.scrollWidth || 0) / avatars.length;
|
||||
const numberOfPages = avatars ? Math.ceil(avatars.length / pageSize) : 0;
|
||||
const widthPerAvatar = avatars ? (cardRef?.scrollWidth || 0) / avatars.length : 0;
|
||||
|
||||
const hasNextPage = currentPage < numberOfPages && numberOfPages > 1;
|
||||
const hasPrevPage = currentPage > 1 && numberOfPages > 1;
|
||||
|
@ -85,17 +83,11 @@ const FeedCarousel = () => {
|
|||
}
|
||||
}, [width, widthPerAvatar]);
|
||||
|
||||
useEffect(() => {
|
||||
if (features.feedUserFiltering) {
|
||||
dispatch(fetchCarouselAvatars());
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!features.feedUserFiltering) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
if (isError) {
|
||||
return (
|
||||
<Card variant='rounded' size='lg' data-testid='feed-carousel-error'>
|
||||
<Text align='center'>
|
||||
|
@ -105,7 +97,7 @@ const FeedCarousel = () => {
|
|||
);
|
||||
}
|
||||
|
||||
if (avatars.length === 0) {
|
||||
if (avatars?.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -133,14 +125,14 @@ const FeedCarousel = () => {
|
|||
style={{ transform: `translateX(-${(currentPage - 1) * 100}%)` }}
|
||||
ref={setCardRef}
|
||||
>
|
||||
{isLoading ? (
|
||||
{isFetching ? (
|
||||
new Array(pageSize).fill(0).map((_, idx) => (
|
||||
<div className='w-16 text-center' key={idx}>
|
||||
<PlaceholderAvatar size={56} withText />
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
avatars.map((avatar) => (
|
||||
avatars?.map((avatar) => (
|
||||
<CarouselItem
|
||||
key={avatar.account_id}
|
||||
avatar={avatar}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
@ -119,6 +120,7 @@ import { WrappedRoute } from './util/react_router_helpers';
|
|||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import 'soapbox/components/status';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
|
||||
const EmptyPage = HomePage;
|
||||
|
||||
|
@ -648,51 +650,53 @@ const UI: React.FC = ({ children }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
||||
<div ref={node} style={style}>
|
||||
<BackgroundShapes />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HotKeys keyMap={keyMap} handlers={me ? handlers : undefined} ref={setHotkeysRef} attach={window} focused>
|
||||
<div ref={node} style={style}>
|
||||
<BackgroundShapes />
|
||||
|
||||
<div className='z-10 flex flex-col'>
|
||||
<Navbar />
|
||||
<div className='z-10 flex flex-col'>
|
||||
<Navbar />
|
||||
|
||||
<Layout>
|
||||
<Layout.Sidebar>
|
||||
{!standalone && <SidebarNavigation />}
|
||||
</Layout.Sidebar>
|
||||
<Layout>
|
||||
<Layout.Sidebar>
|
||||
{!standalone && <SidebarNavigation />}
|
||||
</Layout.Sidebar>
|
||||
|
||||
<SwitchingColumnsArea>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
</Layout>
|
||||
<SwitchingColumnsArea>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
</Layout>
|
||||
|
||||
{me && floatingActionButton}
|
||||
{me && floatingActionButton}
|
||||
|
||||
<BundleContainer fetchComponent={UploadArea}>
|
||||
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
|
||||
</BundleContainer>
|
||||
<BundleContainer fetchComponent={UploadArea}>
|
||||
{Component => <Component active={draggingOver} onClose={closeUploadModal} />}
|
||||
</BundleContainer>
|
||||
|
||||
{me && (
|
||||
<BundleContainer fetchComponent={SidebarMenu}>
|
||||
{me && (
|
||||
<BundleContainer fetchComponent={SidebarMenu}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
{me && features.chats && !mobile && (
|
||||
<BundleContainer fetchComponent={ChatPanes}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
<ThumbNavigation />
|
||||
|
||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
{me && features.chats && !mobile && (
|
||||
<BundleContainer fetchComponent={ChatPanes}>
|
||||
|
||||
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
)}
|
||||
<ThumbNavigation />
|
||||
|
||||
<BundleContainer fetchComponent={ProfileHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<BundleContainer fetchComponent={StatusHoverCard}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
</HotKeys>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { API } from './client';
|
||||
|
||||
type Avatar = {
|
||||
account_id: string
|
||||
account_avatar: string
|
||||
username: string
|
||||
}
|
||||
|
||||
const getCarouselAvatars = async() => {
|
||||
const { data } = await API.get('/api/v1/truth/carousels/avatars');
|
||||
return data;
|
||||
};
|
||||
|
||||
export default function useCarouselAvatars(): { data: Avatar[], isFetching: boolean, isError: boolean } {
|
||||
const { data, isFetching, isError } = useQuery<Avatar[]>(['carouselAvatars'], getCarouselAvatars, {
|
||||
placeholderData: [],
|
||||
});
|
||||
const avatars = data as Avatar[];
|
||||
|
||||
return {
|
||||
data: avatars,
|
||||
isFetching,
|
||||
isError,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import axios from 'axios';
|
||||
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
import { STORAGE_KEY } from 'soapbox/reducers/auth';
|
||||
import { isURL, parseBaseURL } from 'soapbox/utils/auth';
|
||||
|
||||
const cachedAuth = JSON.parse(localStorage.getItem(STORAGE_KEY) as string);
|
||||
const token = cachedAuth.users[cachedAuth.me].access_token;
|
||||
const baseURL = parseBaseURL(cachedAuth.users[cachedAuth.me].url);
|
||||
|
||||
const maybeParseJSON = (data: string) => {
|
||||
try {
|
||||
return JSON.parse(data);
|
||||
} catch (Exception) {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
const API = axios.create({
|
||||
// When BACKEND_URL is set, always use it.
|
||||
baseURL: isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL,
|
||||
headers: Object.assign(token ? {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
} : {}),
|
||||
|
||||
transformResponse: [maybeParseJSON],
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 60000, // 1 minute
|
||||
cacheTime: Infinity,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export { API, queryClient };
|
|
@ -0,0 +1,47 @@
|
|||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
import { STORAGE_KEY } from 'soapbox/reducers/auth';
|
||||
import { getAccessToken, getAppToken, isURL, parseBaseURL } from 'soapbox/utils/auth';
|
||||
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
const cachedAuth = JSON.parse(localStorage.getItem(STORAGE_KEY) as string);
|
||||
const baseURL = parseBaseURL(cachedAuth.users[cachedAuth.me].url);
|
||||
|
||||
const getToken = (state: RootState, authType: string) => {
|
||||
return authType === 'app' ? getAppToken(state) : getAccessToken(state);
|
||||
};
|
||||
|
||||
type CarouselAccount = {
|
||||
account_id: string
|
||||
account_avatar: string
|
||||
username: string
|
||||
}
|
||||
|
||||
// Define a service using a base URL and expected endpoints
|
||||
export const api = createApi({
|
||||
reducerPath: 'soapboxApi',
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : baseURL,
|
||||
prepareHeaders: (headers, { getState }): Headers => {
|
||||
const state = getState() as RootState;
|
||||
const accessToken: string = getToken(state, 'user');
|
||||
|
||||
if (accessToken) {
|
||||
headers.set('Authorization', `Bearer ${accessToken}`);
|
||||
}
|
||||
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getCarouselAvatars: builder.query<CarouselAccount[], null>({
|
||||
query: () => '/api/v1/truth/carousels/avatars',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export hooks for usage in functional components, which are
|
||||
// auto-generated based on the defined endpoints
|
||||
export const { useGetCarouselAvatarsQuery } = api;
|
|
@ -1,50 +0,0 @@
|
|||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
CAROUSEL_AVATAR_REQUEST,
|
||||
CAROUSEL_AVATAR_SUCCESS,
|
||||
CAROUSEL_AVATAR_FAIL,
|
||||
} from 'soapbox/actions/carousels';
|
||||
|
||||
import reducer from '../carousels';
|
||||
|
||||
describe('carousels reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {} as AnyAction)).toEqual({
|
||||
avatars: [],
|
||||
error: false,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('CAROUSEL_AVATAR_REQUEST', () => {
|
||||
it('sets "isLoading" to "true"', () => {
|
||||
const initialState = { isLoading: false, avatars: [], error: false };
|
||||
const action = { type: CAROUSEL_AVATAR_REQUEST };
|
||||
expect(reducer(initialState, action).isLoading).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CAROUSEL_AVATAR_SUCCESS', () => {
|
||||
it('sets the next state', () => {
|
||||
const initialState = { isLoading: true, avatars: [], error: false };
|
||||
const action = { type: CAROUSEL_AVATAR_SUCCESS, payload: [45] };
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
expect(result.isLoading).toEqual(false);
|
||||
expect(result.avatars).toEqual([45]);
|
||||
expect(result.error).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CAROUSEL_AVATAR_FAIL', () => {
|
||||
it('sets "isLoading" to "true"', () => {
|
||||
const initialState = { isLoading: true, avatars: [], error: false };
|
||||
const action = { type: CAROUSEL_AVATAR_FAIL };
|
||||
const result = reducer(initialState, action);
|
||||
|
||||
expect(result.isLoading).toEqual(false);
|
||||
expect(result.error).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -29,7 +29,7 @@ const buildKey = parts => parts.join(':');
|
|||
// For subdirectory support
|
||||
const NAMESPACE = trim(FE_SUBDIRECTORY, '/') ? `soapbox@${FE_SUBDIRECTORY}` : 'soapbox';
|
||||
|
||||
const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
||||
export const STORAGE_KEY = buildKey([NAMESPACE, 'auth']);
|
||||
const SESSION_KEY = buildKey([NAMESPACE, 'auth', 'me']);
|
||||
|
||||
const getSessionUser = () => {
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
CAROUSEL_AVATAR_REQUEST,
|
||||
CAROUSEL_AVATAR_SUCCESS,
|
||||
CAROUSEL_AVATAR_FAIL,
|
||||
} from '../actions/carousels';
|
||||
|
||||
type Avatar = {
|
||||
account_id: string
|
||||
account_avatar: string
|
||||
username: string
|
||||
}
|
||||
|
||||
type CarouselsState = {
|
||||
avatars: Avatar[]
|
||||
isLoading: boolean
|
||||
error: boolean
|
||||
}
|
||||
|
||||
const initialState: CarouselsState = {
|
||||
avatars: [],
|
||||
isLoading: false,
|
||||
error: false,
|
||||
};
|
||||
|
||||
export default function rules(state: CarouselsState = initialState, action: AnyAction): CarouselsState {
|
||||
switch (action.type) {
|
||||
case CAROUSEL_AVATAR_REQUEST:
|
||||
return { ...state, isLoading: true };
|
||||
case CAROUSEL_AVATAR_SUCCESS:
|
||||
return { ...state, isLoading: false, avatars: action.payload };
|
||||
case CAROUSEL_AVATAR_FAIL:
|
||||
return { ...state, isLoading: false, error: true };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import { combineReducers } from 'redux-immutable';
|
|||
|
||||
import { AUTH_LOGGED_OUT } from 'soapbox/actions/auth';
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
import { api } from 'soapbox/queries/rtk-client';
|
||||
|
||||
import account_notes from './account_notes';
|
||||
import accounts from './accounts';
|
||||
|
@ -15,7 +16,6 @@ import aliases from './aliases';
|
|||
import announcements from './announcements';
|
||||
import auth from './auth';
|
||||
import backups from './backups';
|
||||
import carousels from './carousels';
|
||||
import chat_message_lists from './chat_message_lists';
|
||||
import chat_messages from './chat_messages';
|
||||
import chats from './chats';
|
||||
|
@ -124,8 +124,8 @@ const reducers = {
|
|||
onboarding,
|
||||
rules,
|
||||
history,
|
||||
carousels,
|
||||
announcements,
|
||||
[api.reducerPath]: api.reducer,
|
||||
};
|
||||
|
||||
// Build a default state from all reducers: it has the key and `undefined`
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
"@tabler/icons": "^1.73.0",
|
||||
"@tailwindcss/forms": "^0.4.0",
|
||||
"@tailwindcss/typography": "^0.5.1",
|
||||
"@tanstack/react-query": "^4.0.10",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
"@types/escape-html": "^1.0.1",
|
||||
"@types/http-link-header": "^1.0.3",
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -2286,6 +2286,20 @@
|
|||
lodash.isplainobject "^4.0.6"
|
||||
lodash.merge "^4.6.2"
|
||||
|
||||
"@tanstack/query-core@^4.0.0-beta.1":
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.0.10.tgz#cae6f818006616dc72c95c863592f5f68b47548a"
|
||||
integrity sha512-9LsABpZXkWZHi4P1ozRETEDXQocLAxVzQaIhganxbNuz/uA3PsCAJxJTiQrknG5htLMzOF5MqM9G10e6DCxV1A==
|
||||
|
||||
"@tanstack/react-query@^4.0.10":
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.0.10.tgz#92c71a2632c06450d848d4964959bd216cde03c0"
|
||||
integrity sha512-Wn5QhZUE5wvr6rGClV7KeQIUsdTmYR9mgmMZen7DSRWauHW2UTynFg3Kkf6pw+XlxxOLsyLWwz/Q6q1lSpM3TQ==
|
||||
dependencies:
|
||||
"@tanstack/query-core" "^4.0.0-beta.1"
|
||||
"@types/use-sync-external-store" "^0.0.3"
|
||||
use-sync-external-store "^1.2.0"
|
||||
|
||||
"@testing-library/dom@^8.0.0":
|
||||
version "8.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.12.0.tgz#fef5e545533fb084175dda6509ee71d7d2f72e23"
|
||||
|
@ -2866,6 +2880,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||
|
||||
"@types/use-sync-external-store@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43"
|
||||
integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==
|
||||
|
||||
"@types/uuid@^8.3.4":
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
|
@ -11561,6 +11580,11 @@ use-latest@^1.2.1:
|
|||
dependencies:
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
use-sync-external-store@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
|
Ładowanie…
Reference in New Issue