|
3 | 3 | import { headers } from "next/headers"; |
4 | 4 | import * as v from "valibot"; |
5 | 5 | import { Resend } from "resend"; |
6 | | -import * as Sentry from "@sentry/nextjs"; |
7 | 6 | import * as config from "../../lib/config"; |
8 | 7 |
|
9 | 8 | const ContactSchema = v.object({ |
@@ -47,74 +46,67 @@ export type ContactState = { |
47 | 46 | }; |
48 | 47 |
|
49 | 48 | export const sendMessage = async (prevState: ContactState, formData: FormData): Promise<ContactState> => { |
50 | | - return await Sentry.withServerActionInstrumentation( |
51 | | - "sendMessage", |
52 | | - { |
53 | | - formData, |
54 | | - headers: headers(), |
55 | | - recordResponse: true, |
56 | | - }, |
57 | | - async () => { |
58 | | - try { |
59 | | - const data = v.safeParse(ContactSchema, Object.fromEntries(formData)); |
| 49 | + try { |
| 50 | + // TODO: remove after debugging why automated spam entries are causing 500 errors |
| 51 | + console.debug("[contact form] received data:", formData); |
60 | 52 |
|
61 | | - if (!data.success) { |
62 | | - return { |
63 | | - success: false, |
64 | | - message: "Please make sure all fields are filled in.", |
65 | | - errors: v.flatten(data.issues).nested, |
66 | | - }; |
67 | | - } |
| 53 | + const data = v.safeParse(ContactSchema, Object.fromEntries(formData)); |
68 | 54 |
|
69 | | - // validate captcha |
70 | | - const turnstileResponse = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", { |
71 | | - method: "POST", |
72 | | - headers: { "Content-Type": "application/json" }, |
73 | | - body: JSON.stringify({ |
74 | | - secret: process.env.TURNSTILE_SECRET_KEY || "1x0000000000000000000000000000000AA", |
75 | | - response: data.output["cf-turnstile-response"], |
76 | | - remoteip: (await headers()).get("x-forwarded-for") || "", |
77 | | - }), |
78 | | - cache: "no-store", |
79 | | - signal: AbortSignal.timeout(5000), // 5 second timeout |
80 | | - }); |
| 55 | + if (!data.success) { |
| 56 | + return { |
| 57 | + success: false, |
| 58 | + message: "Please make sure all fields are filled in.", |
| 59 | + errors: v.flatten(data.issues).nested, |
| 60 | + }; |
| 61 | + } |
| 62 | + |
| 63 | + // validate captcha |
| 64 | + const turnstileResponse = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", { |
| 65 | + method: "POST", |
| 66 | + headers: { "Content-Type": "application/json" }, |
| 67 | + body: JSON.stringify({ |
| 68 | + secret: process.env.TURNSTILE_SECRET_KEY || "1x0000000000000000000000000000000AA", |
| 69 | + response: data.output["cf-turnstile-response"], |
| 70 | + remoteip: (await headers()).get("x-forwarded-for") || "", |
| 71 | + }), |
| 72 | + cache: "no-store", |
| 73 | + signal: AbortSignal.timeout(5000), // 5 second timeout |
| 74 | + }); |
81 | 75 |
|
82 | | - if (!turnstileResponse || !turnstileResponse.ok) { |
83 | | - throw new Error(`[contact form] turnstile validation failed: ${turnstileResponse.status}`); |
84 | | - } |
| 76 | + if (!turnstileResponse || !turnstileResponse.ok) { |
| 77 | + throw new Error(`[contact form] turnstile validation failed: ${turnstileResponse.status}`); |
| 78 | + } |
85 | 79 |
|
86 | | - const turnstileData = (await turnstileResponse.json()) as { success: boolean }; |
| 80 | + const turnstileData = (await turnstileResponse.json()) as { success: boolean }; |
87 | 81 |
|
88 | | - if (!turnstileData.success) { |
89 | | - return { |
90 | | - success: false, |
91 | | - message: "Did you complete the CAPTCHA? (If you're human, that is...)", |
92 | | - }; |
93 | | - } |
| 82 | + if (!turnstileData.success) { |
| 83 | + return { |
| 84 | + success: false, |
| 85 | + message: "Did you complete the CAPTCHA? (If you're human, that is...)", |
| 86 | + }; |
| 87 | + } |
94 | 88 |
|
95 | | - if (!process.env.RESEND_FROM_EMAIL) { |
96 | | - console.warn("[contact form] RESEND_FROM_EMAIL not set, falling back to [email protected]."); |
97 | | - } |
| 89 | + if (!process.env.RESEND_FROM_EMAIL) { |
| 90 | + console.warn("[contact form] RESEND_FROM_EMAIL not set, falling back to [email protected]."); |
| 91 | + } |
98 | 92 |
|
99 | | - // send email |
100 | | - const resend = new Resend(process.env.RESEND_API_KEY); |
101 | | - await resend.emails.send({ |
102 | | - from: `${data.output.name} <${process.env.RESEND_FROM_EMAIL ?? "[email protected]"}>`, |
103 | | - replyTo: `${data.output.name} <${data.output.email}>`, |
104 | | - to: [config.authorEmail], |
105 | | - subject: `[${config.siteName}] Contact Form Submission`, |
106 | | - text: data.output.message, |
107 | | - }); |
| 93 | + // send email |
| 94 | + const resend = new Resend(process.env.RESEND_API_KEY); |
| 95 | + await resend.emails.send({ |
| 96 | + from: `${data.output.name} <${process.env.RESEND_FROM_EMAIL ?? "[email protected]"}>`, |
| 97 | + replyTo: `${data.output.name} <${data.output.email}>`, |
| 98 | + to: [config.authorEmail], |
| 99 | + subject: `[${config.siteName}] Contact Form Submission`, |
| 100 | + text: data.output.message, |
| 101 | + }); |
108 | 102 |
|
109 | | - return { success: true, message: "Thanks! You should hear from me soon." }; |
110 | | - } catch (error) { |
111 | | - Sentry.captureException(error); |
| 103 | + return { success: true, message: "Thanks! You should hear from me soon." }; |
| 104 | + } catch (error) { |
| 105 | + console.error("[contact form] fatal error:", error); |
112 | 106 |
|
113 | | - return { |
114 | | - success: false, |
115 | | - message: "Internal server error. Please try again later or shoot me an email.", |
116 | | - }; |
117 | | - } |
118 | | - } |
119 | | - ); |
| 107 | + return { |
| 108 | + success: false, |
| 109 | + message: "Internal server error. Please try again later or shoot me an email.", |
| 110 | + }; |
| 111 | + } |
120 | 112 | }; |
0 commit comments