Skip to content
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

Open
wants to merge 11 commits into
base: web2.0-dev
Choose a base branch
from
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_API_URL = patv9V491TkNGUhyA.0e452983e9bc1b7541a5bcc3029deb4e6882327d620244e2c44b4399f9700c28
Copy link
Member

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 variable NEXT_PUBLIC_API_URL there and please add in the README.md what is the use for this variable.

My two cents are that we should rename NEXT_PUBLIC_API_URL to NEXT_AIRTABLE_API_KEY as it makes it obvious for the people to understand what it is for.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel
Expand Down
57 changes: 57 additions & 0 deletions app/contact/formUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export const handleInputChange = (e, setFormData) => {
Copy link
Member

Choose a reason for hiding this comment

The 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);
}
};

1 change: 1 addition & 0 deletions app/contact/mail.json

Large diffs are not rendered by default.

233 changes: 233 additions & 0 deletions app/contact/page.tsx
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 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use types when you are using Typescript else there is no objective to using Typescript.

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"
>
Email
</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;

Loading