mirror of
https://github.com/XFox111/my-website.git
synced 2026-04-22 07:28:01 +03:00
init: First version
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { useFormStatus } from "react-dom";
|
||||
|
||||
// Since useFormStatus requires to be inside of the form, I moved it to a separate helper component.
|
||||
// Could it be done better? Probably.
|
||||
// Did I do it? No.
|
||||
|
||||
/** Renders a React component that tracks the form status and calls the provided callback function when the form status changes. */
|
||||
const FormStatusTracker: React.FC<FormStatusTrackerProps> = ({ onPendingChanged }) =>
|
||||
{
|
||||
const { pending } = useFormStatus();
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
onPendingChanged(pending);
|
||||
}, [pending, onPendingChanged]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default FormStatusTracker;
|
||||
|
||||
/** Props for the `FormStatusTracker` component. */
|
||||
export type FormStatusTrackerProps =
|
||||
{
|
||||
/** The callback function that is called when the form status changes. */
|
||||
// For some reason ESLint shows a warning for "unused" "pending" parameter.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onPendingChanged: (pending: boolean) => void;
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
"use server";
|
||||
|
||||
import { canonicalName } from "@/_data/metadata";
|
||||
import nodemailer from "nodemailer";
|
||||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email().max(60),
|
||||
subject: z.string().max(120),
|
||||
message: z.string().min(100).max(2000),
|
||||
timezone: z.string().optional()
|
||||
});
|
||||
|
||||
const mailClient = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: parseInt(process.env.SMTP_PORT!),
|
||||
priority: "high",
|
||||
auth:
|
||||
{
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD
|
||||
}
|
||||
});
|
||||
|
||||
export default async function sendInquiry(_: FormStatus, formData: FormData): Promise<FormStatus>
|
||||
{
|
||||
const { success, data } = schema.safeParse({
|
||||
email: formData.get("email"),
|
||||
subject: formData.get("subject"),
|
||||
message: formData.get("message"),
|
||||
timezone: formData.get("timezone")
|
||||
});
|
||||
|
||||
if (!success)
|
||||
return {
|
||||
status: "error",
|
||||
message: "Invalid request"
|
||||
};
|
||||
|
||||
await mailClient.sendMail({
|
||||
from: process.env.SMTP_FROM_EMAIL,
|
||||
to: process.env.SMTP_TO_EMAIL,
|
||||
subject: `${canonicalName.hostname}: Contact Inquiry`,
|
||||
text: getTemplate(data)
|
||||
});
|
||||
|
||||
return {
|
||||
status: "success"
|
||||
};
|
||||
}
|
||||
|
||||
const getTemplate = (data: InquiryData): string => (
|
||||
`From: ${data.email}
|
||||
Received on: ${new Date()}
|
||||
Sender timezone: ${data.timezone ?? "Unknown"}
|
||||
|
||||
Subject: ${data.subject}
|
||||
|
||||
${data.message}`
|
||||
);
|
||||
|
||||
export type FormStatus =
|
||||
{
|
||||
status: "idle" | "success" | "error";
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type InquiryData =
|
||||
{
|
||||
email: string;
|
||||
subject: string;
|
||||
message: string;
|
||||
timezone?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user