mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(home): unify stats loading state with feed status string
Show a single centered loader without stat labels until all directory stats resolve, using useFeedStateString like popular threads. Center directory table status loading in its cell.
This commit is contained in:
@@ -145,8 +145,16 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ownerCell {
|
.ownerCell,
|
||||||
|
.statusCell {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusLoading {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ownerName {
|
.ownerName {
|
||||||
|
|||||||
@@ -135,7 +135,9 @@ const DirectoryRow = ({ board, nowSeconds, rank, onVote }: DirectoryRowProps) =>
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
) : status === 'loading' ? (
|
) : status === 'loading' ? (
|
||||||
<LoadingEllipsis string={t('loading')} />
|
<span className={styles.statusLoading}>
|
||||||
|
<LoadingEllipsis string={t('loading')} />
|
||||||
|
</span>
|
||||||
) : status === 'unknown' ? (
|
) : status === 'unknown' ? (
|
||||||
<span className={styles.statusUnavailable}>{DIRECTORY_STATUS_UNAVAILABLE_MARKER}</span>
|
<span className={styles.statusUnavailable}>{DIRECTORY_STATUS_UNAVAILABLE_MARKER}</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createElement } from 'react';
|
|||||||
import { createRoot, type Root } from 'react-dom/client';
|
import { createRoot, type Root } from 'react-dom/client';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { useFeedStateString } from '../../../hooks/use-state-string';
|
||||||
import Home from '../home';
|
import Home from '../home';
|
||||||
|
|
||||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
@@ -19,6 +20,7 @@ const testState = vi.hoisted(() => ({
|
|||||||
navigateMock: vi.fn(),
|
navigateMock: vi.fn(),
|
||||||
communities: {} as Record<string, unknown>,
|
communities: {} as Record<string, unknown>,
|
||||||
communityStats: {} as Record<string, { allPostCount?: number; weekActiveUserCount?: number; state?: string }>,
|
communityStats: {} as Record<string, { allPostCount?: number; weekActiveUserCount?: number; state?: string }>,
|
||||||
|
feedStateString: 'Downloading boards',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('react-i18next', () => ({
|
vi.mock('react-i18next', () => ({
|
||||||
@@ -76,6 +78,10 @@ vi.mock('../../../hooks/use-now-seconds', () => ({
|
|||||||
useNowSeconds: () => testState.nowSeconds,
|
useNowSeconds: () => testState.nowSeconds,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/use-state-string', () => ({
|
||||||
|
useFeedStateString: vi.fn(() => testState.feedStateString),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../components/loading-ellipsis', () => ({
|
vi.mock('../../../components/loading-ellipsis', () => ({
|
||||||
default: ({ string }: { string: string }) => createElement('span', { 'data-testid': 'loading-ellipsis' }, string),
|
default: ({ string }: { string: string }) => createElement('span', { 'data-testid': 'loading-ellipsis' }, string),
|
||||||
}));
|
}));
|
||||||
@@ -139,6 +145,7 @@ describe('Home', () => {
|
|||||||
'music-posting.eth': { allPostCount: 5, weekActiveUserCount: 2 },
|
'music-posting.eth': { allPostCount: 5, weekActiveUserCount: 2 },
|
||||||
'tech-posting.eth': { allPostCount: 7, weekActiveUserCount: 5 },
|
'tech-posting.eth': { allPostCount: 7, weekActiveUserCount: 5 },
|
||||||
};
|
};
|
||||||
|
testState.feedStateString = 'Downloading boards';
|
||||||
|
|
||||||
container = document.createElement('div');
|
container = document.createElement('div');
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
@@ -153,6 +160,7 @@ describe('Home', () => {
|
|||||||
it('renders the home view chrome, child sections, collectors, and aggregated stats', () => {
|
it('renders the home view chrome, child sections, collectors, and aggregated stats', () => {
|
||||||
renderHome();
|
renderHome();
|
||||||
|
|
||||||
|
expect(vi.mocked(useFeedStateString)).not.toHaveBeenCalled();
|
||||||
expect(document.title).toBe('5chan');
|
expect(document.title).toBe('5chan');
|
||||||
expect(container.querySelector('[data-testid="disclaimer-modal"]')?.textContent).toBe('disclaimer-modal');
|
expect(container.querySelector('[data-testid="disclaimer-modal"]')?.textContent).toBe('disclaimer-modal');
|
||||||
expect(container.querySelector('[data-testid="directory-modal"]')?.textContent).toBe('directory-modal');
|
expect(container.querySelector('[data-testid="directory-modal"]')?.textContent).toBe('directory-modal');
|
||||||
@@ -175,8 +183,12 @@ describe('Home', () => {
|
|||||||
renderHome();
|
renderHome();
|
||||||
|
|
||||||
const loadingValues = Array.from(container.querySelectorAll('[data-testid="loading-ellipsis"]'));
|
const loadingValues = Array.from(container.querySelectorAll('[data-testid="loading-ellipsis"]'));
|
||||||
expect(loadingValues).toHaveLength(3);
|
expect(loadingValues).toHaveLength(1);
|
||||||
expect(loadingValues.map((value) => value.textContent)).toEqual(['loading', 'loading', 'loading']);
|
expect(loadingValues[0]?.textContent).toBe('Downloading boards');
|
||||||
|
expect(vi.mocked(useFeedStateString)).toHaveBeenCalledWith(['music-posting.eth', 'tech-posting.eth']);
|
||||||
|
expect(container.textContent).not.toContain('total_posts');
|
||||||
|
expect(container.textContent).not.toContain('current_users');
|
||||||
|
expect(container.textContent).not.toContain('boards_tracked');
|
||||||
expect(container.textContent).not.toContain('total_posts 5');
|
expect(container.textContent).not.toContain('total_posts 5');
|
||||||
expect(container.textContent).not.toContain('current_users 2');
|
expect(container.textContent).not.toContain('current_users 2');
|
||||||
expect(container.textContent).not.toContain('boards_tracked 1');
|
expect(container.textContent).not.toContain('boards_tracked 1');
|
||||||
|
|||||||
+22
-18
@@ -9,6 +9,7 @@ import PopularThreadsBox from './popular-threads-box';
|
|||||||
import BoardsList from './boards-list';
|
import BoardsList from './boards-list';
|
||||||
import SiteLegalMeta from '../../components/site-legal-meta';
|
import SiteLegalMeta from '../../components/site-legal-meta';
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
|
import { useFeedStateString } from '../../hooks/use-state-string';
|
||||||
import useDirectoryModalStore from '../../stores/use-directory-modal-store';
|
import useDirectoryModalStore from '../../stores/use-directory-modal-store';
|
||||||
import DisclaimerModal from '../../components/disclaimer-modal';
|
import DisclaimerModal from '../../components/disclaimer-modal';
|
||||||
import DirectoryModal from '../../components/directory-modal';
|
import DirectoryModal from '../../components/directory-modal';
|
||||||
@@ -88,19 +89,18 @@ const InfoBox = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface StatValueProps {
|
|
||||||
isLoaded: boolean;
|
|
||||||
loadingLabel: string;
|
|
||||||
value: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type HomepageStats = {
|
type HomepageStats = {
|
||||||
allPostCount?: number;
|
allPostCount?: number;
|
||||||
weekActiveUserCount?: number;
|
weekActiveUserCount?: number;
|
||||||
state?: string;
|
state?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StatValue = ({ isLoaded, loadingLabel, value }: StatValueProps) => (isLoaded ? <>{value}</> : <LoadingEllipsis string={loadingLabel} />);
|
const StatsLoading = ({ communityAddresses }: { communityAddresses: string[] }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const loadingStateString = useFeedStateString(communityAddresses) || t('loading');
|
||||||
|
|
||||||
|
return <LoadingEllipsis string={loadingStateString} />;
|
||||||
|
};
|
||||||
|
|
||||||
const STATS_DIRECTORY_FALLBACK_DELAY_SECONDS = 30;
|
const STATS_DIRECTORY_FALLBACK_DELAY_SECONDS = 30;
|
||||||
|
|
||||||
@@ -193,8 +193,6 @@ const Stats = ({ directories }: { directories: DirectoryCommunity[] }) => {
|
|||||||
};
|
};
|
||||||
}, [communitiesStats, defaultDirectoryAddresses, directories, listsByCode]);
|
}, [communitiesStats, defaultDirectoryAddresses, directories, listsByCode]);
|
||||||
|
|
||||||
const loadingLabel = t('loading');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Render collectors to fetch stats for each community */}
|
{/* Render collectors to fetch stats for each community */}
|
||||||
@@ -206,15 +204,21 @@ const Stats = ({ directories }: { directories: DirectoryCommunity[] }) => {
|
|||||||
<h2 className='capitalize'>{t('stats')}</h2>
|
<h2 className='capitalize'>{t('stats')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.boxContent} ${styles.stats}`}>
|
<div className={`${styles.boxContent} ${styles.stats}`}>
|
||||||
<div className={styles.stat}>
|
{allDirectoryStatsLoaded ? (
|
||||||
<b>{t('total_posts')}</b> <StatValue isLoaded={allDirectoryStatsLoaded} loadingLabel={loadingLabel} value={totalPosts} />
|
<>
|
||||||
</div>
|
<div className={styles.stat}>
|
||||||
<div className={styles.stat}>
|
<b>{t('total_posts')}</b> {totalPosts}
|
||||||
<b>{t('current_users')}</b> <StatValue isLoaded={allDirectoryStatsLoaded} loadingLabel={loadingLabel} value={currentUsers} />
|
</div>
|
||||||
</div>
|
<div className={styles.stat}>
|
||||||
<div className={styles.stat}>
|
<b>{t('current_users')}</b> {currentUsers}
|
||||||
<b>{t('boards_tracked')}</b> <StatValue isLoaded={allDirectoryStatsLoaded} loadingLabel={loadingLabel} value={boardsTracked} />
|
</div>
|
||||||
</div>
|
<div className={styles.stat}>
|
||||||
|
<b>{t('boards_tracked')}</b> {boardsTracked}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<StatsLoading communityAddresses={collectorAddresses} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user