mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-10-25 22:35:49 -04:00
refactor .js files to ES modules where possible
This commit is contained in:
@@ -4,7 +4,7 @@ import { DefaultSeo, SocialProfileJsonLd } from "next-seo";
|
||||
import * as Fathom from "fathom-client";
|
||||
import { ThemeProvider } from "../contexts/ThemeContext";
|
||||
import Layout from "../components/Layout";
|
||||
import * as config from "../lib/config";
|
||||
import config from "../lib/config";
|
||||
import { defaultSeo, socialProfileJsonLd } from "../lib/config/seo";
|
||||
import { globalStyles, classNames } from "../lib/styles/stitches.config";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
@@ -26,6 +26,11 @@ const App = ({ Component, pageProps }: AppProps) => {
|
||||
const canonical = `${process.env.NEXT_PUBLIC_BASE_URL || ""}${router.pathname === "/" ? "" : router.pathname}/`;
|
||||
|
||||
useEffect(() => {
|
||||
// bail immediately if the site ID is not set
|
||||
if (!process.env.NEXT_PUBLIC_FATHOM_SITE_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't track pageviews on branch/deploy previews and localhost
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production") {
|
||||
return;
|
||||
@@ -33,7 +38,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
||||
|
||||
// https://usefathom.com/docs/integrations/next
|
||||
// https://vercel.com/guides/deploying-nextjs-using-fathom-analytics-with-vercel
|
||||
Fathom.load(config.fathomSiteId, {
|
||||
Fathom.load(process.env.NEXT_PUBLIC_FATHOM_SITE_ID, {
|
||||
includedDomains: [config.siteDomain],
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
import { getCssText, theme } from "../lib/styles/stitches.config";
|
||||
import * as config from "../lib/config";
|
||||
import config from "../lib/config";
|
||||
|
||||
// https://nextjs.org/docs/advanced-features/custom-document
|
||||
const Document = () => {
|
||||
|
||||
@@ -1,40 +1,47 @@
|
||||
import nodemailer from "nodemailer";
|
||||
import queryString from "query-string";
|
||||
import fetcher from "../../lib/helpers/fetcher";
|
||||
import { siteDomain, authorEmail, hcaptchaSiteKey } from "../../lib/config";
|
||||
import config from "../../lib/config";
|
||||
import type { NextApiHandler } from "next";
|
||||
|
||||
const handler: NextApiHandler = async (req, res) => {
|
||||
// redirect GET requests to this endpoint to the contact form itself
|
||||
if (req.method === "GET") {
|
||||
return res.redirect(`${process.env.NEXT_PUBLIC_BASE_URL || `https://${siteDomain}`}/contact/`);
|
||||
// only allow POST requests, otherwise return a 405 Method Not Allowed
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).send(null);
|
||||
}
|
||||
|
||||
// possible weirdness? https://github.com/orgs/vercel/discussions/78#discussioncomment-5089059
|
||||
const data = req.body;
|
||||
try {
|
||||
// possible weirdness? https://github.com/orgs/vercel/discussions/78#discussioncomment-5089059
|
||||
const data = req.body;
|
||||
|
||||
// these are both backups to client-side validations just in case someone squeezes through without them. the codes
|
||||
// are identical so they're caught in the same fashion.
|
||||
if (!data.name || !data.email || !data.message) {
|
||||
// all fields are required
|
||||
throw new Error("MISSING_DATA");
|
||||
// these are both backups to client-side validations just in case someone squeezes through without them. the codes
|
||||
// are identical so they're caught in the same fashion.
|
||||
if (!data.name || !data.email || !data.message) {
|
||||
// all fields are required
|
||||
throw new Error("missing_data");
|
||||
}
|
||||
if (!data["h-captcha-response"] || !(await validateCaptcha(data["h-captcha-response"]))) {
|
||||
// either the captcha is wrong or completely missing
|
||||
throw new Error("invalid_captcha");
|
||||
}
|
||||
|
||||
// throw an internal error, not user's fault
|
||||
if (!(await sendMessage(data))) {
|
||||
throw new Error("nodemailer_error");
|
||||
}
|
||||
|
||||
// disable caching on both ends. see:
|
||||
// https://vercel.com/docs/concepts/functions/edge-functions/edge-caching
|
||||
res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
||||
|
||||
// success! let the client know
|
||||
return res.status(201).json({ success: true });
|
||||
} catch (
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
error: any
|
||||
) {
|
||||
return res.status(400).json({ error: error.message ?? "Bad request." });
|
||||
}
|
||||
if (!data["h-captcha-response"] || !(await validateCaptcha(data["h-captcha-response"]))) {
|
||||
// either the captcha is wrong or completely missing
|
||||
throw new Error("INVALID_CAPTCHA");
|
||||
}
|
||||
|
||||
// throw an internal error, not user's fault
|
||||
if (!(await sendMessage(data))) {
|
||||
throw new Error("NODEMAILER_ERROR");
|
||||
}
|
||||
|
||||
// disable caching on both ends. see:
|
||||
// https://vercel.com/docs/concepts/functions/edge-functions/edge-caching
|
||||
res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
||||
|
||||
// success! let the client know
|
||||
return res.status(201).json({ success: true });
|
||||
};
|
||||
|
||||
const validateCaptcha = async (formResponse: unknown): Promise<unknown> => {
|
||||
@@ -46,7 +53,7 @@ const validateCaptcha = async (formResponse: unknown): Promise<unknown> => {
|
||||
body: queryString.stringify({
|
||||
response: formResponse,
|
||||
// fallback to dummy secret for testing: https://docs.hcaptcha.com/#integration-testing-test-keys
|
||||
sitekey: hcaptchaSiteKey || "10000000-ffff-ffff-ffff-000000000001",
|
||||
sitekey: process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY || "10000000-ffff-ffff-ffff-000000000001",
|
||||
secret: process.env.HCAPTCHA_SECRET_KEY || "0x0000000000000000000000000000000000000000",
|
||||
}),
|
||||
});
|
||||
@@ -57,19 +64,22 @@ const validateCaptcha = async (formResponse: unknown): Promise<unknown> => {
|
||||
const sendMessage = async (data: Record<string, unknown>): Promise<boolean> => {
|
||||
try {
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: "mailgun",
|
||||
// https://resend.com/docs/send-with-nodemailer-smtp
|
||||
host: "smtp.resend.com",
|
||||
secure: true,
|
||||
port: 465,
|
||||
auth: {
|
||||
user: process.env.MAILGUN_SMTP_USER,
|
||||
pass: process.env.MAILGUN_SMTP_PASS,
|
||||
user: "resend",
|
||||
pass: process.env.RESEND_API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
await transporter.sendMail({
|
||||
from: `${data.name} <${process.env.MAILGUN_SMTP_USER}>`,
|
||||
sender: `nodemailer <${process.env.MAILGUN_SMTP_USER}>`,
|
||||
from: `${data.name} <${process.env.RESEND_DOMAIN ? `noreply@${process.env.RESEND_DOMAIN}` : "onboarding@resend.dev"}>`,
|
||||
sender: `nodemailer <${process.env.RESEND_DOMAIN ? `noreply@${process.env.RESEND_DOMAIN}` : "onboarding@resend.dev"}>`,
|
||||
replyTo: `${data.name} <${data.email}>`,
|
||||
to: `<${authorEmail}>`,
|
||||
subject: `[${siteDomain}] Contact Form Submission`,
|
||||
to: `<${config.authorEmail}>`,
|
||||
subject: `[${config.siteDomain}] Contact Form Submission`,
|
||||
// TODO: add markdown parsing as promised on the form.
|
||||
text: `${data.message}`,
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { buildFeed } from "../lib/helpers/build-feed";
|
||||
import type { GetServerSideFeedProps } from "../lib/helpers/build-feed";
|
||||
|
||||
export const getServerSideProps: GetServerSideFeedProps = async (context) => {
|
||||
return buildFeed(context, "atom");
|
||||
return buildFeed(context, { format: "atom" });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
|
||||
@@ -2,7 +2,7 @@ import { buildFeed } from "../lib/helpers/build-feed";
|
||||
import type { GetServerSideFeedProps } from "../lib/helpers/build-feed";
|
||||
|
||||
export const getServerSideProps: GetServerSideFeedProps = async (context) => {
|
||||
return buildFeed(context, "rss");
|
||||
return buildFeed(context, { format: "rss" });
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
|
||||
@@ -6,7 +6,7 @@ import PostMeta from "../../components/PostMeta";
|
||||
import Comments from "../../components/Comments";
|
||||
import * as mdxComponents from "../../lib/helpers/mdx-components";
|
||||
import { getPostSlugs, compilePost } from "../../lib/helpers/posts";
|
||||
import * as config from "../../lib/config";
|
||||
import config from "../../lib/config";
|
||||
import { articleJsonLd } from "../../lib/config/seo";
|
||||
import { meJpg } from "../../lib/config/favicons";
|
||||
import type { GetStaticProps, GetStaticPaths, InferGetStaticPropsType } from "next";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextSeo } from "next-seo";
|
||||
import Content from "../../components/Content";
|
||||
import PostsList from "../../components/PostsList";
|
||||
import { getAllPosts } from "../../lib/helpers/posts";
|
||||
import { authorName } from "../../lib/config";
|
||||
import config from "../../lib/config";
|
||||
import type { GetStaticProps, InferGetStaticPropsType } from "next";
|
||||
import type { PostsByYear } from "../../types";
|
||||
|
||||
@@ -11,7 +11,7 @@ const Notes = ({ notesByYear }: InferGetStaticPropsType<typeof getStaticProps>)
|
||||
<>
|
||||
<NextSeo
|
||||
title="Notes"
|
||||
description={`Recent posts by ${authorName}.`}
|
||||
description={`Recent posts by ${config.authorName}.`}
|
||||
openGraph={{
|
||||
title: "Notes",
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,7 @@ import Link from "../components/Link";
|
||||
import RepositoryCard from "../components/RepositoryCard";
|
||||
import { SiGithub } from "react-icons/si";
|
||||
import { styled, theme } from "../lib/styles/stitches.config";
|
||||
import { authorSocial } from "../lib/config";
|
||||
import config from "../lib/config";
|
||||
import type { GetStaticProps, InferGetStaticPropsType } from "next";
|
||||
import type { User, Repository } from "@octokit/graphql-schema";
|
||||
import type { Project } from "../types";
|
||||
@@ -60,7 +60,7 @@ const Projects = ({ repos }: InferGetStaticPropsType<typeof getStaticProps>) =>
|
||||
</Wrapper>
|
||||
|
||||
<ViewMore>
|
||||
<Link href={`https://github.com/${authorSocial.github}`}>
|
||||
<Link href={`https://github.com/${config.authorSocial.github}`}>
|
||||
View more on <GitHubLogo /> GitHub...
|
||||
</Link>
|
||||
</ViewMore>
|
||||
@@ -73,7 +73,7 @@ export const getStaticProps: GetStaticProps<{
|
||||
repos: Project[];
|
||||
}> = async () => {
|
||||
// don't fail the entire site build if the required API key for this page is missing
|
||||
if (typeof process.env.GH_PUBLIC_TOKEN === "undefined" || process.env.GH_PUBLIC_TOKEN === "") {
|
||||
if (!process.env.GH_PUBLIC_TOKEN || process.env.GH_PUBLIC_TOKEN === "") {
|
||||
console.warn(`ERROR: I can't fetch any GitHub projects without "GH_PUBLIC_TOKEN" set! Skipping for now...`);
|
||||
|
||||
return {
|
||||
@@ -113,7 +113,7 @@ export const getStaticProps: GetStaticProps<{
|
||||
}
|
||||
`,
|
||||
{
|
||||
username: authorSocial.github,
|
||||
username: config.authorSocial.github,
|
||||
sort: "STARGAZERS",
|
||||
limit: 12,
|
||||
headers: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as config from "../lib/config";
|
||||
import config from "../lib/config";
|
||||
import { chrome512Png, chrome192Png, maskable512Png, maskable192Png } from "../lib/config/favicons";
|
||||
import type { GetServerSideProps } from "next";
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { SitemapStream, EnumChangefreq } from "sitemap";
|
||||
import { getAllPosts } from "../lib/helpers/posts";
|
||||
import { siteDomain } from "../lib/config";
|
||||
import config from "../lib/config";
|
||||
import type { GetServerSideProps } from "next";
|
||||
import type { SitemapItemLoose } from "sitemap";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => {
|
||||
const { res } = context;
|
||||
const stream = new SitemapStream({ hostname: process.env.NEXT_PUBLIC_BASE_URL || `https://${siteDomain}` });
|
||||
const stream = new SitemapStream({ hostname: process.env.NEXT_PUBLIC_BASE_URL || `https://${config.siteDomain}` });
|
||||
|
||||
// cache on edge for 12 hours
|
||||
res.setHeader("cache-control", "public, max-age=0, s-maxage=43200, stale-while-revalidate");
|
||||
|
||||
Reference in New Issue
Block a user