implemented contact form using Node.js and Postmark.

This commit is contained in:
Joshua Ashton
2025-01-04 14:36:32 -07:00
parent 5688ae674f
commit 3227c4d699
6 changed files with 967 additions and 59 deletions
+3 -4
View File
@@ -25,11 +25,12 @@ Bug Fixes:
Week 2: 24/12/24 - 30/12/24 (Adding key information)
- [ ] Resume page (HTML and PDF options).
- [x] Resume page (HTML and PDF options).
- [ ] Convert SLCC e-portfolio to my own website
- [ ] Meet required pages and content
- [ ] Embed PDF course reflections/signature assignments.
- [ ] Contact page.
- [x] Contact page.
- [ ] Implement MailTrap for sending me automated emails when there is a contact form submission.
Week 3: 31/12/24 - 6/12/24
@@ -41,5 +42,3 @@ Week 3: 31/12/24 - 6/12/24
- [ ] Separate user for self-hosting, with reduced permissions.
- [ ] Dockerize
- [ ] Blog page (stretch goal).
Don't be the person who informs people against their will.
+866 -2
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -4,6 +4,9 @@
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"cors": "^2.8.5",
"express": "^4.21.2",
"postmark": "^4.0.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-pdf": "^9.2.1",
+60 -43
View File
@@ -1,5 +1,4 @@
import React, { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
/**
* TODO: Add email validation function.
@@ -7,10 +6,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function Contact() {
const [formData, setFormData] = useState({
name: "",
email: "",
emailAddress: "",
message: "",
});
const [isSubmitted, setIsSubmitted] = useState(false);
const handleChange = (event) => {
setFormData({
...formData,
@@ -18,53 +19,69 @@ export default function Contact() {
});
};
const handleSubmit = (event) => {
event.preventDefault();
// Handle form submission here, e.g., send data to server
console.log(formData);
const handleSubmit = async (event) => {
event.preventDefault(); // Prevent default form submission behavior
setIsSubmitted(true);
try {
const response = await fetch("http://localhost:3001/api/send-email", {
method: "POST", // Use POST for sending data
headers: { "Content-Type": "application/json" }, // Set the content type
body: JSON.stringify(formData), // Convert the JS object to JSON string
});
const json = await response.json();
setData(json);
} catch (error) {
console.error("Error fetching data:", error);
}
};
return (
<div id="contact-section">
<h1>Contact Me</h1>
<form onSubmit={handleSubmit}>
<div className="row">
<input
className="shadow"
placeholder="Name"
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
<input
className="shadow"
placeholder="example@email.com"
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div className="row">
<textarea
className="shadow"
placeholder="Message"
id="message"
name="message"
value={formData.message}
onChange={handleChange}
/>
</div>
<div id="form-container"></div>
{isSubmitted ? (
<p>Thanks for reaching out! I'll get in touch shortly.</p>
) : (
<form onSubmit={handleSubmit}>
<div className="row">
<input
className="shadow"
placeholder="Name"
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
/>
<input
className="shadow"
placeholder="example@email.com"
type="email"
id="emailAddress"
name="emailAddress"
value={formData.emailAddress}
onChange={handleChange}
/>
</div>
<div className="row">
<textarea
className="shadow"
placeholder="Message"
id="message"
name="message"
value={formData.message}
onChange={handleChange}
/>
</div>
<div className="row">
<button type="submit" className="button shadow">
Submit
</button>
</div>
</form>
<div className="row">
<button type="submit" className="button shadow">
Submit
</button>
</div>
</form>
)}
</div>
);
}
+6 -10
View File
@@ -63,6 +63,11 @@ h4 {
font-size: clamp(1rem, 1.5vw, 1.25rem);
}
p {
font-size: clamp(1rem, 1.5rem, 2rem);
color: #ebd2ff;
}
.material-icons {
font-variation-settings:
"FILL" 0,
@@ -191,23 +196,16 @@ h4 {
align-items: center;
justify-content: center;
width: var(--width);
aspect-ratio: 1/1;
margin: 0 auto;
border-radius: 50%;
overflow: hidden;
}
#header-image img {
display: block;
width: calc(var(--width) / 2);
filter: drop-shadow(5px 6px 5px rgba(36, 36, 42, 0.5));
user-select: none;
z-index: 1;
border-radius: 50%;
}
#header-image img {
animation: imgMoveDown 0.8s ease-in forwards 0.2s;
border-radius: 50%;
}
#skill-cards {
@@ -350,8 +348,6 @@ h4 {
flex-direction: column;
justify-content: center;
align-items: center;
font-size: clamp(1rem, 1.5rem, 2rem);
color: #ebd2ff;
}
.row {
+29
View File
@@ -0,0 +1,29 @@
import express from "express";
import cors from "cors";
import postmark from "postmark";
const app = express();
const port = 3001; // Example port
const client = new postmark.ServerClient(
"d15a4575-17f1-4ead-8e53-69eb2faea982",
);
app.use(cors());
app.use(express.json()); // Parse incoming JSON data
// Example API endpoint
app.post("/api/send-email", (req, res) => {
const email = {
From: "contact@joshashton.dev",
To: "me@joshashton.dev",
Subject: req.body.name + " - " + req.body.emailAddress,
TextBody: req.body.message,
MessageStream: "outbound",
};
client.sendEmail(email);
res.json({ text: "email sent!" });
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});