diff --git a/app/my-funding/components/MyFundingPage.tsx b/app/my-funding/components/MyFundingPage.tsx
index 9fd425f6f..fd6fa2592 100644
--- a/app/my-funding/components/MyFundingPage.tsx
+++ b/app/my-funding/components/MyFundingPage.tsx
@@ -464,7 +464,7 @@ function heroCovers(
): HeroCover[] {
// Ordered by what was given and described by it, so the deck is the same
// before and after the activity loads.
- // A funded proposal without a cover is drawn with the proposal icon.
+ // A funded proposal or RFP pool without a cover is drawn with its kind's icon.
const funded = [...fundedRows]
.sort((a, b) => b.youGave.usd - a.youGave.usd || b.youGave.rsc - a.youGave.rsc)
.map(
@@ -472,6 +472,7 @@ function heroCovers(
key: row.key,
title: row.title,
image: row.image,
+ tone: row.kind === 'pool' ? 'blue' : 'green',
status: `You gave ${format(row.youGave, { shorten: true })}`,
detail: row.scientist.fullName,
})
diff --git a/components/Funding/dashboard/FundedResearchList.tsx b/components/Funding/dashboard/FundedResearchList.tsx
index 2a8f9937d..292b6d74a 100644
--- a/components/Funding/dashboard/FundedResearchList.tsx
+++ b/components/Funding/dashboard/FundedResearchList.tsx
@@ -10,6 +10,8 @@ import {
import type { FundedRow } from '@/components/Funding/dashboard/lib/myFundingModel';
import { useMoneyFormat } from '@/components/Funding/dashboard/lib/useMoneyFormat';
import { formatTimeAgo } from '@/utils/date';
+import { pluralizeSuffix } from '@/utils/stringUtils';
+import { cn } from '@/utils/styles';
interface FundedResearchListProps {
readonly rows: readonly FundedRow[];
@@ -21,34 +23,63 @@ interface FundedResearchListProps {
/**
* Research you funded: each proposal with its scientist, the latest word from
- * its author or reviewers, and what you gave.
+ * its author or reviewers, and what you gave; then each RFP pool you put money
+ * into, with its creator and what you gave.
*/
export function FundedResearchList({ rows, limit, onSeeAll }: FundedResearchListProps) {
if (rows.length === 0) return null;
const shown = limit ? rows.slice(0, limit) : rows;
+ const shownProposals = shown.filter((row) => row.kind === 'proposal');
+ const shownPools = shown.filter((row) => row.kind === 'pool');
+ const proposalCount = rows.filter((row) => row.kind === 'proposal').length;
+ const poolCount = rows.length - proposalCount;
return (
0 && `${proposalCount} proposal${pluralizeSuffix(proposalCount)}`,
+ poolCount > 0 && `${poolCount} RFP pool${pluralizeSuffix(poolCount)}`,
+ ]
+ .filter(Boolean)
+ .join(' · ')}
action={
limit != null &&
rows.length > limit &&
onSeeAll && See all {rows.length}
}
/>
- {/* One card holding every row, like Your peer reviews. */}
-
- {shown.map((row) => (
-
- ))}
-
+ {shownProposals.length > 0 && }
+ {shownPools.length > 0 && (
+ <>
+ 0 && 'mt-6'
+ )}
+ >
+ RFP pools you funded
+
+
+ >
+ )}
);
}
+/** One card holding every row, like Your peer reviews. */
+function FundedResearchRows({ rows }: { readonly rows: readonly FundedRow[] }) {
+ return (
+
+ {rows.map((row) => (
+
+ ))}
+
+ );
+}
+
function FundedResearchRow({ row }: { readonly row: FundedRow }) {
const format = useMoneyFormat();
diff --git a/components/Funding/dashboard/lib/myFundingModel.ts b/components/Funding/dashboard/lib/myFundingModel.ts
index b6b996153..e4068ddd4 100644
--- a/components/Funding/dashboard/lib/myFundingModel.ts
+++ b/components/Funding/dashboard/lib/myFundingModel.ts
@@ -141,9 +141,10 @@ export function latestNewsByPost(entries: readonly FeedEntry[]): Map
): FundedRow[] {
if (!overview) return [];
- const rows = overview.supportedProposals.map(
- (proposal): FundedRow => ({
- key: `funded-${proposal.id}`,
- postId: proposal.id,
- title: proposal.title.trim() || 'Untitled proposal',
- href: buildWorkUrl({ id: proposal.id, slug: proposal.slug, contentType: 'preregistration' }),
- image: proposal.image,
- scientist: proposal.createdBy.authorProfile,
- youGave: proposal.fundedAmount,
- news: news.get(proposal.id),
- })
- );
+ const rows = [
+ ...overview.supportedProposals.map(
+ (proposal): FundedRow => ({
+ key: `funded-${proposal.id}`,
+ kind: 'proposal',
+ postId: proposal.id,
+ title: proposal.title.trim() || 'Untitled proposal',
+ href: buildWorkUrl({
+ id: proposal.id,
+ slug: proposal.slug,
+ contentType: 'preregistration',
+ }),
+ image: proposal.image,
+ scientist: proposal.createdBy.authorProfile,
+ youGave: proposal.fundedAmount,
+ news: news.get(proposal.id),
+ })
+ ),
+ ...overview.supportedFundingPools.map(
+ (pool): FundedRow => ({
+ key: `funded-${pool.id}`,
+ kind: 'pool',
+ postId: pool.id,
+ title: pool.title.trim() || 'Untitled RFP',
+ href: buildWorkUrl({ id: pool.id, slug: pool.slug, contentType: 'funding_request' }),
+ image: pool.image,
+ scientist: pool.createdBy.authorProfile,
+ youGave: pool.fundedAmount,
+ })
+ ),
+ ];
const newsAt = (row: FundedRow) => (row.news ? new Date(row.news.timestamp).getTime() : 0);
return rows.sort(
@@ -184,11 +204,11 @@ export function buildFundedRows(
);
}
-/** A scientist the user supports, with what they gave across that scientist's proposals. */
+/** A scientist the user supports, with what they gave across their proposals and RFP pools. */
export interface SupportedPerson {
readonly profile: AuthorProfile;
readonly youGave: Money;
- /** The proposal of theirs the user gave the most to. */
+ /** The title of the proposal or RFP of theirs the user gave the most to. */
readonly proposalTitle?: string;
}
diff --git a/types/funder.ts b/types/funder.ts
index ab615063d..d9183b723 100644
--- a/types/funder.ts
+++ b/types/funder.ts
@@ -11,11 +11,12 @@ export interface SupportedResearcher {
fundedAmount: CurrencyAmount;
}
-export interface SupportedProposal {
+/** A proposal the user funded, or an RFP whose funding pool they put money into. */
+export interface SupportedPost {
id: number;
title: string;
slug: string;
- /** The proposal's cover, when it has one. */
+ /** The post's cover, when it has one. */
image: string | null;
createdBy: {
id: number;
@@ -35,7 +36,8 @@ export interface FunderOverview {
matchedFunds: CurrencyAmount;
distributedFunds: CurrencyAmount;
supportedResearchers: SupportedResearcher[];
- supportedProposals: SupportedProposal[];
+ supportedProposals: SupportedPost[];
+ supportedFundingPools: SupportedPost[];
// Display headline ($1.24M) — alias of distributedFunds for the hero copy.
totalGiven: CurrencyAmount;
// Alias of matchedFunds for the hero copy.
@@ -67,7 +69,7 @@ function formatMatchRatio(given: CurrencyAmount, match: CurrencyAmount): string
}
export function transformFunderOverview(raw: any): FunderOverview {
- const proposals = extractProposals(raw.supported_proposals);
+ const proposals = extractPosts(raw.supported_proposals);
const researchers = extractResearchers(proposals);
const distributedFunds: CurrencyAmount = {
@@ -94,6 +96,7 @@ export function transformFunderOverview(raw: any): FunderOverview {
distributedFunds,
supportedResearchers: researchers,
supportedProposals: proposals,
+ supportedFundingPools: extractPosts(raw.supported_funding_pools),
totalGiven,
communityMatch,
totalDeployed,
@@ -104,8 +107,8 @@ export function transformFunderOverview(raw: any): FunderOverview {
};
}
-function extractProposals(rawProposals: any[]): SupportedProposal[] {
- return (rawProposals ?? [])
+function extractPosts(rawPosts: any[]): SupportedPost[] {
+ return (rawPosts ?? [])
.filter((p: any) => p.created_by)
.map((p: any) => ({
id: p.id,
@@ -123,7 +126,7 @@ function extractProposals(rawProposals: any[]): SupportedProposal[] {
}));
}
-function extractResearchers(proposals: SupportedProposal[]): SupportedResearcher[] {
+function extractResearchers(proposals: SupportedPost[]): SupportedResearcher[] {
const map = new Map();
for (const proposal of proposals) {