Add board directory view (#1132)

* feat(directory): add board directory view

* fix(directory): populate board status

* style(directory): tighten board table

* docs(board manager): point board owners to manager

* fix(directory): show loading status

* style(directory): center board column in directory table

* perf(directory): cap board status checks

* style(directory): simplify board row links

* test(ci): stabilize coverage run

* test(ci): stabilize coverage harness

* test(ci): avoid async app flush act

* test(app): narrow layout harness coverage

* test(ci): stabilize app update distribution mock

* test(ci): preload app harness before route tests

* fix(directory): address final review findings
This commit is contained in:
Tommaso Casaburi
2026-05-19 23:34:33 +07:00
committed by GitHub
parent 4370e89174
commit cda1f7519f
62 changed files with 1572 additions and 171 deletions
@@ -323,13 +323,14 @@ describe('BoardButtons', () => {
localStorage.clear();
});
it('renders desktop board actions for browsing boards, then searches OPs and triggers refresh, vote, subscribe, and archive flows', async () => {
it('renders desktop board actions for browsing boards, then searches OPs and triggers refresh, directory, subscribe, and archive flows', async () => {
await renderWithRoute(createElement(DesktopBoardButtons), '/mu');
expect(container.querySelector('[data-testid="mod-queue-button"]')?.textContent).toBe('mu');
expect(container.textContent).toContain('subscribe');
expect(container.textContent).toContain('vote');
expect(container.textContent).toContain('directory');
expect(findButtonLink('catalog')?.getAttribute('href')).toBe('/mu/catalog');
expect(findButtonLink('directory')?.getAttribute('href')).toBe('/mu/directory');
const searchInput = container.querySelector<HTMLInputElement>('input[type="text"]');
expect(searchInput).toBeTruthy();
@@ -345,14 +346,18 @@ describe('BoardButtons', () => {
await clickButton('refresh');
await clickButton('subscribe');
await clickButton('vote');
await clickButton('archive');
expect(testState.resetMock).toHaveBeenCalledTimes(1);
expect(testState.subscribeMock).toHaveBeenCalledTimes(1);
expect(testState.navigateMock).toHaveBeenCalledWith('/mu/archive');
expect(globalThis.alert).toHaveBeenNthCalledWith(1, 'vote_button_unavailable_intro\n\nvote_button_unavailable_outro');
expect(globalThis.alert).toHaveBeenCalledTimes(1);
expect(globalThis.alert).not.toHaveBeenCalled();
});
it('does not render the directory button on a full board address route', async () => {
await renderWithRoute(createElement(DesktopBoardButtons), '/music-posting.eth');
expect(container.textContent).not.toContain('directory');
});
it('renders desktop catalog controls and wires sort, style, filter, and refresh updates', async () => {
@@ -30,10 +30,6 @@
width: 10px;
}
.disabledButton:hover {
cursor: not-allowed !important;
}
.mobileBoardButtons a:not([class~='button']),
.desktopBoardButtons a:not([class~='button']) {
all: unset;
+12 -22
View File
@@ -7,7 +7,7 @@ import { usePostPageNumber } from '../../hooks/use-post-page-number';
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-directory-addresses';
import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils';
import { getBoardPath, isDirectoryRoute } from '../../lib/utils/route-utils';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
@@ -165,26 +165,20 @@ export const ReturnButton = ({ address, isInAllView, isInSubscriptionsView, isIn
);
};
const VoteButton = () => {
const DirectoryButton = () => {
const { t } = useTranslation();
const params = useParams();
const directories = useDirectories();
// Get the boardIdentifier from params (try boardIdentifier first, then communityAddress for backward compatibility)
const boardIdentifier = params.boardIdentifier;
// Only render the vote button if we're on a directory board route
if (!boardIdentifier || !isDirectoryBoard(boardIdentifier, directories)) {
if (!boardIdentifier || !isDirectoryRoute(boardIdentifier, directories)) {
return null;
}
const values = { boardIdentifier };
const message = `${t('vote_button_unavailable_intro', values)}\n\n${t('vote_button_unavailable_outro', values)}`;
return (
<button className={`button ${styles.disabledButton}`} onClick={() => window.alert(message)}>
{t('vote')}
</button>
<Link className='button' to={`/${boardIdentifier}/directory`}>
{t('directory')}
</Link>
);
};
@@ -508,7 +502,6 @@ export const MobileAllFeedFilter = () => (
);
export const MobileBoardButtons = () => {
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const isInAllView = isAllView(location.pathname);
@@ -529,10 +522,9 @@ export const MobileBoardButtons = () => {
const effectiveInfiniteScroll = isMultiboard || enableInfiniteScroll;
const showBottomButton = !effectiveInfiniteScroll;
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
const showDirectoryButton = boardIdentifier && isDirectoryRoute(boardIdentifier, directories);
return (
<div className={`${styles.mobileBoardButtons} ${!isInCatalogView ? styles.addMargin : ''}`}>
@@ -602,7 +594,7 @@ export const MobileBoardButtons = () => {
<CatalogButton address={communityAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
<RefreshButton />
<div className={styles.secondRow}>
{showVoteButton && <VoteButton />}
{showDirectoryButton && <DirectoryButton />}
{!(isInAllView || isInSubscriptionsView || isInModView) && <SubscribeButton address={communityAddress} />}
{!(isInAllView || isInSubscriptionsView) && <ModQueueButton boardIdentifier={boardIdentifier} isMobile={true} />}
</div>
@@ -696,7 +688,6 @@ export const CatalogSearchResultsLabel = () => {
};
export const DesktopBoardButtons = () => {
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex });
@@ -716,10 +707,9 @@ export const DesktopBoardButtons = () => {
const effectiveInfiniteScroll = isMultiboard || enableInfiniteScroll;
const showBottomButton = (isInCatalogView || isInPostView || isInPendingPostPage) && !effectiveInfiniteScroll;
// Check if we should show the vote button (only for directory boards)
const directories = useDirectories();
const boardIdentifier = params.boardIdentifier;
const showVoteButton = boardIdentifier && isDirectoryBoard(boardIdentifier, directories);
const showDirectoryButton = boardIdentifier && isDirectoryRoute(boardIdentifier, directories);
return (
<>
@@ -818,12 +808,12 @@ export const DesktopBoardButtons = () => {
{showTimeFilter && (
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />
)}
{showVoteButton && (
{showDirectoryButton && (
<>
[<VoteButton />]
[<DirectoryButton />]
</>
)}
{showVoteButton && !(isInAllView || isInSubscriptionsView || isInModView) && ' '}
{showDirectoryButton && !(isInAllView || isInSubscriptionsView || isInModView) && ' '}
{!(isInAllView || isInSubscriptionsView || isInModView) && (
<>
[<SubscribeButton address={communityAddress} />]