Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 3 additions & 29 deletions app/earn/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 { BountyHubSelector as HubsSelector, Hub } from '@/components/Earn/BountyHubSelector';
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() {
Expand Down Expand Up @@ -122,12 +119,7 @@ export default function EarnPage() {
{/* Top filter bar */}
<div className="flex items-center gap-0 sm:gap-2 flex-wrap justify-between">
<div className="w-1/2 sm:!w-[220px] flex-1 sm:!flex-none pr-1 sm:!pr-0">
<HubsSelector
selectedHubs={selectedHubs}
onChange={handleHubsChange}
displayCountOnly
hideSelectedItems={true}
/>
<HubsSelector selectedHubs={selectedHubs} onChange={handleHubsChange} hubType="bounty" />
</div>
<div className="w-1/2 sm:!w-[120px] flex-1 sm:!flex-none pl-1 sm:!pl-0">
<SortDropdown
Expand All @@ -138,26 +130,8 @@ export default function EarnPage() {
</div>
</div>

{/* Selected hubs badges */}
{selectedHubs.length > 0 && (
<div className="flex flex-wrap gap-2">
{selectedHubs.map((hub) => (
<Badge
key={hub.id}
variant="default"
className="flex items-center gap-1 pr-1 bg-gray-50"
>
<span>Topic: {hub.name}</span>
<button
type="button"
onClick={() => handleHubsChange(selectedHubs.filter((h) => h.id !== hub.id))}
className="text-gray-500 hover:text-gray-700 ml-1"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
<HubsSelected selectedHubs={selectedHubs} onChange={handleHubsChange} />
)}
</div>
);
Expand Down
137 changes: 103 additions & 34 deletions app/fund/components/FundPageContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,128 @@ 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, useEffect } from 'react';
import SortDropdown, { SortOption } from '@/components/ui/SortDropdown';
import { HubsSelector, HubsSelected, Hub } from '@/components/Hub/HubSelector';

const SORT_OPTIONS_MAP: Record<MarketplaceTab, SortOption[]> = {
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<MarketplaceTab, string> = {
grants: 'end_date',
'needs-funding': 'end_date',
'previously-funded': 'newest',
};

// Needs replaced with getPageInfo from layouts/TopBar.tsx
const PAGE_TITLE_MAP: Record<MarketplaceTab, string> = {
grants: 'Request for Proposals',
'needs-funding': 'Proposals',
'previously-funded': 'Previously Funded',
};

// Needs replaced with getPageInfo from layouts/TopBar.tsx
const PAGE_SUBTITLE_MAP: Record<MarketplaceTab, string> = {
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<MarketplaceTab, 'grant' | 'needs-funding' | 'bounty' | undefined> = {
grants: 'grant',
'needs-funding': 'needs-funding',
'previously-funded': 'bounty',
};

interface FundPageContentProps {
marketplaceTab: MarketplaceTab;
}

export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
const [sort, setSort] = useState<string>(DEFAULT_SORT_MAP[marketplaceTab]);
const [selectedHubs, setSelectedHubs] = useState<Hub[]>([]);
const [managedEntries, setManagedEntries] = useState<any[]>([]);

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', {
const { entries, isLoading, hasMore, loadMore, refresh } = useFeed('all', {
contentType: marketplaceTab === 'grants' ? 'GRANT' : 'PREREGISTRATION',
endpoint: marketplaceTab === 'grants' ? 'grant_feed' : 'funding_feed',
fundraiseStatus: getFundraiseStatus(marketplaceTab),
ordering: getOrdering(marketplaceTab),
ordering: sort,
hubIds: selectedHubs.map((h) => h.id),
});

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 '';
}
};
// Manage the entries separate from hook to allow for clearing the feed when filter and sort options change.
useEffect(() => {
setManagedEntries(entries);
}, [entries]);

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 '';
}
useEffect(() => {
setManagedEntries([]);
refresh();
}, [sort, selectedHubs]);

const handleHubsChange = (hubs: any[]) => {
setSelectedHubs(hubs);
};

const renderFilters = () => (
<div className="space-y-3">
{/* Top filter bar */}
<div className="flex items-center gap-0 sm:gap-2 flex-wrap justify-between">
<div className="w-1/2 sm:!w-[220px] flex-1 sm:!flex-none pr-1 sm:!pr-0">
<HubsSelector
selectedHubs={selectedHubs}
onChange={handleHubsChange}
hubType={HUB_TYPE_MAP[marketplaceTab]}
/>
</div>
<div className="w-1/2 sm:!w-[160px] flex-1 sm:!flex-none pl-1 sm:!pl-0">
<SortDropdown
value={sort}
onChange={(opt: SortOption) => setSort(opt.value)}
options={SORT_OPTIONS_MAP[marketplaceTab]}
/>
</div>
</div>

{selectedHubs.length > 0 && (
<HubsSelected selectedHubs={selectedHubs} onChange={handleHubsChange} />
)}
</div>
);

// Special headers for mobile. Needs resolved with TopBar
const header = (
<MainPageHeader
icon={<Icon name="solidHand" size={26} color="#3971ff" />}
title={getTitle(marketplaceTab)}
subtitle={getSubtitle(marketplaceTab)}
title={PAGE_TITLE_MAP[marketplaceTab]}
subtitle={PAGE_SUBTITLE_MAP[marketplaceTab]}
/>
);

Expand All @@ -72,12 +139,14 @@ export function FundPageContent({ marketplaceTab }: FundPageContentProps) {
<PageLayout rightSidebar={rightSidebar}>
{header}
<MarketplaceTabs activeTab={marketplaceTab} onTabChange={() => {}} />

<FeedContent
entries={entries}
entries={managedEntries}
isLoading={isLoading}
hasMore={hasMore}
loadMore={loadMore}
showGrantHeaders={false}
filters={renderFilters()}
/>
</PageLayout>
);
Expand Down
1 change: 1 addition & 0 deletions app/layouts/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const isRootNavigationPage = (pathname: string): boolean => {
'/earn',
'/fund/grants',
'/fund/needs-funding', // Fundraises page
'/fund/previously-funded',
'/journal',
'/notebook',
'/leaderboard',
Expand Down
Loading