Skip to content

Commit bc753d3

Browse files
committed
feat: move ContactForm to root layout and improve employment UI
- Create ContactFormContext for global form state management - Move ContactForm to root layout (highest HTML structure below body) - Update ContactForm to use context instead of props - Update ContactBanner to use context for opening form - Create smaller pill UI for employment status selection - Replace large connected pills with compact rounded pills - Use gap-2 spacing and smaller padding (px-3 py-1.5) - Improve form positioning by placing at root level - Ensure form appears on top of all other content - Maintain all existing functionality with better architecture
1 parent ca18323 commit bc753d3

File tree

4 files changed

+58
-31
lines changed

4 files changed

+58
-31
lines changed

src/app/layout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
22
import { Nunito, Noto_Sans_KR } from "next/font/google";
33
import "./globals.css";
44
import { LanguageProvider } from "@/contexts/LanguageContext";
5+
import { ContactFormProvider } from "@/contexts/ContactFormContext";
6+
import ContactForm from "@/components/ui/ContactForm";
57

68
const nunito = Nunito({
79
subsets: ["latin"],
@@ -41,7 +43,10 @@ export default function RootLayout({
4143
<html lang="en" className={`${nunito.variable} ${notoKR.variable}`}>
4244
<body className="min-h-screen bg-background antialiased">
4345
<LanguageProvider>
44-
{children}
46+
<ContactFormProvider>
47+
{children}
48+
<ContactForm />
49+
</ContactFormProvider>
4550
</LanguageProvider>
4651
</body>
4752
</html>

src/components/layout/ContactBanner.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
'use client';
22

3-
import { useState } from 'react';
43
import { motion } from 'framer-motion';
54
import { Coffee } from 'lucide-react';
65
import { useLanguage } from '@/contexts/LanguageContext';
7-
import ContactForm from '../ui/ContactForm';
6+
import { useContactForm } from '@/contexts/ContactFormContext';
87

98
const ContactBanner = () => {
10-
const [isFormOpen, setIsFormOpen] = useState(false);
9+
const { openForm } = useContactForm();
1110
const { t } = useLanguage();
1211

1312
return (
@@ -30,7 +29,7 @@ const ContactBanner = () => {
3029

3130
{/* CTA Button */}
3231
<motion.button
33-
onClick={() => setIsFormOpen(true)}
32+
onClick={openForm}
3433
whileHover={{ scale: 1.02 }}
3534
whileTap={{ scale: 0.98 }}
3635
className="ml-4 flex items-center space-x-2 px-4 py-2 bg-white/20 text-white rounded-lg hover:bg-white/30 transition-all duration-200 text-sm font-medium shadow-sm"
@@ -40,12 +39,6 @@ const ContactBanner = () => {
4039
</motion.button>
4140
</div>
4241
</div>
43-
44-
{/* Contact Form Modal */}
45-
<ContactForm
46-
isOpen={isFormOpen}
47-
onClose={() => setIsFormOpen(false)}
48-
/>
4942
</motion.div>
5043
);
5144
};

src/components/ui/ContactForm.tsx

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ import { useState } from 'react';
44
import { motion, AnimatePresence } from 'framer-motion';
55
import { X, Send, User, Mail, Briefcase, Linkedin, Building, Check, AlertCircle } from 'lucide-react';
66
import emailjs from '@emailjs/browser';
7+
import { useContactForm } from '@/contexts/ContactFormContext';
78

8-
interface ContactFormProps {
9-
isOpen: boolean;
10-
onClose: () => void;
11-
}
12-
13-
const ContactForm = ({ isOpen, onClose }: ContactFormProps) => {
9+
const ContactForm = () => {
10+
const { isFormOpen, closeForm } = useContactForm();
1411
const [formData, setFormData] = useState({
1512
name: '',
1613
email: '',
@@ -76,7 +73,7 @@ const ContactForm = ({ isOpen, onClose }: ContactFormProps) => {
7673
setIsSubmitted(true);
7774
setTimeout(() => {
7875
setIsSubmitted(false);
79-
onClose();
76+
closeForm();
8077
resetForm();
8178
}, 2000);
8279
} else {
@@ -108,7 +105,7 @@ ${formData.discussion}
108105
setIsSubmitted(true);
109106
setTimeout(() => {
110107
setIsSubmitted(false);
111-
onClose();
108+
closeForm();
112109
resetForm();
113110
}, 2000);
114111
}
@@ -133,7 +130,7 @@ ${formData.discussion}
133130
setError('');
134131
};
135132

136-
if (!isOpen) return null;
133+
if (!isFormOpen) return null;
137134

138135
return (
139136
<AnimatePresence>
@@ -151,7 +148,7 @@ ${formData.discussion}
151148
bottom: 0,
152149
zIndex: 9998
153150
}}
154-
onClick={onClose}
151+
onClick={closeForm}
155152
/>
156153

157154
{/* Form Container */}
@@ -173,7 +170,7 @@ ${formData.discussion}
173170
{/* Header */}
174171
<div className="absolute top-8 right-8 z-10">
175172
<button
176-
onClick={onClose}
173+
onClick={closeForm}
177174
className="p-2 rounded-lg hover:bg-white/10 transition-colors"
178175
>
179176
<X className="w-6 h-6 text-white" />
@@ -244,13 +241,13 @@ ${formData.discussion}
244241
<div className="space-y-4">
245242
<h2 className="text-lg font-semibold text-white">Employment Status</h2>
246243

247-
{/* Employment Status - Connected Pills */}
244+
{/* Employment Status - Small Pills */}
248245
<div>
249246
<label className="block text-sm font-medium text-white mb-3">
250247
Are you currently employed? *
251248
</label>
252-
<div className="flex">
253-
<label className="flex-1 cursor-pointer">
249+
<div className="flex gap-2">
250+
<label className="cursor-pointer">
254251
<input
255252
type="radio"
256253
name="employmentStatus"
@@ -259,7 +256,7 @@ ${formData.discussion}
259256
onChange={handleRadioChange}
260257
className="sr-only"
261258
/>
262-
<div className={`px-4 py-3 text-center border border-white/20 transition-all ${
259+
<div className={`px-3 py-1.5 text-sm rounded-full border border-white/20 transition-all ${
263260
formData.employmentStatus === 'yes'
264261
? 'bg-white text-black border-white'
265262
: 'bg-white/10 text-white hover:bg-white/20'
@@ -268,7 +265,7 @@ ${formData.discussion}
268265
</div>
269266
</label>
270267

271-
<label className="flex-1 cursor-pointer">
268+
<label className="cursor-pointer">
272269
<input
273270
type="radio"
274271
name="employmentStatus"
@@ -277,7 +274,7 @@ ${formData.discussion}
277274
onChange={handleRadioChange}
278275
className="sr-only"
279276
/>
280-
<div className={`px-4 py-3 text-center border-t border-b border-white/20 transition-all ${
277+
<div className={`px-3 py-1.5 text-sm rounded-full border border-white/20 transition-all ${
281278
formData.employmentStatus === 'no'
282279
? 'bg-white text-black border-white'
283280
: 'bg-white/10 text-white hover:bg-white/20'
@@ -286,7 +283,7 @@ ${formData.discussion}
286283
</div>
287284
</label>
288285

289-
<label className="flex-1 cursor-pointer">
286+
<label className="cursor-pointer">
290287
<input
291288
type="radio"
292289
name="employmentStatus"
@@ -295,7 +292,7 @@ ${formData.discussion}
295292
onChange={handleRadioChange}
296293
className="sr-only"
297294
/>
298-
<div className={`px-4 py-3 text-center border border-white/20 transition-all ${
295+
<div className={`px-3 py-1.5 text-sm rounded-full border border-white/20 transition-all ${
299296
formData.employmentStatus === 'student'
300297
? 'bg-white text-black border-white'
301298
: 'bg-white/10 text-white hover:bg-white/20'
@@ -393,7 +390,7 @@ ${formData.discussion}
393390
<div className="flex justify-end space-x-4 pt-8">
394391
<button
395392
type="button"
396-
onClick={onClose}
393+
onClick={closeForm}
397394
className="px-8 py-3 text-white/70 hover:text-white transition-colors font-medium"
398395
>
399396
Cancel
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use client';
2+
3+
import { createContext, useContext, useState, ReactNode } from 'react';
4+
5+
interface ContactFormContextType {
6+
isFormOpen: boolean;
7+
openForm: () => void;
8+
closeForm: () => void;
9+
}
10+
11+
const ContactFormContext = createContext<ContactFormContextType | undefined>(undefined);
12+
13+
export const ContactFormProvider = ({ children }: { children: ReactNode }) => {
14+
const [isFormOpen, setIsFormOpen] = useState(false);
15+
16+
const openForm = () => setIsFormOpen(true);
17+
const closeForm = () => setIsFormOpen(false);
18+
19+
return (
20+
<ContactFormContext.Provider value={{ isFormOpen, openForm, closeForm }}>
21+
{children}
22+
</ContactFormContext.Provider>
23+
);
24+
};
25+
26+
export const useContactForm = () => {
27+
const context = useContext(ContactFormContext);
28+
if (context === undefined) {
29+
throw new Error('useContactForm must be used within a ContactFormProvider');
30+
}
31+
return context;
32+
};

0 commit comments

Comments
 (0)