175 lines
6.7 KiB
TypeScript
175 lines
6.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { motion, AnimatePresence } from "motion/react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
export function ContactModal() {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const [submitStatus, setSubmitStatus] = useState<"idle" | "success" | "error">("idle");
|
|
|
|
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
setIsSubmitting(true);
|
|
const formData = new FormData(event.currentTarget);
|
|
formData.append("access_key", "861ad586-6ce2-4a29-a967-a64fed1a431f");
|
|
|
|
const object = Object.fromEntries(formData);
|
|
const json = JSON.stringify(object);
|
|
|
|
try {
|
|
const response = await fetch("https://api.web3forms.com/submit", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Accept: "application/json"
|
|
},
|
|
body: json
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
setSubmitStatus("success");
|
|
setTimeout(() => {
|
|
setIsOpen(false);
|
|
setSubmitStatus("idle");
|
|
}, 2000);
|
|
} else {
|
|
setSubmitStatus("error");
|
|
}
|
|
} catch (error) {
|
|
setSubmitStatus("error");
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<button
|
|
onClick={() => setIsOpen(true)}
|
|
className="text-xs text-[color:var(--accent)] hover:underline transition-colors duration-200"
|
|
>
|
|
Email me
|
|
</button>
|
|
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.9, opacity: 0 }}
|
|
animate={{ scale: 1, opacity: 1 }}
|
|
exit={{ scale: 0.9, opacity: 0 }}
|
|
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
|
className="relative w-full max-w-md rounded-lg shadow-2xl glass-strong glass-refract"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-neutral-100">Contact Me</h2>
|
|
<button
|
|
onClick={() => setIsOpen(false)}
|
|
className="p-1 rounded-full hover:bg-white/5 transition-colors"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label htmlFor="name" className="block text-sm font-medium mb-1 text-neutral-300">
|
|
Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
name="name"
|
|
required
|
|
className="w-full px-3 py-2 rounded-md border border-white/10 bg-black/20 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="email" className="block text-sm font-medium mb-1 text-neutral-300">
|
|
Email
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
required
|
|
className="w-full px-3 py-2 rounded-md border border-white/10 bg-black/20 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)]"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="message" className="block text-sm font-medium mb-1 text-neutral-300">
|
|
Message
|
|
</label>
|
|
<textarea
|
|
id="message"
|
|
name="message"
|
|
required
|
|
rows={4}
|
|
className="w-full px-3 py-2 rounded-md border border-white/10 bg-black/20 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)] resize-none"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex gap-3 pt-2">
|
|
<button
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
className={cn(
|
|
"flex-1 px-4 py-2 rounded-md glass text-neutral-200 font-medium transition-colors hover:opacity-95 focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)]",
|
|
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
)}
|
|
>
|
|
{isSubmitting ? "Sending..." : "Send Message"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOpen(false)}
|
|
className="px-4 py-2 rounded-md border border-white/10 text-neutral-300 hover:bg-white/5 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
{submitStatus === "success" && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="mt-4 p-3 glass text-neutral-200 rounded-md text-sm"
|
|
>
|
|
Message sent successfully! I'll get back to you soon.
|
|
</motion.div>
|
|
)}
|
|
|
|
{submitStatus === "error" && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="mt-4 p-3 glass text-neutral-200 rounded-md text-sm"
|
|
>
|
|
Something went wrong. Please try again or email me directly.
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</>
|
|
);
|
|
}
|