fix(board status): resolve offline indicators with strict community refs

This commit is contained in:
Tommaso Casaburi
2026-04-17 12:51:14 +07:00
parent 1c2be5a172
commit 4cae9c3caa
7 changed files with 86 additions and 64 deletions
@@ -10,6 +10,8 @@ const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise
const testState = vi.hoisted(() => ({
accountComment: undefined as { communityAddress?: string } | undefined,
community: { address: 'music-posting.eth' } as { address?: string; name?: string; publicKey?: string } | undefined,
communityIdentifier: { name: 'music-posting.eth' } as { name?: string; publicKey?: string } | undefined,
directories: [{ address: 'music-posting.eth', title: '/mu/ - Music' }] as Array<{ address: string; title?: string }>,
directoriesMetadata: { title: '/all/ - Directories' } as { title?: string } | undefined,
isMobile: false,
@@ -52,6 +54,7 @@ vi.mock('react-router-dom', async () => {
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccount: () => undefined,
useAccountComment: () => testState.accountComment,
useCommunity: () => testState.community,
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/stores/accounts', () => ({
@@ -82,6 +85,10 @@ vi.mock('../../../hooks/use-directories', () => ({
useDirectoriesMetadata: () => testState.directoriesMetadata,
}));
vi.mock('../../../hooks/use-community-identifiers', () => ({
useCommunityIdentifier: () => testState.communityIdentifier,
}));
vi.mock('../../../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedAddress,
}));
@@ -122,6 +129,8 @@ describe('BoardHeader', () => {
beforeEach(() => {
vi.clearAllMocks();
testState.accountComment = undefined;
testState.community = { address: 'music-posting.eth' };
testState.communityIdentifier = { name: 'music-posting.eth' };
testState.directories = [{ address: 'music-posting.eth', title: '/mu/ - Music' }];
testState.directoriesMetadata = { title: '/all/ - Directories' };
testState.isMobile = false;
+5 -4
View File
@@ -1,9 +1,10 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useParams, useNavigate } from 'react-router-dom';
import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
import useAccountsStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/accounts';
import useCommunitiesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities';
import getShortAddress from '../../lib/get-short-address';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import { useStableCommunity } from '../../hooks/use-stable-community';
import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils';
import { isArchiveRoute } from '../../lib/utils/route-utils';
@@ -27,9 +28,9 @@ const ImageBanner = () => {
// Separate component for offline indicator to isolate rerenders from updatingState
// Only this component will rerender when updatingState changes, not the whole BoardHeader
const OfflineIndicator = ({ communityAddress }: { communityAddress: string | undefined }) => {
// Subscribe to full community including transient state for offline detection
const community = useCommunitiesStore((state) => (communityAddress ? state.communities[communityAddress] : undefined));
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsCommunityOffline(community);
const communityIdentifier = useCommunityIdentifier(communityAddress);
const community = useCommunity(communityIdentifier ? { community: communityIdentifier } : undefined);
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsCommunityOffline(community, communityAddress);
if (!isOffline && !isOnlineStatusLoading) {
return null;
@@ -1,35 +1,9 @@
import { useMemo } from 'react';
import useCommunitiesStore from '@bitsocialnet/bitsocial-react-hooks/dist/stores/communities';
import { normalizeBoardAddress, useDirectoryByAddress } from '../../hooks/use-directories';
import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import { useDirectoryByAddress } from '../../hooks/use-directories';
import useIsCommunityOffline from '../../hooks/use-is-community-offline';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
const BOARD_ALIAS_SUFFIXES = ['.bso', '.eth'] as const;
const getBoardAddressCandidates = (addresses: Array<string | undefined>) => {
const uniqueCandidates = new Set<string>();
const addCandidate = (candidate: string | undefined) => {
if (candidate) {
uniqueCandidates.add(candidate);
}
};
addresses.forEach((address) => {
if (!address) {
return;
}
addCandidate(address);
const normalizedAddress = normalizeBoardAddress(address);
addCandidate(normalizedAddress);
BOARD_ALIAS_SUFFIXES.forEach((suffix) => addCandidate(`${normalizedAddress}${suffix}`));
});
return Array.from(uniqueCandidates);
};
interface BoardOfflineAlertProps {
className: string;
hidden?: boolean;
@@ -39,23 +13,10 @@ interface BoardOfflineAlertProps {
const BoardOfflineAlert = ({ className, hidden = false, communityAddress }: BoardOfflineAlertProps) => {
const resolvedCommunityAddress = useResolvedCommunityAddress();
const directoryEntry = useDirectoryByAddress(resolvedCommunityAddress || communityAddress);
const addressCandidates = useMemo(
() => getBoardAddressCandidates([resolvedCommunityAddress, directoryEntry?.address, communityAddress]),
[directoryEntry?.address, resolvedCommunityAddress, communityAddress],
);
// Probe common aliases first so loading/offline state stays consistent across route and post payload address formats.
const community = useCommunitiesStore((state) => {
for (const candidate of addressCandidates) {
const matchedCommunity = state.communities[candidate];
if (matchedCommunity) {
return matchedCommunity;
}
}
return undefined;
});
const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsCommunityOffline(community);
const canonicalCommunityAddress = resolvedCommunityAddress || directoryEntry?.address || communityAddress;
const communityIdentifier = useCommunityIdentifier(canonicalCommunityAddress);
const community = useCommunity(communityIdentifier ? { community: communityIdentifier } : undefined);
const { isOffline, isOnlineStatusLoading, offlineTitle } = useIsCommunityOffline(community, canonicalCommunityAddress);
if (hidden || (!isOffline && !isOnlineStatusLoading)) {
return null;
@@ -67,6 +67,10 @@ vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
setAccount: (account: unknown) => testState.setAccountMock(account),
useAccount: () => testState.account,
useAccountComment: () => testState.accountComment,
useCommunity: (options?: { community?: { name?: string; publicKey?: string } }) => {
const communityKey = options?.community?.name ?? options?.community?.publicKey;
return communityKey ? testState.communities[communityKey] : undefined;
},
useEditedComment: () => ({ editedComment: testState.editedComment }),
}));
@@ -88,6 +92,10 @@ vi.mock('../../../hooks/use-directories', () => ({
normalizeBoardAddress: (address: string) => address.replace(/\.(bso|eth)$/, ''),
}));
vi.mock('../../../hooks/use-community-identifiers', () => ({
useCommunityIdentifier: (address?: string) => (address ? { name: address } : undefined),
}));
vi.mock('../../../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedCommunityAddress,
}));
@@ -73,6 +73,10 @@ vi.mock('react-i18next', () => ({
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
setAccount: (account: unknown) => testState.setAccountMock(account),
useAccount: () => testState.account,
useCommunity: (options?: { community?: { name?: string; publicKey?: string } }) => {
const communityKey = options?.community?.name ?? options?.community?.publicKey;
return communityKey ? testState.communities[communityKey] : undefined;
},
}));
vi.mock('@bitsocialnet/bitsocial-react-hooks/dist/stores/communities', () => ({
@@ -126,6 +130,10 @@ vi.mock('../../../hooks/use-directories', () => ({
normalizeBoardAddress: (address: string) => address.replace(/\.(bso|eth)$/, ''),
}));
vi.mock('../../../hooks/use-community-identifiers', () => ({
useCommunityIdentifier: (address?: string) => (address ? { name: address } : undefined),
}));
vi.mock('../../../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedCommunityAddress,
}));