Unify following-tab empty states with shared FeedEmptyState component

This commit is contained in:
Chad Curtis
2026-03-03 01:42:57 -06:00
parent bb55743ceb
commit 984fc2d869
6 changed files with 88 additions and 52 deletions
+15 -5
View File
@@ -4,6 +4,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { ComposeBox } from '@/components/ComposeBox';
import { NoteCard } from '@/components/NoteCard';
import { PullToRefresh } from '@/components/PullToRefresh';
import { FeedEmptyState } from '@/components/FeedEmptyState';
import { Skeleton } from '@/components/ui/skeleton';
import { Button } from '@/components/ui/button';
import { Loader2 } from 'lucide-react';
@@ -234,11 +235,20 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage }: F
)}
</div>
) : (
<div className="py-16 px-8 text-center">
<p className="text-muted-foreground text-lg">
{emptyMessage ?? 'No posts yet. Follow some people or switch to the Global tab to discover content.'}
</p>
</div>
<FeedEmptyState
message={
emptyMessage ?? (
activeTab === 'follows'
? 'No posts yet. Follow some people to see their content here.'
: 'No posts found. Check your relay connections or come back soon.'
)
}
onSwitchToGlobal={
activeTab === 'follows' && showGlobalFeed
? () => setActiveTab('global')
: undefined
}
/>
)}
</PullToRefresh>
+36
View File
@@ -0,0 +1,36 @@
import { cn } from '@/lib/utils';
interface FeedEmptyStateProps {
/** Primary empty-state message. */
message: string;
/** Called when the user clicks "Switch to Global". Omit to hide the button. */
onSwitchToGlobal?: () => void;
className?: string;
}
/**
* Consistent empty state for Follows/Global feed tabs across all feed pages.
*
* - Follows tab: pass `onSwitchToGlobal` to render a "Switch to Global" CTA.
* - Global tab: omit `onSwitchToGlobal`; the message should guide the user
* to check their relay connections.
*/
export function FeedEmptyState({
message,
onSwitchToGlobal,
className,
}: FeedEmptyStateProps) {
return (
<div className={cn('py-16 px-8 text-center space-y-3', className)}>
<p className="text-muted-foreground">{message}</p>
{onSwitchToGlobal && (
<button
className="text-sm text-primary hover:underline"
onClick={onSwitchToGlobal}
>
Switch to Global
</button>
)}
</div>
);
}
+9 -13
View File
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import { useInView } from 'react-intersection-observer';
import { useQueryClient } from '@tanstack/react-query';
import { ArrowLeft, CalendarDays, Loader2 } from 'lucide-react';
import { FeedEmptyState } from '@/components/FeedEmptyState';
import { useSeoMeta } from '@unhead/react';
import type { NostrEvent } from '@nostrify/nostrify';
@@ -151,19 +152,14 @@ export function EventsFeedPage() {
)}
</div>
) : (
<div className="py-16 px-8 text-center space-y-3">
<CalendarDays className="size-10 text-muted-foreground/40 mx-auto" />
<p className="text-muted-foreground">
{activeTab === 'follows'
? 'No events from people you follow yet. Try the Global tab.'
: 'No calendar events found. Check your relay connections or try again later.'}
</p>
{activeTab === 'follows' && (
<button className="text-sm text-primary hover:underline" onClick={() => setActiveTab('global')}>
Switch to Global
</button>
)}
</div>
<FeedEmptyState
message={
activeTab === 'follows'
? 'No events from people you follow yet.'
: 'No calendar events found. Check your relay connections or try again later.'
}
onSwitchToGlobal={activeTab === 'follows' ? () => setActiveTab('global') : undefined}
/>
)}
</PullToRefresh>
</main>
+10 -11
View File
@@ -8,10 +8,10 @@
import { useState, useEffect, useMemo } from 'react';
import { Link } from 'react-router-dom';
import { ArrowLeft, Camera } from 'lucide-react';
import { ArrowLeft, Camera, Loader2 } from 'lucide-react';
import { useSeoMeta } from '@unhead/react';
import { useInView } from 'react-intersection-observer';
import { Loader2 } from 'lucide-react';
import { FeedEmptyState } from '@/components/FeedEmptyState';
import type { NostrEvent } from '@nostrify/nostrify';
import { useAppContext } from '@/hooks/useAppContext';
import { useCurrentUser } from '@/hooks/useCurrentUser';
@@ -133,15 +133,14 @@ export function PhotosFeedPage() {
{showSkeleton ? (
<MediaGridSkeleton count={15} />
) : photoEvents.length === 0 ? (
<div className="py-16 px-8 text-center">
<Camera className="size-10 text-muted-foreground/30 mx-auto mb-3" />
<p className="text-muted-foreground">
No photos yet.
{activeTab === 'follows'
? ' Follow some photographers or switch to Global.'
: ' Check your relay connections or come back soon.'}
</p>
</div>
<FeedEmptyState
message={
activeTab === 'follows'
? 'No photos yet. Follow some photographers to see their photos here.'
: 'No photos found. Check your relay connections or come back soon.'
}
onSwitchToGlobal={activeTab === 'follows' ? () => setActiveTab('global') : undefined}
/>
) : (
<>
<MediaGrid
+9 -6
View File
@@ -16,6 +16,7 @@
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { ArrowLeft, Film, Radio, Play, Eye } from 'lucide-react';
import { FeedEmptyState } from '@/components/FeedEmptyState';
import { useSeoMeta } from '@unhead/react';
import { nip19 } from 'nostr-tools';
import { Blurhash } from 'react-blurhash';
@@ -676,12 +677,14 @@ export function VideosFeedPage() {
</div>
</div>
) : videoEvents.length === 0 ? (
<div className="py-16 px-8 text-center">
<p className="text-muted-foreground">
No videos yet.{feedTab === 'follows' ? ' Follow some creators or switch to Global.' : ' Check your relay connections or come back soon.'}
</p>
</div>
<FeedEmptyState
message={
feedTab === 'follows'
? 'No videos yet. Follow some creators to see their videos here.'
: 'No videos found. Check your relay connections or come back soon.'
}
onSwitchToGlobal={feedTab === 'follows' ? () => setFeedTab('global') : undefined}
/>
) : (
<div className="pt-3 pb-8">
{/* Normal videos — 2-column grid */}
+9 -17
View File
@@ -16,8 +16,8 @@ import {
MoreHorizontal,
Play,
Heart,
} from 'lucide-react';
import { FeedEmptyState } from '@/components/FeedEmptyState';
import type { NostrEvent } from '@nostrify/nostrify';
import { useNostr } from '@nostrify/react';
@@ -726,22 +726,14 @@ export function VinesFeedPage() {
<div className="flex-1 min-w-0 flex flex-col">
<VinesTabBar tab={tab} onTabChange={setTab} hasUser={!!user} />
<div className="flex-1 flex items-center justify-center">
<div className="text-center space-y-3 px-8">
<p className="text-lg font-semibold">No vines yet</p>
<p className="text-muted-foreground text-sm">
{tab === 'follows'
? 'None of the people you follow have posted vines. Try Global.'
: 'Short-form videos will appear here. Check back soon!'}
</p>
{tab === 'follows' && (
<button
className="text-sm text-primary hover:underline"
onClick={() => setTab('global')}
>
Switch to Global
</button>
)}
</div>
<FeedEmptyState
message={
tab === 'follows'
? 'None of the people you follow have posted vines yet.'
: 'No vines found. Check your relay connections or come back soon.'
}
onSwitchToGlobal={tab === 'follows' ? () => setTab('global') : undefined}
/>
</div>
</div>
);