diff --git a/.env.example b/.env.example index 9cb92d7..3afec4a 100644 --- a/.env.example +++ b/.env.example @@ -7,3 +7,6 @@ CONTAINER_REGISTRY="" DEV_ADMIN_CLERK_ID="" SENTRY_AUTH_TOKEN="" SQUIGGLE_CONTACT_EMAIL="" +RESEND_API_KEY="" +TURNSTILE_SITE_KEY=1x00000000000000000000AA +TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA \ No newline at end of file diff --git a/app/components/navbar.tsx b/app/components/navbar.tsx index b582ff9..6e83d40 100644 --- a/app/components/navbar.tsx +++ b/app/components/navbar.tsx @@ -50,6 +50,11 @@ export function Navbar({ isAdmin }: NavbarProps) { Rules + + + Support + + {isAdmin && ( @@ -122,6 +127,13 @@ export function Navbar({ isAdmin }: NavbarProps) { > Rules + setOpen(false)} + > + Support + {isAdmin && ( SUBJECT_MAX) { + return data({ error: `Subject must be ${SUBJECT_MAX} characters or fewer.` }); + } + if (message.length > MESSAGE_MAX) { + return data({ error: `Message must be ${MESSAGE_MAX} characters or fewer.` }); + } + + // Turnstile verification + const turnstileSecret = process.env.TURNSTILE_SECRET_KEY; + if (turnstileSecret) { + if (!turnstileToken) { + return data({ error: "Please complete the verification." }); + } + const verifyResponse = await fetch( + "https://challenges.cloudflare.com/turnstile/v0/siteverify", + { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + secret: turnstileSecret, + response: turnstileToken, + }), + } + ); + const verifyResult = (await verifyResponse.json()) as { success: boolean }; + if (!verifyResult.success) { + return data({ error: "Verification failed. Please try again." }); + } + } + + // Send email via Resend + const resendKey = process.env.RESEND_API_KEY; + if (!resendKey) { + return data({ error: "Server configuration error. Please try again later." }); + } + + const resend = new Resend(resendKey); + const { error: sendError } = await resend.emails.send({ + from: "Brackt Support Form ", + to: "support@brackt.com", + replyTo: email, + subject: `[Support] ${subject}`, + text: `Name: ${name}\nEmail: ${email}\n\n${message}`, + }); + + if (sendError) { + return data({ error: "Failed to send your message. Please try again." }); + } + + return data({ success: true as const }); +} + +export default function Support() { + const { turnstileSiteKey } = useLoaderData(); + const actionData = useActionData(); + const success = actionData && "success" in actionData; + const error = actionData && "error" in actionData ? actionData.error : null; + + return ( +
+

Support

+

+ Need help? Join our Discord community for the fastest response, or send + us a message directly. +

+ +
+ {/* Discord */} +
+
+ +

Discord Community

+
+

+ The fastest way to get help is to join our Discord server. You can + ask questions, report bugs, and chat with other Brackt players. +

+ +
+ + {/* Contact Form */} +
+
+ +

Send a Message

+
+

+ Prefer email? Fill out the form below and we'll get back to you. +

+ + {success ? ( +
+ +

Message sent! We'll get back to you as soon as we can.

+
+ ) : ( +
+ {/* Honeypot — visually and positionally hidden; not using display:none so bots can't trivially detect it */} + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +