feat(revamp): Block C — funding signal (closes #42)#54
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
6 tasks
Block C of the Phase 1 revamp. Teams can declare they're looking for funding (grant / bounty / pre-seed / seed / other) with an optional amount range and short description. One row per project, updated in place. Partner-side discovery is deliberately out of scope. Phase 1 revamp Block C (single-issue block), issue #42. See docs/stadium-revamp-phase-1-spec.md §4.4 and §5 Issue 7. Server: - supabase/migrations/20260422020000_create_project_funding_signals.sql — one-row-per-project table with CHECK on funding_type enum and description length ≤ 500; partial index on is_seeking = true. - server/api/repositories/funding-signal.repository.js — getByProject + upsert (by project_id). - server/api/services/funding-signal.service.js — thin wrap. - server/api/controllers/project.controller.js — getFundingSignal (public, 404 on unknown project, default shape when no row), updateFundingSignal (requireTeamMemberOrAdmin, trims inputs, 422 on validation failure). - server/api/routes/m2-program.routes.js — GET + PATCH endpoints. - server/api/utils/validation.js — validateFundingSignal + ALLOWED_FUNDING_TYPES constant. - server/api/middleware/auth.middleware.js — adds SIWS statement + regex pattern for "Update funding signal for <project> on Stadium". - 7 new unit tests covering GET 404/default/hit and PATCH 404/invalid-type/too-long-description/happy-path/clear-toggle. Client: - client/src/lib/api.ts — ApiFundingSignal type + api.getFundingSignal + api.updateFundingSignal with mock-mode fallbacks. - client/src/lib/mockFundingSignals.ts (new) — seeded for Plata Mia and Kleo Protocol so the badge renders in preview mode. - client/src/lib/siwsUtils.ts — new 'update-funding-signal' action producing the statement that matches the server-side regex. - client/src/components/project/FundingSignalBadge.tsx (new) — renders a small "Looking for <type> · <amount>" badge + optional description when is_seeking = true; null when not. - client/src/components/project/EditFundingSignalModal.tsx (new) — Switch for is_seeking, Select for type, inline-validated fields (amount range ≤ 100 chars, description ≤ 500), SIWS sign + PATCH. - client/src/pages/ProjectDetailsPage.tsx — fetches funding signal alongside the project; renders badge + edit affordance at the top of the Overview tab; mounts the modal. Validation strategy (per spec §10): - Description bound (500) identical on client and server. - funding_type enum identical on client and server. - isSeeking is boolean-coerced server-side. Verification: - server/npm test: 29/29 passing (7 new + 22 existing). - client/npm run build: clean (pre-existing warnings only).
e18947f to
3511ac9
Compare
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.
Block C of the Phase 1 revamp — single issue. Teams can declare they're looking for funding.
Closes #42.
Journey slice (per spec §12)
"I can flag that we're looking for a grant." Team member opens project → Overview tab → clicks Edit → toggles is_seeking, picks type + amount range + short description → signs SIWS → "Looking for a grant · 30k–60k USD" badge renders on the Overview.
Summary
Schema + API + UI shipped together in one commit since all three layers are small and the feature doesn't make sense without all of them.
project_funding_signalstable — 1:1 with projects but stored as a separate table so future history tracking doesn't need another migration. CHECK on funding_type enum (grant | bounty | pre_seed | seed | other), description length ≤ 500, partial index onis_seeking = true.GET /api/m2-program/:id/funding-signal(public, returns a default no-signal shape when no row exists).PATCHsame path gated byrequireTeamMemberOrAdmin. SIWS statementUpdate funding signal for <project> on Stadiumadded to the valid statement list.FundingSignalBadgerenders whenisSeeking=true;EditFundingSignalModalwith switch, select, inline-validated fields, SIWS sign + PATCH. Both rendered on the Overview tab of the project detail page.Test plan
Automated:
cd server && npm test→ 29/29 (7 new + 22 existing).cd client && npm run build→ clean.Playwright (auto-verifiable):
/m2-program/plata-mia-15ac43, Overview tab shows a "Looking for a grant · 30k–60k USD" badge.GET /api/m2-program/<any-project-id>/funding-signalreturns{ data: { isSeeking: false, ... } }when no row exists.GET /api/m2-program/plata-mia-15ac43/funding-signalreturns the seeded signal withisSeeking: true, fundingType: "grant".Manual SIWS QA:
is_seeking = true, type = grant, amountRange = "30k–60k USD", description = "looking for grant"→ sign → badge appears on Overview without page reload.is_seeking = false, previous metadata retained on server).Out of scope (per spec)
Schema note
The partial index
WHERE is_seeking = TRUEis intentional — most projects won't have a signal at all, and the common query is "list projects currently seeking", so an index on that predicate only is lean. Can be removed if query shapes evolve.