diff --git a/dev/contact-form-plan.md b/dev/contact-form-plan.md new file mode 100644 index 0000000..9185a15 --- /dev/null +++ b/dev/contact-form-plan.md @@ -0,0 +1,262 @@ +# Contact Form → n8n Webhook with Personalized MDX Response Plan + +## Overview +Wire the existing Astro contact form to an n8n webhook using `PUBLIC_N8N_WEBHOOK_URL`, enable n8n to return a personalized MDX/Markdown message, render it on the client, and implement automatic fallback to a standard toast notification when n8n is down or fails. + +--- + +## Implementation Steps + +### 1. n8n Webhook + Environment Setup + +**Verify n8n Webhook Configuration:** +- In your n8n instance, create or verify a Webhook node configured for `POST` requests +- Use a path like `contact-form` +- Note the complete webhook URL (e.g., `https://your-n8n-instance.com/webhook/contact-form`) + +**Define Response Contract:** +The n8n workflow should return JSON in one of these formats: +- **Success:** `{ success: true, format: 'mdx', message: '...markdown/mdx string...' }` +- **Handled Error:** `{ success: false, error: 'Human-friendly error message' }` + +**Environment Variable:** +- Confirm `PUBLIC_N8N_WEBHOOK_URL` is set in `.env` with the webhook URL +- Ensure the same variable is configured in your Cloudflare Pages environment settings +- Optional: Update `env.d.ts` to type `import.meta.env.PUBLIC_N8N_WEBHOOK_URL` for TypeScript safety + +--- + +### 2. Wire Astro Contact Form to n8n (with Robust Error Detection) + +**File to modify:** `src/pages/contact.astro` + +**Form Markup Updates:** +- Add `id="contact-form"` to the form element +- Remove `action="#"` and `method="POST"` attributes (JavaScript will handle submission) +- Preserve all existing classes, labels, and the custom subject dropdown + +**Client-Side Submit Handler:** +Add a new script block (or extend the existing one) with: + +1. **Form submission interception:** + - Attach a `submit` event listener that calls `preventDefault()` + - Extract form data using `FormData` API + - Build JSON payload including: + - `name`, `email`, `subject`, `message` + - Metadata: `timestamp` (ISO string), `source: 'portfolio-website'` + +2. **Fetch with timeout wrapper:** + - Use `fetch(import.meta.env.PUBLIC_N8N_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) })` + - Wrap with `AbortController` or `Promise.race` for 8-10 second timeout + +3. **Failure detection conditions** (any of these triggers fallback): + - Network error or thrown exception + - Timeout reached + - Non-2xx HTTP response + - 2xx response with `success: false` in JSON + +4. **Success path:** + - Extract the `message` field from response + - Pass to MDX/Markdown rendering logic (see Step 3) + - Show brief success state on submit button + +5. **Failure path:** + - Display standard toast notification with error message + - Keep form data intact (don't reset) + - Re-enable submit button + +**Button UX States:** +- **Waiting:** Disable button, change text to "Transmitting..." +- **Success:** Briefly show "Message Sent!" then re-enable +- **Failure:** Show "Transmission Failed" then revert to original text + +--- + +### 3. Render Personalized MDX/Markdown Response + +**Add Markdown Renderer:** +- Install a lightweight markdown library via `pnpm add marked` (or `markdown-it`) +- Import it in the client-side script section + +**Response Panel UI:** +- Create a dedicated container near the form submit area (e.g., bordered card) +- Initially hidden (`hidden` class or `display: none`) +- Becomes visible only when successful response is received +- Style with existing design system classes for consistency + +**Rendering Logic:** +When response has `success: true` and `format: 'mdx'`: +1. Convert the `message` string to HTML using the markdown library +2. Inject into response panel using `innerHTML` +3. Apply typography classes (`prose` or custom) for proper formatting +4. If markdown conversion throws, treat as failure and show fallback toast + +**Accessibility:** +- Add `role="status"` to the response panel +- Ensure proper color contrast +- Test with keyboard navigation and screen readers + +**Security:** +- Since content comes from your own n8n instance, it's trusted +- Still avoid allowing script tags in the markdown content +- Keep response panel visually constrained + +--- + +### 4. n8n Workflow Processing & Templating + +**In your n8n workflow (after the Webhook node):** + +**Template the Personalized Message:** +- Use Set or Function nodes to build a Markdown/MDX string +- Use incoming fields like `{{ $json.name }}`, `{{ $json.subject }}`, `{{ $json.message }}` +- Example structure: + ```markdown + # Thanks, {{ name }}! + + I received your message about **{{ subject }}**. + + I'll review it and get back to you within 24-48 hours at {{ email }}. + + In the meantime, feel free to check out [my recent work](/work) or [blog posts](/blog). + + — Nicholai + ``` + +**Workflow Branches:** +- **Validation node:** Check for required fields (name, email, message) + - If missing: Return `{ success: false, error: 'Please fill in all required fields' }` +- **Email notification node:** Send yourself a formatted email with the submission details +- **Optional logging node:** Save to Google Sheets, database, or CRM + +**Webhook Response Node:** +- At the end of the workflow, add a "Respond to Webhook" node +- Return JSON matching the contract: + - Success: `{ success: true, format: 'mdx', message: '...' }` + - Error: `{ success: false, error: '...' }` +- For unexpected internal errors, either: + - Let workflow fail (frontend timeout will catch it), or + - Wrap in try/catch and still return `{ success: false }` + +--- + +### 5. Fallback Toast & Automatic Failure Detection UX + +**Toast Notification Implementation:** +- Create a reusable toast function (if not already present) +- Should support both success and error styles +- Position in top-right or bottom-right of viewport +- Auto-dismiss after 5-7 seconds with smooth fade-out + +**Error Toast Content:** +``` +"We couldn't reach the messaging system. Please try again or email me directly at nicholai@nicholai.work" +``` + +**Automatic Detection:** +- Trigger error toast for any failure condition from Step 2 +- Works even if n8n is completely unreachable (DNS/SSL issues, 500 errors, timeouts) + +**User Experience:** +- On failure: **Do not clear the form** (preserves user's work) +- Optional: Add inline text under submit button: "Auto-response unavailable; message will still be delivered via email" +- Ensure toast has `role="alert"` for accessibility + +--- + +### 6. Testing & Validation + +**Happy Path Tests:** +- With n8n workflow active and webhook listening: + 1. Submit form with various subject/message combinations + 2. Verify n8n receives correct payload with all fields + 3. Confirm n8n builds expected personalized MDX string + 4. Check that frontend displays rendered response panel with proper formatting + 5. Verify email notification is sent + 6. Test that form resets appropriately + +**Failure Path Tests:** +1. **n8n completely down:** + - Stop n8n instance or point env var to invalid URL + - Submit form + - Confirm: Timeout triggers, error toast appears, form data preserved, no response panel shown + +2. **n8n returns error:** + - Modify workflow to return `{ success: false, error: 'Test error' }` + - Submit form + - Confirm: Error toast shows n8n's error message, no response panel + +3. **Network timeout:** + - Add artificial delay in n8n workflow (>10 seconds) + - Confirm: Frontend timeout triggers fallback + +4. **Invalid markdown:** + - Have n8n return malformed markdown that breaks the parser + - Confirm: Rendering error is caught and fallback toast appears + +**Browser & Responsiveness:** +- Test on desktop (Chrome, Firefox, Safari) +- Test on mobile viewport (iOS Safari, Chrome Android) +- Verify response panel and toasts don't break layout +- Check animations and transitions are smooth +- Test with keyboard-only navigation +- Test with screen reader (VoiceOver or NVDA) + +**Production Verification:** +- After deploying with env var configured: + 1. Submit real test message from live site + 2. Confirm end-to-end flow works + 3. Check browser console for CORS errors (adjust n8n/proxy if needed) + 4. Verify SSL/HTTPS works correctly + 5. Test from different networks (WiFi, mobile data) + +--- + +### 7. Future-Proofing Options + +**Server-Side Proxy (Optional):** +If you want to hide the webhook URL and do MDX→HTML conversion server-side: + +1. Create an Astro API route (e.g., `/api/contact.ts`) or Cloudflare Worker +2. Have it: + - Accept form JSON from browser + - Add server-side validation/rate limiting + - Call n8n webhook + - Convert returned MDX to HTML server-side + - Return normalized `{ success, html }` to client +3. Frontend code changes minimally (just POST URL changes) + +**Benefits:** +- Webhook URL never exposed to client +- Additional security layer +- Server-side rate limiting +- Can add spam protection (honeypot, CAPTCHA) + +**Richer MDX Components:** +If you later want actual MDX components (not just markdown): +- Add runtime MDX renderer like `@mdx-js/mdx` on client +- Or render MDX to React components server-side in the proxy route +- Would allow n8n to return interactive components, not just static markdown + +--- + +## Critical Files + +- **`src/pages/contact.astro`** - Main file to modify (form markup + client script) +- **`.env`** - Contains `PUBLIC_N8N_WEBHOOK_URL` +- **`env.d.ts`** - Optional TypeScript environment variable typing +- **n8n workflow** - Webhook node + processing nodes + response node + +--- + +## Success Criteria + +✅ Form submits to n8n webhook successfully +✅ n8n returns personalized MDX message +✅ Frontend renders markdown as HTML in response panel +✅ Timeout/error conditions trigger fallback toast +✅ Form data preserved on failure +✅ Works on desktop and mobile +✅ Accessible to keyboard and screen reader users +✅ No CORS issues in production +✅ Email notifications sent from n8n \ No newline at end of file