From 2bb9b1669dc117e7ec65548f31de6247ce0e7cbc Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Fri, 29 Aug 2025 15:10:14 -0600
Subject: [PATCH 01/12] add sort and hub filter dropdowns
---
app/earn/page.tsx | 3 +-
app/fund/components/FundPageContent.tsx | 110 ++++++++++++++++--
.../HubSelector.tsx} | 19 ++-
services/grant.service.ts | 18 +++
4 files changed, 136 insertions(+), 14 deletions(-)
rename components/{Earn/BountyHubSelector.tsx => Hub/HubSelector.tsx} (93%)
diff --git a/app/earn/page.tsx b/app/earn/page.tsx
index 24a472708..3450d9fc3 100644
--- a/app/earn/page.tsx
+++ b/app/earn/page.tsx
@@ -9,7 +9,7 @@ import { EarnRightSidebar } from '@/components/Earn/EarnRightSidebar';
import { Coins } from 'lucide-react';
import { MainPageHeader } from '@/components/ui/MainPageHeader';
import Icon from '@/components/ui/icons/Icon';
-import { BountyHubSelector as HubsSelector, Hub } from '@/components/Earn/BountyHubSelector';
+import { HubsSelector, Hub } from '@/components/Hub/HubSelector';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { Badge } from '@/components/ui/Badge';
import { X } from 'lucide-react';
@@ -127,6 +127,7 @@ export default function EarnPage() {
onChange={handleHubsChange}
displayCountOnly
hideSelectedItems={true}
+ hubType="bounty"
/>
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index f0b8e691e..5677b2cec 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -8,28 +8,32 @@ import { GrantRightSidebar } from '@/components/Fund/GrantRightSidebar';
import { MainPageHeader } from '@/components/ui/MainPageHeader';
import { MarketplaceTabs, MarketplaceTab } from '@/components/Fund/MarketplaceTabs';
import Icon from '@/components/ui/icons/Icon';
+import { useState } from 'react';
+import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
+import { Badge } from '@/components/ui/Badge';
+import { HubsSelector } from '@/components/Hub/HubSelector';
+
+import { X, ChevronDown, Filter } from 'lucide-react';
interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
}
export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
+ const [sort, setSort] = useState
('-created_date');
+ const [selectedHubs, setSelectedHubs] = useState([]);
+
const getFundraiseStatus = (tab: MarketplaceTab): 'OPEN' | 'CLOSED' | undefined => {
- if (tab === 'needs-funding') return 'OPEN';
+ if (tab === 'needs-funding' || tab === 'grants') return 'OPEN';
if (tab === 'previously-funded') return 'CLOSED';
return undefined;
};
- const getOrdering = (tab: MarketplaceTab): string | undefined => {
- if (tab === 'needs-funding') return 'amount_raised';
- return undefined;
- };
-
const { entries, isLoading, hasMore, loadMore } = useFeed('all', {
contentType: marketplaceTab === 'grants' ? 'GRANT' : 'PREREGISTRATION',
endpoint: marketplaceTab === 'grants' ? 'grant_feed' : 'funding_feed',
fundraiseStatus: getFundraiseStatus(marketplaceTab),
- ordering: getOrdering(marketplaceTab),
+ ordering: sort,
});
const getTitle = (tab: MarketplaceTab): string => {
@@ -58,6 +62,80 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
}
};
+ const getSortOptions = (tab: MarketplaceTab): SortOption[] => {
+ switch (tab) {
+ case 'grants':
+ return grantSortOptions;
+ case 'needs-funding':
+ return fundingProposalSortOption;
+ case 'previously-funded':
+ return previouslyFundedSortOptions;
+ }
+ };
+
+ const getHubType = (tab: MarketplaceTab): 'grant' | 'bounty' | undefined => {
+ switch (tab) {
+ case 'grants':
+ return 'grant';
+ case 'needs-funding':
+ return 'bounty';
+ case 'previously-funded':
+ return 'bounty';
+ default:
+ return undefined;
+ }
+ };
+
+ const handleHubsChange = (hubs: any[]) => {
+ setSelectedHubs(hubs);
+ };
+
+ const renderFilters = () => (
+
+ {/* Top filter bar */}
+
+
+
+
+
+ setSort(opt.value)}
+ options={getSortOptions(marketplaceTab)}
+ />
+
+
+
+ {/* Selected hubs badges */}
+ {selectedHubs.length > 0 && (
+
+ {selectedHubs.map((hub) => (
+
+ Topic: {hub.name}
+
+
+ ))}
+
+ )}
+
+ );
+
const header = (
}
@@ -66,18 +144,36 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
/>
);
+ // Available sort options
+ const grantSortOptions = [
+ { value: '-unified_document__grants__amount', label: 'Amount' },
+ { value: '-created_date', label: 'Created Date' },
+ { value: 'unified_document__grants__end_date', label: 'Expiring soon' },
+ ];
+
+ const fundingProposalSortOption = [
+ { value: '-unified_document__funding_proposals__amount', label: 'Amount Raised' },
+ { value: 'test', label: 'Almost Funded' },
+ { value: '-created_date', label: 'Created Date' },
+ { value: 'unified_document__funding_proposals__end_date', label: 'Expiring soon' },
+ ];
+
+ const previouslyFundedSortOptions = [{ value: '-created_date', label: 'Created Date' }];
+
const rightSidebar = marketplaceTab === 'grants' ? : ;
return (
{header}
{}} />
+
);
diff --git a/components/Earn/BountyHubSelector.tsx b/components/Hub/HubSelector.tsx
similarity index 93%
rename from components/Earn/BountyHubSelector.tsx
rename to components/Hub/HubSelector.tsx
index 5dcd87ead..f070f7cd6 100644
--- a/components/Earn/BountyHubSelector.tsx
+++ b/components/Hub/HubSelector.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import { useEffect, useState, useCallback, useRef } from 'react';
import {
MultiSelectOption,
@@ -10,6 +8,7 @@ import { Button } from '@/components/ui/Button';
import { X, ChevronDown, Filter } from 'lucide-react';
import { BaseMenu } from '@/components/ui/form/BaseMenu';
import { BountyService } from '@/services/bounty.service';
+import { GrantService } from '@/services/grant.service';
import { Topic } from '@/types/topic';
export interface Hub {
@@ -19,21 +18,23 @@ export interface Hub {
color?: string;
}
-interface BountyHubSelectorProps {
+interface HubsSelectorProps {
selectedHubs: Hub[];
onChange: (hubs: Hub[]) => void;
error?: string | null;
displayCountOnly?: boolean;
hideSelectedItems?: boolean;
+ hubType?: 'grant' | 'bounty';
}
-export function BountyHubSelector({
+export function HubsSelector({
selectedHubs,
onChange,
error,
displayCountOnly = false,
hideSelectedItems = false,
-}: BountyHubSelectorProps) {
+ hubType,
+}: HubsSelectorProps) {
const [allHubs, setAllHubs] = useState([]);
const [menuOpen, setMenuOpen] = useState(false);
const menuContentRef = useRef(null);
@@ -64,7 +65,13 @@ export function BountyHubSelector({
// fetch all hubs at mount
useEffect(() => {
(async () => {
- const hubs = await BountyService.getBountyHubs();
+ let hubs;
+ if (hubType === 'grant') {
+ hubs = await GrantService.getGrantHubs();
+ setAllHubs(hubs);
+ } else {
+ hubs = await BountyService.getBountyHubs();
+ }
setAllHubs(hubs);
})();
}, []);
diff --git a/services/grant.service.ts b/services/grant.service.ts
index 78a56e5ab..b830824da 100644
--- a/services/grant.service.ts
+++ b/services/grant.service.ts
@@ -1,4 +1,5 @@
import { ApiClient } from './client';
+import { Topic } from '@/types/topic';
export class GrantService {
private static readonly BASE_PATH = '/api/grant';
@@ -28,4 +29,21 @@ export class GrantService {
});
return response;
}
+
+ static async getGrantHubs(): Promise {
+ const path = `/api/grant_feed/hubs/`;
+ try {
+ const response = await ApiClient.get(path);
+ return response.map((raw) => ({
+ id: raw.id,
+ name: raw.name || '',
+ slug: raw.slug || '',
+ description: raw.description,
+ imageUrl: raw.hub_image || undefined,
+ }));
+ } catch (error) {
+ console.error('Error fetching grant hubs:', error);
+ return [];
+ }
+ }
}
From e3a841776837764c060c32e6835900f8d9725a19 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Fri, 29 Aug 2025 16:47:30 -0600
Subject: [PATCH 02/12] move HubsSelected to Hubs selector component
---
app/earn/page.tsx | 25 ++-------------------
app/fund/components/FundPageContent.tsx | 29 +++++--------------------
components/Hub/HubSelector.tsx | 25 +++++++++++++++++++++
3 files changed, 32 insertions(+), 47 deletions(-)
diff --git a/app/earn/page.tsx b/app/earn/page.tsx
index 3450d9fc3..9448fbc13 100644
--- a/app/earn/page.tsx
+++ b/app/earn/page.tsx
@@ -6,13 +6,10 @@ import { FeedContent } from '@/components/Feed/FeedContent';
import { BountyService } from '@/services/bounty.service';
import { FeedEntry } from '@/types/feed';
import { EarnRightSidebar } from '@/components/Earn/EarnRightSidebar';
-import { Coins } from 'lucide-react';
import { MainPageHeader } from '@/components/ui/MainPageHeader';
import Icon from '@/components/ui/icons/Icon';
-import { HubsSelector, Hub } from '@/components/Hub/HubSelector';
+import { HubsSelector, HubsSelected, Hub } from '@/components/Hub/HubSelector';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
-import { Badge } from '@/components/ui/Badge';
-import { X } from 'lucide-react';
import { useClickContext } from '@/contexts/ClickContext';
export default function EarnPage() {
@@ -139,26 +136,8 @@ export default function EarnPage() {
- {/* Selected hubs badges */}
{selectedHubs.length > 0 && (
-
- {selectedHubs.map((hub) => (
-
- Topic: {hub.name}
-
-
- ))}
-
+
)}
);
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index 5677b2cec..dc695bf81 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -10,10 +10,9 @@ import { MarketplaceTabs, MarketplaceTab } from '@/components/Fund/MarketplaceTa
import Icon from '@/components/ui/icons/Icon';
import { useState } from 'react';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
-import { Badge } from '@/components/ui/Badge';
-import { HubsSelector } from '@/components/Hub/HubSelector';
+import { HubsSelector, HubsSelected, Hub } from '@/components/Hub/HubSelector';
-import { X, ChevronDown, Filter } from 'lucide-react';
+import { X } from 'lucide-react';
interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
@@ -21,7 +20,7 @@ interface FundPageContentProps {
export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
const [sort, setSort] = useState('-created_date');
- const [selectedHubs, setSelectedHubs] = useState([]);
+ const [selectedHubs, setSelectedHubs] = useState([]);
const getFundraiseStatus = (tab: MarketplaceTab): 'OPEN' | 'CLOSED' | undefined => {
if (tab === 'needs-funding' || tab === 'grants') return 'OPEN';
@@ -91,7 +90,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
};
const renderFilters = () => (
-
+
{/* Top filter bar */}
@@ -112,26 +111,8 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
- {/* Selected hubs badges */}
{selectedHubs.length > 0 && (
-
- {selectedHubs.map((hub) => (
-
- Topic: {hub.name}
-
-
- ))}
-
+
)}
);
diff --git a/components/Hub/HubSelector.tsx b/components/Hub/HubSelector.tsx
index f070f7cd6..66524b8d0 100644
--- a/components/Hub/HubSelector.tsx
+++ b/components/Hub/HubSelector.tsx
@@ -203,3 +203,28 @@ export function HubsSelector({
);
}
+
+export function HubsSelected({
+ selectedHubs,
+ onChange,
+}: {
+ selectedHubs: Hub[];
+ onChange: (hubs: Hub[]) => void;
+}) {
+ return (
+
+ {selectedHubs.map((hub) => (
+
+ Topic: {hub.name}
+
+
+ ))}
+
+ );
+}
From 43e8f9df2d92708b7acd2a93017a0cff5b38aee0 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Tue, 2 Sep 2025 08:24:28 -0600
Subject: [PATCH 03/12] add hub filtering
---
app/fund/components/FundPageContent.tsx | 1 +
hooks/useFeed.ts | 13 ++++++++++++-
services/feed.service.ts | 4 ++++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index dc695bf81..2511e5e4d 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -33,6 +33,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
endpoint: marketplaceTab === 'grants' ? 'grant_feed' : 'funding_feed',
fundraiseStatus: getFundraiseStatus(marketplaceTab),
ordering: sort,
+ hubIds: selectedHubs.map((h) => h.id),
});
const getTitle = (tab: MarketplaceTab): string => {
diff --git a/hooks/useFeed.ts b/hooks/useFeed.ts
index 19c3fc8b5..4b1ef7574 100644
--- a/hooks/useFeed.ts
+++ b/hooks/useFeed.ts
@@ -19,6 +19,7 @@ interface UseFeedOptions {
entries: FeedEntry[];
hasMore: boolean;
};
+ hubIds?: (string | number)[]; // Hub id's to filter by
}
export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions = {}) => {
@@ -56,6 +57,13 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
}
}, [status, activeTab]);
+ const arraysEqual = (a?: (string | number)[], b?: (string | number)[]) => {
+ if (a === b) return true;
+ if (!a || !b) return false;
+ if (a.length !== b.length) return false;
+ return a.every((val, i) => val === b[i]);
+ };
+
// Check if options have changed
useEffect(() => {
// Compare relevant options (excluding initialData which shouldn't trigger a reload)
@@ -66,7 +74,8 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
options.endpoint !== currentOptions.endpoint ||
options.fundraiseStatus !== currentOptions.fundraiseStatus ||
options.createdBy !== currentOptions.createdBy ||
- options.ordering !== currentOptions.ordering;
+ options.ordering !== currentOptions.ordering ||
+ !arraysEqual(options.hubIds, currentOptions.hubIds);
if (relevantOptionsChanged) {
setCurrentOptions(options);
@@ -88,6 +97,7 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
fundraiseStatus: options.fundraiseStatus,
createdBy: options.createdBy,
ordering: options.ordering,
+ hubIds: options.hubIds,
});
setEntries(result.entries);
setHasMore(result.hasMore);
@@ -116,6 +126,7 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
fundraiseStatus: options.fundraiseStatus,
createdBy: options.createdBy,
ordering: options.ordering,
+ hubIds: options.hubIds,
});
setEntries((prev) => [...prev, ...result.entries]);
setHasMore(result.hasMore);
diff --git a/services/feed.service.ts b/services/feed.service.ts
index f58822b26..d038c8ae7 100644
--- a/services/feed.service.ts
+++ b/services/feed.service.ts
@@ -22,6 +22,7 @@ export class FeedService {
grantId?: number;
createdBy?: number;
ordering?: string;
+ hubIds?: (string | number)[];
}): Promise<{ entries: FeedEntry[]; hasMore: boolean }> {
const queryParams = new URLSearchParams();
if (params?.page) queryParams.append('page', params.page.toString());
@@ -34,6 +35,9 @@ export class FeedService {
if (params?.grantId) queryParams.append('grant_id', params.grantId.toString());
if (params?.createdBy) queryParams.append('created_by', params.createdBy.toString());
if (params?.ordering) queryParams.append('ordering', params.ordering);
+ if (params?.hubIds && params.hubIds.length > 0) {
+ queryParams.append('hub_ids', JSON.stringify(params.hubIds));
+ }
// Determine which endpoint to use
const basePath =
From 5329fd9a0ff187f3c7091b612134d75ef5120b89 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Tue, 2 Sep 2025 16:23:07 -0600
Subject: [PATCH 04/12] add filters and sort to funded
---
app/fund/components/FundPageContent.tsx | 17 ++++++---
components/Hub/HubSelector.tsx | 9 +++--
services/feed.service.ts | 50 +++++++++++++++++++++----
services/grant.service.ts | 17 ---------
4 files changed, 58 insertions(+), 35 deletions(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index 2511e5e4d..46678c3f0 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -73,12 +73,12 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
}
};
- const getHubType = (tab: MarketplaceTab): 'grant' | 'bounty' | undefined => {
+ const getHubType = (tab: MarketplaceTab): 'grant' | 'needs-funding' | 'bounty' | undefined => {
switch (tab) {
case 'grants':
return 'grant';
case 'needs-funding':
- return 'bounty';
+ return 'needs-funding';
case 'previously-funded':
return 'bounty';
default:
@@ -134,13 +134,18 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
];
const fundingProposalSortOption = [
- { value: '-unified_document__funding_proposals__amount', label: 'Amount Raised' },
- { value: 'test', label: 'Almost Funded' },
+ { value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
+ { value: 'amount_raised', label: 'Amount Raised' },
{ value: '-created_date', label: 'Created Date' },
- { value: 'unified_document__funding_proposals__end_date', label: 'Expiring soon' },
+ { value: 'unified_document__fundraises__end_date', label: 'Expiring soon' },
+ { value: '-unified_document__hot_score', label: 'Popular' },
];
- const previouslyFundedSortOptions = [{ value: '-created_date', label: 'Created Date' }];
+ const previouslyFundedSortOptions = [
+ { value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
+ { value: 'amount_raised', label: 'Amount Raised' },
+ { value: '-created_date', label: 'Created Date' },
+ ];
const rightSidebar = marketplaceTab === 'grants' ? : ;
diff --git a/components/Hub/HubSelector.tsx b/components/Hub/HubSelector.tsx
index 66524b8d0..838da8c09 100644
--- a/components/Hub/HubSelector.tsx
+++ b/components/Hub/HubSelector.tsx
@@ -4,11 +4,10 @@ import {
SearchableMultiSelect,
} from '@/components/ui/form/SearchableMultiSelect';
import { Badge } from '@/components/ui/Badge';
-import { Button } from '@/components/ui/Button';
import { X, ChevronDown, Filter } from 'lucide-react';
import { BaseMenu } from '@/components/ui/form/BaseMenu';
import { BountyService } from '@/services/bounty.service';
-import { GrantService } from '@/services/grant.service';
+import { FeedService } from '@/services/feed.service';
import { Topic } from '@/types/topic';
export interface Hub {
@@ -24,7 +23,7 @@ interface HubsSelectorProps {
error?: string | null;
displayCountOnly?: boolean;
hideSelectedItems?: boolean;
- hubType?: 'grant' | 'bounty';
+ hubType?: 'grant' | 'needs-funding' | 'bounty';
}
export function HubsSelector({
@@ -67,8 +66,10 @@ export function HubsSelector({
(async () => {
let hubs;
if (hubType === 'grant') {
- hubs = await GrantService.getGrantHubs();
+ hubs = await FeedService.getFeedHubs('grant_feed');
setAllHubs(hubs);
+ } else if (hubType === 'needs-funding') {
+ hubs = await FeedService.getFeedHubs('funding_feed');
} else {
hubs = await BountyService.getBountyHubs();
}
diff --git a/services/feed.service.ts b/services/feed.service.ts
index d038c8ae7..069ff3886 100644
--- a/services/feed.service.ts
+++ b/services/feed.service.ts
@@ -4,12 +4,27 @@ import { Bounty, BountyType, transformBounty } from '@/types/bounty';
import { transformUser, User } from '@/types/user';
import { transformAuthorProfile } from '@/types/authorProfile';
import { Fundraise, transformFundraise } from '@/types/funding';
+import { Topic } from '@/types/topic';
+
+type Endpoints = 'feed' | 'funding_feed' | 'grant_feed' | undefined;
export class FeedService {
private static readonly BASE_PATH = '/api/feed';
private static readonly FUNDING_PATH = '/api/funding_feed';
private static readonly GRANT_PATH = '/api/grant_feed';
+ // Determine which endpoint to use
+ private static getEndpointPath(endpoint: Endpoints) {
+ switch (endpoint) {
+ case 'funding_feed':
+ return this.FUNDING_PATH;
+ case 'grant_feed':
+ return this.GRANT_PATH;
+ default:
+ return this.BASE_PATH;
+ }
+ }
+
static async getFeed(params?: {
page?: number;
pageSize?: number;
@@ -17,7 +32,7 @@ export class FeedService {
hubSlug?: string;
contentType?: string;
source?: 'all' | 'researchhub';
- endpoint?: 'feed' | 'funding_feed' | 'grant_feed';
+ endpoint?: Endpoints;
fundraiseStatus?: 'OPEN' | 'CLOSED';
grantId?: number;
createdBy?: number;
@@ -39,13 +54,7 @@ export class FeedService {
queryParams.append('hub_ids', JSON.stringify(params.hubIds));
}
- // Determine which endpoint to use
- const basePath =
- params?.endpoint === 'funding_feed'
- ? this.FUNDING_PATH
- : params?.endpoint === 'grant_feed'
- ? this.GRANT_PATH
- : this.BASE_PATH;
+ const basePath = this.getEndpointPath(params?.endpoint);
const url = `${basePath}/${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
try {
@@ -247,4 +256,29 @@ export class FeedService {
return transformFundraise(formattedRawFundraise);
}
+
+ static async getFeedHubs(endpoint: Endpoints): Promise {
+ // Hub search not implemented for feed
+ if (endpoint === 'feed') {
+ return [];
+ }
+
+ let basePath = this.getEndpointPath(endpoint);
+ const path = `${basePath}/hubs/`;
+
+ try {
+ const response = await ApiClient.get(path);
+ // Use transformTopic to normalize
+ return response.map((raw) => ({
+ id: raw.id,
+ name: raw.name || '',
+ slug: raw.slug || '',
+ description: raw.description,
+ imageUrl: raw.hub_image || undefined,
+ }));
+ } catch (error) {
+ console.error(`Error fetching ${endpoint} hubs`, error);
+ return [];
+ }
+ }
}
diff --git a/services/grant.service.ts b/services/grant.service.ts
index b830824da..ba79e75de 100644
--- a/services/grant.service.ts
+++ b/services/grant.service.ts
@@ -29,21 +29,4 @@ export class GrantService {
});
return response;
}
-
- static async getGrantHubs(): Promise {
- const path = `/api/grant_feed/hubs/`;
- try {
- const response = await ApiClient.get(path);
- return response.map((raw) => ({
- id: raw.id,
- name: raw.name || '',
- slug: raw.slug || '',
- description: raw.description,
- imageUrl: raw.hub_image || undefined,
- }));
- } catch (error) {
- console.error('Error fetching grant hubs:', error);
- return [];
- }
- }
}
From f3088442488e2afd4cd6bbac419757132542bc12 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Wed, 3 Sep 2025 12:40:25 -0600
Subject: [PATCH 05/12] manage feed entries, add sort icon
---
app/fund/components/FundPageContent.tsx | 20 +++++++++++++++-----
components/ui/SortDropdown.tsx | 5 +++--
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index 46678c3f0..3007f3cc0 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -11,8 +11,7 @@ import Icon from '@/components/ui/icons/Icon';
import { useState } from 'react';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { HubsSelector, HubsSelected, Hub } from '@/components/Hub/HubSelector';
-
-import { X } from 'lucide-react';
+import { useEffect } from 'react';
interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
@@ -21,6 +20,7 @@ interface FundPageContentProps {
export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
const [sort, setSort] = useState('-created_date');
const [selectedHubs, setSelectedHubs] = useState([]);
+ const [managedEntries, setManagedEntries] = useState([]);
const getFundraiseStatus = (tab: MarketplaceTab): 'OPEN' | 'CLOSED' | undefined => {
if (tab === 'needs-funding' || tab === 'grants') return 'OPEN';
@@ -28,7 +28,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
return undefined;
};
- const { entries, isLoading, hasMore, loadMore } = useFeed('all', {
+ const { entries, isLoading, hasMore, loadMore, refresh } = useFeed('all', {
contentType: marketplaceTab === 'grants' ? 'GRANT' : 'PREREGISTRATION',
endpoint: marketplaceTab === 'grants' ? 'grant_feed' : 'funding_feed',
fundraiseStatus: getFundraiseStatus(marketplaceTab),
@@ -36,6 +36,16 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
hubIds: selectedHubs.map((h) => h.id),
});
+ // Manage entries separate of hook to allow for clearing on filter and sort change.
+ useEffect(() => {
+ setManagedEntries(entries);
+ }, [entries]);
+
+ useEffect(() => {
+ setManagedEntries([]);
+ refresh();
+ }, [sort, selectedHubs]);
+
const getTitle = (tab: MarketplaceTab): string => {
switch (tab) {
case 'grants':
@@ -103,7 +113,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
hubType={getHubType(marketplaceTab)}
/>
-
+
setSort(opt.value)}
@@ -155,7 +165,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
{}} />
= ({
type="button"
className={`flex w-full items-center gap-2 border border-gray-200 bg-gray-50 hover:bg-gray-100 rounded-lg px-3 py-1.5 text-sm min-w-[120px] justify-between ${className}`}
>
+
{activeOption.label}
-
+
);
From 47b91d200234400dbdcd4af64cffe7a190726ccd Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Wed, 3 Sep 2025 16:17:01 -0600
Subject: [PATCH 06/12] sort dropdown push text start
---
components/ui/SortDropdown.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/components/ui/SortDropdown.tsx b/components/ui/SortDropdown.tsx
index e0f4c9b86..7bac78bef 100644
--- a/components/ui/SortDropdown.tsx
+++ b/components/ui/SortDropdown.tsx
@@ -35,7 +35,7 @@ export const SortDropdown: FC = ({
className={`flex w-full items-center gap-2 border border-gray-200 bg-gray-50 hover:bg-gray-100 rounded-lg px-3 py-1.5 text-sm min-w-[120px] justify-between ${className}`}
>
- {activeOption.label}
+ {activeOption.label}
);
From 2b053d1ef612f1ee4d5e2f3d40bdee19e451956b Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Wed, 3 Sep 2025 17:21:47 -0600
Subject: [PATCH 07/12] remove dead code
---
services/grant.service.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/services/grant.service.ts b/services/grant.service.ts
index ba79e75de..78a56e5ab 100644
--- a/services/grant.service.ts
+++ b/services/grant.service.ts
@@ -1,5 +1,4 @@
import { ApiClient } from './client';
-import { Topic } from '@/types/topic';
export class GrantService {
private static readonly BASE_PATH = '/api/grant';
From 23c3b78b52613ab01bca6d1f255f5227972be298 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Wed, 3 Sep 2025 17:27:04 -0600
Subject: [PATCH 08/12] update comment
---
app/fund/components/FundPageContent.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index 3007f3cc0..257c3c2d5 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -36,7 +36,7 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
hubIds: selectedHubs.map((h) => h.id),
});
- // Manage entries separate of hook to allow for clearing on filter and sort change.
+ // Manage the entries separate from hook to allow for clearing the feed when filter and sort options change.
useEffect(() => {
setManagedEntries(entries);
}, [entries]);
From 3e32c695d7d1722ff57f70c33e42335225319fb8 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Thu, 4 Sep 2025 13:00:42 -0600
Subject: [PATCH 09/12] remove dead code
---
app/earn/page.tsx | 8 +-
app/fund/components/FundPageContent.tsx | 5 +-
components/Hub/HubSelector.tsx | 111 +++++++-----------------
3 files changed, 32 insertions(+), 92 deletions(-)
diff --git a/app/earn/page.tsx b/app/earn/page.tsx
index 9448fbc13..2aec26d5c 100644
--- a/app/earn/page.tsx
+++ b/app/earn/page.tsx
@@ -119,13 +119,7 @@ export default function EarnPage() {
{/* Top filter bar */}
-
+
diff --git a/components/Hub/HubSelector.tsx b/components/Hub/HubSelector.tsx
index 838da8c09..f097b15c4 100644
--- a/components/Hub/HubSelector.tsx
+++ b/components/Hub/HubSelector.tsx
@@ -21,8 +21,6 @@ interface HubsSelectorProps {
selectedHubs: Hub[];
onChange: (hubs: Hub[]) => void;
error?: string | null;
- displayCountOnly?: boolean;
- hideSelectedItems?: boolean;
hubType?: 'grant' | 'needs-funding' | 'bounty';
}
@@ -30,10 +28,8 @@ export function HubsSelector({
selectedHubs,
onChange,
error,
- displayCountOnly = false,
- hideSelectedItems = false,
hubType,
-}: HubsSelectorProps) {
+}: Readonly
) {
const [allHubs, setAllHubs] = useState([]);
const [menuOpen, setMenuOpen] = useState(false);
const menuContentRef = useRef(null);
@@ -99,7 +95,7 @@ export function HubsSelector({
const allHubOptions = hubsToOptions(topicsToHubs(allHubs));
- // Local search within allHubs
+ // Local search within all Hubs
const filterHubs = useCallback(
async (query: string): Promise => {
if (!query) {
@@ -114,104 +110,57 @@ export function HubsSelector({
const handleChange = (options: MultiSelectOption[]) => {
onChange(optionsToHubs(options));
- if (displayCountOnly) {
- setMenuOpen(false);
- }
+ setMenuOpen(false);
};
- const CustomSelectedItems = () => (
-
- {selectedHubs.map((hub) => (
-
{
- e.preventDefault();
- e.stopPropagation();
- onChange(selectedHubs.filter((h) => h.id !== hub.id));
- if (displayCountOnly) {
- setMenuOpen(false);
- }
- }}
- >
- {hub.color && (
-
- )}
- {hub.name}
-
- ))}
-
+ const trigger = (
+
);
- if (displayCountOnly) {
- const trigger = (
-
- );
-
- return (
-
-
-
-
-
- );
- }
-
return (
-
-
+
+
- {selectedHubs.length > 0 && !hideSelectedItems && }
-
+
);
}
export function HubsSelected({
selectedHubs,
onChange,
-}: {
+}: Readonly<{
selectedHubs: Hub[];
onChange: (hubs: Hub[]) => void;
-}) {
+}>) {
return (
{selectedHubs.map((hub) => (
From 0a1d6ae0aeb26b6c2765ac9e241b32e643948ff6 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Sat, 6 Sep 2025 14:31:15 -0600
Subject: [PATCH 10/12] partial refactor of useFeed, update feed.service
---
hooks/useFeed.ts | 85 ++++++++++++----------------------------
services/feed.service.ts | 11 +-----
2 files changed, 28 insertions(+), 68 deletions(-)
diff --git a/hooks/useFeed.ts b/hooks/useFeed.ts
index 4b1ef7574..2d8c054c6 100644
--- a/hooks/useFeed.ts
+++ b/hooks/useFeed.ts
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { FeedEntry } from '@/types/feed';
import { FeedService } from '@/services/feed.service';
import { useSession } from 'next-auth/react';
+import { isEqual, omit } from 'lodash';
export type FeedTab = 'following' | 'latest' | 'popular';
export type FundingTab = 'all' | 'open' | 'closed';
@@ -31,6 +32,18 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
const [currentTab, setCurrentTab] = useState
(activeTab);
const [currentOptions, setCurrentOptions] = useState(options);
+ // Re-load the feed if any of the relevant options change
+ const omitCheckKeys = ['initialData']; // Keys to ignore when comparing options
+ useEffect(() => {
+ const filteredOptions = omit(options, omitCheckKeys);
+ const filteredCurrentOptions = omit(currentOptions, omitCheckKeys);
+
+ if (!isEqual(filteredOptions, filteredCurrentOptions)) {
+ setCurrentOptions(options);
+ loadFeed();
+ }
+ }, [options]);
+
// Only load the feed when the component mounts or when the session status changes
// We no longer reload when activeTab changes, as that will be handled by page navigation
useEffect(() => {
@@ -57,66 +70,16 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
}
}, [status, activeTab]);
- const arraysEqual = (a?: (string | number)[], b?: (string | number)[]) => {
- if (a === b) return true;
- if (!a || !b) return false;
- if (a.length !== b.length) return false;
- return a.every((val, i) => val === b[i]);
- };
-
- // Check if options have changed
- useEffect(() => {
- // Compare relevant options (excluding initialData which shouldn't trigger a reload)
- const relevantOptionsChanged =
- options.hubSlug !== currentOptions.hubSlug ||
- options.contentType !== currentOptions.contentType ||
- options.source !== currentOptions.source ||
- options.endpoint !== currentOptions.endpoint ||
- options.fundraiseStatus !== currentOptions.fundraiseStatus ||
- options.createdBy !== currentOptions.createdBy ||
- options.ordering !== currentOptions.ordering ||
- !arraysEqual(options.hubIds, currentOptions.hubIds);
-
- if (relevantOptionsChanged) {
- setCurrentOptions(options);
- loadFeed();
- }
- }, [options]);
-
- const loadFeed = async () => {
- setIsLoading(true);
- try {
- const result = await FeedService.getFeed({
- page: 1,
- pageSize: 20,
- feedView: activeTab as FeedTab, // Only pass feedView if it's a FeedTab
- hubSlug: options.hubSlug,
- contentType: options.contentType,
- source: options.source,
- endpoint: options.endpoint,
- fundraiseStatus: options.fundraiseStatus,
- createdBy: options.createdBy,
- ordering: options.ordering,
- hubIds: options.hubIds,
- });
- setEntries(result.entries);
- setHasMore(result.hasMore);
- setPage(1);
- } catch (error) {
- console.error('Error loading feed:', error);
- } finally {
- setIsLoading(false);
+ // Load feed items for first or subsequent pages.
+ const loadFeed = async (pageNumber: number = 1) => {
+ if (pageNumber > 1 && (!hasMore || isLoading)) {
+ return;
}
- };
-
- const loadMore = async () => {
- if (!hasMore || isLoading) return;
setIsLoading(true);
try {
- const nextPage = page + 1;
const result = await FeedService.getFeed({
- page: nextPage,
+ page: pageNumber,
pageSize: 20,
feedView: activeTab as FeedTab, // Only pass feedView if it's a FeedTab
hubSlug: options.hubSlug,
@@ -128,11 +91,15 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
ordering: options.ordering,
hubIds: options.hubIds,
});
- setEntries((prev) => [...prev, ...result.entries]);
+ if (pageNumber === 1) {
+ setEntries(result.entries);
+ } else {
+ setEntries((prev) => [...prev, ...result.entries]);
+ }
setHasMore(result.hasMore);
- setPage(nextPage);
+ setPage(pageNumber);
} catch (error) {
- console.error('Error loading more feed items:', error);
+ console.error('Error loading feed for page:', pageNumber, error);
} finally {
setIsLoading(false);
}
@@ -142,7 +109,7 @@ export const useFeed = (activeTab: FeedTab | FundingTab, options: UseFeedOptions
entries,
isLoading,
hasMore,
- loadMore,
+ loadMore: () => loadFeed(page + 1),
refresh: loadFeed,
};
};
diff --git a/services/feed.service.ts b/services/feed.service.ts
index 069ff3886..092d2e778 100644
--- a/services/feed.service.ts
+++ b/services/feed.service.ts
@@ -4,7 +4,7 @@ import { Bounty, BountyType, transformBounty } from '@/types/bounty';
import { transformUser, User } from '@/types/user';
import { transformAuthorProfile } from '@/types/authorProfile';
import { Fundraise, transformFundraise } from '@/types/funding';
-import { Topic } from '@/types/topic';
+import { Topic, transformTopic } from '@/types/topic';
type Endpoints = 'feed' | 'funding_feed' | 'grant_feed' | undefined;
@@ -268,14 +268,7 @@ export class FeedService {
try {
const response = await ApiClient.get(path);
- // Use transformTopic to normalize
- return response.map((raw) => ({
- id: raw.id,
- name: raw.name || '',
- slug: raw.slug || '',
- description: raw.description,
- imageUrl: raw.hub_image || undefined,
- }));
+ return response.map((raw) => transformTopic(raw));
} catch (error) {
console.error(`Error fetching ${endpoint} hubs`, error);
return [];
From ed1e637c367bac28a542ec52b1933b3bf89d4679 Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Tue, 9 Sep 2025 10:00:21 -0600
Subject: [PATCH 11/12] add most applications and most reviews filters, make
filter case consistent
---
app/fund/components/FundPageContent.tsx | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index 04dcee124..b2cb631b1 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -136,22 +136,24 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
// Available sort options
const grantSortOptions = [
{ value: '-unified_document__grants__amount', label: 'Amount' },
- { value: '-created_date', label: 'Created Date' },
+ { value: '-created_date', label: 'Created date' },
{ value: 'unified_document__grants__end_date', label: 'Expiring soon' },
+ { value: 'application_count', label: 'Most applications' },
];
const fundingProposalSortOption = [
{ value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
- { value: 'amount_raised', label: 'Amount Raised' },
- { value: '-created_date', label: 'Created Date' },
+ { value: 'amount_raised', label: 'Amount raised' },
+ { value: '-created_date', label: 'Created date' },
{ value: 'unified_document__fundraises__end_date', label: 'Expiring soon' },
{ value: '-unified_document__hot_score', label: 'Popular' },
+ { value: 'review_count', label: 'Most reviews' },
];
const previouslyFundedSortOptions = [
{ value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
- { value: 'amount_raised', label: 'Amount Raised' },
- { value: '-created_date', label: 'Created Date' },
+ { value: 'amount_raised', label: 'Amount raised' },
+ { value: '-created_date', label: 'Created date' },
];
const rightSidebar = marketplaceTab === 'grants' ? : ;
From b7e8eab765eaf77faef5cefad7a3c03f2fa28bcf Mon Sep 17 00:00:00 2001
From: Dan <39170265+chillenberger@users.noreply.github.com>
Date: Tue, 9 Sep 2025 17:51:15 -0600
Subject: [PATCH 12/12] clean up sorts and FundPageContent consts
---
app/fund/components/FundPageContent.tsx | 133 ++++++++++--------------
app/layouts/TopBar.tsx | 1 +
2 files changed, 56 insertions(+), 78 deletions(-)
diff --git a/app/fund/components/FundPageContent.tsx b/app/fund/components/FundPageContent.tsx
index b2cb631b1..3917f24e6 100644
--- a/app/fund/components/FundPageContent.tsx
+++ b/app/fund/components/FundPageContent.tsx
@@ -12,12 +12,61 @@ import { useState, useEffect } from 'react';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { HubsSelector, HubsSelected, Hub } from '@/components/Hub/HubSelector';
+const SORT_OPTIONS_MAP: Record = {
+ grants: [
+ { value: 'grants__amount', label: 'Amount' },
+ { value: 'newest', label: 'Created date' },
+ { value: 'end_date', label: 'Expiring soon' },
+ { value: 'application_count', label: 'Most applications' },
+ ],
+ 'needs-funding': [
+ { value: 'newest', label: 'Created date' },
+ { value: 'hot_score', label: 'Popular' },
+ { value: 'upvotes', label: 'Most upvoted' },
+ { value: 'amount_raised', label: 'Amount raised' },
+ { value: 'goal_amount', label: 'Goal' },
+ { value: 'end_date', label: 'Expiring soon' },
+ { value: 'review_count', label: 'Most reviews' },
+ ],
+ 'previously-funded': [
+ { value: 'goal_amount', label: 'Goal' },
+ { value: 'amount_raised', label: 'Amount raised' },
+ { value: 'newest', label: 'Created date' },
+ ],
+};
+
+const DEFAULT_SORT_MAP: Record = {
+ grants: 'end_date',
+ 'needs-funding': 'end_date',
+ 'previously-funded': 'newest',
+};
+
+// Needs replaced with getPageInfo from layouts/TopBar.tsx
+const PAGE_TITLE_MAP: Record = {
+ grants: 'Request for Proposals',
+ 'needs-funding': 'Proposals',
+ 'previously-funded': 'Previously Funded',
+};
+
+// Needs replaced with getPageInfo from layouts/TopBar.tsx
+const PAGE_SUBTITLE_MAP: Record = {
+ grants: 'Explore available funding opportunities',
+ 'needs-funding': 'Fund breakthrough research shaping tomorrow',
+ 'previously-funded': 'Browse research that has been successfully funded',
+};
+
+const HUB_TYPE_MAP: Record = {
+ grants: 'grant',
+ 'needs-funding': 'needs-funding',
+ 'previously-funded': 'bounty',
+};
+
interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
}
export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
- const [sort, setSort] = useState('-created_date');
+ const [sort, setSort] = useState(DEFAULT_SORT_MAP[marketplaceTab]);
const [selectedHubs, setSelectedHubs] = useState([]);
const [managedEntries, setManagedEntries] = useState([]);
@@ -45,56 +94,6 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
refresh();
}, [sort, selectedHubs]);
- const getTitle = (tab: MarketplaceTab): string => {
- switch (tab) {
- case 'grants':
- return 'Request for Proposals';
- case 'needs-funding':
- return 'Proposals';
- case 'previously-funded':
- return 'Previously Funded';
- default:
- return '';
- }
- };
-
- const getSubtitle = (tab: MarketplaceTab): string => {
- switch (tab) {
- case 'grants':
- return 'Explore available funding opportunities';
- case 'needs-funding':
- return 'Fund breakthrough research shaping tomorrow';
- case 'previously-funded':
- return 'Browse research that has been successfully funded';
- default:
- return '';
- }
- };
-
- const getSortOptions = (tab: MarketplaceTab): SortOption[] => {
- switch (tab) {
- case 'grants':
- return grantSortOptions;
- case 'needs-funding':
- return fundingProposalSortOption;
- case 'previously-funded':
- return previouslyFundedSortOptions;
- }
- };
-
- const getHubType = (tab: MarketplaceTab): 'grant' | 'needs-funding' | 'bounty' | undefined => {
- switch (tab) {
- case 'grants':
- return 'grant';
- case 'needs-funding':
- return 'needs-funding';
- case 'previously-funded':
- return 'bounty';
- default:
- return undefined;
- }
- };
-
const handleHubsChange = (hubs: any[]) => {
setSelectedHubs(hubs);
};
@@ -107,14 +106,14 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
setSort(opt.value)}
- options={getSortOptions(marketplaceTab)}
+ options={SORT_OPTIONS_MAP[marketplaceTab]}
/>
@@ -125,37 +124,15 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
);
+ // Special headers for mobile. Needs resolved with TopBar
const header = (
}
- title={getTitle(marketplaceTab)}
- subtitle={getSubtitle(marketplaceTab)}
+ title={PAGE_TITLE_MAP[marketplaceTab]}
+ subtitle={PAGE_SUBTITLE_MAP[marketplaceTab]}
/>
);
- // Available sort options
- const grantSortOptions = [
- { value: '-unified_document__grants__amount', label: 'Amount' },
- { value: '-created_date', label: 'Created date' },
- { value: 'unified_document__grants__end_date', label: 'Expiring soon' },
- { value: 'application_count', label: 'Most applications' },
- ];
-
- const fundingProposalSortOption = [
- { value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
- { value: 'amount_raised', label: 'Amount raised' },
- { value: '-created_date', label: 'Created date' },
- { value: 'unified_document__fundraises__end_date', label: 'Expiring soon' },
- { value: '-unified_document__hot_score', label: 'Popular' },
- { value: 'review_count', label: 'Most reviews' },
- ];
-
- const previouslyFundedSortOptions = [
- { value: '-unified_document__fundraises__goal_amount', label: 'Goal' },
- { value: 'amount_raised', label: 'Amount raised' },
- { value: '-created_date', label: 'Created date' },
- ];
-
const rightSidebar = marketplaceTab === 'grants' ? : ;
return (
diff --git a/app/layouts/TopBar.tsx b/app/layouts/TopBar.tsx
index 03459f501..c99c8d8e4 100644
--- a/app/layouts/TopBar.tsx
+++ b/app/layouts/TopBar.tsx
@@ -47,6 +47,7 @@ const isRootNavigationPage = (pathname: string): boolean => {
'/earn',
'/fund/grants',
'/fund/needs-funding', // Fundraises page
+ '/fund/previously-funded',
'/journal',
'/notebook',
'/leaderboard',