Skip to content

Commit

Permalink
Merge pull request #32 from meteordefect/Fix-form-cors-7
Browse files Browse the repository at this point in the history
Fixing contact form 7
  • Loading branch information
meteordefect authored Dec 22, 2024
2 parents 921b8f3 + 48c4313 commit ccc41db
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 71 deletions.
73 changes: 63 additions & 10 deletions cipher-infra/lib/contact-form-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,76 @@ export class ContactFormStack extends cdk.Stack {
// Create the contact resource
const contact = api.root.addResource('contact');

// Add POST method with Lambda integration
// Add POST method with Enhanced Lambda integration
const integration = new apigateway.LambdaIntegration(contactFormLambda, {
proxy: true,
integrationResponses: [
{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'https://www.cipherprojects.com'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Api-Key'",
'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
},
{
// Handle 4XX errors
selectionPattern: '4\\d{2}',
statusCode: '400',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'https://www.cipherprojects.com'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Api-Key'",
'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
},
{
// Handle 5XX errors
selectionPattern: '5\\d{2}',
statusCode: '500',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': "'https://www.cipherprojects.com'",
'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Api-Key'",
'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
}
]
});

// Add POST method
// Add POST method with expanded method responses
contact.addMethod('POST', integration, {
apiKeyRequired: true,
methodResponses: [{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true,
methodResponses: [
{
statusCode: '200',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true
}
},
{
statusCode: '400',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true
}
},
}],
{
statusCode: '500',
responseParameters: {
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Headers': true,
'method.response.header.Access-Control-Allow-Methods': true,
'method.response.header.Access-Control-Allow-Credentials': true
}
}
]
});

// Add Usage Plan to API stage
Expand Down
128 changes: 67 additions & 61 deletions src/components/Contact/index.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,66 @@
'use client'

// Test:
// curl -X POST -H "Content-Type: application/json" \
// -d '{"name": "Test", "email": "test@example.com", "message": "Hello"}' \
// https://mkz66v3npa.execute-api.ap-southeast-2.amazonaws.com/prod/contact

import { useState } from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'
import { Mail, Phone, MapPin } from 'lucide-react'

export default function ContactPage() {
const [formData, setFormData] = useState({
type FormData = {
name: string
email: string
phone: string
budget: string
message: string
}

type SubmitStatus = 'idle' | 'loading' | 'success' | 'error'

export default function Contact() {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
phone: '',
budget: '',
message: ''
})
const [submitStatus, setSubmitStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('idle')
const [errorMessage, setErrorMessage] = useState('')


const handleSubmit = async (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setSubmitStatus('loading')
console.log('Form submitted with data:', formData)

try {
// Use environment variables if available, otherwise use /api/contact
const endpoint = process.env.NEXT_PUBLIC_API_GATEWAY_URL || '/api/contact'
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
const apiUrl = process.env.NEXT_PUBLIC_API_GATEWAY_URL
const apiKey = process.env.NEXT_PUBLIC_API_KEY

// Add API key if available
if (process.env.NEXT_PUBLIC_API_KEY) {
headers['x-api-key'] = process.env.NEXT_PUBLIC_API_KEY
}
// Check if API URL is available
if (!apiUrl) {
setSubmitStatus('error')
setErrorMessage('API configuration error')
return
}

try {
console.log('Submitting to:', apiUrl)
console.log('Using API Key:', apiKey ? 'Yes' : 'No')

const response = await fetch(endpoint, {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(process.env.NEXT_PUBLIC_API_KEY && {
'x-api-key': process.env.NEXT_PUBLIC_API_KEY
})
...(apiKey && { 'x-api-key': apiKey })
},
credentials: 'include',
body: JSON.stringify(formData)
});
})

if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.message || `HTTP error! status: ${response.status}`)
}

const data = await response.json()
console.log('Response:', data)

if (!response.ok) {
throw new Error(data.error || 'Failed to send message')
}

setSubmitStatus('success')
setFormData({
name: '',
Expand All @@ -67,21 +69,28 @@ export default function ContactPage() {
budget: '',
message: ''
})

} catch (error) {
console.error('Error:', error)
setSubmitStatus('error')
setErrorMessage(error instanceof Error ? error.message : 'Failed to send message')
}
}

const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value
}))
}

