diff --git a/src/components/Feed.tsx b/src/components/Feed.tsx index beb6663e..37ad9b78 100644 --- a/src/components/Feed.tsx +++ b/src/components/Feed.tsx @@ -24,8 +24,8 @@ import { useResolveTabFilter } from '@/hooks/useResolveTabFilter'; import { getEnabledFeedKinds } from '@/lib/extraKinds'; import { isRepostKind } from '@/lib/feedUtils'; import { isEventMuted } from '@/lib/muteHelpers'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { DITTO_RELAY } from '@/lib/appRelays'; -import { cn } from '@/lib/utils'; import type { FeedItem } from '@/lib/feedUtils'; import type { NostrEvent } from '@nostrify/nostrify'; import type { SavedFeed } from '@/contexts/AppContext'; @@ -97,10 +97,16 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee return 'Community'; })(); - const [activeTab, handleSetActiveTab] = useFeedTab(feedId); + const [rawActiveTab, handleSetActiveTab] = useFeedTab(feedId); const [loginDialogOpen, setLoginDialogOpen] = useState(false); const { startSignup } = useOnboarding(); + // Kind-specific pages only support Follows + Global. Clamp any other + // persisted tab (e.g. 'ditto', 'communities') back to 'follows'. + const activeTab: FeedTab = kinds && rawActiveTab !== 'follows' && rawActiveTab !== 'global' + ? 'follows' + : rawActiveTab; + // Is the active tab a saved feed? const activeSavedFeed = useMemo( () => savedFeeds.find((f) => f.id === activeTab) ?? null, @@ -115,7 +121,8 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee const useTopFeedForLoggedOut = !user && !kinds; // When the Ditto tab is active (logged in), show the same hot-sorted curated feed. - const useDittoTab = user && activeTab === 'ditto'; + // Disabled on kind-specific pages — the Ditto tab is not shown there. + const useDittoTab = user && activeTab === 'ditto' && !kinds; // Standard feed query (used when logged in, or on kind-specific pages, or core tabs) const isCoreFeedTab = activeTab === 'follows' || activeTab === 'global' || activeTab === 'communities' || activeTab === 'ditto'; @@ -209,8 +216,10 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee const showSkeleton = isPending || (isLoading && !rawData); - // Saved feed tabs are only shown on the main home feed (no kinds/tagFilters override) - const showSavedFeedTabs = user && !kinds && !tagFilters; + // Kind-specific pages (e.g. Development, WebXDC) only show Follows + Global tabs. + // Extra tabs (Ditto, Community, saved feeds, hashtags) are only for the home feed. + const isKindSpecificPage = !!kinds; + const showSavedFeedTabs = user && !isKindSpecificPage && !tagFilters; return (
@@ -221,18 +230,18 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee {/* Tabs (logged in) or CTA (logged out, main feed only) */} {user ? (
- handleSetActiveTab('follows')} /> - {showDittoFeed && ( - handleSetActiveTab('ditto')} /> + handleSetActiveTab('follows')} /> + {!isKindSpecificPage && showDittoFeed && ( + handleSetActiveTab('ditto')} /> )} - {showCommunityFeed && ( - handleSetActiveTab('communities')} /> + {!isKindSpecificPage && showCommunityFeed && ( + handleSetActiveTab('communities')} /> )} - {showGlobalFeed && ( - handleSetActiveTab('global')} /> + {(isKindSpecificPage || showGlobalFeed) && ( + handleSetActiveTab('global')} /> )} {showSavedFeedTabs && savedFeeds.map((feed) => ( - ))} {showSavedFeedTabs && hashtags.map((tag) => ( - void }) { - return ( - - ); -} - function NoteCardSkeleton() { return (
diff --git a/src/components/FeedTabButton.tsx b/src/components/FeedTabButton.tsx new file mode 100644 index 00000000..1c21b786 --- /dev/null +++ b/src/components/FeedTabButton.tsx @@ -0,0 +1,30 @@ +import { cn } from '@/lib/utils'; + +interface FeedTabButtonProps { + label: string; + active: boolean; + onClick: () => void; + disabled?: boolean; + className?: string; +} + +/** Shared tab button used across feed pages (Follows / Global / etc.). */ +export function FeedTabButton({ label, active, onClick, disabled, className }: FeedTabButtonProps) { + return ( + + ); +} diff --git a/src/pages/BooksPage.tsx b/src/pages/BooksPage.tsx index 52c0e90a..60887c1d 100644 --- a/src/pages/BooksPage.tsx +++ b/src/pages/BooksPage.tsx @@ -5,6 +5,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { ArrowLeft, BookMarked, Loader2, Search, X } from 'lucide-react'; import { useSeoMeta } from '@unhead/react'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { PullToRefresh } from '@/components/PullToRefresh'; @@ -17,7 +18,6 @@ import { usePrefetchBookSummaries } from '@/hooks/useBookSummary'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useFeedTab } from '@/hooks/useFeedTab'; import { useAppContext } from '@/hooks/useAppContext'; -import { cn } from '@/lib/utils'; import type { ExtraKindDef } from '@/lib/extraKinds'; type FeedTab = 'follows' | 'global'; @@ -111,8 +111,8 @@ export function BooksPage() { {/* Follows / Global tabs */} {user && (
- setActiveTab('follows')} /> - setActiveTab('global')} /> + setActiveTab('follows')} /> + setActiveTab('global')} />
)} @@ -330,19 +330,4 @@ function BookSearchResultItem({ book, onSelect }: { book: BookSearchResult; onSe // Tab Button // --------------------------------------------------------------------------- -function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) { - return ( - - ); -} + diff --git a/src/pages/EventsFeedPage.tsx b/src/pages/EventsFeedPage.tsx index c6a5927c..63763704 100644 --- a/src/pages/EventsFeedPage.tsx +++ b/src/pages/EventsFeedPage.tsx @@ -16,11 +16,11 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useFeedTab } from '@/hooks/useFeedTab'; import { useAppContext } from '@/hooks/useAppContext'; import { useLayoutOptions } from '@/contexts/LayoutContext'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { useMuteList } from '@/hooks/useMuteList'; import { isEventMuted } from '@/lib/muteHelpers'; import { getExtraKindDef } from '@/lib/extraKinds'; import { sidebarItemIcon } from '@/lib/sidebarItems'; -import { cn } from '@/lib/utils'; type FeedTab = 'follows' | 'global'; @@ -120,8 +120,8 @@ export function EventsFeedPage() { {/* Follows / Global tabs */} {user && (
- setActiveTab('follows')} /> - setActiveTab('global')} /> + setActiveTab('follows')} /> + setActiveTab('global')} />
)} @@ -184,21 +184,4 @@ function EventCardSkeleton() { ); } -// ─── TabButton ──────────────────────────────────────────────────────────────── -function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) { - return ( - - ); -} diff --git a/src/pages/PhotosFeedPage.tsx b/src/pages/PhotosFeedPage.tsx index 25da1c6c..fd25b740 100644 --- a/src/pages/PhotosFeedPage.tsx +++ b/src/pages/PhotosFeedPage.tsx @@ -23,8 +23,8 @@ import { useMuteList } from '@/hooks/useMuteList'; import { isEventMuted } from '@/lib/muteHelpers'; import { KindInfoButton } from '@/components/KindInfoButton'; import { sidebarItemIcon } from '@/lib/sidebarItems'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { getExtraKindDef } from '@/lib/extraKinds'; -import { cn } from '@/lib/utils'; import type { FeedItem } from '@/lib/feedUtils'; import { MediaCollage, MediaCollageSkeleton, eventToMediaItem } from '@/components/MediaCollage'; @@ -33,26 +33,6 @@ const photosDef = getExtraKindDef('photos')!; type FeedTab = 'follows' | 'global'; -// ── Tab button ──────────────────────────────────────────────────────────────── - -function TabButton({ label, active, onClick, disabled }: { - label: string; active: boolean; onClick: () => void; disabled?: boolean; -}) { - return ( - - ); -} - // ── Page ────────────────────────────────────────────────────────────────────── export function PhotosFeedPage() { @@ -124,8 +104,8 @@ export function PhotosFeedPage() { {/* Tabs */}
- setActiveTab('follows')} disabled={!user} /> - setActiveTab('global')} /> + setActiveTab('follows')} disabled={!user} /> + setActiveTab('global')} />
{/* Grid */} diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index d95e7949..fdad20d3 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -6,6 +6,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useSeoMeta } from '@unhead/react'; import { nip19 } from 'nostr-tools'; import { Zap, Flame, MoreHorizontal, Share2, ClipboardCopy, ExternalLink, VolumeX, Flag, Bitcoin, Users, Pin, X, QrCode, Check, Copy, Loader2, Download, Palette, Pencil, Trash2, Eye, EyeOff, RefreshCw, MessageSquare, Globe, Mail, Plus, GripVertical, ListPlus, Award } from 'lucide-react'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { getAvatarShape, isEmoji, emojiAvatarBorderStyle } from '@/lib/avatarShape'; import { Button } from '@/components/ui/button'; @@ -364,22 +365,7 @@ function FollowingListModal({ pubkeys, open, onOpenChange, displayName }: Follow // ----- Tab Button ----- -function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) { - return ( - - ); -} + type EditableTab = { label: string; isCore: boolean; tab?: ProfileTab }; @@ -1923,7 +1909,7 @@ export function ProfilePage() { {!tabEditMode && profileTabsQuery.isFetched && viewTabs.map((tab) => { const tabId = CORE_TAB_IDS[tab.label] ?? tab.label; return ( -
- setActiveTab('posts')} /> - setActiveTab('accounts')} /> + setActiveTab('posts')} className="sidebar:py-5" /> + setActiveTab('accounts')} className="sidebar:py-5" />
@@ -815,22 +816,7 @@ export function SearchPage() { /* ── Shared sub-components ── */ -function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) { - return ( - - ); -} + function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metadata: Record; event?: { tags: string[][] } }; isFollowed: boolean }) { const npub = useMemo(() => nip19.npubEncode(profile.pubkey), [profile.pubkey]); diff --git a/src/pages/ThemesPage.tsx b/src/pages/ThemesPage.tsx index b989f48a..3db4127a 100644 --- a/src/pages/ThemesPage.tsx +++ b/src/pages/ThemesPage.tsx @@ -6,6 +6,7 @@ import { Link } from 'react-router-dom'; import { useSeoMeta } from '@unhead/react'; import type { NostrEvent } from '@nostrify/nostrify'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { NoteCard } from '@/components/NoteCard'; import { PullToRefresh } from '@/components/PullToRefresh'; import { FeedEmptyState } from '@/components/FeedEmptyState'; @@ -18,7 +19,6 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useTheme } from '@/hooks/useTheme'; import { useAppContext } from '@/hooks/useAppContext'; import { useLayoutOptions } from '@/contexts/LayoutContext'; -import { cn } from '@/lib/utils'; type ThemesTab = 'my-themes' | 'follows' | 'global'; @@ -115,9 +115,9 @@ export function ThemesPage() { {/* Tabs */}
- setActiveTab('my-themes')} /> - setActiveTab('follows')} disabled={!user} /> - setActiveTab('global')} /> + setActiveTab('my-themes')} /> + setActiveTab('follows')} disabled={!user} /> + setActiveTab('global')} />
{/* Tab content */} @@ -190,24 +190,7 @@ export function ThemesPage() { // Tab Button // --------------------------------------------------------------------------- -function TabButton({ label, active, onClick, disabled }: { label: string; active: boolean; onClick: () => void; disabled?: boolean }) { - return ( - - ); -} + // --------------------------------------------------------------------------- // Skeleton diff --git a/src/pages/VideosFeedPage.tsx b/src/pages/VideosFeedPage.tsx index fcdad937..375156a3 100644 --- a/src/pages/VideosFeedPage.tsx +++ b/src/pages/VideosFeedPage.tsx @@ -45,6 +45,7 @@ import { useVideoThumbnail } from '@/components/VideoPlayer'; import { sidebarItemIcon } from '@/lib/sidebarItems'; import { getExtraKindDef } from '@/lib/extraKinds'; import { timeAgo } from '@/lib/timeAgo'; +import { FeedTabButton } from '@/components/FeedTabButton'; import { cn } from '@/lib/utils'; import { getEffectiveStreamStatus } from '@/lib/streamStatus'; import type { FeedItem } from '@/lib/feedUtils'; @@ -194,26 +195,6 @@ function useDragScroll() { return { ref, onMouseDown, onMouseMove, onMouseUp, onMouseLeave }; } -// ── Tab button ──────────────────────────────────────────────────────────────── - -function TabButton({ label, active, onClick, disabled }: { - label: string; active: boolean; onClick: () => void; disabled?: boolean; -}) { - return ( - - ); -} - // ── Video grid card (kind 21) — YouTube-style ──────────────────────────────── function VideoGridCard({ event }: { event: NostrEvent }) { @@ -809,8 +790,8 @@ export function VideosFeedPage() { {/* Follows / Global tabs */}
- setFeedTab('follows')} disabled={!user} /> - setFeedTab('global')} /> + setFeedTab('follows')} disabled={!user} /> + setFeedTab('global')} />
{/* Live streams strip — follows tab filters by followed authors */}