-
- Community contributions are pooled and awarded to the strongest proposals by {funderLabel}.
-
-
+
{breakdownRow(funderLabel, formatAmount(grantAmount, showUSD))}
- {breakdownRow('Community', formatAmount(raised, showUSD))}
+ {breakdownRow(backersLabel, formatAmount(raised, showUSD))}
+
+
{breakdownRow('Allocated to proposals', formatAmount(allocated, showUSD))}
- {showHolding && breakdownRow('Ready to allocate', formatAmount(holding, showUSD), true)}
+ {showHolding && breakdownRow('Available to allocate', formatAmount(holding, showUSD), true)}
);
@@ -114,14 +177,21 @@ export function GrantFundingPoolWidget({
const nothingToDo = !canApply && !isOpen;
return (
+ // Raised white panel, matching the FundingPowerCard that sits above the
+ // sidebar rail — at lg+ this widget occupies that same column, and on
+ // mobile the card keeps the thin funding bar from reading as a stray
+ // page rule.
-
+
Funding pool
- {!isOpen && · Closed }
+ {!isOpen && · Closed }
-
+
@@ -139,7 +209,7 @@ export function GrantFundingPoolWidget({
- {/* Fixed-width labels ("Funder", "Community") so this line never wraps; the org name is in the subtitle beside it. */}
-
-
+ {/* Legend for the bar above: green is the funder, indigo is the community.
+ The backers' side spends its label budget on faces instead of the word
+ — the +N chip carries the indigo, so the pairing still reads — because
+ in RSC the two amounts alone take 123px of the 270px this row gets at
+ the narrowest card width. Fixed h-6, the height of one avatar, so the
+ card never grows as backers arrive. */}
+
+
Funder
-
+
{formatCompact(grantAmount, showUSD)}
- {hasCommunityFunding ? (
-
-
- Community
-
+
+
+ {hasFaces ? (
+ ({
+ src: backer.profileImage,
+ alt: backer.fullName,
+ tooltip: backer.fullName,
+ authorId: backer.authorProfileId,
+ }))}
+ size="xs"
+ maxItems={MAX_FACES}
+ spacing={-6}
+ showExtraCount
+ totalItemsCount={backerCount}
+ extraCountLabel={`${backerCount - MAX_FACES} more ${backerNoun}`}
+ extraCountClassName="bg-indigo-500"
+ extraCountLabelClassName="text-white"
+ showLabel={false}
+ />
+ ) : (
+
+ )}
+ {hasCommunityFunding ? (
+
{formatCompact(raised, showUSD)}
-
- ) : (
-
-
- {isOpen ? 'Be the first to add' : 'No community funding'}
-
- )}
+ ) : (
+
+ {isOpen ? 'Be the first backer' : 'No backers'}
+
+ )}
+
- {nothingToDo ? (
- This RFP is closed
- ) : (
-
- {canApply && (
-
-
- Apply with proposal
-
-
-
- )}
- {isOpen && (
+ {/* Add to pool carries the card — these links get shared at funders, so it
+ stays the only filled control. Applying is the same size but outlined,
+ so the two domains never read as one flow. */}
+ {isOpen && (
+
+
+ Add to pool
+
+ )}
+
+ {canApply && (
+
+
-
- Add to the pool
+
+ Submit proposal
- )}
+
)}
+
+ {nothingToDo &&
This RFP is closed
}
);
}
diff --git a/components/work/WorkHeader/WorkHeaderGrant.tsx b/components/work/WorkHeader/WorkHeaderGrant.tsx
index 63cdbad76..dace2d839 100644
--- a/components/work/WorkHeader/WorkHeaderGrant.tsx
+++ b/components/work/WorkHeader/WorkHeaderGrant.tsx
@@ -1,8 +1,11 @@
'use client';
import { type ReactNode, useState, useCallback } from 'react';
-import { ArrowUpFromLine, Coins, Lock } from 'lucide-react';
-import { useRouter, useSearchParams } from 'next/navigation';
+import { Bell, Coins, FileText, FileUp, Lock } from 'lucide-react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faFileSignature } from '@fortawesome/pro-light-svg-icons';
+import { faFileSignature as faFileSignatureSolid } from '@fortawesome/pro-solid-svg-icons';
+import { useRouter } from 'next/navigation';
import { Work } from '@/types/work';
import { WorkMetadata } from '@/services/metadata.service';
import { Button } from '@/components/ui/Button';
@@ -12,26 +15,13 @@ import { ContributeToFundraiseModal } from '@/components/modals/ContributeToFund
import { useGrantTab, type GrantBannerTab } from '@/components/Funding/GrantPageContent';
import { useFundraises } from '@/contexts/FundraiseContext';
import { useUser } from '@/contexts/UserContext';
-import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext';
import type { FundingPool, FundingPoolAmount, GrantApplicationVisibility } from '@/types/grant';
-import { formatCurrency } from '@/utils/currency';
import { ID } from '@/types/root';
import { WorkHeader } from './WorkHeader';
import { WorkHeaderGrantEyebrow } from './WorkHeaderGrantEyebrow';
import { GrantFundingPoolWidget } from './GrantFundingPoolWidget';
import { PendingReviewBadge } from './PendingReviewBadge';
-const RFP_FUNDING_POOL_PARAM = 'rfpFundingPool';
-
-function formatPoolAmount(amount: { usd: number; rsc: number }, showUSD: boolean): string {
- return formatCurrency({
- amount: showUSD ? amount.usd : amount.rsc,
- showUSD,
- exchangeRate: 1,
- skipConversion: true,
- });
-}
-
interface WorkHeaderGrantProps {
work: Work;
metadata: WorkMetadata;
@@ -65,12 +55,7 @@ export function WorkHeaderGrant({
preTitle,
}: WorkHeaderGrantProps) {
const router = useRouter();
- const searchParams = useSearchParams();
const { user } = useUser();
- const { showUSD } = useCurrencyPreference();
- const isRfpFundingPoolEnabled =
- searchParams.get(RFP_FUNDING_POOL_PARAM) === 'true' ||
- searchParams.get(RFP_FUNDING_POOL_PARAM) === '1';
const [isApplyModalOpen, setIsApplyModalOpen] = useState(false);
const [isContributeModalOpen, setIsContributeModalOpen] = useState(false);
const {
@@ -96,8 +81,8 @@ export function WorkHeaderGrant({
const canManagePool = isGrantCreator || !!user?.isModerator;
// The pool widget replaces the amount eyebrow and the bare Contribute button
- // whenever the flag is on and the grant has a pool, open or closed.
- const showPoolWidget = isRfpFundingPoolEnabled && !!fundingPool && !!grantAmount;
+ // whenever the grant has a pool, open or closed.
+ const showPoolWidget = !!fundingPool && !!grantAmount;
const isPoolOpen = !!grantId && isActive && fundingPool?.status === 'OPEN';
const eyebrow = showPoolWidget ? (
@@ -109,7 +94,6 @@ export function WorkHeaderGrant({
);
const requiresPrivateApplications = applicationVisibility === 'PRIVATE';
- const canContributeToPool = isRfpFundingPoolEnabled && isPoolOpen;
const handleContributeSuccess = useCallback(
(updatedPool?: FundingPool) => {
@@ -138,7 +122,7 @@ export function WorkHeaderGrant({
className="gap-2 w-full sm:flex-1 max-sm:!text-xs max-sm:!h-8 max-sm:!px-2"
>
Submit Proposal
-
+
) : null;
@@ -150,17 +134,14 @@ export function WorkHeaderGrant({
) : null;
- const showLegacyPoolHolding =
- !showPoolWidget &&
- isRfpFundingPoolEnabled &&
- canManagePool &&
- fundingPool?.status === 'OPEN' &&
- (fundingPool.amountHolding.rsc ?? 0) >= 0;
-
let primaryAction: ReactNode;
if (showPoolWidget && fundingPool && grantAmount) {
primaryAction = (
-
+ // At lg+ the right sidebar appears, so the widget takes the sidebar's
+ // exact column: w-80 to match, and -mr-8 to cancel the header's px-8 so
+ // its right edge lands on the shared 1180px container edge like the
+ // sidebar's does. Below lg there is no sidebar to align to.
+
setIsApplyModalOpen(true)}
onContribute={() => setIsContributeModalOpen(true)}
/>
+ {isPoolOpen && (
+
+ Every dollar you add goes to the proposals. {organization || 'the funder'} picks.
+
+ )}
);
} else if (grantId && isActive) {
primaryAction = (
<>
- {canContributeToPool && (
+ {isPoolOpen && (
- {showLegacyPoolHolding && fundingPool && (
-
-
- Pool holding{' '}
-
- {formatPoolAmount(fundingPool.amountHolding, showUSD)}
-
-
- {(fundingPool.amountDistributed.rsc ?? 0) > 0 && (
-
- Distributed{' '}
-
- {formatPoolAmount(fundingPool.amountDistributed, showUSD)}
-
-
- )}
-
- )}
{privateApplicationsNote}
>
);
@@ -223,11 +191,23 @@ export function WorkHeaderGrant({
activityCount > 0 && activity.hasMore ? `${activityCount}+` : activityCount;
const grantTabs = [
- { id: 'details' as const, label: 'Details' },
+ {
+ id: 'details' as const,
+ label: (
+
+
+ Details
+
+ ),
+ },
{
id: 'proposals' as const,
label: (
+
Proposals
{proposalCount > 0 && (
+
Updates
{activityCount > 0 && (
- {isRfpFundingPoolEnabled && fundingPool?.status === 'OPEN' && (
+ {fundingPool?.status === 'OPEN' && (