Skip to content

Commit 113c95a

Browse files
committed
Contact me button working
1 parent 050cac0 commit 113c95a

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { NextResponse } from 'next/server';
2+
import fs from 'fs/promises';
3+
import path from 'path';
4+
5+
const submissionsDir = path.resolve(process.cwd(), 'contact-submissions');
6+
const submissionsFile = path.resolve(submissionsDir, 'submissions.json');
7+
8+
async function getSubmissions() {
9+
try {
10+
const data = await fs.readFile(submissionsFile, 'utf8');
11+
return JSON.parse(data);
12+
} catch (error) {
13+
return [];
14+
}
15+
}
16+
17+
export async function GET() {
18+
try {
19+
const submissions = await getSubmissions();
20+
return NextResponse.json(submissions, { status: 200 });
21+
} catch (error) {
22+
console.error(error);
23+
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
24+
}
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import fs from 'fs/promises';
3+
import path from 'path';
4+
5+
const submissionsDir = path.resolve(process.cwd(), 'contact-submissions');
6+
const submissionsFile = path.resolve(submissionsDir, 'submissions.json');
7+
8+
async function ensureSubmissionsDirExists() {
9+
try {
10+
await fs.mkdir(submissionsDir, { recursive: true });
11+
} catch (error) {
12+
console.error('Error creating submissions directory:', error);
13+
}
14+
}
15+
16+
async function getSubmissions() {
17+
try {
18+
await ensureSubmissionsDirExists();
19+
const data = await fs.readFile(submissionsFile, 'utf8');
20+
return JSON.parse(data);
21+
} catch (error) {
22+
return [];
23+
}
24+
}
25+
26+
async function saveSubmission(data: any) {
27+
const submissions = await getSubmissions();
28+
submissions.push(data);
29+
await fs.writeFile(submissionsFile, JSON.stringify(submissions, null, 2));
30+
}
31+
32+
export async function POST(req: NextRequest) {
33+
try {
34+
const { name, email, message } = await req.json();
35+
36+
if (!name || !email || !message) {
37+
return NextResponse.json({ message: 'Missing required fields' }, { status: 400 });
38+
}
39+
40+
const newSubmission = {
41+
name,
42+
email,
43+
message,
44+
date: new Date().toISOString(),
45+
};
46+
47+
await saveSubmission(newSubmission);
48+
49+
return NextResponse.json({ message: 'Submission saved' }, { status: 200 });
50+
} catch (error) {
51+
console.error(error);
52+
return NextResponse.json({ message: 'Internal Server Error' }, { status: 500 });
53+
}
54+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
'use client';
3+
4+
import { useState } from 'react';
5+
import ContactForm from './ContactForm';
6+
7+
export default function ContactButton() {
8+
const [showForm, setShowForm] = useState(false);
9+
10+
return (
11+
<>
12+
<button
13+
className="fixed bottom-4 right-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full shadow-lg"
14+
onClick={() => setShowForm(true)}
15+
>
16+
Contact Me
17+
</button>
18+
{showForm && <ContactForm onClose={() => setShowForm(false)} />}
19+
</>
20+
);
21+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
2+
'use client';
3+
4+
import { useState } from 'react';
5+
6+
export default function ContactForm({ onClose }: { onClose: () => void }) {
7+
const [name, setName] = useState('');
8+
const [email, setEmail] = useState('');
9+
const [message, setMessage] = useState('');
10+
const [submitting, setSubmitting] = useState(false);
11+
const [error, setError] = useState('');
12+
const [success, setSuccess] = useState(false);
13+
14+
const handleSubmit = async (e: React.FormEvent) => {
15+
e.preventDefault();
16+
setSubmitting(true);
17+
setError('');
18+
setSuccess(false);
19+
20+
try {
21+
const response = await fetch('/api/contact', {
22+
method: 'POST',
23+
headers: {
24+
'Content-Type': 'application/json',
25+
},
26+
body: JSON.stringify({ name, email, message }),
27+
});
28+
29+
if (response.ok) {
30+
setSuccess(true);
31+
setName('');
32+
setEmail('');
33+
setMessage('');
34+
} else {
35+
const data = await response.json();
36+
setError(data.message || 'Something went wrong.');
37+
}
38+
} catch (error) {
39+
setError('Something went wrong.');
40+
} finally {
41+
setSubmitting(false);
42+
}
43+
};
44+
45+
return (
46+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
47+
<div className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-lg w-full max-w-md">
48+
<h2 className="text-2xl font-bold mb-4">Contact Me</h2>
49+
{success ? (
50+
<div className="text-green-500">Your message has been sent successfully!</div>
51+
) : (
52+
<form onSubmit={handleSubmit}>
53+
<div className="mb-4">
54+
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
55+
Name
56+
</label>
57+
<input
58+
type="text"
59+
id="name"
60+
value={name}
61+
onChange={(e) => setName(e.target.value)}
62+
className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
63+
required
64+
/>
65+
</div>
66+
<div className="mb-4">
67+
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
68+
Email
69+
</label>
70+
<input
71+
type="email"
72+
id="email"
73+
value={email}
74+
onChange={(e) => setEmail(e.target.value)}
75+
className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
76+
required
77+
/>
78+
</div>
79+
<div className="mb-4">
80+
<label htmlFor="message" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
81+
Message
82+
</label>
83+
<textarea
84+
id="message"
85+
value={message}
86+
onChange={(e) => setMessage(e.target.value)}
87+
rows={4}
88+
className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
89+
required
90+
></textarea>
91+
</div>
92+
{error && <div className="text-red-500 mb-4">{error}</div>}
93+
<div className="flex justify-end">
94+
<button
95+
type="button"
96+
onClick={onClose}
97+
className="mr-2 bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-gray-200 font-bold py-2 px-4 rounded"
98+
disabled={submitting}
99+
>
100+
Cancel
101+
</button>
102+
<button
103+
type="submit"
104+
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
105+
disabled={submitting}
106+
>
107+
{submitting ? 'Submitting...' : 'Submit'}
108+
</button>
109+
</div>
110+
</form>
111+
)}
112+
</div>
113+
</div>
114+
);
115+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
5+
const PASSWORD = 'password'; // Replace with a strong password
6+
7+
export default function ContactSubmissions() {
8+
const [password, setPassword] = useState('');
9+
const [authenticated, setAuthenticated] = useState(false);
10+
const [submissions, setSubmissions] = useState([]);
11+
const [error, setError] = useState('');
12+
13+
const handlePasswordSubmit = (e: React.FormEvent) => {
14+
e.preventDefault();
15+
if (password === PASSWORD) {
16+
setAuthenticated(true);
17+
} else {
18+
setError('Incorrect password');
19+
}
20+
};
21+
22+
useEffect(() => {
23+
if (authenticated) {
24+
const fetchSubmissions = async () => {
25+
try {
26+
const response = await fetch('/api/contact-submissions');
27+
if (response.ok) {
28+
const data = await response.json();
29+
setSubmissions(data);
30+
} else {
31+
setError('Failed to fetch submissions.');
32+
}
33+
} catch (error) {
34+
setError('Failed to fetch submissions.');
35+
}
36+
};
37+
fetchSubmissions();
38+
}
39+
}, [authenticated]);
40+
41+
if (!authenticated) {
42+
return (
43+
<div className="flex items-center justify-center h-screen">
44+
<form onSubmit={handlePasswordSubmit} className="bg-white dark:bg-gray-800 p-8 rounded-lg shadow-lg">
45+
<h2 className="text-2xl font-bold mb-4">Enter Password</h2>
46+
<div className="mb-4">
47+
<label htmlFor="password" a className="block text-sm font-medium text-gray-700 dark:text-gray-300">
48+
Password
49+
</label>
50+
<input
51+
type="password"
52+
id="password"
53+
value={password}
54+
onChange={(e) => setPassword(e.target.value)}
55+
className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
56+
/>
57+
</div>
58+
{error && <div className="text-red-500 mb-4">{error}</div>}
59+
<button
60+
type="submit"
61+
className="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
62+
>
63+
Submit
64+
</button>
65+
</form>
66+
</div>
67+
);
68+
}
69+
70+
return (
71+
<div className="container mx-auto p-4">
72+
<h1 className="text-3xl font-bold mb-4">Contact Submissions</h1>
73+
{submissions.length === 0 ? (
74+
<p>No submissions yet.</p>
75+
) : (
76+
<div className="space-y-4">
77+
{submissions.map((submission: any, index: number) => (
78+
<div key={index} className="bg-white dark:bg-gray-800 p-4 rounded-lg shadow">
79+
<p><strong>Name:</strong> {submission.name}</p>
80+
<p><strong>Email:</strong> {submission.email}</p>
81+
<p><strong>Message:</strong> {submission.message}</p>
82+
<p><strong>Date:</strong> {new Date(submission.date).toLocaleString()}</p>
83+
</div>
84+
))}
85+
</div>
86+
)}
87+
</div>
88+
);
89+
}

next-js-app/src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Metadata } from "next";
22
import { Geist, Geist_Mono } from "next/font/google";
33
import "./globals.css";
4+
import ContactButton from "./components/contact/ContactButton";
45

56
const geistSans = Geist({
67
variable: "--font-geist-sans",
@@ -28,6 +29,7 @@ export default function RootLayout({
2829
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
2930
>
3031
{children}
32+
<ContactButton />
3133
</body>
3234
</html>
3335
);

0 commit comments

Comments
 (0)