return (
<main className="min-h-screen pt-48 pb-32">
<div className="container">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-32">
{/* Left Column - Header & Contact Info */}
<div>
{/* Hero Text */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
Expand All @@ -92,84 +101,80 @@ export default function ContactPage() {
Let's talk about your next project.
</h1>
<p className="text-xl opacity-60">
The more details you include, the better we can understand your needs and provide the right solution.
The more details you include, the better we can understand your needs.
</p>
</motion.div>

{/* Contact Info */}
<div className="space-y-8">
<h2 className="text-2xl font-normal">Get in Touch</h2>
<div className="space-y-6">
<Link
href="tel:+61261761580"
className="flex items-center gap-4 text-lg hover:opacity-60 transition-opacity duration-300"
>
<Link href="tel:+61261761580" className="flex items-center gap-4 text-lg hover:opacity-60 transition-opacity duration-300">
<Phone size={24} className="opacity-60" />
<span>+61 2 6176 1580</span>
</Link>
<Link
href="mailto:[email protected]"
className="flex items-center gap-4 text-lg hover:opacity-60 transition-opacity duration-300"
>
<Link href="mailto:[email protected]" className="flex items-center gap-4 text-lg hover:opacity-60 transition-opacity duration-300">
<Mail size={24} className="opacity-60" />
<span>[email protected]</span>
</Link>
<div className="flex items-center gap-4 text-lg">
<MapPin size={24} className="opacity-60" />
<span>
Mawson 2607
<br />
Canberra, Australia
</span>
<span>Mawson 2607<br />Canberra, Australia</span>
</div>
</div>
</div>
</div>

{/* Right Column - Contact Form */}
<div>
<form onSubmit={handleSubmit} className="space-y-8">
<div>
<label className="text-lg mb-2 block">Your Name</label>
<label htmlFor="name" className="text-lg mb-2 block">Your Name</label>
<input
id="name"
name="name"
type="text"
className="w-full bg-transparent border-b border-current py-4 focus:outline-none text-lg"
placeholder="First and Last Name"
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
onChange={handleChange}
required
/>
</div>

<div>
<label className="text-lg mb-2 block">Email</label>
<label htmlFor="email" className="text-lg mb-2 block">Email</label>
<input
id="email"
name="email"
type="email"
className="w-full bg-transparent border-b border-current py-4 focus:outline-none text-lg"
placeholder="[email protected]"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
onChange={handleChange}
required
/>
</div>

<div>
<label className="text-lg mb-2 block">Phone</label>
<label htmlFor="phone" className="text-lg mb-2 block">Phone</label>
<input
id="phone"
name="phone"
type="tel"
className="w-full bg-transparent border-b border-current py-4 focus:outline-none text-lg"
placeholder="Your phone number"
value={formData.phone}
onChange={(e) => setFormData({...formData, phone: e.target.value})}
onChange={handleChange}
/>
</div>

<div>
<label className="text-lg mb-2 block">Budget</label>
<label htmlFor="budget" className="text-lg mb-2 block">Budget</label>
<select
id="budget"
name="budget"
className="w-full bg-transparent border-b border-current py-4 focus:outline-none text-lg appearance-none cursor-pointer"
value={formData.budget}
onChange={(e) => setFormData({...formData, budget: e.target.value})}
onChange={handleChange}
>
<option value="">Select a budget range</option>
<option value="under-25k">Under $25,000</option>
Expand All @@ -180,12 +185,14 @@ export default function ContactPage() {
</div>

<div>
<label className="text-lg mb-2 block">Message</label>
<label htmlFor="message" className="text-lg mb-2 block">Message</label>
<textarea
id="message"
name="message"
className="w-full bg-transparent border-b border-current py-4 focus:outline-none text-lg min-h-[120px]"
placeholder="Tell us about your project"
value={formData.message}
onChange={(e) => setFormData({...formData, message: e.target.value})}
onChange={handleChange}
required
/>
</div>
Expand All @@ -202,7 +209,6 @@ export default function ContactPage() {
{submitStatus === 'loading' ? 'Sending...' : 'Send Message'}
</button>

{/* Status Messages */}
{submitStatus === 'success' && (
<motion.div
initial={{ opacity: 0, y: 10 }}
Expand Down

0 comments on commit ccc41db

Please sign in to comment.