From 8b910ee775ea6c4b22a739e2be44c22b479eb8a9 Mon Sep 17 00:00:00 2001
From: nicktytarenko
Date: Sat, 1 Aug 2026 16:36:28 +0300
Subject: [PATCH 1/2] Make Activity the homepage hub with shared Fund/Proposals
shell.
Move Activity, Fund, and Proposals under a persistent (home) layout with HomeTabs and FundSidebar so tab switches only swap the feed slot.
Co-authored-by: Cursor
---
app/(home)/fund/loading.tsx | 16 +++
app/{ => (home)}/fund/page.tsx | 38 +-------
app/(home)/fund/proposals/loading.tsx | 16 +++
app/(home)/fund/proposals/page.tsx | 19 ++++
app/(home)/layout.tsx | 19 ++++
app/(home)/loading.tsx | 11 +++
app/(home)/page.tsx | 13 +++
app/about/components/CTASection.tsx | 2 +-
.../ExpertFinderClientLayout.tsx | 2 +-
app/fund/loading.tsx | 26 -----
app/fund/proposals/loading.tsx | 25 -----
app/fund/proposals/page.tsx | 69 -------------
.../components/RightSidebarContainer.tsx | 2 +-
app/layouts/topbar/pageRoutes.tsx | 14 +--
app/moderators/ModerationClientLayout.tsx | 2 +-
app/page.tsx | 37 -------
app/robots.ts | 2 -
app/sitemap.ts | 4 +-
.../Funding/FundActivityPageContent.tsx | 39 ++++++++
components/Funding/FundingBannerSkeleton.tsx | 45 ---------
components/Funding/HomeTabs.tsx | 20 ++++
components/Funding/MarketplaceCards.tsx | 32 ------
components/landing/LandingPageHero.tsx | 2 +-
hooks/useFeedTabs.ts | 4 +-
hooks/useFundTabs.ts | 65 -------------
hooks/useFundTabs.tsx | 97 +++++++++++++++++++
utils/navigation.ts | 28 ++----
27 files changed, 276 insertions(+), 373 deletions(-)
create mode 100644 app/(home)/fund/loading.tsx
rename app/{ => (home)}/fund/page.tsx (66%)
create mode 100644 app/(home)/fund/proposals/loading.tsx
create mode 100644 app/(home)/fund/proposals/page.tsx
create mode 100644 app/(home)/layout.tsx
create mode 100644 app/(home)/loading.tsx
create mode 100644 app/(home)/page.tsx
delete mode 100644 app/fund/loading.tsx
delete mode 100644 app/fund/proposals/loading.tsx
delete mode 100644 app/fund/proposals/page.tsx
delete mode 100644 app/page.tsx
create mode 100644 components/Funding/FundActivityPageContent.tsx
delete mode 100644 components/Funding/FundingBannerSkeleton.tsx
create mode 100644 components/Funding/HomeTabs.tsx
delete mode 100644 components/Funding/MarketplaceCards.tsx
delete mode 100644 hooks/useFundTabs.ts
create mode 100644 hooks/useFundTabs.tsx
diff --git a/app/(home)/fund/loading.tsx b/app/(home)/fund/loading.tsx
new file mode 100644
index 000000000..7e50ad550
--- /dev/null
+++ b/app/(home)/fund/loading.tsx
@@ -0,0 +1,16 @@
+import { FeedItemSkeleton } from '@/components/Feed/FeedItemSkeleton';
+
+export default function FundLoading() {
+ return (
+
+
+
+ {Array.from({ length: 3 }, (_, i) => (
+
+ ))}
+
+
+ );
+}
diff --git a/app/fund/page.tsx b/app/(home)/fund/page.tsx
similarity index 66%
rename from app/fund/page.tsx
rename to app/(home)/fund/page.tsx
index a5892037b..ef2eacae6 100644
--- a/app/fund/page.tsx
+++ b/app/(home)/fund/page.tsx
@@ -1,44 +1,16 @@
-import { Suspense } from 'react';
import { Metadata } from 'next';
import { buildOpenGraphMetadata } from '@/lib/metadata';
-import { PageLayout } from '@/app/layouts/PageLayout';
-import { FundingSidebarServer } from '@/components/Funding/FundingSidebarServer';
-import { ActivitySidebarSkeleton } from '@/components/Funding/ActivitySidebarSkeleton';
-import { HeroHeader } from '@/components/ui/HeroHeader';
-import { FundGrantsPageContent } from './FundGrantsPageContent';
-import { MarketplaceCards } from '@/components/Funding/MarketplaceCards';
-import { FundingHeroPanel } from '@/components/Funding/FundingHeroPanel';
-import { OpenFundingOpportunityCTA } from './OpenFundingOpportunityCTA';
+import { FundGrantsPageContent } from '@/app/fund/FundGrantsPageContent';
export const metadata: Metadata = buildOpenGraphMetadata({
- title: 'Funding Opportunities',
+ title: 'Fund',
description: 'Apply for funding opportunities via proposals.',
url: '/fund',
});
-export default async function FundPage() {
+export default function FundPage() {
return (
-
- Apply for funding opportunities via proposals.
-
- }
- cta={} />}
- alignTop
- >
-
-
- }
- rightSidebar={
- }>
-
-
- }
- >
+ <>
ResearchHub provides direct funding pathways for scientific research. Browse open funding
@@ -75,6 +47,6 @@ export default async function FundPage() {
-
+ >
);
}
diff --git a/app/(home)/fund/proposals/loading.tsx b/app/(home)/fund/proposals/loading.tsx
new file mode 100644
index 000000000..8c6fe7880
--- /dev/null
+++ b/app/(home)/fund/proposals/loading.tsx
@@ -0,0 +1,16 @@
+import { FeedItemSkeleton } from '@/components/Feed/FeedItemSkeleton';
+
+export default function FundProposalsLoading() {
+ return (
+
+
+
+ {Array.from({ length: 3 }, (_, i) => (
+
+ ))}
+
+
+ );
+}
diff --git a/app/(home)/fund/proposals/page.tsx b/app/(home)/fund/proposals/page.tsx
new file mode 100644
index 000000000..45dc8e745
--- /dev/null
+++ b/app/(home)/fund/proposals/page.tsx
@@ -0,0 +1,19 @@
+import { Metadata } from 'next';
+import { buildOpenGraphMetadata } from '@/lib/metadata';
+import { ProposalFeed } from '@/components/Funding/ProposalFeed';
+import { ProposalSortAndFilters } from '@/components/Funding/ProposalSortAndFilters';
+
+export const metadata: Metadata = buildOpenGraphMetadata({
+ title: 'Proposals',
+ description: 'Propose research, get reviewed, receive funding.',
+ url: '/fund/proposals',
+});
+
+export default function FundProposalsPage() {
+ return (
+
+ );
+}
diff --git a/app/(home)/layout.tsx b/app/(home)/layout.tsx
new file mode 100644
index 000000000..7c9573e32
--- /dev/null
+++ b/app/(home)/layout.tsx
@@ -0,0 +1,19 @@
+import { ReactNode } from 'react';
+import { PageLayout } from '@/app/layouts/PageLayout';
+import { FundSidebar } from '@/components/Funding/FundSidebar';
+import { HomeTabs } from '@/components/Funding/HomeTabs';
+
+/**
+ * Shared shell for homepage hub tabs (Activity / Fund / Proposals).
+ * Keeps PageLayout, HomeTabs, and FundSidebar mounted while only the feed slot swaps.
+ *
+ * `/fund/dashboard` lives outside this group and keeps its own layout.
+ */
+export default function HomeLayout({ children }: { children: ReactNode }) {
+ return (
+ }>
+
+ {children}
+
+ );
+}
diff --git a/app/(home)/loading.tsx b/app/(home)/loading.tsx
new file mode 100644
index 000000000..e8b789e45
--- /dev/null
+++ b/app/(home)/loading.tsx
@@ -0,0 +1,11 @@
+import { ActivityCardSkeleton } from '@/components/Activity/ActivityCardSkeleton';
+
+export default function HomeLoading() {
+ return (
+
+ {[...Array(6)].map((_, i) => (
+
+ ))}
+
+ );
+}
diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx
new file mode 100644
index 000000000..15e121f31
--- /dev/null
+++ b/app/(home)/page.tsx
@@ -0,0 +1,13 @@
+import { Metadata } from 'next';
+import { buildOpenGraphMetadata } from '@/lib/metadata';
+import { FundActivityPageContent } from '@/components/Funding/FundActivityPageContent';
+
+export const metadata: Metadata = buildOpenGraphMetadata({
+ title: 'Activity',
+ description: 'Recent activity across funding opportunities and proposals.',
+ url: '/',
+});
+
+export default function HomeActivityPage() {
+ return ;
+}
diff --git a/app/about/components/CTASection.tsx b/app/about/components/CTASection.tsx
index be22ae7fe..59448b13d 100644
--- a/app/about/components/CTASection.tsx
+++ b/app/about/components/CTASection.tsx
@@ -45,7 +45,7 @@ export const CTASection = () => {
{
kind: 'action',
label: 'Join ResearchHub',
- onAction: () => showAuthModal(() => router.push('/popular')),
+ onAction: () => showAuthModal(() => router.push('/')),
},
{ kind: 'link', href: '/fund', label: 'Fund science', external: true },
];
diff --git a/app/expert-finder/ExpertFinderClientLayout.tsx b/app/expert-finder/ExpertFinderClientLayout.tsx
index 2d19c9a61..0b960b393 100644
--- a/app/expert-finder/ExpertFinderClientLayout.tsx
+++ b/app/expert-finder/ExpertFinderClientLayout.tsx
@@ -29,7 +29,7 @@ export default function ExpertFinderClientLayout({ children }: ExpertFinderClien
useEffect(() => {
if (!isLoading && !canAccessExpertFinder) {
- router.push('/popular');
+ router.push('/');
}
}, [isLoading, canAccessExpertFinder, router]);
diff --git a/app/fund/loading.tsx b/app/fund/loading.tsx
deleted file mode 100644
index 14d6d1fb6..000000000
--- a/app/fund/loading.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { PageLayout } from '@/app/layouts/PageLayout';
-import { ActivitySidebarSkeleton } from '@/components/Funding/ActivitySidebarSkeleton';
-import { FundingBannerSkeleton } from '@/components/Funding/FundingBannerSkeleton';
-import { FeedItemSkeleton } from '@/components/Feed/FeedItemSkeleton';
-
-export default function FundLoading() {
- return (
- }
- rightSidebar={}
- >
-
-
-
- {/* Feed skeletons */}
-
- {Array.from({ length: 3 }, (_, i) => (
-
- ))}
-
-
-
- );
-}
diff --git a/app/fund/proposals/loading.tsx b/app/fund/proposals/loading.tsx
deleted file mode 100644
index aa48bffd6..000000000
--- a/app/fund/proposals/loading.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { PageLayout } from '@/app/layouts/PageLayout';
-import { ActivitySidebarSkeleton } from '@/components/Funding/ActivitySidebarSkeleton';
-import { FundingBannerSkeleton } from '@/components/Funding/FundingBannerSkeleton';
-import { FeedItemSkeleton } from '@/components/Feed/FeedItemSkeleton';
-
-export default function FundProposalsLoading() {
- return (
- }
- rightSidebar={}
- >
-
-
-
-
- {Array.from({ length: 3 }, (_, i) => (
-
- ))}
-
-
-
- );
-}
diff --git a/app/fund/proposals/page.tsx b/app/fund/proposals/page.tsx
deleted file mode 100644
index 7629cee78..000000000
--- a/app/fund/proposals/page.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Suspense } from 'react';
-import { Metadata } from 'next';
-import Link from 'next/link';
-import { ArrowUpFromLine } from 'lucide-react';
-import { buildOpenGraphMetadata } from '@/lib/metadata';
-import { PageLayout } from '@/app/layouts/PageLayout';
-import { ProposalFeed } from '@/components/Funding/ProposalFeed';
-import { ProposalSortAndFilters } from '@/components/Funding/ProposalSortAndFilters';
-import { FundingSidebarServer } from '@/components/Funding/FundingSidebarServer';
-import { ActivitySidebarSkeleton } from '@/components/Funding/ActivitySidebarSkeleton';
-import { HeroHeader } from '@/components/ui/HeroHeader';
-import { Button } from '@/components/ui/Button';
-import { SubmitProposalTooltip } from '@/components/tooltips/SubmitProposalTooltip';
-import { MarketplaceCards } from '@/components/Funding/MarketplaceCards';
-import { FundingHeroPanel } from '@/components/Funding/FundingHeroPanel';
-
-export const metadata: Metadata = buildOpenGraphMetadata({
- title: 'Proposals',
- description: 'Propose research, get reviewed, receive funding.',
- url: '/fund/proposals',
-});
-
-function SubmitProposalCTA() {
- return (
-
-
-
-
-
- );
-}
-
-export default async function FundProposalsPage() {
- return (
-
- Propose research, get reviewed, receive funding.
-
- }
- cta={} />}
- alignTop
- >
-
-
- }
- rightSidebar={
- }>
-
-
- }
- >
-
-
- );
-}
diff --git a/app/layouts/components/RightSidebarContainer.tsx b/app/layouts/components/RightSidebarContainer.tsx
index 79a67d315..481f8af49 100644
--- a/app/layouts/components/RightSidebarContainer.tsx
+++ b/app/layouts/components/RightSidebarContainer.tsx
@@ -11,7 +11,7 @@ import { RightSidebar } from '../RightSidebar';
function getSidebarInstanceKey(pathname: string, rightSidebar: boolean | ReactNode): string {
if (typeof rightSidebar !== 'boolean') {
- return `custom:${pathname}`;
+ return 'custom';
}
if (pathname.startsWith('/paper/create')) {
diff --git a/app/layouts/topbar/pageRoutes.tsx b/app/layouts/topbar/pageRoutes.tsx
index 961c236ac..6579fac18 100644
--- a/app/layouts/topbar/pageRoutes.tsx
+++ b/app/layouts/topbar/pageRoutes.tsx
@@ -12,6 +12,7 @@ import { Icon } from '@/components/ui/icons';
import { getTopicEmoji } from '@/components/Topic/TopicEmojis';
import { toTitleCase } from '@/utils/stringUtils';
import { getSourceLogo, getPreprintDisplayName } from '@/utils/preprintUtil';
+import { HOME_TAB_PATHS, isHomeTabPath } from '@/hooks/useFundTabs';
export interface PageInfo {
title: string;
@@ -19,15 +20,8 @@ export interface PageInfo {
}
export const ROOT_NAVIGATION_PATHS = new Set([
- '/',
- '/following',
- '/latest',
- '/popular',
- '/for-you',
- '/feed',
+ ...HOME_TAB_PATHS,
'/earn',
- '/fund',
- '/fund/proposals',
'/journal',
'/notebook',
'/browse',
@@ -47,7 +41,7 @@ interface RouteRule {
const ROUTE_RULES: RouteRule[] = [
{
- match: (p) => ['/', '/following', '/latest', '/popular', '/for-you', '/feed'].includes(p),
+ match: (p) => isHomeTabPath(p),
getInfo: () => ({
title: 'Home',
icon: ,
@@ -152,7 +146,7 @@ const ROUTE_RULES: RouteRule[] = [
}),
},
{
- match: (p) => p === '/fund' || p.startsWith('/fund/') || p.startsWith('/grant/'),
+ match: (p) => p.startsWith('/fund/') || p.startsWith('/grant/'),
getInfo: () => ({
title: 'Fund',
icon: ,
diff --git a/app/moderators/ModerationClientLayout.tsx b/app/moderators/ModerationClientLayout.tsx
index ae7ea4830..bd58db083 100644
--- a/app/moderators/ModerationClientLayout.tsx
+++ b/app/moderators/ModerationClientLayout.tsx
@@ -26,7 +26,7 @@ export default function ModerationClientLayout({ children }: ModerationClientLay
useEffect(() => {
if (!isLoading && !isModerator) {
- router.push('/popular');
+ router.push('/');
}
}, [isLoading, isModerator, router]);
diff --git a/app/page.tsx b/app/page.tsx
deleted file mode 100644
index 23b44dca0..000000000
--- a/app/page.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Metadata } from 'next';
-import { getServerSession } from 'next-auth/next';
-import { buildOpenGraphMetadata } from '@/lib/metadata';
-import { handleTrendingRedirect } from '@/utils/navigation';
-import { LandingPage } from '@/components/landing/LandingPage';
-
-export const metadata: Metadata = buildOpenGraphMetadata({
- title: 'Open Science Community & Research Platform',
- description:
- 'ResearchHub is a collaborative platform for sharing, reviewing, and funding scientific research. Join the open science community.',
- url: '/',
-});
-
-export default async function Home({
- searchParams,
-}: {
- searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
-}) {
- const session = await getServerSession();
-
- const resolvedSearchParams = await searchParams;
-
- const urlSearchParams = new URLSearchParams();
- Object.entries(resolvedSearchParams).forEach(([key, value]) => {
- if (typeof value === 'string') {
- urlSearchParams.set(key, value);
- } else if (Array.isArray(value)) {
- if (value.length > 0) {
- urlSearchParams.set(key, value[0]);
- }
- }
- });
-
- handleTrendingRedirect(!!session?.user, urlSearchParams);
-
- return ;
-}
diff --git a/app/robots.ts b/app/robots.ts
index e824a1714..f6c1ff37f 100644
--- a/app/robots.ts
+++ b/app/robots.ts
@@ -15,8 +15,6 @@ export default function robots(): MetadataRoute.Robots {
'/dashboard/',
'/notifications/',
'/activity/',
- '/for-you/',
- '/following/',
'/notebook/',
'/moderators/',
'/expert-finder/',
diff --git a/app/sitemap.ts b/app/sitemap.ts
index 3d6ef82c3..e6c74d4f6 100644
--- a/app/sitemap.ts
+++ b/app/sitemap.ts
@@ -66,9 +66,7 @@ async function buildSlugSitemap(
}
const STATIC_ROUTES: MetadataRoute.Sitemap = [
- { url: SITE_URL, changeFrequency: 'daily', priority: 1 },
- { url: `${SITE_URL}/popular`, changeFrequency: 'hourly', priority: 0.9 },
- { url: `${SITE_URL}/latest`, changeFrequency: 'hourly', priority: 0.8 },
+ { url: SITE_URL, changeFrequency: 'hourly', priority: 1 },
{ url: `${SITE_URL}/fund`, changeFrequency: 'daily', priority: 0.8 },
{ url: `${SITE_URL}/fund/proposals`, changeFrequency: 'daily', priority: 0.7 },
{ url: `${SITE_URL}/earn`, changeFrequency: 'daily', priority: 0.8 },
diff --git a/components/Funding/FundActivityPageContent.tsx b/components/Funding/FundActivityPageContent.tsx
new file mode 100644
index 000000000..cbc6be4a6
--- /dev/null
+++ b/components/Funding/FundActivityPageContent.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { useInView } from 'react-intersection-observer';
+import { ActivityCardFull } from '@/components/Activity/ActivityCardFull';
+import { ActivityCardSkeleton } from '@/components/Activity/ActivityCardSkeleton';
+import { useActivityFeed } from '@/hooks/useActivityFeed';
+
+export function FundActivityPageContent() {
+ const { entries, isLoading, isLoadingMore, hasMore, loadMore } = useActivityFeed({});
+
+ const { ref: sentinelRef } = useInView({
+ threshold: 0,
+ rootMargin: '200px',
+ onChange: (inView) => {
+ if (inView && hasMore && !isLoading && !isLoadingMore) {
+ loadMore();
+ }
+ },
+ });
+
+ return (
+
+ {entries.map((entry) => (
+
+ ))}
+
+ {(isLoading || isLoadingMore) &&
+ [...Array(6)].map((_, i) =>
)}
+
+ {!isLoading && !isLoadingMore && entries.length === 0 && (
+
+ )}
+
+ {!isLoading && !isLoadingMore && hasMore &&
}
+
+ );
+}
diff --git a/components/Funding/FundingBannerSkeleton.tsx b/components/Funding/FundingBannerSkeleton.tsx
deleted file mode 100644
index a411457f8..000000000
--- a/components/Funding/FundingBannerSkeleton.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { ResearchCoinSnapshotSkeleton } from '@/components/ResearchCoin/ResearchCoinSnapshot';
-
-interface FundingBannerSkeletonProps {
- showTabs?: boolean;
-}
-
-export function FundingBannerSkeleton({ showTabs = false }: FundingBannerSkeletonProps) {
- return (
-
-
-
-
-
-
- {showTabs && (
-
- )}
-
-
- );
-}
diff --git a/components/Funding/HomeTabs.tsx b/components/Funding/HomeTabs.tsx
new file mode 100644
index 000000000..de1b63e62
--- /dev/null
+++ b/components/Funding/HomeTabs.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { FeedTabs } from '@/components/Feed/FeedTabs';
+import { useContentTabsVisibilitySentinel } from '@/hooks/useContentTabsVisibilitySentinel';
+import { useFundTabs } from '@/hooks/useFundTabs';
+
+/**
+ * Homepage hub tabs (Activity / Fund / Proposals). Pill style matches the old
+ * for-you feed; the sentinel lifts a sticky copy into the TopBar on scroll.
+ */
+export function HomeTabs() {
+ const { tabs, highlightedTab, handleTabChange } = useFundTabs();
+ const tabsSentinelRef = useContentTabsVisibilitySentinel();
+
+ return (
+
+
+
+ );
+}
diff --git a/components/Funding/MarketplaceCards.tsx b/components/Funding/MarketplaceCards.tsx
deleted file mode 100644
index fa7357a94..000000000
--- a/components/Funding/MarketplaceCards.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-'use client';
-
-import { Tabs } from '@/components/ui/Tabs';
-import { useUser } from '@/contexts/UserContext';
-import { useContentTabsVisibilitySentinel } from '@/hooks/useContentTabsVisibilitySentinel';
-import { FUND_TABS, type FundTab } from '@/hooks/useFundTabs';
-
-export type MarketplaceTab = FundTab;
-
-interface MarketplaceCardsProps {
- selected?: MarketplaceTab;
-}
-
-export function MarketplaceCards({ selected = 'grants' }: MarketplaceCardsProps) {
- const { user, isLoading: isLoadingUser } = useUser();
- // Pull the tabs up only when the panel is or will be taller (snapshot visible).
- // Logged-out users see just the CTA (shorter panel), so no offset is needed.
- const snapshotVisible = isLoadingUser || !!user;
- const tabsSentinelRef = useContentTabsVisibilitySentinel();
-
- return (
-
- {}}
- variant="primary"
- className="mt-4"
- />
-
- );
-}
diff --git a/components/landing/LandingPageHero.tsx b/components/landing/LandingPageHero.tsx
index 3feee2a37..f293dc4b8 100644
--- a/components/landing/LandingPageHero.tsx
+++ b/components/landing/LandingPageHero.tsx
@@ -16,7 +16,7 @@ export function LandingPageHero() {
};
const handleExplore = () => {
- router.push('/popular');
+ router.push('/');
};
return (
diff --git a/hooks/useFeedTabs.ts b/hooks/useFeedTabs.ts
index 384def73d..57caa33ed 100644
--- a/hooks/useFeedTabs.ts
+++ b/hooks/useFeedTabs.ts
@@ -18,7 +18,9 @@ export const useFeedTabs = (onBeforeNavigate?: () => void) => {
const isTopicPage = pathname.startsWith('/topic/');
const isJournalPage = pathname.startsWith('/journal');
- const isHomeFeedPage = ['/', '/following', '/latest', '/popular', '/for-you', '/feed'].includes(
+ // Legacy research-feed routes (redirected to `/` for homepage hub).
+ // `/` is now the Activity hub tab — not a research feed.
+ const isHomeFeedPage = ['/following', '/latest', '/popular', '/for-you', '/feed'].includes(
pathname
);
diff --git a/hooks/useFundTabs.ts b/hooks/useFundTabs.ts
deleted file mode 100644
index 78c44293b..000000000
--- a/hooks/useFundTabs.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-'use client';
-
-import { useMemo } from 'react';
-import { usePathname, useRouter } from 'next/navigation';
-import { ArrowDownCircle, ArrowUpCircle } from 'lucide-react';
-
-export type FundTab = 'grants' | 'proposals';
-
-export const FUND_TABS = [
- {
- id: 'grants' as const,
- label: 'Funding Opportunities',
- href: '/fund',
- icon: ArrowDownCircle,
- iconClassName: 'w-5 h-5',
- activeClassName: 'text-emerald-600 border-b-emerald-600',
- },
- {
- id: 'proposals' as const,
- label: 'Proposals',
- href: '/fund/proposals',
- icon: ArrowUpCircle,
- iconClassName: 'w-5 h-5',
- activeClassName: 'text-primary-600 border-b-primary-600',
- },
-];
-
-export function useFundTabs() {
- const pathname = usePathname();
- const router = useRouter();
-
- const isFundPage = pathname === '/fund' || pathname === '/fund/proposals';
-
- const activeTab = useMemo((): FundTab => {
- if (pathname === '/fund/proposals') return 'proposals';
- return 'grants';
- }, [pathname]);
-
- const tabs = useMemo(() => FUND_TABS, []);
-
- const handleTabChange = (tab: string, e?: React.MouseEvent) => {
- if (tab === activeTab) {
- e?.preventDefault();
- return;
- }
-
- const href = FUND_TABS.find((t) => t.id === tab)?.href;
- if (!href) return;
-
- if (e && !e.metaKey && !e.ctrlKey && !e.shiftKey && e.button === 0) {
- e.preventDefault();
- router.push(href);
- } else if (!e) {
- router.push(href);
- }
- };
-
- return {
- tabs,
- activeTab,
- highlightedTab: activeTab,
- handleTabChange,
- isFundPage,
- };
-}
diff --git a/hooks/useFundTabs.tsx b/hooks/useFundTabs.tsx
new file mode 100644
index 000000000..8a099685b
--- /dev/null
+++ b/hooks/useFundTabs.tsx
@@ -0,0 +1,97 @@
+'use client';
+
+import { useMemo } from 'react';
+import { usePathname, useRouter } from 'next/navigation';
+import { FileText, Waves, type LucideIcon, type LucideProps } from 'lucide-react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faBullhorn } from '@fortawesome/free-solid-svg-icons';
+import { useScrollContainer } from '@/contexts/ScrollContainerContext';
+
+/** Sized via PillTabs' className to match Lucide stroke icons in the pills. */
+function BullhornIcon({ className }: LucideProps) {
+ return ;
+}
+
+export type FundTab = 'activity' | 'fund' | 'proposals';
+
+/** Routes that make up the homepage hub — all of them highlight "Home" in the nav. */
+export const HOME_TAB_PATHS = ['/', '/fund', '/fund/proposals'];
+
+export const isHomeTabPath = (pathname: string) => HOME_TAB_PATHS.includes(pathname);
+
+export const FUND_TABS = [
+ {
+ id: 'activity' as const,
+ label: 'Activity',
+ href: '/',
+ icon: Waves,
+ scroll: false,
+ },
+ {
+ id: 'fund' as const,
+ label: 'Fund',
+ href: '/fund',
+ icon: BullhornIcon as LucideIcon,
+ scroll: false,
+ },
+ {
+ id: 'proposals' as const,
+ label: 'Proposals',
+ href: '/fund/proposals',
+ icon: FileText,
+ scroll: false,
+ },
+];
+
+/** Homepage hub: Activity / Fund / Proposals (shared shell + FundSidebar). */
+export function useFundTabs() {
+ const pathname = usePathname();
+ const router = useRouter();
+ const scrollContainerRef = useScrollContainer();
+
+ const isFundPage = isHomeTabPath(pathname);
+
+ const activeTab = useMemo((): FundTab => {
+ if (pathname === '/fund/proposals') return 'proposals';
+ if (pathname === '/fund') return 'fund';
+ return 'activity';
+ }, [pathname]);
+
+ const tabs = useMemo(() => FUND_TABS, []);
+
+ const scrollToTop = () => {
+ const container = scrollContainerRef?.current;
+ if (container) {
+ container.scrollTop = 0;
+ } else {
+ window.scrollTo(0, 0);
+ }
+ };
+
+ const handleTabChange = (tab: string, e?: React.MouseEvent) => {
+ if (tab === activeTab) {
+ e?.preventDefault();
+ return;
+ }
+
+ const href = FUND_TABS.find((t) => t.id === tab)?.href;
+ if (!href) return;
+
+ if (e && !e.metaKey && !e.ctrlKey && !e.shiftKey && e.button === 0) {
+ e.preventDefault();
+ scrollToTop();
+ router.push(href, { scroll: false });
+ } else if (!e) {
+ scrollToTop();
+ router.push(href, { scroll: false });
+ }
+ };
+
+ return {
+ tabs,
+ activeTab,
+ highlightedTab: activeTab,
+ handleTabChange,
+ isFundPage,
+ };
+}
diff --git a/utils/navigation.ts b/utils/navigation.ts
index 74dac91e5..865bcf316 100644
--- a/utils/navigation.ts
+++ b/utils/navigation.ts
@@ -124,27 +124,15 @@ export function handleMissingSlugRedirect(
}
/**
- * Handles redirection to trending page if user is authorized
- * @param isUserLoggedIn Whether the user is logged in
- * @param searchParams Optional search parameters to preserve in the redirect
+ * Redirects into the homepage Activity hub.
+ * Preserves search parameters when provided.
*/
-export function handleTrendingRedirect(isUserLoggedIn: boolean, searchParams?: URLSearchParams) {
- if (isUserLoggedIn) {
- let redirectUrl = '/for-you';
+export function handleTrendingRedirect(_isUserLoggedIn: boolean, searchParams?: URLSearchParams) {
+ let redirectUrl = '/';
- // Preserve search parameters if provided
- if (searchParams && searchParams.toString()) {
- redirectUrl += `?${searchParams.toString()}`;
- }
-
- redirect(redirectUrl);
- } else {
- let popularUrl = '/popular';
-
- if (searchParams && searchParams.toString()) {
- popularUrl += `?${searchParams.toString()}`;
- }
-
- redirect(popularUrl);
+ if (searchParams && searchParams.toString()) {
+ redirectUrl += `?${searchParams.toString()}`;
}
+
+ redirect(redirectUrl);
}
From 2134f1181757cfeb6189d0730d23dd9ab2ccc637 Mon Sep 17 00:00:00 2001
From: nicktytarenko
Date: Sun, 2 Aug 2026 17:35:33 +0300
Subject: [PATCH 2/2] cleanup
---
app/(home)/layout.tsx | 6 ------
components/Funding/HomeTabs.tsx | 4 ----
hooks/useFeedTabs.ts | 16 +++++++++-------
hooks/useFundTabs.tsx | 4 +---
4 files changed, 10 insertions(+), 20 deletions(-)
diff --git a/app/(home)/layout.tsx b/app/(home)/layout.tsx
index 7c9573e32..e77f8c13c 100644
--- a/app/(home)/layout.tsx
+++ b/app/(home)/layout.tsx
@@ -3,12 +3,6 @@ import { PageLayout } from '@/app/layouts/PageLayout';
import { FundSidebar } from '@/components/Funding/FundSidebar';
import { HomeTabs } from '@/components/Funding/HomeTabs';
-/**
- * Shared shell for homepage hub tabs (Activity / Fund / Proposals).
- * Keeps PageLayout, HomeTabs, and FundSidebar mounted while only the feed slot swaps.
- *
- * `/fund/dashboard` lives outside this group and keeps its own layout.
- */
export default function HomeLayout({ children }: { children: ReactNode }) {
return (
}>
diff --git a/components/Funding/HomeTabs.tsx b/components/Funding/HomeTabs.tsx
index de1b63e62..d23500658 100644
--- a/components/Funding/HomeTabs.tsx
+++ b/components/Funding/HomeTabs.tsx
@@ -4,10 +4,6 @@ import { FeedTabs } from '@/components/Feed/FeedTabs';
import { useContentTabsVisibilitySentinel } from '@/hooks/useContentTabsVisibilitySentinel';
import { useFundTabs } from '@/hooks/useFundTabs';
-/**
- * Homepage hub tabs (Activity / Fund / Proposals). Pill style matches the old
- * for-you feed; the sentinel lifts a sticky copy into the TopBar on scroll.
- */
export function HomeTabs() {
const { tabs, highlightedTab, handleTabChange } = useFundTabs();
const tabsSentinelRef = useContentTabsVisibilitySentinel();
diff --git a/hooks/useFeedTabs.ts b/hooks/useFeedTabs.ts
index 57caa33ed..920ca8aef 100644
--- a/hooks/useFeedTabs.ts
+++ b/hooks/useFeedTabs.ts
@@ -18,15 +18,17 @@ export const useFeedTabs = (onBeforeNavigate?: () => void) => {
const isTopicPage = pathname.startsWith('/topic/');
const isJournalPage = pathname.startsWith('/journal');
- // Legacy research-feed routes (redirected to `/` for homepage hub).
- // `/` is now the Activity hub tab — not a research feed.
- const isHomeFeedPage = ['/following', '/latest', '/popular', '/for-you', '/feed'].includes(
- pathname
- );
+ const isPersonalizedFeedPage = [
+ '/following',
+ '/latest',
+ '/popular',
+ '/for-you',
+ '/feed',
+ ].includes(pathname);
const isFeedPage = useMemo(
- () => isHomeFeedPage || isTopicPage || isJournalPage,
- [isHomeFeedPage, isTopicPage, isJournalPage]
+ () => isPersonalizedFeedPage || isTopicPage || isJournalPage,
+ [isPersonalizedFeedPage, isTopicPage, isJournalPage]
);
const topicSlug = isTopicPage ? pathname.split('/')[2] : null;
diff --git a/hooks/useFundTabs.tsx b/hooks/useFundTabs.tsx
index 8a099685b..836e88703 100644
--- a/hooks/useFundTabs.tsx
+++ b/hooks/useFundTabs.tsx
@@ -7,14 +7,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBullhorn } from '@fortawesome/free-solid-svg-icons';
import { useScrollContainer } from '@/contexts/ScrollContainerContext';
-/** Sized via PillTabs' className to match Lucide stroke icons in the pills. */
function BullhornIcon({ className }: LucideProps) {
return ;
}
export type FundTab = 'activity' | 'fund' | 'proposals';
-/** Routes that make up the homepage hub — all of them highlight "Home" in the nav. */
export const HOME_TAB_PATHS = ['/', '/fund', '/fund/proposals'];
export const isHomeTabPath = (pathname: string) => HOME_TAB_PATHS.includes(pathname);
@@ -43,7 +41,7 @@ export const FUND_TABS = [
},
];
-/** Homepage hub: Activity / Fund / Proposals (shared shell + FundSidebar). */
+/** Homepage tabs: Activity / Fund / Proposals. */
export function useFundTabs() {
const pathname = usePathname();
const router = useRouter();