mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Merge branch 'codex/fix/catalog-search-empty-state'
This commit is contained in:
@@ -523,7 +523,6 @@ export const MobileBoardButtons = () => {
|
||||
const resolvedAddress = useResolvedCommunityAddress();
|
||||
const communityAddress = resolvedAddress || accountComment?.communityAddress;
|
||||
|
||||
const { filteredCount, searchText } = useCatalogFiltersStore();
|
||||
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
|
||||
const isMultiboard = isInAllView || isInSubscriptionsView || isInModView;
|
||||
const showTimeFilter = isMultiboard;
|
||||
@@ -574,19 +573,7 @@ export const MobileBoardButtons = () => {
|
||||
isInModView={isInModView}
|
||||
isMobilePlacement={true}
|
||||
/>
|
||||
{searchText ? (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
- {t('search_results_for')}: <strong>{searchText}</strong>
|
||||
</span>
|
||||
) : (
|
||||
filteredCount > 0 && (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
- {t('filtered_threads')}: <strong>{filteredCount}</strong>
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
<CatalogSearchResultsLabel />
|
||||
{(isInAllView || showTimeFilter || isInCatalogView) && (
|
||||
<>
|
||||
<hr />
|
||||
@@ -683,6 +670,31 @@ export const PostPageStats = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const CatalogSearchResultsLabel = () => {
|
||||
const { t } = useTranslation();
|
||||
const { filteredCount, searchText } = useCatalogFiltersStore();
|
||||
|
||||
if (searchText) {
|
||||
return (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
— {t('search_results_for')}: <strong>{searchText}</strong>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredCount > 0) {
|
||||
return (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
— {t('filtered_threads')}: <strong>{filteredCount}</strong>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const DesktopBoardButtons = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
@@ -698,7 +710,6 @@ export const DesktopBoardButtons = () => {
|
||||
const isInModView = isModView(location.pathname);
|
||||
const isInModQueueView = isModQueueView(location.pathname);
|
||||
|
||||
const { filteredCount, searchText } = useCatalogFiltersStore();
|
||||
const enableInfiniteScroll = useFeedViewSettingsStore((state) => state.enableInfiniteScroll);
|
||||
const isMultiboard = isInAllView || isInSubscriptionsView || isInModView;
|
||||
const showTimeFilter = isMultiboard;
|
||||
@@ -794,20 +805,7 @@ export const DesktopBoardButtons = () => {
|
||||
<ModQueueButton boardIdentifier={boardIdentifier} isMobile={false} />
|
||||
</>
|
||||
)}
|
||||
{isInCatalogView && searchText ? (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
- {t('search_results_for')}: <strong>{searchText}</strong>
|
||||
</span>
|
||||
) : (
|
||||
isInCatalogView &&
|
||||
filteredCount > 0 && (
|
||||
<span className={styles.filteredThreadsCount}>
|
||||
{' '}
|
||||
- {t('filtered_threads')}: <strong>{filteredCount}</strong>
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
{isInCatalogView && <CatalogSearchResultsLabel />}
|
||||
<span className={styles.rightSideButtons}>
|
||||
{isInCatalogView && (
|
||||
<>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
CatalogFooterFirstRow,
|
||||
CatalogFooterStyleRow,
|
||||
PageFooterDesktop,
|
||||
PageFooterMobile,
|
||||
StyleOnlyFooterFirstRow,
|
||||
@@ -51,6 +52,7 @@ vi.mock('../../style-selector/style-selector', () => ({
|
||||
}));
|
||||
|
||||
vi.mock('../../board-buttons/board-buttons', () => ({
|
||||
CatalogSearchResultsLabel: () => null,
|
||||
AutoButton: () => createElement('button', { type: 'button' }, 'auto-button'),
|
||||
CatalogButton: ({
|
||||
address,
|
||||
@@ -154,6 +156,7 @@ describe('footer', () => {
|
||||
isInAllView: true,
|
||||
communityAddress: 'music-posting.eth',
|
||||
}),
|
||||
createElement(CatalogFooterStyleRow),
|
||||
createElement(ThreadFooterStyleRow),
|
||||
createElement(PageFooterMobile, {
|
||||
children: createElement('div', { 'data-testid': 'mobile-child' }, 'mobile-child'),
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.footerCatalog {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.firstRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -76,7 +80,6 @@
|
||||
}
|
||||
|
||||
.styleLabel {
|
||||
font-size: 12px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,16 @@ import { useComment } from '@bitsocial/bitsocial-react-hooks';
|
||||
import BoardsBar from '../boards-bar';
|
||||
import SiteLegalMeta from '../site-legal-meta';
|
||||
import StyleSelector from '../style-selector/style-selector';
|
||||
import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats, RefreshButton } from '../board-buttons/board-buttons';
|
||||
import {
|
||||
CatalogSearchResultsLabel,
|
||||
ReturnButton,
|
||||
CatalogButton,
|
||||
TopButton,
|
||||
UpdateButton,
|
||||
AutoButton,
|
||||
PostPageStats,
|
||||
RefreshButton,
|
||||
} from '../board-buttons/board-buttons';
|
||||
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
|
||||
import useReplyModalStore from '../../stores/use-reply-modal-store';
|
||||
import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store';
|
||||
@@ -22,15 +31,17 @@ import styles from './footer.module.css';
|
||||
|
||||
interface PageFooterDesktopProps {
|
||||
/** Mode-specific first row content (e.g. board pagination or thread controls) */
|
||||
firstRow: React.ReactNode;
|
||||
firstRow?: React.ReactNode;
|
||||
/** Optional row between first row and BoardsBar (e.g. style selector on thread page) */
|
||||
styleRow?: React.ReactNode;
|
||||
/** Catalog pages omit the default footer top margin */
|
||||
variant?: 'catalog';
|
||||
}
|
||||
|
||||
export const PageFooterDesktop = ({ firstRow, styleRow }: PageFooterDesktopProps) => (
|
||||
<footer className={styles.footer}>
|
||||
export const PageFooterDesktop = ({ firstRow, styleRow, variant }: PageFooterDesktopProps) => (
|
||||
<footer className={variant === 'catalog' ? `${styles.footer} ${styles.footerCatalog}` : styles.footer}>
|
||||
<hr />
|
||||
<div className={styles.firstRow}>{firstRow}</div>
|
||||
{firstRow != null ? <div className={styles.firstRow}>{firstRow}</div> : null}
|
||||
{styleRow != null ? <div className={styles.styleRow}>{styleRow}</div> : null}
|
||||
<div className={styles.boardsBarRow}>
|
||||
<BoardsBar />
|
||||
@@ -60,7 +71,7 @@ export const StyleOnlyFooterFirstRow = () => {
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* CatalogFooterFirstRow
|
||||
* Catalog footer first row: Return, Archive, Top, Refresh on left; Style selector on right.
|
||||
* Catalog footer nav row: Return, Catalog, Top, Refresh, plus search/filter label.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
interface CatalogFooterFirstRowProps {
|
||||
@@ -71,7 +82,6 @@ interface CatalogFooterFirstRowProps {
|
||||
}
|
||||
|
||||
export const CatalogFooterFirstRow = ({ communityAddress, isInAllView = false, isInSubscriptionsView = false, isInModView = false }: CatalogFooterFirstRowProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={styles.footerRow}>
|
||||
<div className={styles.footerLeft}>
|
||||
@@ -87,15 +97,27 @@ export const CatalogFooterFirstRow = ({ communityAddress, isInAllView = false, i
|
||||
<span>
|
||||
[<RefreshButton />]
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.footerRight}>
|
||||
<span className={styles.styleLabel}>{t('style')}:</span>
|
||||
<StyleSelector />
|
||||
<CatalogSearchResultsLabel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* CatalogFooterStyleRow
|
||||
* Catalog style selector on its own row (below nav, above BoardsBar).
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
export const CatalogFooterStyleRow = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<span className={styles.styleRowContent}>
|
||||
<span className={styles.styleLabel}>{t('style')}:</span>
|
||||
<StyleSelector />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* ThreadFooterStyleRow
|
||||
* Style selector on its own row (thread page only, below first row, above BoardsBar).
|
||||
|
||||
@@ -3,6 +3,7 @@ export {
|
||||
PageFooterMobile,
|
||||
StyleOnlyFooterFirstRow,
|
||||
CatalogFooterFirstRow,
|
||||
CatalogFooterStyleRow,
|
||||
ThreadFooterFirstRow,
|
||||
ThreadFooterStyleRow,
|
||||
ThreadFooterMobile,
|
||||
|
||||
@@ -322,8 +322,15 @@ vi.mock('../../../components/catalog-row', () => ({
|
||||
|
||||
vi.mock('../../../components/footer', () => ({
|
||||
CatalogFooterFirstRow: ({ communityAddress }: { communityAddress?: string }) =>
|
||||
createElement('div', { 'data-testid': 'catalog-first-row' }, communityAddress || 'multi'),
|
||||
PageFooterDesktop: ({ firstRow }: { firstRow: React.ReactNode }) => createElement('div', { 'data-testid': 'catalog-footer-desktop' }, firstRow),
|
||||
createElement(
|
||||
'div',
|
||||
{ 'data-testid': 'catalog-first-row' },
|
||||
communityAddress || 'multi',
|
||||
testState.searchText ? ` - search_results_for: ${testState.searchText}` : null,
|
||||
),
|
||||
CatalogFooterStyleRow: () => createElement('div', { 'data-testid': 'catalog-style-row' }, 'style'),
|
||||
PageFooterDesktop: ({ firstRow, styleRow }: { firstRow?: React.ReactNode; styleRow?: React.ReactNode }) =>
|
||||
createElement('div', { 'data-testid': 'catalog-footer-desktop' }, firstRow, styleRow),
|
||||
PageFooterMobile: ({ children }: { children: React.ReactNode }) => createElement('div', { 'data-testid': 'catalog-footer-mobile' }, children),
|
||||
}));
|
||||
|
||||
@@ -902,6 +909,22 @@ describe('Catalog', () => {
|
||||
expect(container.querySelector('[data-testid="catalog-first-row"]')?.textContent).toBe('music-posting.eth');
|
||||
});
|
||||
|
||||
it('shows centered nothing found when catalog search has no matches', async () => {
|
||||
testState.feed = [{ cid: 'network-post', title: 'cats on stage', communityAddress: 'music-posting.eth' }];
|
||||
testState.searchText = 'asddasd';
|
||||
testState.hasMore = false;
|
||||
|
||||
await renderCatalog({ initialEntry: '/mu/catalog?q=asddasd', routePath: '/:boardIdentifier/catalog' });
|
||||
|
||||
expect(container.textContent).toContain('nothing_found');
|
||||
expect(container.textContent).not.toContain('no_threads');
|
||||
expect(container.querySelector('[role="status"]')?.className).toContain('searchNothingFound');
|
||||
expect(container.querySelector('[role="status"]')?.textContent).toBe('nothing_found');
|
||||
expect(container.querySelectorAll('[data-testid="catalog-row"]')).toHaveLength(0);
|
||||
expect(container.textContent).toContain('search_results_for');
|
||||
expect(container.textContent).toContain('asddasd');
|
||||
});
|
||||
|
||||
it('queries scoped recent account posts and still applies local search filtering before injecting them', async () => {
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000);
|
||||
testState.feed = [{ cid: 'network-post', title: 'cats on stage', communityAddress: 'music-posting.eth' }];
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
color: var(--post-mobile-abbr-text-color);
|
||||
}
|
||||
|
||||
.stateString {
|
||||
padding: 15px 0 10px 5px;
|
||||
color: var(--post-mobile-abbr-text-color);
|
||||
}
|
||||
|
||||
.searchNothingFound {
|
||||
color: red;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 1em 0;
|
||||
text-align: center;
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.morePostsSuggestion {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import useSortingStore from '../../stores/use-sorting-store';
|
||||
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
|
||||
import { getCommunityAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils';
|
||||
import CatalogRow from '../../components/catalog-row';
|
||||
import { CatalogFooterFirstRow, PageFooterDesktop, PageFooterMobile } from '../../components/footer';
|
||||
import { CatalogFooterFirstRow, CatalogFooterStyleRow, PageFooterDesktop, PageFooterMobile } from '../../components/footer';
|
||||
import { ReturnButton, ArchiveButton, TopButton, RefreshButton } from '../../components/board-buttons/board-buttons';
|
||||
import mobileFooterStyles from '../../components/footer/footer.module.css';
|
||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||
@@ -131,6 +131,20 @@ const CatalogFooter = ({
|
||||
|
||||
// Separate component for the loading state when there's no feed
|
||||
// This also calls useFeedStateString internally to isolate re-renders
|
||||
interface CatalogSearchNothingFoundProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const CatalogSearchNothingFound = ({ className }: CatalogSearchNothingFoundProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={className ?? styles.searchNothingFound} role='status'>
|
||||
{t('nothing_found')}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface CatalogLoadingProps {
|
||||
communityAddresses: string[];
|
||||
hasMore: boolean;
|
||||
@@ -138,9 +152,10 @@ interface CatalogLoadingProps {
|
||||
state: string | undefined;
|
||||
subscriptionsLength: number;
|
||||
error: Error | undefined;
|
||||
hasActiveSearch: boolean;
|
||||
}
|
||||
|
||||
const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state, subscriptionsLength, error }: CatalogLoadingProps) => {
|
||||
const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state, subscriptionsLength, error, hasActiveSearch }: CatalogLoadingProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const rawFeedStateString = useFeedStateString(communityAddresses);
|
||||
@@ -153,7 +168,9 @@ const CatalogLoading = ({ communityAddresses, hasMore, combinedFeedLength, state
|
||||
) : subscriptionsLength === 0 ? (
|
||||
<span className='red'>{t('not_subscribed_to_any_board')}</span>
|
||||
) : !hasMore && combinedFeedLength === 0 ? (
|
||||
t('no_threads')
|
||||
hasActiveSearch ? null : (
|
||||
t('no_threads')
|
||||
)
|
||||
) : (
|
||||
hasMore && <LoadingEllipsis string={loadingStateString} />
|
||||
)}
|
||||
@@ -587,6 +604,11 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
const footerMoreThreadsSuggestion = showHiddenThreads ? null : moreThreadsSuggestion;
|
||||
const footerShowLoadingEllipsis = showHiddenThreads ? isLoadingHiddenCatalogThreads : effectiveInfiniteScroll;
|
||||
|
||||
const catalogFooterFirstRow = (
|
||||
<CatalogFooterFirstRow communityAddress={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
);
|
||||
const catalogFooterStyleRow = <CatalogFooterStyleRow />;
|
||||
|
||||
// Memoize footer component to preserve identity across renders (Virtuoso optimization)
|
||||
// Note: useFeedStateString is called inside CatalogFooter to isolate re-renders from backend state changes
|
||||
const footerComponents = useMemo(
|
||||
@@ -604,16 +626,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
onExpandTimeWindow={expandSuggestionTimeWindow}
|
||||
showLoadingEllipsis={footerShowLoadingEllipsis}
|
||||
/>
|
||||
<PageFooterDesktop
|
||||
firstRow={
|
||||
<CatalogFooterFirstRow
|
||||
communityAddress={communityAddress}
|
||||
isInAllView={isInAllView}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
|
||||
<PageFooterMobile>
|
||||
<div className={mobileFooterStyles.mobileFooterButtons}>
|
||||
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
@@ -633,6 +646,8 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
footerMoreThreadsSuggestion,
|
||||
moreThreadsSuggestionPathname,
|
||||
expandSuggestionTimeWindow,
|
||||
catalogFooterFirstRow,
|
||||
catalogFooterStyleRow,
|
||||
communityAddress,
|
||||
isInAllView,
|
||||
isInSubscriptionsView,
|
||||
@@ -655,16 +670,7 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
onExpandTimeWindow={expandSuggestionTimeWindow}
|
||||
showLoadingEllipsis={footerShowLoadingEllipsis}
|
||||
/>
|
||||
<PageFooterDesktop
|
||||
firstRow={
|
||||
<CatalogFooterFirstRow
|
||||
communityAddress={communityAddress}
|
||||
isInAllView={isInAllView}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
|
||||
<PageFooterMobile>
|
||||
<div className={mobileFooterStyles.mobileFooterButtons}>
|
||||
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
@@ -683,6 +689,8 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
footerMoreThreadsSuggestion,
|
||||
moreThreadsSuggestionPathname,
|
||||
expandSuggestionTimeWindow,
|
||||
catalogFooterFirstRow,
|
||||
catalogFooterStyleRow,
|
||||
communityAddress,
|
||||
isInAllView,
|
||||
isInSubscriptionsView,
|
||||
@@ -692,6 +700,9 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
],
|
||||
);
|
||||
const isFeedLoaded = feed.length > 0 || hiddenThreadsCount > 0 || state === 'failed';
|
||||
const hasActiveSearch = searchText.trim().length > 0;
|
||||
const showSearchNothingFound =
|
||||
hasActiveSearch && !footerHasMore && footerCombinedFeedLength === 0 && state !== 'failed' && !(isInSubscriptionsView && (subscriptions?.length || 0) === 0);
|
||||
|
||||
// Process the feed to move "top" posts to the top (applied after display sort)
|
||||
const processedFeed = useMemo(() => {
|
||||
@@ -896,6 +907,21 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : showSearchNothingFound ? (
|
||||
<>
|
||||
<CatalogSearchNothingFound />
|
||||
<hr />
|
||||
{catalogFooterFirstRow}
|
||||
<PageFooterDesktop variant='catalog' styleRow={catalogFooterStyleRow} />
|
||||
<PageFooterMobile>
|
||||
<div className={mobileFooterStyles.mobileFooterButtons}>
|
||||
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
<ArchiveButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
<TopButton />
|
||||
<RefreshButton />
|
||||
</div>
|
||||
</PageFooterMobile>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.footer}>
|
||||
@@ -906,18 +932,10 @@ const Catalog = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp,
|
||||
state={state}
|
||||
subscriptionsLength={isInSubscriptionsView ? subscriptions?.length || 0 : 1}
|
||||
error={error}
|
||||
hasActiveSearch={hasActiveSearch}
|
||||
/>
|
||||
</div>
|
||||
<PageFooterDesktop
|
||||
firstRow={
|
||||
<CatalogFooterFirstRow
|
||||
communityAddress={communityAddress}
|
||||
isInAllView={isInAllView}
|
||||
isInSubscriptionsView={isInSubscriptionsView}
|
||||
isInModView={isInModView}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageFooterDesktop variant='catalog' firstRow={catalogFooterFirstRow} styleRow={catalogFooterStyleRow} />
|
||||
<PageFooterMobile>
|
||||
<div className={mobileFooterStyles.mobileFooterButtons}>
|
||||
<ReturnButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
|
||||
|
||||
Reference in New Issue
Block a user