Show an on-screen success message instead of emailing visitors
This commit is contained in:
@@ -1,20 +1,15 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { after } from "next/server";
|
|
||||||
|
|
||||||
export type ContactState = { status: "idle" | "sent" | "error"; message?: string; copySent?: boolean };
|
export type ContactState = { status: "idle" | "sent" | "error"; message?: string };
|
||||||
|
|
||||||
// Where enquiries land. Override with CONTACT_TO in the environment if it ever changes.
|
// Where enquiries land. Override with CONTACT_TO in the environment if it ever changes.
|
||||||
const TO = process.env.CONTACT_TO || "rey@aigrowth360.io";
|
const TO = process.env.CONTACT_TO || "rey@aigrowth360.io";
|
||||||
// Resend's shared sender works without a verified domain, but only delivers to the address that
|
// Resend's shared sender only delivers to the address that owns the Resend account. To send to TO
|
||||||
// owns the Resend account. Set CONTACT_FROM to an address on a domain verified in Resend
|
// from any other account, set CONTACT_FROM to an address on a domain verified in Resend
|
||||||
// (e.g. "AI Growth Technologies <hello@aigrowth360.io>") to also send visitors a confirmation.
|
// (e.g. "AI Growth Website <hello@aigrowthtechnologies.com>").
|
||||||
const FROM = process.env.CONTACT_FROM || "AI Growth Website <onboarding@resend.dev>";
|
const FROM = process.env.CONTACT_FROM || "AI Growth Website <onboarding@resend.dev>";
|
||||||
const CAN_EMAIL_VISITORS = Boolean(process.env.CONTACT_FROM);
|
|
||||||
const SITE = process.env.VERCEL_PROJECT_PRODUCTION_URL
|
|
||||||
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
|
||||||
: "https://www.aigrowthtechnologies.com";
|
|
||||||
|
|
||||||
const MIN_FILL_MS = 3000;
|
const MIN_FILL_MS = 3000;
|
||||||
const MAX_LINKS = 3;
|
const MAX_LINKS = 3;
|
||||||
@@ -55,56 +50,6 @@ async function sendEmail(apiKey: string, payload: Record<string, unknown>) {
|
|||||||
if (!res.ok) throw new Error(`Resend ${res.status}: ${await res.text()}`);
|
if (!res.ok) throw new Error(`Resend ${res.status}: ${await res.text()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmationEmail(name: string, rows: [string, string][], message: string) {
|
|
||||||
const first = name.split(/\s+/)[0];
|
|
||||||
const text = [
|
|
||||||
`Hi ${first},`,
|
|
||||||
"",
|
|
||||||
"Thanks for reaching out to AI Growth Technologies. Your message is with our team, and a strategist will review what you shared and get back to you shortly.",
|
|
||||||
"",
|
|
||||||
"Here's a copy of what you sent:",
|
|
||||||
"",
|
|
||||||
...rows.map(([k, v]) => `${k}: ${v}`),
|
|
||||||
"",
|
|
||||||
message,
|
|
||||||
"",
|
|
||||||
"— The AI Growth Technologies team",
|
|
||||||
SITE,
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div style="background:#000;padding:32px 16px;font-family:Arial,Helvetica,sans-serif">
|
|
||||||
<div style="max-width:560px;margin:0 auto;background:#111;border:1px solid #303030;color:#fff">
|
|
||||||
<div style="padding:28px 32px;border-bottom:1px solid #303030">
|
|
||||||
<img src="${SITE}/logo-mark.png" width="44" alt="AI Growth Technologies" style="display:block">
|
|
||||||
</div>
|
|
||||||
<div style="padding:32px">
|
|
||||||
<p style="margin:0 0 6px;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:#da291c">Message received</p>
|
|
||||||
<h1 style="margin:0 0 20px;font-size:24px;font-weight:400;line-height:1.25">Thanks, ${escape(first)}. We’ve got it.</h1>
|
|
||||||
<p style="margin:0 0 24px;font-size:15px;line-height:1.7;color:#cfcfcf">
|
|
||||||
Your message is with our team. A strategist will review what you shared and get back to you shortly.
|
|
||||||
</p>
|
|
||||||
<p style="margin:0 0 10px;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:#8f8f8f">What you sent</p>
|
|
||||||
<table cellpadding="0" cellspacing="0" style="width:100%;font-size:14px;border-top:1px solid #303030">
|
|
||||||
${rows
|
|
||||||
.map(
|
|
||||||
([k, v]) =>
|
|
||||||
`<tr><td style="padding:10px 0;border-bottom:1px solid #303030;color:#8f8f8f;width:130px">${k}</td><td style="padding:10px 0;border-bottom:1px solid #303030;color:#fff">${escape(v)}</td></tr>`,
|
|
||||||
)
|
|
||||||
.join("")}
|
|
||||||
</table>
|
|
||||||
<p style="margin:20px 0 0;font-size:14px;line-height:1.7;color:#cfcfcf;white-space:pre-wrap">${escape(message)}</p>
|
|
||||||
</div>
|
|
||||||
<div style="padding:20px 32px;border-top:1px solid #303030;font-size:12px;color:#8f8f8f">
|
|
||||||
AI Growth Technologies · Dubai · Houston · Miami · New York<br>
|
|
||||||
<a href="${SITE}" style="color:#fff">${SITE.replace(/^https?:\/\//, "")}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
return { subject: "We received your message — AI Growth Technologies", text, html };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function sendContact(_prev: ContactState, formData: FormData): Promise<ContactState> {
|
export async function sendContact(_prev: ContactState, formData: FormData): Promise<ContactState> {
|
||||||
// 1. Honeypot: real visitors never see or fill this field.
|
// 1. Honeypot: real visitors never see or fill this field.
|
||||||
if (clean(formData.get("company_url"), 200)) return silentlyDrop("honeypot");
|
if (clean(formData.get("company_url"), 200)) return silentlyDrop("honeypot");
|
||||||
@@ -165,16 +110,5 @@ export async function sendContact(_prev: ContactState, formData: FormData): Prom
|
|||||||
return { status: "error", message: "We couldn’t send your message." };
|
return { status: "error", message: "We couldn’t send your message." };
|
||||||
}
|
}
|
||||||
|
|
||||||
// The visitor's copy goes out after the response, and never fails the submission.
|
return { status: "sent" };
|
||||||
if (CAN_EMAIL_VISITORS) {
|
|
||||||
after(async () => {
|
|
||||||
try {
|
|
||||||
await sendEmail(apiKey, { to: [email], reply_to: TO, ...confirmationEmail(name, rows, message) });
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Contact form: could not send the visitor confirmation", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: "sent", copySent: CAN_EMAIL_VISITORS };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2598,3 +2598,28 @@ a {
|
|||||||
font-size: clamp(22px, 2.2vw, 30px);
|
font-size: clamp(22px, 2.2vw, 30px);
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sent-mark {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sent-mark circle {
|
||||||
|
stroke: var(--grafite);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sent-mark path {
|
||||||
|
stroke: var(--rosso);
|
||||||
|
stroke-dasharray: 32;
|
||||||
|
stroke-dashoffset: 32;
|
||||||
|
animation: draw-check 0.7s var(--ease) 0.15s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes draw-check {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ export default function ContactForm() {
|
|||||||
if (state.status === "sent") {
|
if (state.status === "sent") {
|
||||||
return (
|
return (
|
||||||
<div className="contact-form form-sent" role="status">
|
<div className="contact-form form-sent" role="status">
|
||||||
<span className="card-num">Received</span>
|
<svg className="sent-mark" viewBox="0 0 48 48" aria-hidden="true">
|
||||||
<h3 className="card-title">Thanks — your message is with the team.</h3>
|
<circle cx="24" cy="24" r="23" />
|
||||||
<p className="copy">
|
<path d="M14 24.5l7 7 13-14" />
|
||||||
We’ll review what you shared and get back to you shortly.
|
</svg>
|
||||||
{state.copySent && " A confirmation with a copy of your message is on its way to your inbox."}
|
<h3 className="card-title">Your message was sent successfully.</h3>
|
||||||
</p>
|
<p className="copy">Thank you for reaching out. Our team will review what you shared and get back to you shortly.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user