Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
# Feature Flags API Configuration (Development)
VITE_FEATURE_FLAGS_API_URL=http://localhost:8787/api/flags

# Contact Form API Configuration (Development)
VITE_CONTACT_FORM_API_URL=http://localhost:8788/api/contact

# Cloudflare Turnstile Configuration (Development)
# Use "1x00000000000000000000AA" for always pass in development
# Use "2x00000000000000000000AB" for always block in development
VITE_TURNSTILE_SITE_KEY=1x00000000000000000000AA
7 changes: 7 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Feature Flags API Configuration (Production)
VITE_FEATURE_FLAGS_API_URL=https://portfolio-feature-flags.tyler-a-earls.workers.dev/api/flags

# Contact Form API Configuration (Production)
VITE_CONTACT_FORM_API_URL=https://portfolio-contact-form.tyler-a-earls.workers.dev/api/contact

# Cloudflare Turnstile Configuration (Production)
# Replace with your actual Turnstile site key from Cloudflare Dashboard
VITE_TURNSTILE_SITE_KEY=YOUR_PRODUCTION_SITE_KEY
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ jobs:
run: npm run test:integration
env:
VITE_FEATURE_FLAGS_API_URL: http://localhost:8787/api/flags
VITE_CONTACT_FORM_API_URL: http://localhost:8788/api/contact
VITE_TURNSTILE_SITE_KEY: 1x00000000000000000000AA

build:
name: Build
Expand Down
129 changes: 104 additions & 25 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Executive Summary

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.
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.

**Current Focus**: Feature flag infrastructure (#72) complete! Performance optimization sprint done with #61, #62, #63, #11, and #27. Next: Contact form (#14).
**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!

---

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

## Open Issues Summary

### Priority Breakdown (8 Total - 6 Completed)
### Priority Breakdown (7 Total - 7 Completed)

#### 🔴 Critical Priority (0 issues)

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

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

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

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

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

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

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

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

**Next Up**:

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

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

Expand Down Expand Up @@ -315,11 +313,11 @@ Phase 7: Accessibility & Core Web Vitals ✅ COMPLETE
├── 🔵 #64 WCAG AAA contrast (2-4h) - ENHANCEMENT
└── 🔵 #65 Font size readability (1-2h) - ENHANCEMENT

Phase 7: Performance & UX (CURRENT FOCUS)
Phase 7: Performance & UX ✅ CORE COMPLETE
├── ✅ #11 SVG Preloading (30min) - COMPLETED
├── ✅ #27 Lazy Routes (2h) - COMPLETED
├── ✅ #72 Feature Flags (8-12h) - COMPLETED - Runtime toggling infrastructure
└── #14 Contact Form (1-2 days) - User engagement - NEXT
└── #14 Contact Form (1-2 days) - COMPLETED - Postmark + Turnstile + spam protection

Phase 8: Research (Anytime)
├── #33 Graphite spike (1-2h)
Expand Down Expand Up @@ -393,9 +391,9 @@ _None - All prerequisites for #43 are complete. Ready to implement._
| ----------- | ----- | ----------- | -------------------- | ---------- |
| 🔴 Critical | 0 | 0 | 1 | 0 |
| 🟡 High | 0 | 0 | 10 | 0 |
| 🟢 Medium | 1 | 0 | 4 | 1 |
| 🟢 Medium | 0 | 0 | 5 | 0 |
| 🔵 Low | 7 | 0 | 0 | 7 |
| **TOTAL** | **8** | **0** | **15** | **8** |
| **TOTAL** | **7** | **0** | **16** | **7** |

### Issues by Category

Expand All @@ -404,18 +402,18 @@ _None - All prerequisites for #43 are complete. Ready to implement._
**Infrastructure** (0 open, 1 closed): Closed: #72 (feature flags)
**CI/CD** (0 open, 2 closed): Closed: #18 (GitHub Actions pipeline), #72 (feature flags)
**Accessibility** (2 open, 3 closed): Open: #64, #65 | Closed: #61 (navigation contrast), #62 (touch targets), #63 (CLS mobile)
**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)
**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)
**Research** (2 open): #33, #34

