feat(programs): non-member apply flow — emails the team (#140)#150
Merged
Conversation
Lets someone without a Stadium project apply to a program. A public form
emails the team; they approve manually and reply (matching the original ask).
- POST /api/programs/:slug/applications/non-member (public, no auth): validates
{name, email, walletAddress?, pitch}, honeypot field, then emails the team.
- non-member-application.service: sends ONE email to info@joinwebzero.com with
sacha@joinwebzero.com cc'd and applicant details in the body. Fixed recipients
only (applicant address never used as a recipient) so it can't be abused as an
open relay. HTML-escaped body. Returns 503 if Resend isn't configured.
- email-transport: forward cc to Resend.
- Client: NonMemberApplyModal + a 'Don't have a Stadium project yet?' CTA on
open program detail pages; api.submitNonMemberApplication.
- Tests: validation + the email to/cc/body + HTML-escaping.
Note: the original confirmation-to-applicant was intentionally dropped — it'd
let the endpoint email arbitrary addresses from our verified domain.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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
Closes #140. Lets someone without a Stadium project apply to a program: a public form emails the team, who approve manually and reply — exactly the original ask.
Design decision (flag if you disagree)
The original feedback was "sends an email to info@joinwebzero.com with sacha@ cc'd with the person's request… then I add them in and they get a mail back saying they're approved." So the auto email goes only to the team (fixed recipients); the "you're approved" reply is sent manually later. I deliberately did not auto-send a confirmation to the applicant's address — doing so would let anyone use our verified Resend domain to email arbitrary addresses (open-relay / phishing amplification). The applicant's details live in the email body, never as a recipient.
Server
POST /api/programs/:slug/applications/non-member— public, no auth (program.routes.js).program.controller.submitNonMemberApplication— honeypot check, resolve program, validate, send, return 200/400/404/503.non-member-application.service.js— validates{name, email, walletAddress?, pitch}; sends one emailto: info@joinwebzero.com,cc: [sacha@joinwebzero.com]with an HTML-escaped body. Returns503(graceful "email us directly") if Resend isn't configured.email-transport.js— forwardccto Resend (backward-compatible).Client
NonMemberApplyModal.tsx— name / email / wallet (optional) / pitch (≤1000) + hidden honeypot.ProgramDetailPage— a "Don't have a Stadium project yet? Apply here ▸" CTA on open programs opens the modal.api.submitNonMemberApplication(slug, payload)(public; mock-mode returns success).Test plan
npm test(server) — 264 passed (7 new: validation + email to/cc/body + HTML-escaping).npm run build+npm run lint(client) — green.stadium-tester@ localhost (mock) — 6/6 PASS (CTA on open program; modal fields; empty-submit validation w/ no success; valid submit → success toast; completed program has no CTA).Prod requirement
Needs
RESEND_API_KEY+RESEND_FROM_EMAIL(verified domain) on the Railway env. Without them the endpoint returns 503 with a "please email info@joinwebzero.com directly" message — no crash.Per CLAUDE.md §6: draft, never merging.