1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-01-15 08:22:59 -05:00

finally got myself a real contact form 📬

This commit is contained in:
2021-10-14 08:12:40 -04:00
parent d4ad28b494
commit 2106b3703d
17 changed files with 403 additions and 54 deletions

View File

@@ -3,6 +3,7 @@ import "./src/emoji.js";
import "./src/counter.js";
import "./src/clipboard.js";
import "./src/anchor.js";
import "./src/contact.js";
import "./src/projects.js";
export default () => {};

79
assets/js/src/contact.js Normal file
View File

@@ -0,0 +1,79 @@
import "vanilla-hcaptcha";
import fetch from "cross-fetch";
// don't continue if there isn't a contact form on this page
// TODO: be better and only do any of this on /contact/
const contactForm = document.getElementById("contact-form");
if (contactForm) {
contactForm.addEventListener("submit", (event) => {
// immediately prevent <form> from actually submitting to a new page
event.preventDefault();
const submitButton = document.getElementById("contact-form-btn-submit");
// disable the whole form if the button has been disabled below (on success)
if (submitButton.disabled === true) {
return;
}
// change button appearance between click and server response
submitButton.innerText = "Sending...";
submitButton.disabled = true; // prevent accidental multiple submissions
submitButton.style.cursor = "default";
// feedback <span>s for later
const successSpan = document.getElementById("contact-form-result-success");
const errorSpan = document.getElementById("contact-form-result-error");
// https://simonplend.com/how-to-use-fetch-to-post-form-data-as-json-to-your-api/
const formData = Object.fromEntries(new FormData(event.currentTarget).entries());
fetch(contactForm.action, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(formData),
})
.then((response) => response.json())
.then((data) => {
if (data.success === true) {
// handle successful submission
// we can disable submissions & hide the send button now
submitButton.disabled = true;
submitButton.style.display = "none";
// just in case there *was* a PEBCAK error and it was corrected
errorSpan.style.display = "none";
// let user know we were successful
successSpan.innerText = "Success! You should hear from me soon. :)";
} else {
// pass on an error sent by the server
throw new Error(data.message);
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : "Unknown";
// give user feedback based on the error message returned
if (message === "invalidCaptcha") {
errorSpan.innerText = "Error: Did you remember to click the CAPTCHA?";
} else if (message === "invalidEmail") {
errorSpan.innerText = "Error: Please double check your email address.";
} else if (message === "missingData") {
errorSpan.innerText = "Error: Please make sure that all fields are filled in.";
} else {
// something else went wrong, and it's probably my fault...
errorSpan.innerText = "Internal server error. Try again later?";
}
// reset submit button to let user try again
submitButton.innerText = "Try Again";
submitButton.disabled = false;
submitButton.style.cursor = "pointer";
});
});
}

View File

@@ -11,6 +11,7 @@
@use "../pages/videos";
@use "../pages/etc";
@use "../pages/projects";
@use "../pages/contact";
@use "../pages/fourOhFour";
// Responsive Awesomeness
@@ -26,5 +27,6 @@
@include videos.responsive();
@include etc.responsive();
@include projects.responsive();
@include contact.responsive();
@include fourOhFour.responsive();
}

View File

@@ -61,6 +61,8 @@ $themes: (
super-light: #f4f4f4,
super-duper-light: #fbfbfb,
links: #0e6dc2,
success: #0b890f,
error: #ff1b1b,
),
dark: (
background-inner: #1e1e1e,
@@ -74,6 +76,8 @@ $themes: (
super-light: #272727,
super-duper-light: #1f1f1f,
links: #88c7ff,
success: #78df55,
error: #f54545,
),
);

View File

@@ -16,6 +16,7 @@
@use "pages/videos";
@use "pages/etc";
@use "pages/projects";
@use "pages/contact";
@use "pages/fourOhFour";
// Miscellaneous

View File

@@ -0,0 +1,111 @@
@use "../abstracts/themes";
// Contact Styles
div.layout-contact {
max-width: 600px;
padding: 1.5em 0;
h1 {
margin-bottom: 0.4em;
text-align: center;
}
p {
font-size: 0.9em;
margin-bottom: 0.5em;
}
input[type="text"],
input[type="email"],
select,
textarea {
width: 100%;
padding: 0.8em;
margin: 0.6em 0;
border: 1px solid;
border-radius: 0.3em;
font-size: 0.9em;
@include themes.themed(color, "text");
@include themes.themed(background-color, "super-duper-light");
@include themes.themed(border-color, "light");
}
textarea {
height: 10em;
margin-bottom: 0;
// allow vertical resizing & disable horizontal
resize: vertical; // stylelint-disable-line plugin/no-unsupported-browser-features
}
div#contact-form-action-row {
display: flex;
align-items: center;
button {
padding: 0.8em 1.2em;
margin-right: 1.5em;
border: 0;
border-radius: 0.3em;
cursor: pointer;
line-height: 1.5;
user-select: none;
@include themes.themed(color, "text");
@include themes.themed(background-color, "kinda-light");
&:hover,
&:focus {
@include themes.themed(color, "super-duper-light");
@include themes.themed(background-color, "links");
}
img.emoji {
margin-right: 0.4em;
cursor: inherit;
}
}
span {
font-size: 0.9em;
font-weight: 600;
&#contact-form-result-success {
@include themes.themed(color, "success");
}
&#contact-form-result-error {
@include themes.themed(color, "error");
}
}
}
// hcaptcha widget
#contact-form-captcha {
display: block;
margin: 1.2em 0;
}
span#contact-form-md-info {
display: block;
font-size: 0.75em;
margin-top: 0.25em;
margin-left: 0.75em;
a {
// disable fancy underline without `.no-underline`
background: none !important;
padding: 0;
&:first-of-type {
font-weight: 500;
}
}
}
}
// Responsive
// stylelint-disable-next-line block-no-empty
@mixin responsive() {
}