### Effort Distribution (Open Issues Only)

| Effort Level | Count | Issues |
| ------------- | ----- | ------------------------------ |
| Small (< 2h) | 4 | #13, #33, #34, #65 |
| Medium (2-8h) | 2 | #64, #10 |
| Large (> 8h) | 2 | #14 (1-2 days), #15 (1-2 days) |
| Effort Level | Count | Issues |
| ------------- | ----- | ------------------ |
| Small (< 2h) | 4 | #13, #33, #34, #65 |
| Medium (2-8h) | 2 | #64, #10 |
| Large (> 8h) | 1 | #15 (1-2 days) |

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

---

Expand Down Expand Up @@ -500,7 +498,88 @@ _None - All prerequisites for #43 are complete. Ready to implement._
- ✅ Production build successful
- ✅ No breaking changes (minor version update within React 19)

**Next Actions**: Continue with #14 (Add Working Email Contact Form)
---

### 2025-12-07 - Issue #14 Completed: Add Working Email Contact Form

- **Completed**: #14 - Add Working Email Contact Form
- **Priority**: 🟢 MEDIUM (GitHub labels: `type: feature`, `area: ui`, `priority: medium`, `effort: large`)
- **Status**: Completed Dec 7, 2025
- **Effort**: ~1-2 days (actual)
- **Impact**: Professional contact method with enterprise-grade spam protection

**Implementation Summary**:

Delivered a complete, production-ready contact form with:

1. **Cloudflare Worker Backend** (`packages/contact-form/`)
- POST `/api/contact` endpoint for form submission
- Cloudflare Turnstile verification (privacy-friendly CAPTCHA)
- Honeypot field for bot detection
- Rate limiting: 5 requests/hour/IP using Cloudflare KV
- Postmark API integration for email delivery
- Input validation with proper error messages
- CORS configuration for allowed origins
- Full TypeScript implementation

2. **React Frontend Enhancement** (`src/components/ContactEmailForm.tsx`)
- Complete form with name, email, message fields
- Cloudflare Turnstile widget integration (@marsidev/react-turnstile)
- Hidden honeypot field for additional bot protection
- Client-side email validation with inline errors
- Loading, success, and error states
- Character counter for message field (5000 char limit)
- Accessibility: ARIA attributes, screen reader announcements, role="alert"
- Form clears and resets Turnstile on successful submission

3. **Testing Suite**
- Unit tests with Vitest (22 tests for ContactEmailForm)
- Integration tests with Cypress (form display, validation, accessibility)
- Tests cover: rendering, validation, submission, error handling, Turnstile integration

**Files Created**:

- `packages/contact-form/src/index.ts` - Main Worker with full contact form logic
- `packages/contact-form/package.json` - Package configuration
- `packages/contact-form/tsconfig.json` - TypeScript config
- `packages/contact-form/wrangler.toml` - Cloudflare Worker config
- `tests/unit/components/ContactEmailForm.test.tsx` - Unit tests (22 tests)
- `tests/integration/contact-form.cy.ts` - Integration tests

**Files Modified**:

- `src/components/ContactEmailForm.tsx` - Complete rewrite with full functionality
- `src/util/constants.ts` - Added API_URIS.CONTACT, TURNSTILE_SITE_KEY
- `src/vite-env.d.ts` - Added new environment variable types
- `.env.development` - Added contact form and Turnstile URLs
- `.env.production` - Added contact form and Turnstile URLs
- `package.json` (root) - Added workspace, scripts, @marsidev/react-turnstile dependency

**Technical Highlights**:

