Skip to content

Commit 41d483b

Browse files
committed
Update ProjectRequestForm.tsx
1 parent 89b4d80 commit 41d483b

1 file changed

Lines changed: 178 additions & 59 deletions

File tree

src/components/ProjectRequestForm.tsx

Lines changed: 178 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState, useEffect } from "react";
3+
import { useState, useEffect, ChangeEvent, FormEvent, JSX } from "react";
44
import { motion, AnimatePresence } from "framer-motion";
55
import emailjs from "emailjs-com";
66
import {
@@ -9,125 +9,181 @@ import {
99
FaCommentDots,
1010
FaTimes,
1111
FaCheckCircle,
12-
FaPaperPlane
12+
FaExclamationCircle,
13+
FaPaperPlane,
14+
FaSpinner
1315
} from "react-icons/fa";
1416

15-
export default function ContactForm() {
16-
const [form, setForm] = useState({ name: "", number: "", message: "" });
17-
const [isSubmitting, setIsSubmitting] = useState(false);
18-
const [showModal, setShowModal] = useState(false);
17+
// ============================================================
18+
// TYPES
19+
// ============================================================
1920

20-
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
21-
setForm({ ...form, [e.target.name]: e.target.value });
21+
interface FormData {
22+
name: string;
23+
number: string;
24+
message: string;
25+
}
26+
27+
// ============================================================
28+
// CONTACT FORM COMPONENT
29+
// ============================================================
30+
31+
export default function ContactForm(): JSX.Element {
32+
const [form, setForm] = useState<FormData>({
33+
name: "",
34+
number: "",
35+
message: ""
36+
});
37+
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
38+
const [modalStatus, setModalStatus] = useState<"idle" | "success" | "error">("idle");
39+
const [showModal, setShowModal] = useState<boolean>(false);
40+
41+
// ============================================================
42+
// HANDLERS
43+
// ============================================================
44+
45+
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
46+
const { name, value } = e.target;
47+
setForm((prev) => ({ ...prev, [name]: value }));
2248
};
2349

24-
const sendEmail = async (e: React.FormEvent) => {
50+
const sendEmail = async (e: FormEvent<HTMLFormElement>): Promise<void> => {
2551
e.preventDefault();
2652
setIsSubmitting(true);
53+
setModalStatus("idle");
2754

2855
try {
2956
await emailjs.send(
30-
"service_97usflj",
57+
"service_np5zft2",
3158
"template_m9immuc",
32-
form,
59+
{ ...form },
3360
"q1s3x3DSUxpAVErUh"
3461
);
3562
setForm({ name: "", number: "", message: "" });
63+
setModalStatus("success");
3664
setShowModal(true);
3765
} catch (error) {
38-
console.error(error);
39-
alert("Failed to send message");
66+
console.error("EmailJS Error:", error);
67+
setModalStatus("error");
68+
setShowModal(true);
4069
} finally {
4170
setIsSubmitting(false);
4271
}
4372
};
4473

74+
const closeModal = (): void => {
75+
setShowModal(false);
76+
setTimeout(() => setModalStatus("idle"), 300);
77+
};
78+
79+
// ============================================================
80+
// EFFECTS
81+
// ============================================================
82+
4583
useEffect(() => {
46-
const handleEsc = (e: KeyboardEvent) => {
47-
if (e.key === "Escape") setShowModal(false);
84+
const handleEsc = (e: KeyboardEvent): void => {
85+
if (e.key === "Escape") closeModal();
4886
};
4987
document.addEventListener("keydown", handleEsc);
5088
return () => document.removeEventListener("keydown", handleEsc);
5189
}, []);
5290

91+
// ============================================================
92+
// RENDER
93+
// ============================================================
94+
5395
return (
54-
<section id="contact" className="min-h-screen flex items-center justify-center px-4 py-20 bg-black">
55-
56-
{/* Simple Glow */}
96+
<section
97+
id="contact"
98+
className="min-h-screen flex flex-col justify-center items-center px-4 py-20 bg-black relative overflow-hidden"
99+
>
100+
{/* Background Glow */}
57101
<div className="absolute inset-0">
58-
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-white/5 rounded-full blur-3xl" />
102+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-orange-400/5 rounded-full blur-3xl" />
103+
<div className="absolute bottom-20 right-1/4 w-[300px] h-[300px] bg-orange-400/5 rounded-full blur-3xl" />
59104
</div>
60105

61106
<div className="max-w-2xl w-full relative z-10">
107+
{/* ==========================================================
108+
HEADER
109+
========================================================== */}
62110

63-
{/* Header */}
64111
<motion.div
65112
initial={{ opacity: 0, y: 20 }}
66113
animate={{ opacity: 1, y: 0 }}
67114
transition={{ duration: 0.5 }}
68115
className="text-center mb-10"
69116
>
117+
<div className="inline-flex items-center gap-2 px-4 py-2 bg-orange-400/10 rounded-full border border-orange-400/20 mb-4">
118+
<FaPaperPlane className="text-orange-300 text-xs" />
119+
<span className="text-[10px] font-medium text-orange-300 tracking-wider uppercase">
120+
Get in Touch
121+
</span>
122+
</div>
123+
70124
<h2 className="text-4xl sm:text-5xl font-light text-white tracking-tight">
71125
Contact
72126
</h2>
73-
<div className="w-12 h-0.5 bg-white/20 mx-auto mt-3" />
127+
<div className="w-12 h-0.5 bg-orange-400/30 mx-auto mt-3" />
74128
<p className="text-gray-500 text-sm mt-4">
75-
Lets work together
129+
Let&apos;s work together
76130
</p>
77131
</motion.div>
78132

79-
{/* Form */}
133+
{/* ==========================================================
134+
FORM
135+
========================================================== */}
136+
80137
<motion.div
81138
initial={{ opacity: 0, y: 30 }}
82139
animate={{ opacity: 1, y: 0 }}
83140
transition={{ duration: 0.5, delay: 0.2 }}
84-
className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6 sm:p-8"
141+
className="bg-white/5 backdrop-blur-xl border border-white/10 rounded-2xl p-6 sm:p-8 hover:border-orange-400/30 transition-all duration-300"
85142
>
86143
<form onSubmit={sendEmail} className="space-y-4">
87-
88-
{/* Name + Phone */}
89-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
90-
<div className="relative">
91-
<FaUser className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-600" size={15} />
144+
{/* Name + Phone - زیر هم */}
145+
<div className="grid grid-cols-1 gap-4">
146+
<div className="relative group">
147+
<FaUser className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-orange-300 transition-colors duration-300" size={15} />
92148
<input
93149
type="text"
94150
name="name"
95151
placeholder="Name"
96152
value={form.name}
97153
onChange={handleChange}
98-
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-white/30 transition-all duration-300"
154+
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-orange-400/50 transition-all duration-300"
99155
required
100156
/>
101157
</div>
102-
<div className="relative">
103-
<FaPhone className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-600" size={15} />
158+
<div className="relative group">
159+
<FaPhone className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-600 group-focus-within:text-orange-300 transition-colors duration-300" size={15} />
104160
<input
105161
type="tel"
106162
name="number"
107163
placeholder="Phone"
108164
value={form.number}
109165
onChange={handleChange}
110-
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-white/30 transition-all duration-300"
166+
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-orange-400/50 transition-all duration-300"
111167
required
112168
/>
113169
</div>
114170
</div>
115171

116172
{/* Message */}
117-
<div className="relative">
118-
<FaCommentDots className="absolute left-4 top-4 text-gray-600" size={15} />
173+
<div className="relative group">
174+
<FaCommentDots className="absolute left-4 top-4 text-gray-600 group-focus-within:text-orange-300 transition-colors duration-300" size={15} />
119175
<textarea
120176
name="message"
121177
placeholder="Message"
122178
value={form.message}
123179
onChange={handleChange}
124180
rows={4}
125-
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-white/30 transition-all duration-300 resize-none"
181+
className="w-full pl-11 pr-4 py-3.5 bg-black/50 border border-white/10 rounded-xl text-white placeholder-gray-600 focus:outline-none focus:border-orange-400/50 transition-all duration-300 resize-none"
126182
required
127183
/>
128184
</div>
129185

130-
{/* Submit */}
186+
{/* Submit Button */}
131187
<motion.button
132188
type="submit"
133189
disabled={isSubmitting}
@@ -136,60 +192,123 @@ export default function ContactForm() {
136192
className={`w-full py-3.5 text-sm font-medium tracking-wider rounded-xl transition-all duration-300 ${
137193
isSubmitting
138194
? "bg-white/5 text-gray-500 cursor-not-allowed"
139-
: "bg-white text-black hover:bg-white/90"
195+
: "bg-orange-400/20 text-orange-300 border border-orange-400/30 hover:bg-orange-400/30 shadow-lg shadow-orange-400/10"
140196
}`}
141197
>
142198
{isSubmitting ? (
143199
<span className="flex items-center justify-center gap-2">
144-
<div className="w-4 h-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />
145-
Sending
200+
<FaSpinner className="animate-spin" size={16} />
201+
Sending...
146202
</span>
147203
) : (
148204
<span className="flex items-center justify-center gap-2">
149-
Send
205+
Send Message
150206
<FaPaperPlane size={13} />
151207
</span>
152208
)}
153209
</motion.button>
154210
</form>
155211
</motion.div>
156212

157-
{/* Modal */}
213+
{/* ==========================================================
214+
SUCCESS/ERROR MODAL - خفن و شیک
215+
========================================================== */}
216+
158217
<AnimatePresence>
159218
{showModal && (
160219
<motion.div
161220
initial={{ opacity: 0 }}
162221
animate={{ opacity: 1 }}
163222
exit={{ opacity: 0 }}
164223
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
165-
onClick={() => setShowModal(false)}
166224
>
167225
<motion.div
168-
initial={{ scale: 0.9, opacity: 0 }}
169-
animate={{ scale: 1, opacity: 1 }}
170-
exit={{ scale: 0.9, opacity: 0 }}
171-
className="relative max-w-sm w-full bg-[#111] border border-white/10 rounded-2xl p-8 text-center"
172-
onClick={(e) => e.stopPropagation()}
226+
initial={{ scale: 0.8, opacity: 0, y: 30 }}
227+
animate={{ scale: 1, opacity: 1, y: 0 }}
228+
exit={{ scale: 0.8, opacity: 0, y: 30 }}
229+
transition={{ type: "spring", stiffness: 300, damping: 25 }}
230+
className="relative max-w-md w-full bg-gradient-to-br from-[#1a1a1a] to-[#0d0d0d] border rounded-2xl p-8 text-center shadow-2xl"
231+
style={{
232+
borderColor: modalStatus === "success"
233+
? "rgba(74, 222, 128, 0.3)"
234+
: "rgba(248, 113, 113, 0.3)"
235+
}}
173236
>
237+
{/* Close Button - فقط با کلیک روی این دکمه بسته میشه */}
174238
<button
175-
onClick={() => setShowModal(false)}
176-
className="absolute top-4 right-4 text-gray-500 hover:text-white transition-colors"
239+
onClick={closeModal}
240+
className="absolute top-4 right-4 text-gray-500 hover:text-white transition-colors p-2 hover:bg-white/5 rounded-full"
177241
>
178242
<FaTimes size={18} />
179243
</button>
180244

181-
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-white/5 border border-white/10 flex items-center justify-center">
182-
<FaCheckCircle className="text-white/60 text-2xl" />
183-
</div>
245+
{/* Icon */}
246+
<motion.div
247+
initial={{ scale: 0 }}
248+
animate={{ scale: 1 }}
249+
transition={{ delay: 0.2, type: "spring", stiffness: 300 }}
250+
className="w-20 h-20 mx-auto mb-4 rounded-full flex items-center justify-center"
251+
style={{
252+
background: modalStatus === "success"
253+
? "rgba(74, 222, 128, 0.15)"
254+
: "rgba(248, 113, 113, 0.15)",
255+
border: `2px solid ${
256+
modalStatus === "success"
257+
? "rgba(74, 222, 128, 0.3)"
258+
: "rgba(248, 113, 113, 0.3)"
259+
}`
260+
}}
261+
>
262+
{modalStatus === "success" ? (
263+
<FaCheckCircle className="text-4xl text-green-400" />
264+
) : (
265+
<FaExclamationCircle className="text-4xl text-red-400" />
266+
)}
267+
</motion.div>
184268

185-
<h3 className="text-xl font-light text-white mb-2">Sent!</h3>
269+
{/* Title */}
270+
<h3
271+
className="text-2xl font-bold mb-2"
272+
style={{
273+
color: modalStatus === "success" ? "#4ADE80" : "#F87171"
274+
}}
275+
>
276+
{modalStatus === "success" ? "Message Sent! 🎉" : "Oops! Something went wrong"}
277+
</h3>
186278

187-
<button
188-
onClick={() => setShowModal(false)}
189-
className="mt-6 w-full py-2.5 bg-white text-black text-sm font-medium rounded-xl hover:bg-white/90 transition-all duration-300"
279+
{/* Message */}
280+
<p className="text-gray-400 text-sm leading-relaxed mb-6">
281+
{modalStatus === "success"
282+
? "Your message has been sent successfully. I'll get back to you as soon as possible."
283+
: "We couldn't send your message. Please check your connection and try again, or contact me directly via email."
284+
}
285+
</p>
286+
287+
{/* Button - فقط با کلیک روی این دکمه بسته میشه */}
288+
<motion.button
289+
onClick={closeModal}
290+
whileHover={{ scale: 1.02 }}
291+
whileTap={{ scale: 0.98 }}
292+
className="w-full py-3 text-sm font-medium rounded-xl transition-all duration-300"
293+
style={{
294+
background: modalStatus === "success"
295+
? "linear-gradient(135deg, #4ADE80, #22C55E)"
296+
: "linear-gradient(135deg, #F87171, #EF4444)",
297+
color: "#0A0A0A"
298+
}}
190299
>
191-
Done
192-
</button>
300+
{modalStatus === "success" ? "Got it! ✅" : "Try Again 🔄"}
301+
</motion.button>
302+
303+
{/* Subtle glow effect */}
304+
<div
305+
className="absolute -inset-1 rounded-2xl blur-3xl opacity-20 -z-10"
306+
style={{
307+
background: modalStatus === "success"
308+
? "radial-gradient(circle, #4ADE80, transparent 70%)"
309+
: "radial-gradient(circle, #F87171, transparent 70%)"
310+
}}
311+
/>
193312
</motion.div>
194313
</motion.div>
195314
)}

0 commit comments

Comments
 (0)