mirror of
https://github.com/plebbit/seedit.git
synced 2026-07-31 09:56:04 -04:00
merge: empty feed completion
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.message {
|
||||
color: var(--red);
|
||||
font-size: 13px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
10
src/components/empty-feed-message/empty-feed-message.tsx
Normal file
10
src/components/empty-feed-message/empty-feed-message.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './empty-feed-message.module.css';
|
||||
|
||||
const EmptyFeedMessage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div className={styles.message}>{t('nothing_found')}</div>;
|
||||
};
|
||||
|
||||
export default EmptyFeedMessage;
|
||||
46
src/components/feed-footer/feed-footer-utils.test.ts
Normal file
46
src/components/feed-footer/feed-footer-utils.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { shouldShowEmptyFeed } from './feed-footer-utils';
|
||||
|
||||
describe('shouldShowEmptyFeed', () => {
|
||||
it.each([0, 1, 60, 1000])('shows the empty state after all %i requested communities load even when pagination has more', (requestedCommunityCount) => {
|
||||
const communities = Array.from({ length: requestedCommunityCount }, (_, index) => ({ updatedAt: index + 1 }));
|
||||
const emptyLoadedFeed = {
|
||||
requestedCommunityCount,
|
||||
communities,
|
||||
feedLength: 0,
|
||||
hasMore: true,
|
||||
};
|
||||
|
||||
expect(shouldShowEmptyFeed(emptyLoadedFeed)).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps loading while any requested community has not loaded', () => {
|
||||
expect(
|
||||
shouldShowEmptyFeed({
|
||||
requestedCommunityCount: 2,
|
||||
communities: [{ updatedAt: 1 }, undefined],
|
||||
feedLength: 0,
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
shouldShowEmptyFeed({
|
||||
requestedCommunityCount: 2,
|
||||
communities: [{ updatedAt: 1 }, {}],
|
||||
feedLength: 0,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('does not show the empty state when posts or a search are present', () => {
|
||||
const loadedFeed = {
|
||||
requestedCommunityCount: 1,
|
||||
communities: [{ updatedAt: 1 }],
|
||||
};
|
||||
|
||||
expect(shouldShowEmptyFeed({ ...loadedFeed, feedLength: 1 })).toBe(false);
|
||||
expect(shouldShowEmptyFeed({ ...loadedFeed, feedLength: 0, isLoadingCommunityData: true })).toBe(false);
|
||||
expect(shouldShowEmptyFeed({ ...loadedFeed, feedLength: 0, isSearching: true })).toBe(false);
|
||||
expect(shouldShowEmptyFeed({ ...loadedFeed, feedLength: 0, searchQuery: 'query' })).toBe(false);
|
||||
});
|
||||
});
|
||||
27
src/components/feed-footer/feed-footer-utils.ts
Normal file
27
src/components/feed-footer/feed-footer-utils.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
interface FeedCommunityLoadState {
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
interface ShouldShowEmptyFeedOptions {
|
||||
requestedCommunityCount: number;
|
||||
communities: Array<FeedCommunityLoadState | undefined>;
|
||||
feedLength: number;
|
||||
isLoadingCommunityData?: boolean;
|
||||
isSearching?: boolean;
|
||||
searchQuery?: string;
|
||||
}
|
||||
|
||||
export const shouldShowEmptyFeed = ({
|
||||
requestedCommunityCount,
|
||||
communities,
|
||||
feedLength,
|
||||
isLoadingCommunityData,
|
||||
isSearching,
|
||||
searchQuery,
|
||||
}: ShouldShowEmptyFeedOptions): boolean => {
|
||||
// `hasMore` also represents pagination. `updatedAt` is the hook's count-safe
|
||||
// evidence that each requested community snapshot loaded.
|
||||
const haveAllRequestedCommunitiesLoaded = communities.length === requestedCommunityCount && communities.every((community) => Boolean(community?.updatedAt));
|
||||
|
||||
return haveAllRequestedCommunitiesLoaded && feedLength === 0 && !isLoadingCommunityData && !isSearching && !searchQuery;
|
||||
};
|
||||
@@ -48,4 +48,4 @@
|
||||
color: var(--red);
|
||||
font-size: 13px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useCommunities } from '@bitsocial/bitsocial-react-hooks';
|
||||
import { isAllView, isModView } from '../../lib/utils/view-utils';
|
||||
import { useCommunityIdentifiers } from '../../hooks/use-community-identifier';
|
||||
import { useFeedStateString } from '../../hooks/use-state-string';
|
||||
import EmptyFeedMessage from '../empty-feed-message/empty-feed-message';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
import { shouldShowEmptyFeed } from './feed-footer-utils';
|
||||
import styles from './feed-footer.module.css';
|
||||
import React from 'react';
|
||||
|
||||
@@ -45,12 +49,55 @@ const FeedFooter = ({
|
||||
const isInModView = isModView(location.pathname);
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
|
||||
const feedCommunityIdentifiers = useCommunityIdentifiers(communityAddresses);
|
||||
const { communities } = useCommunities({ communities: feedCommunityIdentifiers });
|
||||
const feedStateString = useFeedStateString(communityAddresses);
|
||||
const loadingStateString =
|
||||
useFeedStateString(communityAddresses) ||
|
||||
feedStateString ||
|
||||
(!hasFeedLoaded || (feedLength === 0 && !(weeklyFeedLength > feedLength || monthlyFeedLength > feedLength || yearlyFeedLength > feedLength))
|
||||
? t('loading_feed')
|
||||
: t('looking_for_more_posts'));
|
||||
const hasEmptyFeedData = shouldShowEmptyFeed({
|
||||
requestedCommunityCount: feedCommunityIdentifiers.length,
|
||||
communities,
|
||||
feedLength,
|
||||
isLoadingCommunityData: Boolean(feedStateString),
|
||||
isSearching,
|
||||
searchQuery,
|
||||
});
|
||||
|
||||
const widerFeedSuggestion =
|
||||
weeklyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_week'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='weekly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1w'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : monthlyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_month'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='monthly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1m'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : yearlyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_year'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='yearly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1y'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// Add state to track initial loading
|
||||
const [hasFetchedCommunityAddresses, setHasFetchedSubplebbitAddresses] = useState(false);
|
||||
@@ -63,6 +110,8 @@ const FeedFooter = ({
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const showEmptyFeed = hasFetchedCommunityAddresses && hasEmptyFeedData;
|
||||
|
||||
if (!hasFetchedCommunityAddresses) {
|
||||
footerContent = <LoadingEllipsis string={t('loading_feed')} />;
|
||||
}
|
||||
@@ -101,57 +150,32 @@ const FeedFooter = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
hasFeedLoaded &&
|
||||
feedLength === 0 &&
|
||||
!hasMore &&
|
||||
!isSearching &&
|
||||
!searchQuery &&
|
||||
!(weeklyFeedLength > feedLength || monthlyFeedLength > feedLength || yearlyFeedLength > feedLength)
|
||||
) {
|
||||
footerContent = t('no_posts');
|
||||
} else if (showEmptyFeed) {
|
||||
footerContent = (
|
||||
<>
|
||||
{widerFeedSuggestion}
|
||||
<EmptyFeedMessage />
|
||||
</>
|
||||
);
|
||||
} else if (hasMore || communityAddresses.length > 0 || (communityAddresses && communityAddresses.length === 0)) {
|
||||
// Only show newer posts/weekly/monthly suggestions when not searching
|
||||
footerContent = (
|
||||
<>
|
||||
{weeklyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_week'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='weekly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1w'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : monthlyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_month'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='monthly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1m'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : yearlyFeedLength > feedLength && !searchQuery ? (
|
||||
<div className={styles.morePostsSuggestion}>
|
||||
<Trans
|
||||
i18nKey='more_posts_last_year'
|
||||
values={{ currentTimeFilterName, count: feedLength }}
|
||||
components={{
|
||||
1: <Link key='yearly-posts-link' to={(isInModView ? '/s/mod/' : isInAllView ? '/s/all/' : '/') + (params?.sortType || 'hot') + '/1y'} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{widerFeedSuggestion}
|
||||
<div className={styles.stateString}>
|
||||
{communityAddresses.length === 0 ? (
|
||||
isInModView ? (
|
||||
<div className={styles.notModerator}>{t('not_moderator')}</div>
|
||||
) : (
|
||||
<div>
|
||||
<Trans i18nKey='no_communities_found' components={[<a href='https://github.com/bitsocialhq/lists'>https://github.com/bitsocialhq/lists</a>]} />
|
||||
<Trans
|
||||
i18nKey='no_communities_found'
|
||||
components={[
|
||||
<a key='community-lists-link' href='https://github.com/bitsocialhq/lists'>
|
||||
https://github.com/bitsocialhq/lists
|
||||
</a>,
|
||||
]}
|
||||
/>
|
||||
<br />
|
||||
{t('connect_community_notice')}
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { isResolvableCommunityAddress } from '../../lib/utils/directory-codes';
|
||||
import useTimeFilter, { isValidTimeFilterName } from '../../hooks/use-time-filter';
|
||||
import { getCommunityIdentifier, getCommunityIdentifiers } from '../../hooks/use-community-identifier';
|
||||
import ErrorDisplay from '../../components/error-display';
|
||||
import EmptyFeedMessage from '../../components/empty-feed-message/empty-feed-message';
|
||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||
import Over18Warning from '../../components/over-18-warning';
|
||||
import Post from '../../components/post';
|
||||
@@ -31,6 +32,7 @@ interface FooterProps {
|
||||
communityAddress: string;
|
||||
feedLength: number;
|
||||
isOnline: boolean;
|
||||
hasCommunityLoaded: boolean;
|
||||
started: boolean;
|
||||
isSubCreatedButNotYetPublished: boolean;
|
||||
hasMore: boolean;
|
||||
@@ -46,6 +48,7 @@ const Footer = ({
|
||||
communityAddress,
|
||||
feedLength,
|
||||
isOnline,
|
||||
hasCommunityLoaded,
|
||||
started: _started,
|
||||
isSubCreatedButNotYetPublished: _isSubCreatedButNotYetPublished,
|
||||
hasMore,
|
||||
@@ -58,7 +61,8 @@ const Footer = ({
|
||||
const { t } = useTranslation();
|
||||
let footerFirstLine;
|
||||
let footerSecondLine;
|
||||
const loadingStateString = useFeedStateString(communityAddresses) || t('loading');
|
||||
const feedStateString = useFeedStateString(communityAddresses);
|
||||
const loadingStateString = feedStateString || t('loading');
|
||||
|
||||
const [showNoResults, setShowNoResults] = useState(false);
|
||||
const [searchAttemptCompleted, setSearchAttemptCompleted] = useState(false);
|
||||
@@ -180,8 +184,8 @@ const Footer = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (feedLength === 0 && isOnline && !hasMore) {
|
||||
footerFirstLine = t('no_posts');
|
||||
} else if (feedLength === 0 && isOnline && hasCommunityLoaded && !feedStateString) {
|
||||
footerFirstLine = <EmptyFeedMessage />;
|
||||
} else if (feedLength === 0 || !isOnline) {
|
||||
footerFirstLine = loadingString;
|
||||
} else if (hasMore) {
|
||||
@@ -320,6 +324,7 @@ const CommunityView = () => {
|
||||
communityAddress,
|
||||
feedLength: combinedFeed.length || 0,
|
||||
isOnline,
|
||||
hasCommunityLoaded: Boolean(updatedAt),
|
||||
started,
|
||||
isSubCreatedButNotYetPublished,
|
||||
hasMore,
|
||||
|
||||
Reference in New Issue
Block a user