← back to Grant
components/landing/FitFeed.tsx
202 lines
'use client';
import { useMemo, useState } from 'react';
import Link from 'next/link';
/* The four funding sources Grant unifies. Mirrors the real grants.source_api /
funder-type split — federal (Grants.gov + Federal Register), foundation,
corporate, and state/local. */
type Source = 'fed' | 'found' | 'corp' | 'local';
interface FeedItem {
/** mirrors grants.title */
title: string;
/** mirrors grants.funder */
funder: string;
source: Source;
/** mirrors grants.ai_fit_score (0–100) */
fit: number;
/** rendered from grants.amount_min / amount_max */
amount: string;
/** rendered from grants.deadline */
due: string;
/** low-fit row, pushed down by ranking — illustrates the filter */
lowFit?: boolean;
}
const SOURCE_LABEL: Record<Source, string> = {
fed: 'Federal',
found: 'Foundation',
corp: 'Corporate',
local: 'State / Local',
};
/* Ring tint per source, matching the badge colors. */
const SCORE_TINT: Record<Source, { bg: string; color: string }> = {
fed: { bg: '#e4f5ee', color: '#047857' },
found: { bg: '#ede9fe', color: '#7c3aed' },
corp: { bg: '#ffedd5', color: '#ea580c' },
local: { bg: '#cffafe', color: '#0891b2' },
};
const LOW_TINT = { bg: '#eef3f0', color: '#5a6b62' };
/* Representative preview feed — real, public grant programs shown as an example
of what a unified, fit-scored stream looks like. The signed-in app replaces
this with the org's live /api/grants feed scored against its mission. */
const FEED: FeedItem[] = [
{ title: 'NEA Grants for Arts Projects', funder: 'National Endowment for the Arts', source: 'fed', fit: 94, amount: '$10k–$100k', due: 'Due in 6 days' },
{ title: 'Kresge Foundation — Health Equity', funder: 'The Kresge Foundation', source: 'found', fit: 90, amount: 'up to $250k', due: 'Due in 19 days' },
{ title: 'CA Arts Council — Local Impact', funder: 'California Arts Council', source: 'local', fit: 88, amount: '$18k', due: 'Due in 31 days' },
{ title: 'Bank of America Charitable Fund', funder: 'Bank of America', source: 'corp', fit: 81, amount: '$25k', due: 'Due in 44 days' },
{ title: 'Mellon Foundation — Humanities in Place', funder: 'Andrew W. Mellon Foundation', source: 'found', fit: 76, amount: 'up to $500k', due: 'Due in 52 days' },
{ title: 'Rural Broadband Infrastructure Pilot', funder: 'USDA Rural Development', source: 'fed', fit: 38, amount: '$1M+', due: 'Low fit — filtered down', lowFit: true },
];
const FILTERS: { key: 'all' | Source; label: string }[] = [
{ key: 'all', label: 'All' },
{ key: 'fed', label: 'Federal' },
{ key: 'found', label: 'Foundation' },
{ key: 'corp', label: 'Corporate' },
{ key: 'local', label: 'Local' },
];
function FeedCard({ item }: { item: FeedItem }) {
const tint = item.lowFit ? LOW_TINT : SCORE_TINT[item.source];
return (
<div className="gcard">
<div className="score" style={{ background: tint.bg, color: tint.color }}>
<span className="pct">{item.fit}%</span>
<span className="lab">fit</span>
</div>
<div className="gmeta">
<div className="nm">{item.title}</div>
<div className="row">
<span className={`src ${item.source}`}>{SOURCE_LABEL[item.source]}</span>
<span>{item.amount}</span>
<span className={item.lowFit ? 'due low' : 'due'}>{item.due}</span>
</div>
</div>
</div>
);
}
export default function FitFeed() {
const [filter, setFilter] = useState<'all' | Source>('all');
const visible = useMemo(
() => (filter === 'all' ? FEED : FEED.filter((g) => g.source === filter)),
[filter],
);
return (
<div className="ff">
{/* Nav */}
<nav className="nav">
<div className="nbar">
<div className="logo"><span className="lbadge">G</span>Grant</div>
<div className="navlinks">
<a href="#feed">Feed</a>
<a href="#why">Fit scores</a>
<a href="#why">Proposals</a>
<Link href="/login">Sign in</Link>
<Link className="btn" href="/login">Start free</Link>
</div>
</div>
</nav>
{/* Hero + live feed */}
<section className="wrap hero" id="feed">
<div>
<span className="pill"><span className="dot" />One feed, every funding source</span>
<h1>Stop tab-hopping. <span>Every grant, ranked by fit.</span></h1>
<p className="sub">
Federal, foundation, corporate, and local grants — siloed everywhere
else — unified into a single feed and scored against your mission.
See the 94%-fit opportunity before the 40% noise.
</p>
<div className="cta-row">
<Link className="btn btn-lg" href="/login">See your fit scores</Link>
<a className="btn-out" href="#feed">Browse the feed</a>
</div>
<p className="undr">
Rivals are siloed — Grants.gov is federal-only, Candid is
foundation-centric. Grant scores them all together.
</p>
</div>
<div className="feed">
<div className="feedhead">
<div className="t">Your fit-scored feed</div>
<div className="filters" role="tablist" aria-label="Filter feed by source">
{FILTERS.map((f) => (
<button
key={f.key}
role="tab"
aria-selected={filter === f.key}
className={filter === f.key ? 'chip on' : 'chip'}
onClick={() => setFilter(f.key)}
>
{f.label}
</button>
))}
</div>
</div>
<div className="feedlist">
{visible.length === 0 ? (
<div className="feedempty">No {filter} opportunities in this sample feed.</div>
) : (
visible.map((item) => <FeedCard key={item.title} item={item} />)
)}
</div>
</div>
</section>
{/* Stat strip */}
<div className="strip">
<div className="wrap">
<div className="scell"><b>4 source types</b><span>federal · foundation · corporate · local</span></div>
<div className="scell"><b>1 fit score</b><span>tuned to your org profile</span></div>
<div className="scell"><b>No double-tooling</b><span>free federal feed built in</span></div>
<div className="scell"><b>Individuals welcome</b><span>not just funded dev teams</span></div>
</div>
</div>
{/* Why */}
<section className="why wrap" id="why">
<h2>Precision the firehose can’t give you</h2>
<p className="l">GrantWatch lists everything. Candid has the data. Neither ranks it for <em>you</em>.</p>
<div className="cols">
<div className="col">
<span className="k">Source-unified</span>
<h3>One ranked stream</h3>
<p>Federal + foundation + corporate + local + individual in one ranked stream — no more paying for one tool and still tabbing over to Grants.gov.</p>
</div>
<div className="col">
<span className="k">Fit scoring</span>
<h3>Ranked, not listed</h3>
<p>Every opportunity scored against your mission, budget, and history. The 38%-fit gets pushed down; the 94% rises to the top.</p>
</div>
<div className="col">
<span className="k">Action-first</span>
<h3>Discovery into draft</h3>
<p>Click a high-fit grant and draft the LOI in the same screen. Discovery flows straight into a proposal and outreach.</p>
</div>
</div>
</section>
{/* Closer */}
<section className="closer">
<div className="wrap">
<h2>See what actually fits your mission.</h2>
<Link className="btn-w" href="/login">See your fit scores</Link>
</div>
</section>
<footer className="foot">
Grant — Deadline River · One fit-scored feed for under-resourced nonprofits · Federal data via the U.S. Federal Register API
</footer>
</div>
);
}