Reubencf Claude Sonnet 4.6 commited on
Commit
311edd8
·
1 Parent(s): b318aa5

add EmailJS contact form with two-column layout on large screens

Browse files
components/portfolio/sections/ContactSection.tsx CHANGED
@@ -1,12 +1,33 @@
1
  'use client';
2
 
3
- import React from 'react';
 
4
  import { FadeIn } from '../FadeIn';
5
- import { Mail, Linkedin, Twitter } from 'lucide-react';
6
-
7
- const CONTACT_EMAIL = '18reuchagasfernandes@gmail.com';
8
 
9
  export function ContactSection() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  return (
11
  <section id="contact" className="bg-[#FFE0D0] rounded-t-[40px] sm:rounded-t-[50px] md:rounded-t-[60px] -mt-10 sm:-mt-12 md:-mt-14 relative z-30 px-5 sm:px-8 md:px-10 py-20 sm:py-24 md:py-32">
12
  <FadeIn y={30} duration={0.8}>
@@ -15,34 +36,95 @@ export function ContactSection() {
15
  </h2>
16
  </FadeIn>
17
 
18
- <div className="max-w-xl mx-auto flex flex-col items-center gap-10">
19
- <FadeIn delay={0.2} duration={0.8}>
20
- <p className="text-[#1A0F08] font-medium text-[clamp(1.5rem,3vw,2.5rem)] leading-tight text-center">
21
- Have a project in mind? Let&apos;s build something incredible together.
22
- </p>
23
- </FadeIn>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- <FadeIn delay={0.4} duration={0.8}>
26
- <div className="flex flex-col gap-6">
27
- <a href={`mailto:${CONTACT_EMAIL}`} className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
28
- <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
29
- <Mail size={20} />
30
- </div>
31
- <span className="font-medium uppercase tracking-wider text-sm sm:text-base">{CONTACT_EMAIL}</span>
32
- </a>
33
- <a href="https://www.linkedin.com/in/reuben-chagas-fernandes/" target="_blank" rel="noreferrer" className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
34
- <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
35
- <Linkedin size={20} />
36
- </div>
37
- <span className="font-medium uppercase tracking-wider text-sm sm:text-base">LinkedIn</span>
38
- </a>
39
- <a href="https://x.com/18reuchagas" target="_blank" rel="noreferrer" className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
40
- <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
41
- <Twitter size={20} />
42
- </div>
43
- <span className="font-medium uppercase tracking-wider text-sm sm:text-base">Twitter / X</span>
44
- </a>
45
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  </FadeIn>
47
  </div>
48
  </section>
 
1
  'use client';
2
 
3
+ import React, { useState, useRef } from 'react';
4
+ import emailjs from '@emailjs/browser';
5
  import { FadeIn } from '../FadeIn';
6
+ import { Mail, Linkedin, Twitter, Send } from 'lucide-react';
 
 
7
 
8
  export function ContactSection() {
9
+ const formRef = useRef<HTMLFormElement>(null);
10
+ const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
11
+
12
+ async function handleSubmit(e: React.FormEvent) {
13
+ e.preventDefault();
14
+ if (!formRef.current) return;
15
+
16
+ setStatus('sending');
17
+ try {
18
+ await emailjs.sendForm(
19
+ process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID!,
20
+ process.env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID!,
21
+ formRef.current,
22
+ { publicKey: process.env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY! }
23
+ );
24
+ setStatus('sent');
25
+ formRef.current.reset();
26
+ } catch {
27
+ setStatus('error');
28
+ }
29
+ }
30
+
31
  return (
32
  <section id="contact" className="bg-[#FFE0D0] rounded-t-[40px] sm:rounded-t-[50px] md:rounded-t-[60px] -mt-10 sm:-mt-12 md:-mt-14 relative z-30 px-5 sm:px-8 md:px-10 py-20 sm:py-24 md:py-32">
33
  <FadeIn y={30} duration={0.8}>
 
36
  </h2>
37
  </FadeIn>
38
 
39
+ <div className="max-w-5xl mx-auto flex flex-col lg:flex-row lg:items-start lg:gap-16 gap-12">
40
+ {/* Left: intro + contact links */}
41
+ <div className="flex flex-col items-center lg:items-start gap-10 lg:flex-1">
42
+ <FadeIn delay={0.2} duration={0.8}>
43
+ <p className="text-[#1A0F08] font-medium text-[clamp(1.5rem,3vw,2.5rem)] leading-tight text-center lg:text-left">
44
+ Have a project in mind? Let&apos;s build something incredible together.
45
+ </p>
46
+ </FadeIn>
47
+
48
+ <FadeIn delay={0.4} duration={0.8}>
49
+ <div className="flex flex-col gap-6">
50
+ <a href="mailto:18reuchagasfernandes@gmail.com" className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
51
+ <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
52
+ <Mail size={20} />
53
+ </div>
54
+ <span className="font-medium uppercase tracking-wider text-sm sm:text-base">18reuchagasfernandes@gmail.com</span>
55
+ </a>
56
+ <a href="https://www.linkedin.com/in/reuben-chagas-fernandes/" target="_blank" rel="noreferrer" className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
57
+ <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
58
+ <Linkedin size={20} />
59
+ </div>
60
+ <span className="font-medium uppercase tracking-wider text-sm sm:text-base">LinkedIn</span>
61
+ </a>
62
+ <a href="https://x.com/18reuchagas" target="_blank" rel="noreferrer" className="flex items-center gap-4 text-[#1A0F08] hover:text-[#E63F19] transition-colors group">
63
+ <div className="w-12 h-12 rounded-full border-2 border-[#1A0F08] flex items-center justify-center group-hover:border-[#E63F19] transition-colors">
64
+ <Twitter size={20} />
65
+ </div>
66
+ <span className="font-medium uppercase tracking-wider text-sm sm:text-base">Twitter / X</span>
67
+ </a>
68
+ </div>
69
+ </FadeIn>
70
+ </div>
71
 
72
+ {/* Right: contact form */}
73
+ <FadeIn delay={0.3} duration={0.8} className="w-full lg:flex-1">
74
+ <form ref={formRef} onSubmit={handleSubmit} className="w-full flex flex-col gap-6">
75
+ <div className="flex flex-col gap-2">
76
+ <label htmlFor="name" className="text-[#1A0F08] font-mono text-xs sm:text-sm uppercase tracking-widest font-bold ml-1">Name</label>
77
+ <input
78
+ id="name"
79
+ name="name"
80
+ type="text"
81
+ placeholder="John Doe"
82
+ required
83
+ className="w-full bg-transparent border-2 border-[#1A0F08] rounded-2xl px-5 py-4 text-[#1A0F08] placeholder-[#1A0F08]/40 font-mono text-sm sm:text-base focus:outline-none focus:border-[#E63F19] focus:shadow-[4px_4px_0px_0px_#E63F19] hover:shadow-[4px_4px_0px_0px_#1A0F08] hover:-translate-y-0.5 transition-all duration-200"
84
+ />
85
+ </div>
86
+ <div className="flex flex-col gap-2">
87
+ <label htmlFor="from_email" className="text-[#1A0F08] font-mono text-xs sm:text-sm uppercase tracking-widest font-bold ml-1">Email</label>
88
+ <input
89
+ id="from_email"
90
+ name="from_email"
91
+ type="email"
92
+ placeholder="john@example.com"
93
+ required
94
+ className="w-full bg-transparent border-2 border-[#1A0F08] rounded-2xl px-5 py-4 text-[#1A0F08] placeholder-[#1A0F08]/40 font-mono text-sm sm:text-base focus:outline-none focus:border-[#E63F19] focus:shadow-[4px_4px_0px_0px_#E63F19] hover:shadow-[4px_4px_0px_0px_#1A0F08] hover:-translate-y-0.5 transition-all duration-200"
95
+ />
96
+ </div>
97
+ <div className="flex flex-col gap-2">
98
+ <label htmlFor="message" className="text-[#1A0F08] font-mono text-xs sm:text-sm uppercase tracking-widest font-bold ml-1">Message</label>
99
+ <textarea
100
+ id="message"
101
+ name="message"
102
+ placeholder="Tell me about your project..."
103
+ rows={5}
104
+ required
105
+ className="w-full bg-transparent border-2 border-[#1A0F08] rounded-2xl px-5 py-4 text-[#1A0F08] placeholder-[#1A0F08]/40 font-mono text-sm sm:text-base focus:outline-none focus:border-[#E63F19] focus:shadow-[4px_4px_0px_0px_#E63F19] hover:shadow-[4px_4px_0px_0px_#1A0F08] hover:-translate-y-0.5 transition-all duration-200 resize-none"
106
+ />
107
+ </div>
108
+ <button
109
+ type="submit"
110
+ disabled={status === 'sending' || status === 'sent'}
111
+ className="mt-2 flex items-center justify-center gap-3 w-full bg-[#1A0F08] text-[#FFE0D0] font-black uppercase tracking-widest text-sm py-4 rounded-2xl hover:bg-[#E63F19] hover:-translate-y-1 hover:shadow-[6px_6px_0px_0px_#1A0F08] transition-all duration-200 disabled:opacity-60 disabled:cursor-not-allowed disabled:hover:translate-y-0 disabled:hover:shadow-none"
112
+ >
113
+ {status === 'sending' && 'Sending...'}
114
+ {status === 'sent' && 'Message sent!'}
115
+ {status === 'error' && 'Try again'}
116
+ {status === 'idle' && (
117
+ <>
118
+ Send message <Send size={16} />
119
+ </>
120
+ )}
121
+ </button>
122
+ {status === 'error' && (
123
+ <p className="text-[#E63F19] text-sm text-center font-mono font-medium">
124
+ Something went wrong. Please email me directly.
125
+ </p>
126
+ )}
127
+ </form>
128
  </FadeIn>
129
  </div>
130
  </section>
package-lock.json CHANGED
@@ -8,6 +8,7 @@
8
  "name": "reuben-fernandes",
9
  "version": "0.1.0",
10
  "dependencies": {
 
11
  "@google/generative-ai": "^0.21.0",
12
  "@lobehub/icons": "^5.4.0",
13
  "@phosphor-icons/react": "^2.1.10",
@@ -321,6 +322,15 @@
321
  "node": ">=6.9.0"
322
  }
323
  },
 
 
 
 
 
 
 
 
 
324
  "node_modules/@emnapi/core": {
325
  "version": "1.10.0",
326
  "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
 
8
  "name": "reuben-fernandes",
9
  "version": "0.1.0",
10
  "dependencies": {
11
+ "@emailjs/browser": "^4.4.1",
12
  "@google/generative-ai": "^0.21.0",
13
  "@lobehub/icons": "^5.4.0",
14
  "@phosphor-icons/react": "^2.1.10",
 
322
  "node": ">=6.9.0"
323
  }
324
  },
325
+ "node_modules/@emailjs/browser": {
326
+ "version": "4.4.1",
327
+ "resolved": "https://registry.npmjs.org/@emailjs/browser/-/browser-4.4.1.tgz",
328
+ "integrity": "sha512-DGSlP9sPvyFba3to2A50kDtZ+pXVp/0rhmqs2LmbMS3I5J8FSOgLwzY2Xb4qfKlOVHh29EAutLYwe5yuEZmEFg==",
329
+ "license": "BSD-3-Clause",
330
+ "engines": {
331
+ "node": ">=14.0.0"
332
+ }
333
+ },
334
  "node_modules/@emnapi/core": {
335
  "version": "1.10.0",
336
  "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
package.json CHANGED
@@ -9,6 +9,7 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
 
12
  "@google/generative-ai": "^0.21.0",
13
  "@lobehub/icons": "^5.4.0",
14
  "@phosphor-icons/react": "^2.1.10",
 
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
12
+ "@emailjs/browser": "^4.4.1",
13
  "@google/generative-ai": "^0.21.0",
14
  "@lobehub/icons": "^5.4.0",
15
  "@phosphor-icons/react": "^2.1.10",