feat(grants): Add public Grants Explorer#497
Merged
pettinarip merged 48 commits intomasterfrom Mar 11, 2026
Merged
Conversation
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
5 tasks
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
This was referenced Jan 29, 2026
- 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').
Contributor
Author
|
Note: Stage filter reverted to deny-list We attempted to switch the SOQL 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. |
pettinarip
reviewed
Jan 29, 2026
Member
pettinarip
left a comment
There was a problem hiding this comment.
not following the title styles as the other two boxes (bold orange text)

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
00d8d8c to
524b2d1
Compare
524b2d1 to
904a194
Compare
- Change URL from /grants to /funded-projects for consistency with link text - Reorder nav: Home, How to Apply, Funded Projects, About ESP, Blog
- 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.
9bc9717 to
423dc9d
Compare
pettinarip
approved these changes
Mar 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
/funded-projectsFeatures
Test plan
/grantsPreview: https://deploy-preview-497--ecosystem-support.netlify.app/funded-projects