Skip to content

Commit 4e16b70

Browse files
committed
feat: complete portfolio with contact form and mobile optimization
1 parent 84072b8 commit 4e16b70

14 files changed

Lines changed: 411 additions & 685 deletions

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{}
1+
{
2+
"css.lint.unknownAtRules": "ignore"
3+
}

app/actions.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,43 @@ export async function chatWithGemini(userMessage: string) {
5050
return { error: "Internal Server Error. Please try again later." };
5151
}
5252
}
53+
54+
import { z } from "zod";
55+
56+
const contactSchema = z.object({
57+
name: z.string().min(1, "Name is required"),
58+
email: z.string().email("Invalid email address"),
59+
subject: z.string().min(1, "Subject is required"),
60+
message: z.string().min(10, "Message must be at least 10 characters"),
61+
});
62+
63+
export async function sendEmail(prevState: any, formData: FormData) {
64+
const data = {
65+
name: formData.get("name"),
66+
email: formData.get("email"),
67+
subject: formData.get("subject"),
68+
message: formData.get("message"),
69+
};
70+
71+
const validatedFields = contactSchema.safeParse(data);
72+
73+
if (!validatedFields.success) {
74+
return {
75+
success: false,
76+
errors: validatedFields.error.flatten().fieldErrors,
77+
};
78+
}
79+
80+
// Simulate network delay
81+
await new Promise((resolve) => setTimeout(resolve, 1000));
82+
83+
// Log to console (Simulation)
84+
console.log("--- EMAIL SENT ---");
85+
console.log(validatedFields.data);
86+
console.log("------------------");
87+
88+
return {
89+
success: true,
90+
message: "Transmission received. I will establish connection shortly.",
91+
};
92+
}

app/globals.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
.text-balance {
6161
text-wrap: balance;
6262
}
63+
.animate-scan-line {
64+
will-change: transform;
65+
}
6366
}
6467

6568
/* Custom Scrollbar */

app/layout.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const viewport: Viewport = {
2525
themeColor: "#030304",
2626
};
2727

