Skip to content

Commit d915545

Browse files
taearlsclaude
andcommitted
feat: implement email contact form with Postmark API (#14)
Add working email contact form with enterprise-grade spam protection: Backend (Cloudflare Worker): - POST /api/contact endpoint for form submission - Cloudflare Turnstile verification (privacy-friendly CAPTCHA) - Honeypot field for additional bot detection - Rate limiting: 5 requests/hour/IP using Cloudflare KV - Postmark API integration for email delivery - Full input validation with proper error messages - CORS configuration for allowed origins Frontend (React): - Complete form with name, email, message fields - Turnstile widget integration (@marsidev/react-turnstile) - Client-side email validation with inline errors - Loading, success, and error states - Character counter for message field (5000 char limit) - Full accessibility support (ARIA, screen reader announcements) Testing: - 22 unit tests for ContactEmailForm - Integration tests with Cypress - All 182 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ac99a4c commit d915545

14 files changed

Lines changed: 1708 additions & 45 deletions

File tree

.env.development

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
# Feature Flags API Configuration (Development)
22
VITE_FEATURE_FLAGS_API_URL=http://localhost:8787/api/flags
3+
4+
# Contact Form API Configuration (Development)
5+
VITE_CONTACT_FORM_API_URL=http://localhost:8788/api/contact
6+
7+
# Cloudflare Turnstile Configuration (Development)
8+
# Use "1x00000000000000000000AA" for always pass in development
9+
# Use "2x00000000000000000000AB" for always block in development
10+
VITE_TURNSTILE_SITE_KEY=1x00000000000000000000AA

.env.production

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# Feature Flags API Configuration (Production)
22
VITE_FEATURE_FLAGS_API_URL=https://portfolio-feature-flags.tyler-a-earls.workers.dev/api/flags
3+
4+
# Contact Form API Configuration (Production)
5+
VITE_CONTACT_FORM_API_URL=https://portfolio-contact-form.tyler-a-earls.workers.dev/api/contact
6+
7+
# Cloudflare Turnstile Configuration (Production)
8+
# Replace with your actual Turnstile site key from Cloudflare Dashboard
9+
VITE_TURNSTILE_SITE_KEY=YOUR_PRODUCTION_SITE_KEY

ROADMAP.md

Lines changed: 105 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
## Executive Summary
44

5-
This roadmap outlines the development plan for Tyler Earls' portfolio website, focusing on performance optimization, modern React tooling, and enhanced user experience. The project has **completed Phase 5 (React Compiler Integration)** and **Phase 6 (CI/CD setup)**, and is now working through **Phase 7 (UI/UX Enhancements)** with 9 open issues.
5+
This roadmap outlines the development plan for Tyler Earls' portfolio website, focusing on performance optimization, modern React tooling, and enhanced user experience. The project has **completed Phase 5 (React Compiler Integration)** and **Phase 6 (CI/CD setup)**, and is now working through **Phase 7 (UI/UX Enhancements)** with 7 open issues.
66

7-
**Current Focus**: Feature flag infrastructure (#72) complete! Performance optimization sprint done with #61, #62, #63, #11, and #27. Next: Contact form (#14).
7+
**Current Focus**: Contact form (#14) complete! Feature flag infrastructure (#72) and performance optimization sprint done with #61, #62, #63, #11, and #27. Phase 7 core features complete!
88

99
---
1010

@@ -21,7 +21,7 @@ This roadmap outlines the development plan for Tyler Earls' portfolio website, f
2121

2222
## Open Issues Summary
2323

24-
### Priority Breakdown (8 Total - 6 Completed)
24+
### Priority Breakdown (7 Total - 7 Completed)
2525

2626
#### 🔴 Critical Priority (0 issues)
2727

@@ -37,14 +37,13 @@ This roadmap outlines the development plan for Tyler Earls' portfolio website, f
3737

3838
**#63** - Fix Cumulative Layout Shift (CLS) on Mobile - **COMPLETED Nov 23, 2025**
3939

40-
#### 🟢 Medium Priority (1 issue) - Effort: ~8-16 hours
40+
#### 🟢 Medium Priority (0 issues) - Effort: ~8-16 hours
4141

4242
**#11** - Preload Sprite SVG in development and production - **COMPLETED Nov 24, 2025**
4343

4444
**#27** - UI - Lazy Load Routes with React Router - **COMPLETED Nov 24, 2025**
4545

46-
- **#14** - Add Working Email Contact Form - _~1-2 days_
47-
- Impact: User engagement and professional contact method
46+
**#14** - Add Working Email Contact Form - **COMPLETED Dec 7, 2025**
4847

4948
#### 🔵 Low Priority (7 issues) - Effort: ~2-3 weeks
5049

@@ -199,13 +198,12 @@ _Successfully migrated to TailwindCSS v4 with modern config_
199198
- Changes: Complete feature flag system with Cloudflare Workers + KV + React Context
200199
- Result: Runtime feature toggling without redeployment, ready for #14
201200

202-
**Next Up**:
203-
204-
7. **#14** - Add Working Email Contact Form - **START NEXT**
201+
7.**#14** - Add Working Email Contact Form - **COMPLETED**
205202
- Priority: 🟢 MEDIUM
206-
- Effort: ~1-2 days
207-
- Impact: User engagement - professional contact method
208-
- Prerequisites: ✅ #72 (feature flag infrastructure complete)
203+
- Status: Completed Dec 7, 2025
204+
- Effort: ~1-2 days (actual)
205+
- Changes: Complete email contact form with Postmark API, Cloudflare Turnstile CAPTCHA, honeypot, rate limiting
206+
- Result: Professional contact method with triple-layer spam protection
209207

210208
**Recent Sprint Completed (Oct 30 - Nov 13, 2025)**:
211209

@@ -315,11 +313,11 @@ Phase 7: Accessibility & Core Web Vitals ✅ COMPLETE
315313
├── 🔵 #64 WCAG AAA contrast (2-4h) - ENHANCEMENT
316314
└── 🔵 #65 Font size readability (1-2h) - ENHANCEMENT
317315
318-
Phase 7: Performance & UX (CURRENT FOCUS)
316+
Phase 7: Performance & UX ✅ CORE COMPLETE
319317
├── ✅ #11 SVG Preloading (30min) - COMPLETED
320318
├── ✅ #27 Lazy Routes (2h) - COMPLETED
321319
├── ✅ #72 Feature Flags (8-12h) - COMPLETED - Runtime toggling infrastructure
322-
└── #14 Contact Form (1-2 days) - User engagement - NEXT
320+
└── #14 Contact Form (1-2 days) - COMPLETED - Postmark + Turnstile + spam protection
323321
324322
Phase 8: Research (Anytime)
325323
├── #33 Graphite spike (1-2h)
@@ -393,9 +391,9 @@ _None - All prerequisites for #43 are complete. Ready to implement._
393391
| ----------- | ----- | ----------- | -------------------- | ---------- |
394392
| 🔴 Critical | 0 | 0 | 1 | 0 |
395393
| 🟡 High | 0 | 0 | 10 | 0 |
396-
| 🟢 Medium | 1 | 0 | 4 | 1 |
394+
| 🟢 Medium | 0 | 0 | 5 | 0 |
397395
| 🔵 Low | 7 | 0 | 0 | 7 |
398-
| **TOTAL** | **8** | **0** | **15** | **8** |
396+
| **TOTAL** | **7** | **0** | **16** | **7** |
399397

400398
### Issues by Category
401399

@@ -404,18 +402,18 @@ _None - All prerequisites for #43 are complete. Ready to implement._
404402
**Infrastructure** (0 open, 1 closed): Closed: #72 (feature flags)
405403
**CI/CD** (0 open, 2 closed): Closed: #18 (GitHub Actions pipeline), #72 (feature flags)
406404
**Accessibility** (2 open, 3 closed): Open: #64, #65 | Closed: #61 (navigation contrast), #62 (touch targets), #63 (CLS mobile)
407-
**UI/UX** (4 open, 5 closed): Open: #10, #13, #14, #15 | Closed: #58 (left-align text), #28 (React 19 Meta), #11 (SVG preload), #27 (lazy routes), #72 (feature flags)
405+
**UI/UX** (3 open, 6 closed): Open: #10, #13, #15 | Closed: #58 (left-align text), #28 (React 19 Meta), #11 (SVG preload), #27 (lazy routes), #72 (feature flags), #14 (contact form)
408406
**Research** (2 open): #33, #34
409407

410408
### Effort Distribution (Open Issues Only)
411409

412-
| Effort Level | Count | Issues |
413-
| ------------- | ----- | ------------------------------ |
414-
| Small (< 2h) | 4 | #13, #33, #34, #65 |
415-
| Medium (2-8h) | 2 | #64, #10 |
416-
| Large (> 8h) | 2 | #14 (1-2 days), #15 (1-2 days) |
410+
| Effort Level | Count | Issues |
411+
| ------------- | ----- | ------------------ |
412+
| Small (< 2h) | 4 | #13, #33, #34, #65 |
413+
| Medium (2-8h) | 2 | #64, #10 |
414+
| Large (> 8h) | 1 | #15 (1-2 days) |
417415

418-
**Note**: Issue #14 categorized as Large based on 1-2 days estimate (~8-16 hours total effort).
416+
**Note**: All medium and high priority issues complete. Only low priority and enhancements remain.
419417

420418
---
421419

@@ -480,6 +478,89 @@ _None - All prerequisites for #43 are complete. Ready to implement._
480478

481479
## Changelog
482480

481+
### 2025-12-07 - Issue #14 Completed: Add Working Email Contact Form
482+
483+
- **Completed**: #14 - Add Working Email Contact Form
484+
- **Priority**: 🟢 MEDIUM (GitHub labels: `type: feature`, `area: ui`, `priority: medium`, `effort: large`)
485+
- **Status**: Completed Dec 7, 2025
486+
- **Effort**: ~1-2 days (actual)
487+
- **Impact**: Professional contact method with enterprise-grade spam protection
488+
489+
**Implementation Summary**:
490+
491+
Delivered a complete, production-ready contact form with:
492+
493+
1. **Cloudflare Worker Backend** (`packages/contact-form/`)
494+
- POST `/api/contact` endpoint for form submission
495+
- Cloudflare Turnstile verification (privacy-friendly CAPTCHA)
496+
- Honeypot field for bot detection
497+
- Rate limiting: 5 requests/hour/IP using Cloudflare KV
498+
- Postmark API integration for email delivery
499+
- Input validation with proper error messages
500+
- CORS configuration for allowed origins
501+
- Full TypeScript implementation
502+
503+
2. **React Frontend Enhancement** (`src/components/ContactEmailForm.tsx`)
504+
- Complete form with name, email, message fields
505+
- Cloudflare Turnstile widget integration (@marsidev/react-turnstile)
506+
- Hidden honeypot field for additional bot protection
507+
- Client-side email validation with inline errors
508+
- Loading, success, and error states
509+
- Character counter for message field (5000 char limit)
510+
- Accessibility: ARIA attributes, screen reader announcements, role="alert"
511+
- Form clears and resets Turnstile on successful submission
512+
513+
3. **Testing Suite**
514+
- Unit tests with Vitest (22 tests for ContactEmailForm)
515+
- Integration tests with Cypress (form display, validation, accessibility)
516+
- Tests cover: rendering, validation, submission, error handling, Turnstile integration
517+
518+
**Files Created**:
519+
520+
- `packages/contact-form/src/index.ts` - Main Worker with full contact form logic
521+
- `packages/contact-form/package.json` - Package configuration
522+
- `packages/contact-form/tsconfig.json` - TypeScript config
523+
- `packages/contact-form/wrangler.toml` - Cloudflare Worker config
524+
- `tests/unit/components/ContactEmailForm.test.tsx` - Unit tests (22 tests)
525+
- `tests/integration/contact-form.cy.ts` - Integration tests
526+
527+
**Files Modified**:
528+
529+
- `src/components/ContactEmailForm.tsx` - Complete rewrite with full functionality
530+
- `src/util/constants.ts` - Added API_URIS.CONTACT, TURNSTILE_SITE_KEY
531+
- `src/vite-env.d.ts` - Added new environment variable types
532+
- `.env.development` - Added contact form and Turnstile URLs
533+
- `.env.production` - Added contact form and Turnstile URLs
534+
- `package.json` (root) - Added workspace, scripts, @marsidev/react-turnstile dependency
535+
536+
**Technical Highlights**:
537+
538+
- **Security**: Triple-layer spam protection (Turnstile + honeypot + rate limit)
539+
- **Privacy**: Cloudflare Turnstile is privacy-friendly (no tracking cookies)
540+
- **Performance**: Edge-deployed Worker for low latency
541+
- **Reliability**: Graceful error handling, form validation, retry support
542+
- **Email Delivery**: Postmark API with verified sender domain (tylerearls.com)
543+
- **Feature Flag Ready**: Works with existing feature flag infrastructure (#72)
544+
545+
**Deployment Requirements**:
546+
547+
1. Create Cloudflare Turnstile site (get Site Key and Secret Key)
548+
2. Create KV namespace for rate limiting
549+
3. Set Worker secrets: `POSTMARK_SERVER_TOKEN`, `TURNSTILE_SECRET_KEY`
550+
4. Deploy Worker: `npm run deploy:contact`
551+
5. Enable feature flag: `email-contact-form.enabled = true`
552+
553+
**Impact on Roadmap**:
554+
555+
- Completes: Phase 7 core features (contact form was the last major feature)
556+
- Reduces open issues: 8 → 7
557+
- All medium and high priority issues now complete!
558+
- Only low priority and enhancement issues remain
559+
560+
**Next Actions**: Low priority features (#10, #13, #15) or accessibility enhancements (#64, #65) as time permits
561+
562+
---
563+
483564
### 2025-11-24 - Issue #72 Completed: Feature Flag Infrastructure with Cloudflare Workers + KV
484565

485566
- **Completed**: #72 - Implement Feature Flag Infrastructure with Cloudflare Workers + KV
@@ -1163,6 +1244,6 @@ All high-priority accessibility issues (#61, #62, #63) have been resolved. Mediu
11631244

11641245
---
11651246

1166-
**Last Updated**: November 16, 2025 (ROADMAP Refresh - 5 New Accessibility & Performance Issues)
1247+
**Last Updated**: December 7, 2025 (Issue #14 Complete - Contact Form with Postmark + Turnstile)
11671248
**Maintained By**: Tyler Earls
11681249
**Generated With**: Claude Code

package-lock.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
"preview": "vite preview --open",
2626
"ci": "npm run lint:check && npm run oxlint:check && npm run format:check && npm run test:all && npm run build",
2727
"dev:flags": "npm run dev -w @portfolio/feature-flags",
28-
"dev:all": "concurrently \"npm run dev\" \"npm run dev:flags\"",
29-
"deploy:flags": "npm run deploy -w @portfolio/feature-flags"
28+
"dev:contact": "npm run dev -w @portfolio/contact-form",
29+
"dev:all": "concurrently \"npm run dev\" \"npm run dev:flags\" \"npm run dev:contact\"",
30+
"deploy:flags": "npm run deploy -w @portfolio/feature-flags",
31+
"deploy:contact": "npm run deploy -w @portfolio/contact-form"
3032
},
3133
"dependencies": {
3234
"@cloudinary/url-gen": "^1.22.0",
35+
"@marsidev/react-turnstile": "^1.3.1",
3336
"@xstate/react": "^6.0.0",
3437
"react": "^19.2.0",
3538
"react-dom": "^19.2.0",
@@ -46,6 +49,7 @@
4649
"@testing-library/cypress": "^10.1.0",
4750
"@testing-library/jest-dom": "^6.9.1",
4851
"@testing-library/react": "^16.3.0",
52+
"@testing-library/user-event": "^14.6.1",
4953
"@tsconfig/node22": "^22.0.2",
5054
"@types/mocha": "^10.0.10",
5155
"@types/node": "^22.10.4",

packages/contact-form/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "@portfolio/contact-form",
3+
"version": "1.0.0",
4+
"private": true,
5+
"main": "src/index.ts",
6+
"scripts": {
7+
"dev": "wrangler dev --port 8788",
8+
"deploy": "wrangler deploy",
9+
"typecheck": "tsc --noEmit"
10+
},
11+
"devDependencies": {
12+
"@cloudflare/workers-types": "^4.20240117.0",
13+
"typescript": "^5.3.3",
14+
"wrangler": "^4.51.0"
15+
}
16+
}

0 commit comments

Comments
 (0)