Skip to content

feat(grants): Add public Grants Explorer#497

Merged
pettinarip merged 48 commits intomasterfrom
feature/grants-explorer
Mar 11, 2026
Merged

feat(grants): Add public Grants Explorer#497
pettinarip merged 48 commits intomasterfrom
feature/grants-explorer

Conversation

@minimalsm
Copy link
Contributor

@minimalsm minimalsm commented Jan 21, 2026

Summary

  • Add public Grants Explorer page at /funded-projects
  • Display funded projects from Salesforce with pagination, sorting, and filtering
  • Show project details including name, description, domain, output, and fiscal quarter
  • Include year filter and search functionality

Features

  • Server-side data fetching from Salesforce
  • Client-side pagination (15 items per page)
  • Sortable columns (Project, Domain, Output, Quarter)
  • Text search across project name and description
  • Year filter dropdown
  • Click-through to project detail modal

Test plan

  • Verify grants page loads at /grants
  • Test pagination controls
  • Test column sorting
  • Test search filtering
  • Test year filter
  • Verify data matches Salesforce records

Preview: https://deploy-preview-497--ecosystem-support.netlify.app/funded-projects

Add a new /grants page that displays funded projects with:
- Dashboard section with fiscal year bar chart, active this month stat, and category donut chart
- Searchable and filterable table with domain/output filters
- Detail modal showing project info, GitHub link, and contact
- ISR data fetching with 1-hour revalidation

