kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Update explorer with some of recent comments
rodzic
3955977b1f
commit
f8f1292c39
|
@ -27,7 +27,16 @@ const SidebarNavigationLink = forwardRef((props: ISidebarNavigationLink, ref: Re
|
||||||
const { icon, activeIcon, text, to = '', count, countMax, onClick } = props;
|
const { icon, activeIcon, text, to = '', count, countMax, onClick } = props;
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const isActive = pathname === to;
|
const isDefault = to === '' || to === '/';
|
||||||
|
|
||||||
|
let isActive;
|
||||||
|
|
||||||
|
if (isDefault) {
|
||||||
|
isActive = pathname === to;
|
||||||
|
} else {
|
||||||
|
isActive = pathname.includes(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import globeIcon from '@tabler/icons/outline/globe.svg';
|
|
||||||
import trendIcon from '@tabler/icons/outline/trending-up.svg';
|
|
||||||
import userIcon from '@tabler/icons/outline/user.svg';
|
|
||||||
import xIcon from '@tabler/icons/outline/x.svg';
|
import xIcon from '@tabler/icons/outline/x.svg';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage,
|
FormattedMessage,
|
||||||
defineMessages,
|
|
||||||
useIntl,
|
|
||||||
} from 'react-intl';
|
} from 'react-intl';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
expandSearch,
|
expandSearch,
|
||||||
setFilter,
|
|
||||||
setSearchAccount,
|
setSearchAccount,
|
||||||
} from 'soapbox/actions/search.ts';
|
} from 'soapbox/actions/search.ts';
|
||||||
import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses.ts';
|
import { expandTrendingStatuses, fetchTrendingStatuses } from 'soapbox/actions/trending-statuses.ts';
|
||||||
|
@ -20,7 +14,6 @@ import { useAccount } from 'soapbox/api/hooks/index.ts';
|
||||||
import Hashtag from 'soapbox/components/hashtag.tsx';
|
import Hashtag from 'soapbox/components/hashtag.tsx';
|
||||||
import IconButton from 'soapbox/components/icon-button.tsx';
|
import IconButton from 'soapbox/components/icon-button.tsx';
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
|
import ScrollableList from 'soapbox/components/scrollable-list.tsx';
|
||||||
import ExplorerTabs from 'soapbox/components/ui/explorer-tabs.tsx';
|
|
||||||
import HStack from 'soapbox/components/ui/hstack.tsx';
|
import HStack from 'soapbox/components/ui/hstack.tsx';
|
||||||
import Spinner from 'soapbox/components/ui/spinner.tsx';
|
import Spinner from 'soapbox/components/ui/spinner.tsx';
|
||||||
import Text from 'soapbox/components/ui/text.tsx';
|
import Text from 'soapbox/components/ui/text.tsx';
|
||||||
|
@ -29,33 +22,19 @@ import StatusContainer from 'soapbox/containers/status-container.tsx';
|
||||||
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account.tsx';
|
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account.tsx';
|
||||||
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag.tsx';
|
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag.tsx';
|
||||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status.tsx';
|
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status.tsx';
|
||||||
import PublicTimeline from 'soapbox/features/public-timeline/index.tsx';
|
|
||||||
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||||
import { useSuggestions } from 'soapbox/queries/suggestions.ts';
|
import { useSuggestions } from 'soapbox/queries/suggestions.ts';
|
||||||
import { initialState as filterInitialState } from 'soapbox/reducers/search-filter.ts';
|
|
||||||
import { SearchFilter } from 'soapbox/reducers/search.ts';
|
|
||||||
|
|
||||||
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
accounts: { id: 'search_results.accounts', defaultMessage: 'Accounts' },
|
|
||||||
statuses: { id: 'search_results.posts', defaultMessage: 'Posts' },
|
|
||||||
trends: { id: 'search_results.trends', defaultMessage: 'Trends' },
|
|
||||||
search: { id: 'common.search', defaultMessage: 'Search' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const SearchResults = () => {
|
const SearchResults = () => {
|
||||||
const node = useRef<VirtuosoHandle>(null);
|
const node = useRef<VirtuosoHandle>(null);
|
||||||
const filters = useAppSelector((state) => state.search_filter);
|
|
||||||
|
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { data: suggestions } = useSuggestions();
|
const { data: suggestions } = useSuggestions();
|
||||||
const [withFilter, setWithFilter] = useState(false);
|
|
||||||
const [tab, setTab] = useState('global');
|
|
||||||
|
|
||||||
const value = useAppSelector((state) => state.search.submittedValue);
|
const value = useAppSelector((state) => state.search.submittedValue);
|
||||||
const results = useAppSelector((state) => state.search.results);
|
const results = useAppSelector((state) => state.search.results);
|
||||||
|
@ -76,39 +55,6 @@ const SearchResults = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnsetAccount = () => dispatch(setSearchAccount(null));
|
const handleUnsetAccount = () => dispatch(setSearchAccount(null));
|
||||||
const handleAction = (filter: SearchFilter, tab: string) =>{
|
|
||||||
selectFilter(filter);
|
|
||||||
setTab(tab);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
|
|
||||||
|
|
||||||
const renderFilterBar = () => {
|
|
||||||
const items = [];
|
|
||||||
items.push(
|
|
||||||
{
|
|
||||||
label: intl.formatMessage(messages.statuses),
|
|
||||||
action: () => handleAction('statuses', 'global'),
|
|
||||||
name: 'global',
|
|
||||||
icon: globeIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: intl.formatMessage(messages.trends),
|
|
||||||
action: () => handleAction('statuses', 'statuses'),
|
|
||||||
name: 'statuses',
|
|
||||||
icon: trendIcon,
|
|
||||||
},
|
|
||||||
// TODO : limit search accounts to only be able use include
|
|
||||||
{
|
|
||||||
label: intl.formatMessage(messages.accounts),
|
|
||||||
action: () => handleAction('accounts', 'accounts'),
|
|
||||||
name: 'accounts',
|
|
||||||
icon: userIcon,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return <ExplorerTabs items={items} activeItem={tab} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentIndex = (id: string): number => {
|
const getCurrentIndex = (id: string): number => {
|
||||||
return resultsIds?.keySeq().findIndex(key => key === id);
|
return resultsIds?.keySeq().findIndex(key => key === id);
|
||||||
|
@ -143,16 +89,6 @@ const SearchResults = () => {
|
||||||
dispatch(fetchTrendingStatuses());
|
dispatch(fetchTrendingStatuses());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setWithFilter(filters.length !== filterInitialState.length ||
|
|
||||||
!filters.every((filter, index) =>
|
|
||||||
filter.name === filterInitialState[index].name &&
|
|
||||||
filter.status === filterInitialState[index].status &&
|
|
||||||
filter.value === filterInitialState[index].value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, [filters]);
|
|
||||||
|
|
||||||
let searchResults;
|
let searchResults;
|
||||||
let hasMore = false;
|
let hasMore = false;
|
||||||
let loaded;
|
let loaded;
|
||||||
|
@ -247,7 +183,7 @@ const SearchResults = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{filterByAccount ? (
|
{filterByAccount && (
|
||||||
<HStack className='mb-4 border-b border-solid border-gray-200 px-2 pb-4 dark:border-gray-800' space={2}>
|
<HStack className='mb-4 border-b border-solid border-gray-200 px-2 pb-4 dark:border-gray-800' space={2}>
|
||||||
<IconButton iconClassName='h-5 w-5' src={xIcon} onClick={handleUnsetAccount} />
|
<IconButton iconClassName='h-5 w-5' src={xIcon} onClick={handleUnsetAccount} />
|
||||||
<Text truncate>
|
<Text truncate>
|
||||||
|
@ -258,13 +194,9 @@ const SearchResults = () => {
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
) : (
|
|
||||||
<div className='relative px-4'>
|
|
||||||
{renderFilterBar()}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tab === 'global' && !withFilter ? <PublicTimeline /> : (noResultsMessage || (
|
{noResultsMessage || (
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
id='search-results'
|
id='search-results'
|
||||||
ref={node}
|
ref={node}
|
||||||
|
@ -287,7 +219,7 @@ const SearchResults = () => {
|
||||||
>
|
>
|
||||||
{searchResults || []}
|
{searchResults || []}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
))}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +1,58 @@
|
||||||
|
import globeIcon from '@tabler/icons/outline/globe.svg';
|
||||||
|
import trendIcon from '@tabler/icons/outline/trending-up.svg';
|
||||||
|
import userIcon from '@tabler/icons/outline/user.svg';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Route, Switch, useLocation } from 'react-router-dom';
|
||||||
|
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||||
|
|
||||||
|
import { clearSearch, setFilter } from 'soapbox/actions/search.ts';
|
||||||
import { Column } from 'soapbox/components/ui/column.tsx';
|
import { Column } from 'soapbox/components/ui/column.tsx';
|
||||||
import Divider from 'soapbox/components/ui/divider.tsx';
|
import Divider from 'soapbox/components/ui/divider.tsx';
|
||||||
import Stack from 'soapbox/components/ui/stack.tsx';
|
import Stack from 'soapbox/components/ui/stack.tsx';
|
||||||
|
import Tabs from 'soapbox/components/ui/tabs.tsx';
|
||||||
import SearchResults from 'soapbox/features/compose/components/search-results.tsx';
|
import SearchResults from 'soapbox/features/compose/components/search-results.tsx';
|
||||||
|
import Search from 'soapbox/features/compose/components/search.tsx';
|
||||||
import ExplorerCards from 'soapbox/features/explorer/components/explorer-cards.tsx';
|
import ExplorerCards from 'soapbox/features/explorer/components/explorer-cards.tsx';
|
||||||
import ExplorerFilter from 'soapbox/features/explorer/components/explorerFilter.tsx';
|
import ExplorerFilter from 'soapbox/features/explorer/components/explorerFilter.tsx';
|
||||||
|
import AccountsCarousel from 'soapbox/features/explorer/components/popular-accounts.tsx';
|
||||||
|
import { PublicTimeline } from 'soapbox/features/ui/util/async-components.ts';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||||
|
import { IFilters, initialState as filterInitialState } from 'soapbox/reducers/search-filter.ts';
|
||||||
|
import { SearchFilter } from 'soapbox/reducers/search.ts';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.explorer', defaultMessage: 'Explorer' },
|
heading: { id: 'column.explorer', defaultMessage: 'Explorer' },
|
||||||
|
accounts: { id: 'search_results.accounts', defaultMessage: 'Accounts' },
|
||||||
|
statuses: { id: 'search_results.posts', defaultMessage: 'Posts' },
|
||||||
|
trends: { id: 'search_results.trends', defaultMessage: 'Trends' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const SearchPage = () => {
|
const checkFilters = (filters: IFilters[]) => {
|
||||||
const intl = useIntl();
|
return filters.length !== filterInitialState.length ||
|
||||||
|
!filters.every((filter, index) =>
|
||||||
|
filter.name === filterInitialState[index].name &&
|
||||||
|
filter.status === filterInitialState[index].status &&
|
||||||
|
filter.value === filterInitialState[index].value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PostsTab = () => {
|
||||||
|
const path = useLocation().pathname;
|
||||||
|
const inPosts = path === '/explorer';
|
||||||
|
const filters = useAppSelector((state) => state.search_filter);
|
||||||
|
|
||||||
|
const [withFilter, setWithFilter] = useState(checkFilters(filters));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWithFilter(checkFilters(filters));
|
||||||
|
}, [filters]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.heading)} withHeader={false} slim>
|
<Stack space={4}>
|
||||||
|
{inPosts && <>
|
||||||
|
|
||||||
<Stack space={4}>
|
|
||||||
<ExplorerCards />
|
<ExplorerCards />
|
||||||
|
|
||||||
<Divider text='Filters' />
|
<Divider text='Filters' />
|
||||||
|
@ -26,9 +61,107 @@ const SearchPage = () => {
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
|
{!withFilter ? <PublicTimeline /> : <SearchResults /> }
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TrendsTab = () => {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<SearchResults />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccountsTab = () => {
|
||||||
|
return (
|
||||||
|
<Stack space={4} className='pt-1'>
|
||||||
|
<AccountsCarousel />
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Stack space={3}>
|
||||||
|
<div className='px-4'>
|
||||||
|
<Search autoSubmit />
|
||||||
|
</div>
|
||||||
|
|
||||||
<SearchResults />
|
<SearchResults />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const SearchPage = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [selectedFilter, setSelectedFilter] = useState('global');
|
||||||
|
|
||||||
|
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
|
||||||
|
|
||||||
|
const renderFilterBar = () => {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
const handleTabs = (path: string, filter?: SearchFilter) => {
|
||||||
|
if (filter) {
|
||||||
|
selectFilter(filter);
|
||||||
|
dispatch(clearSearch());
|
||||||
|
dispatch(setFilter(filter));
|
||||||
|
} else {
|
||||||
|
dispatch(setFilter('statuses'));
|
||||||
|
}
|
||||||
|
setSelectedFilter(filter ?? 'global');
|
||||||
|
navigate(`/explorer${path}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.statuses),
|
||||||
|
action: () => handleTabs(''),
|
||||||
|
name: 'global',
|
||||||
|
icon: globeIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.trends),
|
||||||
|
action: () => handleTabs('/trends', 'statuses'),
|
||||||
|
name: 'statuses',
|
||||||
|
icon: trendIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.accounts),
|
||||||
|
action: () => handleTabs('/accounts', 'accounts'),
|
||||||
|
name: 'accounts',
|
||||||
|
icon: userIcon,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return <Tabs items={items} activeItem={selectedFilter} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} withHeader={false} slim>
|
||||||
|
|
||||||
|
<Stack space={2}>
|
||||||
|
|
||||||
|
<div className='relative px-4'>
|
||||||
|
{renderFilterBar()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={'/'} component={PostsTab} />
|
||||||
|
<Route exact path={'/explorer'} component={PostsTab} />
|
||||||
|
<Route path={'/explorer/trends'} component={TrendsTab} />
|
||||||
|
<Route path={'/explorer/accounts'} component={AccountsTab} />
|
||||||
|
</Switch>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,6 +48,7 @@ import Navbar from './components/navbar.tsx';
|
||||||
import {
|
import {
|
||||||
Status,
|
Status,
|
||||||
RemoteTimeline,
|
RemoteTimeline,
|
||||||
|
PublicTimeline,
|
||||||
AccountTimeline,
|
AccountTimeline,
|
||||||
AccountGallery,
|
AccountGallery,
|
||||||
HomeTimeline,
|
HomeTimeline,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import Layout from 'soapbox/components/ui/layout.tsx';
|
import Layout from 'soapbox/components/ui/layout.tsx';
|
||||||
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
|
import LinkFooter from 'soapbox/features/ui/components/link-footer.tsx';
|
||||||
import {
|
import {
|
||||||
|
@ -5,6 +7,7 @@ import {
|
||||||
TrendsPanel,
|
TrendsPanel,
|
||||||
SignUpPanel,
|
SignUpPanel,
|
||||||
CtaBanner,
|
CtaBanner,
|
||||||
|
LatestAccountsPanel,
|
||||||
SuggestedGroupsPanel,
|
SuggestedGroupsPanel,
|
||||||
} from 'soapbox/features/ui/util/async-components.ts';
|
} from 'soapbox/features/ui/util/async-components.ts';
|
||||||
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
|
||||||
|
@ -17,6 +20,7 @@ interface ISearchPage {
|
||||||
const SearchPage: React.FC<ISearchPage> = ({ children }) => {
|
const SearchPage: React.FC<ISearchPage> = ({ children }) => {
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
const accountsPath = useLocation().pathname === '/explorer/accounts';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -37,8 +41,9 @@ const SearchPage: React.FC<ISearchPage> = ({ children }) => {
|
||||||
<TrendsPanel limit={5} />
|
<TrendsPanel limit={5} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{features.suggestions && (
|
{features.suggestions && (accountsPath
|
||||||
<WhoToFollowPanel limit={3} />
|
? <LatestAccountsPanel limit={3} />
|
||||||
|
: <WhoToFollowPanel limit={3} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{features.groups && (
|
{features.groups && (
|
||||||
|
|
|
@ -7,8 +7,10 @@ import appReducer from './reducers/index.ts';
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
|
|
||||||
const loadState = () => {
|
const loadState = (pathname: string) => {
|
||||||
try {
|
try {
|
||||||
|
if (pathname !== '/explorer') return undefined;
|
||||||
|
|
||||||
const savedState = localStorage.getItem('soapbox:explorer:filters');
|
const savedState = localStorage.getItem('soapbox:explorer:filters');
|
||||||
return savedState ? JSON.parse(savedState) : undefined;
|
return savedState ? JSON.parse(savedState) : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -17,7 +19,10 @@ const loadState = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const preloadedState = { ...loadState() ? { search_filter: loadState() } : {} };
|
const preloadedState = (() => {
|
||||||
|
const storedFilter = loadState(window.location.pathname);
|
||||||
|
return storedFilter ? { search_filter: storedFilter } : {};
|
||||||
|
})();
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: appReducer,
|
reducer: appReducer,
|
||||||
|
|
Ładowanie…
Reference in New Issue