Fix kind-specific feeds showing Ditto tab content instead of actual kind data
This commit is contained in:
+23
-31
@@ -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<FeedTab>(feedId);
|
||||
const [rawActiveTab, handleSetActiveTab] = useFeedTab<FeedTab>(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 (
|
||||
<main className="flex-1 min-w-0">
|
||||
@@ -221,18 +230,18 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee
|
||||
{/* Tabs (logged in) or CTA (logged out, main feed only) */}
|
||||
{user ? (
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10 overflow-x-auto scrollbar-none">
|
||||
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => handleSetActiveTab('follows')} />
|
||||
{showDittoFeed && (
|
||||
<TabButton label="Ditto" active={activeTab === 'ditto'} onClick={() => handleSetActiveTab('ditto')} />
|
||||
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => handleSetActiveTab('follows')} />
|
||||
{!isKindSpecificPage && showDittoFeed && (
|
||||
<FeedTabButton label="Ditto" active={activeTab === 'ditto'} onClick={() => handleSetActiveTab('ditto')} />
|
||||
)}
|
||||
{showCommunityFeed && (
|
||||
<TabButton label={communityLabel} active={activeTab === 'communities'} onClick={() => handleSetActiveTab('communities')} />
|
||||
{!isKindSpecificPage && showCommunityFeed && (
|
||||
<FeedTabButton label={communityLabel} active={activeTab === 'communities'} onClick={() => handleSetActiveTab('communities')} />
|
||||
)}
|
||||
{showGlobalFeed && (
|
||||
<TabButton label="Global" active={activeTab === 'global'} onClick={() => handleSetActiveTab('global')} />
|
||||
{(isKindSpecificPage || showGlobalFeed) && (
|
||||
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => handleSetActiveTab('global')} />
|
||||
)}
|
||||
{showSavedFeedTabs && savedFeeds.map((feed) => (
|
||||
<TabButton
|
||||
<FeedTabButton
|
||||
key={feed.id}
|
||||
label={feed.label}
|
||||
active={activeTab === feed.id}
|
||||
@@ -240,7 +249,7 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee
|
||||
/>
|
||||
))}
|
||||
{showSavedFeedTabs && hashtags.map((tag) => (
|
||||
<TabButton
|
||||
<FeedTabButton
|
||||
key={`hashtag:${tag}`}
|
||||
label={`#${tag}`}
|
||||
active={activeTab === `hashtag:${tag}`}
|
||||
@@ -426,23 +435,6 @@ function HashtagFeedContent({ tag }: { tag: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex-1 px-4 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 whitespace-nowrap',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-3/4 max-w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteCardSkeleton() {
|
||||
return (
|
||||
<div className="px-4 py-3 border-b border-border">
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 whitespace-nowrap',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
disabled && 'opacity-50 cursor-not-allowed hover:bg-transparent',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-3/4 max-w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
+4
-19
@@ -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 && (
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
|
||||
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
|
||||
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
|
||||
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
|
||||
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
|
||||
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} />
|
||||
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -184,21 +184,4 @@ function EventCardSkeleton() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── TabButton ────────────────────────────────────────────────────────────────
|
||||
|
||||
function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 disabled:opacity-50',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Page ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function PhotosFeedPage() {
|
||||
@@ -124,8 +104,8 @@ export function PhotosFeedPage() {
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
|
||||
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
|
||||
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
|
||||
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
|
||||
@@ -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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex-1 px-4 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 whitespace-nowrap',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-3/4 max-w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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 (
|
||||
<TabButton
|
||||
<FeedTabButton
|
||||
key={tab.label}
|
||||
label={tab.label}
|
||||
active={activeTab === tabId}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { FeedTabButton } from '@/components/FeedTabButton';
|
||||
import { NoteCard } from '@/components/NoteCard';
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { getAvatarShape } from '@/lib/avatarShape';
|
||||
@@ -416,8 +417,8 @@ export function SearchPage() {
|
||||
{/* Tabs — sticky at top */}
|
||||
<div className={cn(STICKY_HEADER_CLASS, 'bg-background/80 backdrop-blur-md z-10 border-b border-border')}>
|
||||
<div className="flex">
|
||||
<TabButton label="Posts" active={activeTab === 'posts'} onClick={() => setActiveTab('posts')} />
|
||||
<TabButton label="Accounts" active={activeTab === 'accounts'} onClick={() => setActiveTab('accounts')} />
|
||||
<FeedTabButton label="Posts" active={activeTab === 'posts'} onClick={() => setActiveTab('posts')} className="sidebar:py-5" />
|
||||
<FeedTabButton label="Accounts" active={activeTab === 'accounts'} onClick={() => setActiveTab('accounts')} className="sidebar:py-5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -815,22 +816,7 @@ export function SearchPage() {
|
||||
|
||||
/* ── Shared sub-components ── */
|
||||
|
||||
function TabButton({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 sidebar:py-5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function AccountItem({ profile, isFollowed }: { profile: { pubkey: string; metadata: Record<string, unknown>; event?: { tags: string[][] } }; isFollowed: boolean }) {
|
||||
const npub = useMemo(() => nip19.npubEncode(profile.pubkey), [profile.pubkey]);
|
||||
|
||||
@@ -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 */}
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
|
||||
<TabButton label="My Themes" active={activeTab === 'my-themes'} onClick={() => setActiveTab('my-themes')} />
|
||||
<TabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
|
||||
<TabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
<FeedTabButton label="My Themes" active={activeTab === 'my-themes'} onClick={() => setActiveTab('my-themes')} />
|
||||
<FeedTabButton label="Follows" active={activeTab === 'follows'} onClick={() => setActiveTab('follows')} disabled={!user} />
|
||||
<FeedTabButton label="Global" active={activeTab === 'global'} onClick={() => setActiveTab('global')} />
|
||||
</div>
|
||||
|
||||
{/* 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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
disabled && 'opacity-50 cursor-not-allowed hover:bg-transparent',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && (
|
||||
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Skeleton
|
||||
|
||||
@@ -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<T extends HTMLElement>() {
|
||||
return { ref, onMouseDown, onMouseMove, onMouseUp, onMouseLeave };
|
||||
}
|
||||
|
||||
// ── Tab button ────────────────────────────────────────────────────────────────
|
||||
|
||||
function TabButton({ label, active, onClick, disabled }: {
|
||||
label: string; active: boolean; onClick: () => void; disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'flex-1 py-3.5 text-center text-sm font-medium transition-colors relative hover:bg-secondary/40 disabled:opacity-50',
|
||||
active ? 'text-foreground' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{active && <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-16 h-1 bg-primary rounded-full" />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Video grid card (kind 21) — YouTube-style ────────────────────────────────
|
||||
|
||||
function VideoGridCard({ event }: { event: NostrEvent }) {
|
||||
@@ -809,8 +790,8 @@ export function VideosFeedPage() {
|
||||
|
||||
{/* Follows / Global tabs */}
|
||||
<div className="flex border-b border-border sticky top-mobile-bar sidebar:top-0 bg-background/80 backdrop-blur-md z-10">
|
||||
<TabButton label="Follows" active={feedTab === 'follows'} onClick={() => setFeedTab('follows')} disabled={!user} />
|
||||
<TabButton label="Global" active={feedTab === 'global'} onClick={() => setFeedTab('global')} />
|
||||
<FeedTabButton label="Follows" active={feedTab === 'follows'} onClick={() => setFeedTab('follows')} disabled={!user} />
|
||||
<FeedTabButton label="Global" active={feedTab === 'global'} onClick={() => setFeedTab('global')} />
|
||||
</div>
|
||||
|
||||
{/* Live streams strip — follows tab filters by followed authors */}
|
||||
|
||||
Reference in New Issue
Block a user