Uses mock data when Salesforce credentials unavailable (development/build).
SF field names need verification against actual schema.
- Add client-side pagination (15 items per page)
- Add column sorting (click header to sort A-Z, Z-A, or clear)
- Add clickable domain/output values to filter by that value
- Add year filter dropdown
- Fix Salesforce query pagination to fetch all records
- Switch to calendar year quarters (Jan-Dec)
- Fixed table column widths to prevent layout shift
minimalsm added a commit that referenced this pull request Jan 23, 2026
Add tests specific to public grants explorer (#497):
- Verify GrantRecord type doesn't expose private fields
- Test PUBLIC_RECORD_TYPES whitelist configuration
- Ensure sensitive record types excluded from public view
- Verify EXCLUDED_STAGES for in-progress grants
- Test query security (no private fields in public query)
- Compare public vs private grant field structures
Mock data used 'FY25 Q3' format while deriveFiscalQuarter() returns
'2025 Q1' format. Also corrected quarter values to match activatedDate.
fix: Align mock fiscal quarter format with deriveFiscalQuarter output
Validate URL fields from Salesforce at the data mapping layer to
prevent javascript:, data:, and other dangerous protocol injection.
Only http/https URLs pass through for projectRepo. Contact values
must be either an email address or a valid http/https URL.
fix(grants): Sanitize grant URL fields to prevent XSS
Distinguish between "SF credentials missing" (development/CI — use
mock data) and "SF login/query failed at runtime" (production — let
the error propagate so ISR serves the last cached page). Previously
both paths silently returned fake grants, which could serve fabricated
data on the public site for up to 1 hour during a transient SF outage.
Replace unstyled chakra('button') wrapper with Chakra's Button component
with outline variant, giving filter dropdowns proper borders and padding.
fix(grants): Only use mock data when Salesforce is not configured
fix: Use Chakra Button for grants filter menus
…erf, theme colors

- Hoist searchQuery.toLowerCase() outside filter loop (#29)
- Validate deriveFiscalQuarter input, return 'Unknown' for malformed dates (#32)
- Extract FilterMenu component to deduplicate 3 identical menu blocks (#37)
- Reference brand theme tokens instead of hardcoded hex in chart colors (#34)
refactor(grants): Deduplicate filters, validate fiscal dates, use theme tokens
The old site had a /grants/ page that was redirected to / when the
site was rebuilt. Now that there's a real page at /grants, these
Netlify redirect rules (with force=true) intercept the request and
301 to the homepage before the page can load.
Add upper bound (CloseDate < current year start) to SOQL query so only
2024 and 2025 grants appear in the explorer, not 2026.
fix(grants): Exclude current year from grants explorer
- Switch EXCLUDED_STAGES deny-list to PUBLIC_STAGES allow-list (StageName IN)
  so unknown/new stages default to excluded
- Add .env to .gitignore to prevent credential exposure
- Tighten email validation in sanitizeContact with proper regex
- Replace IIFE in GrantDetailModal with pre-computed isEmail variable
- Rename "Awarded" label to "Date" (CloseDate is a forecast field)
- Lazy-load GrantsDashboard via next/dynamic to reduce critical bundle
- Add underline affordance to clickable domain/output table cells
- Clarify fiscal year naming in JSDoc and fix stale comment
- Add ISR comment in getStaticProps explaining error propagation
- Set min-width 640px on table so columns scroll horizontally on mobile
  instead of crushing into single-character widths
- Fall back to mock data in development when SF query returns zero
  results (e.g. stage/date filters don't match any records)
Remove the CloseDate upper bound so grants from the current year are
included. The "Active This Month" dashboard stat needs recent data.
…_Round__c

Surface the SF picklist field for named grant rounds (e.g. Academic Grants
Round) as a filterable dimension in the grants explorer, detail modal, and
mock data.
The allow-list (StageName IN 'Closed Won') was too restrictive and
filtered out all grants in the preview deploy. Reverted to the original
deny-list approach (NOT IN 'In Progress', 'Prospecting').
@minimalsm
Copy link
Contributor Author

Note: Stage filter reverted to deny-list

We attempted to switch the SOQL StageName filter from a deny-list (NOT IN ('In Progress', 'Prospecting')) to an allow-list (IN ('Closed Won')), but the allow-list filtered out all 1,282 grants — the SF org uses stages beyond just "Closed Won" for awarded grants.

Reverted to the original deny-list for now. We should revisit this by auditing the actual stage values in Salesforce and building a proper allow-list that includes all valid awarded stages.

Copy link
Member

@pettinarip pettinarip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gap in mobile
Image

not following the title styles as the other two boxes (bold orange text)
Image

Nice to have:

  • could be helpful and nice ux to have a "reset filters" button. Can be annoying to unselect 4 different dropdowns to go back to the initial state
  • there is no link that target this page, no discovery. Is this because we are going in soft launch? later we could add something in the home page

Add DashboardSkeleton component that renders while charts load async,
matching the exact grid layout and card dimensions to eliminate CLS.
Move GrantsDashboard and DashboardSkeleton from GrantsExplorer component
to the grants index page. This improves code organization by having the
dashboard render alongside the explorer at the page level rather than
nested within the explorer component.
Replace deprecated Grantee_Contact_Details__c with three new contact
fields from Salesforce: email, telegram, and twitter. Each now has
dedicated icon buttons in the modal for cleaner UX.

Add support for the new Grant Round object relationship, fetching
both the round name and public description from Salesforce instead
of using hardcoded descriptions in the frontend.

- Update SOQL query to fetch Opportunity_Grant_Round__r relationship
- Add sanitizers for email and social handle validation
- Replace single Contact button with GitHub/Email/Telegram/X icons
- Show grant round description in modal and filter dropdown
- Remove hardcoded GRANT_ROUND_DESCRIPTIONS from GrantsTable
@pettinarip pettinarip force-pushed the feature/grants-explorer branch from 00d8d8c to 524b2d1 Compare March 10, 2026 19:22
@pettinarip pettinarip force-pushed the feature/grants-explorer branch from 524b2d1 to 904a194 Compare March 10, 2026 19:24
- Change URL from /grants to /funded-projects for consistency with link text
- Reorder nav: Home, How to Apply, Funded Projects, About ESP, Blog
@pettinarip pettinarip marked this pull request as ready for review March 11, 2026 13:46
@pettinarip pettinarip requested a review from wackerow as a code owner March 11, 2026 13:46
- Remove unused `extractFiscalYear` from fiscalYear.ts
- Remove unused `Button` import from GrantDetailModal
- Remove pointless `<Box>` wrapper around `<GrantsTable>` in GrantsExplorer
Prevents React from re-creating the component on every render,
avoiding unnecessary unmount/remount cycles.
…elds

Input stays responsive while filtering is debounced. Pre-computes
lowercase projectName/description once when grants change, eliminating
repeated string allocations during search.
Introduce FilterState and FilterOptions interfaces. Replace 15 individual
filter props with filters, filterOptions, and onFilterChange. Adding a new
filter now requires no prop threading.
Extract round descriptions into a deduplicated Record<string, string>
map instead of repeating them on every grant record. Reduces page
payload by eliminating per-record duplication of identical strings.
…ield helper

- Define cardStyle object used by dashboard and skeleton (8 duplicates -> 1)
- Map table headers from TABLE_COLUMNS array (4 copy-pasted Th blocks -> 1)
- Extract countByField helper for domain/output chart data
Move 70-line skeleton from the page file into GrantsDashboard.tsx so
layout changes to the dashboard and its skeleton stay in sync.
@pettinarip pettinarip force-pushed the feature/grants-explorer branch from 9bc9717 to 423dc9d Compare March 11, 2026 14:51
@pettinarip pettinarip merged commit 8c58ee0 into master Mar 11, 2026
6 checks passed
@pettinarip pettinarip deleted the feature/grants-explorer branch March 11, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants