-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Responsive Contact Form #279
base: web2.0-dev
Are you sure you want to change the base?
Changes from 4 commits
f929198
a0d6313
7db6259
c5ae71e
c4721c4
2b1e150
013f3fc
a845d09
f9b166a
d344bc4
05f4141
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
NEXT_PUBLIC_API_URL = patv9V491TkNGUhyA.0e452983e9bc1b7541a5bcc3029deb4e6882327d620244e2c44b4399f9700c28 | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ yarn-error.log* | |
|
||
# local env files | ||
.env*.local | ||
.env | ||
|
||
# vercel | ||
.vercel | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
export const handleInputChange = (e, setFormData) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use Typescript as we are using in the whole project. |
||
const { name, value } = e.target; | ||
setFormData((prevData) => ({ | ||
...prevData, | ||
[name]: value, | ||
})); | ||
}; | ||
|
||
export const handleSubmit = async (e, formData, setLoading, setVisible, setFormSubmitted, setFormData, initialFormData, apiKey) => { | ||
e.preventDefault(); | ||
setLoading(true); | ||
|
||
const requiredFields = ["Name", "Email", "Message"]; | ||
const missingFields = requiredFields.filter((field) => !formData[field]); | ||
|
||
if (missingFields.length > 0) { | ||
alert(`Please fill in the required fields: ${missingFields.join(", ")}`); | ||
setLoading(false); | ||
return; | ||
} | ||
|
||
const requestOptions = { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${apiKey}`, | ||
}, | ||
body: JSON.stringify({ | ||
records: [ | ||
{ | ||
fields: { ...formData }, | ||
}, | ||
], | ||
}), | ||
}; | ||
|
||
try { | ||
const response = await fetch( | ||
"https://api.airtable.com/v0/appt2qGxSivdUxI4b/data", | ||
requestOptions | ||
); | ||
|
||
if (!response.ok) { | ||
throw new Error(`Error: ${response.status} - ${response.statusText}`); | ||
} | ||
|
||
setVisible(false); | ||
setFormSubmitted(true); | ||
} catch (error) { | ||
console.error("Error:", error); | ||
alert("An error occurred while submitting the form. Please try again later."); | ||
setFormData(initialFormData); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
"use client"; | ||
import React, { useState, useEffect } from "react"; | ||
import Lottie from "react-lottie"; | ||
import animationData from "./mail.json"; | ||
import Link from "next/link"; | ||
import { IoMail } from "react-icons/io5"; | ||
import { FaDiscord, FaLinkedin, FaTwitter, FaNetworkWired } from "react-icons/fa"; | ||
import { BallTriangle } from "react-loading-icons"; | ||
import { handleInputChange, handleSubmit } from "./formUtils"; | ||
|
||
const AirtableForm = () => { | ||
const initialFormData = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
Name: "", | ||
Email: "", | ||
Message: "", | ||
}; | ||
|
||
const [formData, setFormData] = useState(initialFormData); | ||
const [isVisible, setVisible] = useState(true); | ||
const [formSubmitted, setFormSubmitted] = useState(false); | ||
const [isLoading, setLoading] = useState(false); | ||
const [isLottieLoaded, setLottieLoaded] = useState(false); | ||
const apiKey = process.env.NEXT_PUBLIC_API_URL; | ||
|
||
const defaultOpt = { | ||
loop: true, | ||
autoplay: true, | ||
animationData: animationData, | ||
rendererSettings: { | ||
preserveAspectRatio: "xMidYMid slice", | ||
}, | ||
}; | ||
|
||
useEffect(() => { | ||
const timer = setTimeout(() => { | ||
setLottieLoaded(true); | ||
}, 1500); | ||
|
||
return () => clearTimeout(timer); | ||
}, []); | ||
|
||
const handleKeyDown = (event) => { | ||
if (event.key === "Enter") { | ||
event.preventDefault(); | ||
handleSubmit( | ||
event, | ||
formData, | ||
setLoading, | ||
setVisible, | ||
setFormSubmitted, | ||
setFormData, | ||
initialFormData, | ||
apiKey | ||
); | ||
} | ||
}; | ||
|
||
if (!isLottieLoaded) { | ||
return ( | ||
<div className="flex items-center justify-center w-full h-screen bg-black"> | ||
<BallTriangle className="text-white text-3xl" /> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="isolate flex flex-col px-6 py-18 sm:py-24 lg:px-8 sm:overflow-x-hidden"> | ||
<div className="w-screen mx-44 text-center sm:mx-auto"> | ||
<h1 className="my-20 text-4xl font-bold tracking-tight sm:text-5xl text-white text-center sm:text-center sm:my-0"> | ||
Contact Us | ||
</h1> | ||
</div> | ||
<div className="flex my-36 absolute gap-24 flex-col p-12 justify-between items-center lg:flex-row sm:gap-6 lg:items-center sm:relative sm:my-0"> | ||
<Lottie options={defaultOpt} height={380} width={500} /> | ||
<form | ||
onSubmit={(e) => | ||
handleSubmit( | ||
e, | ||
formData, | ||
setLoading, | ||
setVisible, | ||
setFormSubmitted, | ||
setFormData, | ||
initialFormData, | ||
apiKey | ||
) | ||
} | ||
className="mx-24 relative mt-2 max-w-2xl p-10 sm:mt-10 border-4 border-emerald-800 rounded-xl sm:mx-auto" | ||
> | ||
{isVisible && ( | ||
<div className="inner-box"> | ||
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2"> | ||
{/* Your form input fields */} | ||
<div> | ||
<label | ||
htmlFor="Name" | ||
className="block text-sm font-semibold leading-6 text-white" | ||
> | ||
Name | ||
</label> | ||
<div className="mt-2.5"> | ||
<input | ||
type="text" | ||
name="Name" | ||
id="Name" | ||
value={formData["Name"]} | ||
autoComplete="Name" | ||
onChange={(e) => handleInputChange(e, setFormData)} | ||
onKeyDown={handleKeyDown} | ||
className="block w-full rounded-md border-0 px-3.5 py-2 text-white shadow-sm ring-1 ring-inset ring-gray-300 bg-neutral-900 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-green-950 sm:text-sm sm:leading-6" | ||
/> | ||
</div> | ||
</div> | ||
<div> | ||
<label | ||
htmlFor="Email" | ||
className="block text-sm font-semibold leading-6 text-white" | ||
> | ||
</label> | ||
<div className="mt-2.5"> | ||
<input | ||
type="email" | ||
name="Email" | ||
id="Email" | ||
value={formData["Email"]} | ||
autoComplete="email" | ||
onChange={(e) => handleInputChange(e, setFormData)} | ||
onKeyDown={handleKeyDown} | ||
className="block w-full rounded-md border-0 px-3.5 py-2 bg-neutral-900 text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-green-950 sm:text-sm sm:leading-6" | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div className="sm:col-span-2"> | ||
<label | ||
htmlFor="message" | ||
className="block text-sm font-semibold leading-6 text-white" | ||
> | ||
Message | ||
</label> | ||
<div className="mt-2.5"> | ||
<textarea | ||
name="Message" | ||
id="Message" | ||
value={formData["Message"]} | ||
rows={4} | ||
onChange={(e) => handleInputChange(e, setFormData)} | ||
onKeyDown={handleKeyDown} | ||
className="block w-full rounded-md border-0 px-3.5 py-2 bg-neutral-900 text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-green-950 sm:text-sm sm:leading-6" | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="mt-10 relative"> | ||
<button | ||
type="submit" | ||
className="block w-full rounded-md bg-green-700 px-3.5 py-2.5 text-center text-sm font-semibold text-white shadow-sm hover:bg-green-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" | ||
> | ||
{isLoading ? "Submitting" : "Let's talk"} | ||
{isLoading && ( | ||
<div className="absolute inset-0 flex items-center justify-center"> | ||
<BallTriangle /> | ||
</div> | ||
)} | ||
</button> | ||
</div> | ||
</div> | ||
)} | ||
{!isVisible && ( | ||
<div className="success-box flex flex-col justify-center items-center"> | ||
<div className="bg-white rounded-full w-5/12 object-cover mb-8 p-8"> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
viewBox="0 0 448 512" | ||
> | ||
<path | ||
fill="#00bd1f" | ||
d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z" | ||
/> | ||
</svg> | ||
</div> | ||
<p className="text-white text-2xl font-semibold mb-4"> | ||
Form Submitted! | ||
</p> | ||
</div> | ||
)} | ||
<div className="mt-8 relative flex gap-12 justify-center"> | ||
{/* Your social media links */} | ||
<Link | ||
href="https://discord.com/invite/4xruwjjU9B" | ||
className="hover:scale-125 transition ease-in-out" | ||
target="discord" | ||
> | ||
<FaDiscord className="size-14 hover:text-violet-500" /> | ||
</Link> | ||
<Link | ||
href="Mailto:[email protected]" | ||
className="hover:scale-125 transition ease-in-out" | ||
target="mail" | ||
> | ||
<IoMail className="size-14 hover:text-red-500" /> | ||
</Link> | ||
<Link | ||
href="https://twitter.com/fosscuk" | ||
className="hover:scale-125 transition ease-in-out" | ||
target="twitter" | ||
> | ||
<FaTwitter className="size-12 hover:text-blue-400" /> | ||
</Link> | ||
<Link | ||
href="https://matrix.to/#/#fosscu:matrix.org" | ||
className="hover:scale-125 transition ease-in-out" | ||
target="network" | ||
> | ||
<FaNetworkWired className="size-12 hover:text-pink-600" /> | ||
</Link> | ||
<Link | ||
href="https://www.linkedin.com/company/fosscu/" | ||
className="hover:scale-125 transition ease-in-out" | ||
target="linkedin" | ||
> | ||
<FaLinkedin className="size-12 hover:text-blue-600" /> | ||
</Link> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AirtableForm; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again @sarthak-stranger you push the
.env
😢 , Please remove it.Also please create
.env.example
and just put the variableNEXT_PUBLIC_API_URL
there and please add in theREADME.md
what is the use for this variable.My two cents are that we should rename
NEXT_PUBLIC_API_URL
toNEXT_AIRTABLE_API_KEY
as it makes it obvious for the people to understand what it is for.