- **Security**: Triple-layer spam protection (Turnstile + honeypot + rate limit)
- **Privacy**: Cloudflare Turnstile is privacy-friendly (no tracking cookies)
- **Performance**: Edge-deployed Worker for low latency
- **Reliability**: Graceful error handling, form validation, retry support
- **Email Delivery**: Postmark API with verified sender domain (tylerearls.com)
- **Feature Flag Ready**: Works with existing feature flag infrastructure (#72)

**Deployment Requirements**:

1. Create Cloudflare Turnstile site (get Site Key and Secret Key)
2. Create KV namespace for rate limiting
3. Set Worker secrets: `POSTMARK_SERVER_TOKEN`, `TURNSTILE_SECRET_KEY`
4. Deploy Worker: `npm run deploy:contact`
5. Enable feature flag: `email-contact-form.enabled = true`

**Impact on Roadmap**:

- Completes: Phase 7 core features (contact form was the last major feature)
- Reduces open issues: 8 → 7
- All medium and high priority issues now complete!
- Only low priority and enhancement issues remain

**Next Actions**: Low priority features (#10, #13, #15) or accessibility enhancements (#64, #65) as time permits

---

Expand Down Expand Up @@ -1187,6 +1266,6 @@ All high-priority accessibility issues (#61, #62, #63) have been resolved. Mediu

---

**Last Updated**: December 13, 2025 (Issue #77 - React 19.2.3 Update)
**Last Updated**: December 13, 2025 (Issue #14 Complete - Contact Form with Postmark + Turnstile)
**Maintained By**: Tyler Earls
**Generated With**: Claude Code
6 changes: 2 additions & 4 deletions eslint.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { flatConfigs as eslintPluginImportFlatConfigs } from "eslint-plugin-impo
import eslintPluginReact from "eslint-plugin-react";
import eslintPluginReactHooks from "eslint-plugin-react-hooks";
import eslintPluginReactRefresh from "eslint-plugin-react-refresh";
import { defineConfig } from "eslint/config";
import globals from "globals";
import typescriptEslint from "typescript-eslint";

const config = defineConfig([
const config = typescriptEslint.config(
{ files: ["**/*.{js,mjs,cjs,ts,mts,jsx,tsx}"] },
{
ignores: [
Expand All @@ -19,7 +18,6 @@ const config = defineConfig([
"node_modules",
"prettier.config.mjs",
"**/.wrangler/**",
"packages/feature-flags/.wrangler/**",
],
},
{
Expand Down Expand Up @@ -149,6 +147,6 @@ const config = defineConfig([
],
},
},
]);
);

export default config;
42 changes: 42 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
"preview": "vite preview --open",
"ci": "npm run lint:check && npm run oxlint:check && npm run format:check && npm run test:all && npm run build",
"dev:flags": "npm run dev -w @portfolio/feature-flags",
"dev:all": "concurrently \"npm run dev\" \"npm run dev:flags\"",
"deploy:flags": "npm run deploy -w @portfolio/feature-flags"
"dev:contact": "npm run dev -w @portfolio/contact-form",
"dev:all": "concurrently \"npm run dev\" \"npm run dev:flags\" \"npm run dev:contact\"",
"deploy:flags": "npm run deploy -w @portfolio/feature-flags",
"deploy:contact": "npm run deploy -w @portfolio/contact-form"
},
"dependencies": {
"@cloudinary/url-gen": "^1.22.0",
"@marsidev/react-turnstile": "^1.3.1",
"@xstate/react": "^6.0.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
Expand All @@ -46,6 +49,7 @@
"@testing-library/cypress": "^10.1.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@tsconfig/node22": "^22.0.2",
"@types/mocha": "^10.0.10",
"@types/node": "^22.10.4",
Expand Down
1 change: 1 addition & 0 deletions packages/contact-form/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
19 changes: 19 additions & 0 deletions packages/contact-form/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@portfolio/contact-form",
"version": "1.0.0",
"private": true,
"main": "src/index.ts",
"scripts": {
"dev": "wrangler dev --port 8788",
"deploy": "wrangler deploy",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@portfolio/shared-types": "*"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240117.0",
"typescript": "^5.3.3",
"wrangler": "^4.51.0"
}
}
Loading