28+
import { LazyMotion, domAnimation } from "framer-motion";
29+
2830
export default function RootLayout({
2931
children,
3032
}: Readonly<{
@@ -33,8 +35,10 @@ export default function RootLayout({
3335
return (
3436
<html lang="en" className={`${inter.variable} ${jetbrainsMono.variable} scroll-smooth`}>
3537
<body suppressHydrationWarning className="font-sans antialiased">
36-
{children}
37-
<ChatWidget />
38+
<LazyMotion features={domAnimation}>
39+
{children}
40+
<ChatWidget />
41+
</LazyMotion>
3842
</body>
3943
</html>
4044
);

components/chat-widget.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,17 @@ export function ChatWidget() {
104104
{!isOpen && showNotification && (
105105
<motion.div
106106
initial={{ opacity: 0, y: 10, scale: 0.9 }}
107-
animate={{ opacity: 1, y: 0, scale: 1 }}
108-
exit={{ opacity: 0, scale: 0.9 }}
109-
transition={{ delay: 2, duration: 0.5 }}
107+
animate={{
108+
opacity: 1,
109+
y: 0,
110+
scale: 1,
111+
transition: { delay: 2, duration: 0.5 }
112+
}}
113+
exit={{
114+
opacity: 0,
115+
scale: 0.9,
116+
transition: { duration: 0.2 }
117+
}}
110118
className="fixed bottom-24 right-6 z-40 max-w-[200px]"
111119
>
112120
<div className="relative bg-background/80 backdrop-blur-md border border-purple-500/80 p-3 rounded-lg shadow-[0_0_15px_rgba(168,85,247,0.15)]">

components/contact-section.tsx

Lines changed: 222 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,242 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { motion } from "framer-motion";
4-
import { Mail, Github, Linkedin, ArrowRight } from "lucide-react";
5+
import { Mail, Github, Linkedin, Send, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
6+
import { useForm } from "react-hook-form";
7+
import { zodResolver } from "@hookform/resolvers/zod";
8+
import * as z from "zod";
9+
import { toast } from "sonner";
10+
import { sendEmail } from "@/app/actions";
11+
12+
const formSchema = z.object({
13+
name: z.string().min(2, "Name must be at least 2 characters"),
14+
email: z.string().email("Please enter a valid email address"),
15+
subject: z.string().min(5, "Subject must be at least 5 characters"),
16+
message: z.string().min(10, "Message must be at least 10 characters"),
17+
});
18+
19+
type FormData = z.infer<typeof formSchema>;
520

621
export function ContactSection() {
22+
const [isSubmitting, setIsSubmitting] = useState(false);
23+
const {
24+
register,
25+
handleSubmit,
26+
reset,
27+
formState: { errors },
28+
} = useForm<FormData>({
29+
resolver: zodResolver(formSchema),
30+
});
31+
32+
const onSubmit = async (data: FormData) => {
33+
setIsSubmitting(true);
34+
const formData = new FormData();
35+
formData.append("name", data.name);
36+
formData.append("email", data.email);
37+
formData.append("subject", data.subject);
38+
formData.append("message", data.message);
39+
40+
try {
41+
const result = await sendEmail(null, formData);
42+
43+
if (result.success) {
44+
toast.success("Transmission Received", {
45+
description: result.message,
46+
icon: <CheckCircle2 className="h-4 w-4 text-green" />,
47+
});
48+
reset();
49+
} else {
50+
toast.error("Transmission Failed", {
51+
description: "Please check your inputs and try again.",
52+
icon: <AlertCircle className="h-4 w-4 text-destructive" />,
53+
});
54+
}
55+
} catch (error) {
56+
toast.error("System Error", {
57+
description: "Communication link unstable. Please try again.",
58+
});
59+
} finally {
60+
setIsSubmitting(false);
61+
}
62+
};
63+
764
return (
865
<section id="contact" className="relative px-6 py-32 border-t border-border/50">
9-
<div className="max-w-3xl mx-auto text-center">
66+
<div className="max-w-4xl mx-auto">
1067
<motion.div
1168
initial={{ opacity: 0, y: 20 }}
1269
whileInView={{ opacity: 1, y: 0 }}
1370
viewport={{ once: true }}
1471
transition={{ duration: 0.6 }}
72+
className="text-center mb-16"
1573
>
16-
<div className="flex items-center justify-center gap-4 mb-8">
17-
<span className="font-mono text-[10px] tracking-[0.3em] text-muted-foreground uppercase">
18-
{"// TRANSMISSION ENDPOINT"}
19-
</span>
20-
</div>
21-
22-
<h2 className="font-mono text-3xl md:text-4xl lg:text-5xl font-bold text-foreground tracking-tight mb-8">
23-
Ready to Collaborate?
24-
</h2>
25-
26-
<p className="font-sans text-lg text-muted-foreground leading-relaxed mb-12 max-w-xl mx-auto">
27-
I am currently open to new opportunities and collaborations.
28-
Whether you have a question or just want to explore a potential project,
29-
initialize a connection below.
30-
</p>
31-
32-
<a
33-
href="mailto:mdevendrasai9@gmail.com"
34-
className="group relative inline-flex items-center gap-3 bg-foreground text-background px-8 py-4 font-mono text-sm tracking-widest uppercase hover:bg-foreground/90 transition-colors"
74+
<div className="flex items-center justify-center gap-4 mb-4">
75+
<span className="font-mono text-[10px] tracking-[0.3em] text-muted-foreground uppercase">
76+
{"// TRANSMISSION ENDPOINT"}
77+
</span>
78+
</div>
79+
80+
<h2 className="font-mono text-3xl md:text-4xl lg:text-5xl font-bold text-foreground tracking-tight mb-4">
81+
Initialize Connection
82+
</h2>
83+
84+
<p className="font-sans text-muted-foreground max-w-xl mx-auto">
85+
Ready to collaborate? Establish a secure link below.
86+
</p>
87+
</motion.div>
88+
89+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-24">
90+
{/* Form */}
91+
<motion.div
92+
initial={{ opacity: 0, x: -20 }}
93+
whileInView={{ opacity: 1, x: 0 }}
94+
viewport={{ once: true }}
95+
transition={{ duration: 0.6, delay: 0.2 }}
3596
>
36-
<span>Initialize Connection</span>
37-
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" />
38-
</a>
39-
40-
{/* Social Links */}
41-
<div className="mt-16 flex justify-center gap-8">
42-
<a href="https://github.com/devendrasaim" target="_blank" rel="noopener noreferrer" className="flex flex-col items-center gap-2 group">
43-
<div className="p-3 border border-border rounded-full group-hover:border-foreground transition-colors">
44-
<Github className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
97+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
98+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
99+
<div className="space-y-2">
100+
<label htmlFor="name" className="font-mono text-xs tracking-wider text-muted-foreground">
101+
NAME
102+
</label>
103+
<input
104+
{...register("name")}
105+
className="w-full bg-background/50 border border-border px-4 py-3 font-mono text-sm text-foreground focus:outline-none focus:border-cyan/50 focus:ring-1 focus:ring-cyan/50 transition-all placeholder:text-muted-foreground/30"
106+
placeholder="ENTER_NAME"
107+
/>
108+
{errors.name && (
109+
<p className="font-mono text-[10px] text-destructive mt-1">{errors.name.message}</p>
110+
)}
111+
</div>
112+
<div className="space-y-2">
113+
<label htmlFor="email" className="font-mono text-xs tracking-wider text-muted-foreground">
114+
EMAIL
115+
</label>
116+
<input
117+
{...register("email")}
118+
className="w-full bg-background/50 border border-border px-4 py-3 font-mono text-sm text-foreground focus:outline-none focus:border-cyan/50 focus:ring-1 focus:ring-cyan/50 transition-all placeholder:text-muted-foreground/30"
119+
placeholder="ENTER_EMAIL"
120+
/>
121+
{errors.email && (
122+
<p className="font-mono text-[10px] text-destructive mt-1">{errors.email.message}</p>
123+
)}
124+
</div>
45125
</div>
46-
<span className="font-mono text-[10px] tracking-wider text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">GITHUB</span>
47-
</a>
48-
<a href="https://www.linkedin.com/in/devendrasaim/" target="_blank" rel="noopener noreferrer" className="flex flex-col items-center gap-2 group">
49-
<div className="p-3 border border-border rounded-full group-hover:border-foreground transition-colors">
50-
<Linkedin className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
126+
127+
<div className="space-y-2">
128+
<label htmlFor="subject" className="font-mono text-xs tracking-wider text-muted-foreground">
129+
SUBJECT
130+
</label>
131+
<input
132+
{...register("subject")}
133+
className="w-full bg-background/50 border border-border px-4 py-3 font-mono text-sm text-foreground focus:outline-none focus:border-cyan/50 focus:ring-1 focus:ring-cyan/50 transition-all placeholder:text-muted-foreground/30"
134+
placeholder="SUBJECT_LINE"
135+
/>
136+
{errors.subject && (
137+
<p className="font-mono text-[10px] text-destructive mt-1">{errors.subject.message}</p>
138+
)}
51139
</div>
52-
<span className="font-mono text-[10px] tracking-wider text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">LINKEDIN</span>
53-
</a>
54-
<a href="mailto:mdevendrasai9@gmail.com" className="flex flex-col items-center gap-2 group">
55-
<div className="p-3 border border-border rounded-full group-hover:border-foreground transition-colors">
56-
<Mail className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
140+
141+
<div className="space-y-2">
142+
<label htmlFor="message" className="font-mono text-xs tracking-wider text-muted-foreground">
143+
MESSAGE
144+
</label>
145+
<textarea
146+
{...register("message")}
147+
className="w-full bg-background/50 border border-border px-4 py-3 font-mono text-sm text-foreground focus:outline-none focus:border-cyan/50 focus:ring-1 focus:ring-cyan/50 transition-all placeholder:text-muted-foreground/30 min-h-[150px] resize-y"
148+
placeholder="INPUT_MESSAGE_DATA..."
149+
/>
150+
{errors.message && (
151+
<p className="font-mono text-[10px] text-destructive mt-1">{errors.message.message}</p>
152+
)}
57153
</div>
58-
<span className="font-mono text-[10px] tracking-wider text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">EMAIL</span>
59-
</a>
60-
</div>
61-
</motion.div>
154+
155+
<button
156+
type="submit"
157+
disabled={isSubmitting}
158+
className="w-full group relative inline-flex items-center justify-center gap-3 bg-foreground text-background px-8 py-4 font-mono text-sm tracking-widest uppercase hover:bg-foreground/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
159+
>
160+
{isSubmitting ? (
161+
<>
162+
<Loader2 className="h-4 w-4 animate-spin" />
163+
<span>Transmitting...</span>
164+
</>
165+
) : (
166+
<>
167+
<span>Send Transmission</span>
168+
<Send className="h-4 w-4 transition-transform group-hover:translate-x-1" />
169+
</>
170+
)}
171+
</button>
172+
</form>
173+
</motion.div>
174+
175+
{/* Sidebar Info */}
176+
<motion.div
177+
initial={{ opacity: 0, x: 20 }}
178+
whileInView={{ opacity: 1, x: 0 }}
179+
viewport={{ once: true }}
180+
transition={{ duration: 0.6, delay: 0.4 }}
181+
className="space-y-12 lg:pt-8"
182+
>
183+
<div className="space-y-6">
184+
<h3 className="font-mono text-lg font-bold text-foreground mb-6">
185+
Direct Protocols
186+
</h3>
187+
188+
<a href="mailto:mdevendrasai9@gmail.com" className="flex items-start gap-4 group">
189+
<div className="p-3 border border-border bg-background/50 group-hover:border-foreground transition-colors">
190+
<Mail className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
191+
</div>
192+
<div>
193+
<span className="block font-mono text-[10px] tracking-wider text-muted-foreground mb-1">EMAIL</span>
194+
<span className="font-sans text-sm text-foreground group-hover:text-cyan transition-colors">mdevendrasai9@gmail.com</span>
195+
</div>
196+
</a>
197+
198+
<a href="https://github.com/devendrasaim" target="_blank" rel="noopener noreferrer" className="flex items-start gap-4 group">
199+
<div className="p-3 border border-border bg-background/50 group-hover:border-foreground transition-colors">
200+
<Github className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
201+
</div>
202+
<div>
203+
<span className="block font-mono text-[10px] tracking-wider text-muted-foreground mb-1">GITHUB</span>
204+
<span className="font-sans text-sm text-foreground group-hover:text-cyan transition-colors">github.com/devendrasaim</span>
205+
</div>
206+
</a>
207+
208+
<a href="https://www.linkedin.com/in/devendrasaim/" target="_blank" rel="noopener noreferrer" className="flex items-start gap-4 group">
209+
<div className="p-3 border border-border bg-background/50 group-hover:border-foreground transition-colors">
210+
<Linkedin className="h-5 w-5 text-muted-foreground group-hover:text-foreground" />
211+
</div>
212+
<div>
213+
<span className="block font-mono text-[10px] tracking-wider text-muted-foreground mb-1">LINKEDIN</span>
214+
<span className="font-sans text-sm text-foreground group-hover:text-cyan transition-colors">in/devendrasaim</span>
215+
</div>
216+
</a>
217+
</div>
218+
219+
<div className="border border-border p-6 relative overflow-hidden">
220+
<div className="absolute inset-0 bg-background/80 flex items-center justify-center pointer-events-none">
221+
<div className="absolute inset-0 bg-grid-pattern opacity-10" />
222+
</div>
223+
<div className="relative z-10 space-y-2">
224+
<div className="flex items-center justify-between text-xs font-mono text-muted-foreground">
225+
<span>STATUS</span>
226+
<span className="text-green">ACTIVE</span>
227+
</div>
228+
<div className="flex items-center justify-between text-xs font-mono text-muted-foreground">
229+
<span>LATENCY</span>
230+
<span>12ms</span>
231+
</div>
232+
<div className="flex items-center justify-between text-xs font-mono text-muted-foreground">
233+
<span>ENCRYPTION</span>
234+
<span>TLS 1.3</span>
235+
</div>
236+
</div>
237+
</div>
238+
</motion.div>
239+
</div>
62240
</div>
63241
</section>
64242
);

0 commit comments

Comments
 (0)