Add support page, update privacy policy with leaderboard disclosure

- New /support page with bug report, feature request, and feedback cards
- ContactForm accepts source prop ('inquiry' | 'support') for differentiated emails
- Support emails go to apps@such.software with 'App feedback' subject
- Added 'High Scores & Leaderboards' section to Bauhaus Echo privacy policy
- Added 'Support & Feedback' link to Bauhaus Echo product page footer
- Updated privacy policy date to February 18, 2026
This commit is contained in:
jwinterm
2026-02-18 17:01:55 -05:00
parent 6a3e609c41
commit 1e4af3da6d
5 changed files with 139 additions and 24 deletions
+17 -7
View File
@@ -9,8 +9,9 @@ const resend = new Resend(process.env.RESEND_API_KEY);
const ContactSchema = z.object({
name: z.string().min(2, "Name is too short"),
email: z.string().email("Invalid email address"),
message: z.string().min(10, "Please tell us more about your project"),
token: z.string().min(1, "Security check failed")
message: z.string().min(10, "Please tell us more"),
token: z.string().min(1, "Security check failed"),
source: z.enum(["inquiry", "support"]).default("inquiry"),
});
export async function submitContactForm(prevState: any, formData: FormData) {
@@ -30,17 +31,23 @@ export async function submitContactForm(prevState: any, formData: FormData) {
// 3. Send email via Resend
try {
const { name, email, message } = validated.data;
const { name, email, message, source } = validated.data;
const contactEmail = process.env.CONTACT_EMAIL || 'sales@such.software';
const isSupport = source === 'support';
const subject = isSupport
? `App feedback from ${name}`
: `New inquiry from ${name}`;
const heading = isSupport ? 'App Support / Feedback' : 'New Project Inquiry';
const { error } = await resend.emails.send({
from: 'Such Software <noreply@such.software>',
to: contactEmail,
to: isSupport ? 'apps@such.software' : contactEmail,
replyTo: email,
subject: `New inquiry from ${name}`,
subject,
text: `Name: ${name}\nEmail: ${email}\n\nMessage:\n${message}`,
html: `
<h2>New Project Inquiry</h2>
<h2>${heading}</h2>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> <a href="mailto:${email}">${email}</a></p>
<h3>Message:</h3>
@@ -53,7 +60,10 @@ export async function submitContactForm(prevState: any, formData: FormData) {
return { success: false, message: "Failed to send message. Please try again." };
}
return { success: true, message: "Request received. We will be in touch shortly." };
return { success: true, message: isSupport
? "Thanks for your feedback! We'll get back to you shortly."
: "Request received. We will be in touch shortly."
};
} catch (e) {
console.error('Email error:', e);
return { success: false, message: "System error. Please contact us via email." };
@@ -84,10 +84,14 @@ export default function BauhausEchoPage() {
</div>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<Link href="/products/bauhaus-echo/privacy" className="hover:text-foreground">
<Link href="/products/bauhaus-echo/privacy" className="hover:text-foreground transition-colors">
Privacy Policy
</Link>
<span>•</span>
<Link href="/support" className="hover:text-foreground transition-colors">
Support &amp; Feedback
</Link>
<span>•</span>
<span>Contact: apps@such.software</span>
</div>
</div>
@@ -21,7 +21,7 @@ export default function BauhausEchoPrivacyPage() {
<div className="glass-card mt-6">
<header className="mb-8 pb-6 border-b border-border">
<h1 className="text-3xl md:text-4xl font-bold">Privacy Policy for Bauhaus Echo</h1>
<p className="text-muted-foreground mt-2">Last Updated: February 6, 2026</p>
<p className="text-muted-foreground mt-2">Last Updated: February 18, 2026</p>
</header>
<div className="prose dark:prose-invert max-w-none">
@@ -89,6 +89,20 @@ export default function BauhausEchoPrivacyPage() {
We do not have access to your payment information. We only receive confirmation of your purchase status.
</p>
<h3>High Scores &amp; Leaderboards</h3>
<p>
If you submit scores to our online leaderboards, we collect:
</p>
<ul>
<li>Your chosen display name (anonymous by default)</li>
<li>Your game scores and statistics</li>
<li>A randomly generated anonymous player ID</li>
</ul>
<p>
We do <strong>not</strong> require or collect your real name, email, or any other
personal information to use leaderboards.
</p>
<h3>Locally Stored Data</h3>
<p>The App stores the following data locally on your device:</p>
<ul>
+67
View File
@@ -0,0 +1,67 @@
import { ContactForm } from "@/components/contact/contact-form";
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Support | Such Software",
description: "Get help with Bauhaus Echo or any Such Software product. Report bugs, request features, or send feedback.",
alternates: { canonical: "/support" },
};
export default function SupportPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-6xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block transition-colors">
&larr; Home
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-4">Support &amp; Feedback</h1>
<p className="text-xl text-muted-foreground mb-12 max-w-2xl text-balance">
Having trouble with one of our apps? Want to suggest a feature? We&apos;d love to hear from you.
</p>
<div className="grid md:grid-cols-3 gap-8 mb-16">
<div className="section-container border-l-4 border-blue-500">
<h3 className="font-bold mb-2">Bug Reports</h3>
<p className="text-sm text-muted-foreground">
Something not working right? Let us know what happened, your device, and OS version — we&apos;ll investigate.
</p>
</div>
<div className="section-container border-l-4 border-emerald-500">
<h3 className="font-bold mb-2">Feature Requests</h3>
<p className="text-sm text-muted-foreground">
Have an idea to make the game better? We&apos;re always looking for ways to improve.
</p>
</div>
<div className="section-container border-l-4 border-amber-500">
<h3 className="font-bold mb-2">General Feedback</h3>
<p className="text-sm text-muted-foreground">
Enjoying the game? Hate something? All feedback is welcome and helps us prioritize.
</p>
</div>
</div>
<ContactForm source="support" />
<div className="mt-16 text-center text-sm text-muted-foreground space-y-2">
<p>
You can also reach us directly at{" "}
<a href="mailto:apps@such.software" className="text-primary hover:underline">
apps@such.software
</a>
</p>
<p>
<Link href="/products/bauhaus-echo/privacy" className="hover:text-foreground transition-colors">
Privacy Policy
</Link>
</p>
</div>
</div>
<MobileNav />
</main>
);
}
@@ -6,7 +6,7 @@ import { TurnstileWidget } from './turnstile-widget';
import { useState, useEffect, useRef } from 'react';
import { Button } from '@repo/ui/components/button';
function SubmitButton() {
function SubmitButton({ label = "Send Message" }: { label?: string }) {
const { pending } = useFormStatus();
return (
<Button
@@ -15,16 +15,22 @@ function SubmitButton() {
size="lg"
className="w-full md:w-64 rounded-xl text-lg font-semibold"
>
{pending ? "Sending..." : "Send Message"}
{pending ? "Sending..." : label}
</Button>
);
}
export function ContactForm() {
interface ContactFormProps {
source?: 'inquiry' | 'support';
}
export function ContactForm({ source = 'inquiry' }: ContactFormProps) {
const [state, formAction] = useActionState(submitContactForm, { success: false, message: '' });
const [token, setToken] = useState<string>('');
const formRef = useRef<HTMLFormElement>(null);
const isSupport = source === 'support';
// Reset form on successful submission
useEffect(() => {
if (state.success && formRef.current) {
@@ -43,19 +49,27 @@ export function ContactForm() {
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-500/5 rounded-full blur-3xl -ml-32 -mb-32" />
<div className="text-left mb-10 relative z-10">
<h3 className="text-3xl font-bold tracking-tight">Project Inquiry</h3>
<h3 className="text-3xl font-bold tracking-tight">
{isSupport ? 'Send Feedback' : 'Project Inquiry'}
</h3>
<p className="text-muted-foreground text-lg mt-3 max-w-2xl text-balance">
Tell us about your technical challenges. From Medusa v2 migrations to real-time custom engines, we're ready to architect your solution.
{isSupport
? 'Found a bug? Have a feature request? We read every message and typically respond within 24 hours.'
: 'Tell us about your technical challenges. From Medusa v2 migrations to real-time custom engines, we\'re ready to architect your solution.'}
</p>
</div>
<input type="hidden" name="source" value={source} />
<div className="grid md:grid-cols-2 gap-8 relative z-10">
<div className="space-y-3">
<label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Full Name</label>
<label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">
{isSupport ? 'Your Name' : 'Full Name'}
</label>
<input
name="name"
id="name"
placeholder="Jamie Doe"
placeholder={isSupport ? "Your name" : "Jamie Doe"}
required
aria-describedby={state?.errors?.name ? "name-error" : undefined}
aria-invalid={state?.errors?.name ? "true" : undefined}
@@ -65,12 +79,14 @@ export function ContactForm() {
</div>
<div className="space-y-3">
<label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Work Email</label>
<label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">
{isSupport ? 'Email' : 'Work Email'}
</label>
<input
name="email"
id="email"
type="email"
placeholder="jamie@example.com"
placeholder={isSupport ? "your@email.com" : "jamie@example.com"}
required
aria-describedby={state?.errors?.email ? "email-error" : undefined}
aria-invalid={state?.errors?.email ? "true" : undefined}
@@ -81,11 +97,15 @@ export function ContactForm() {
</div>
<div className="space-y-3 relative z-10">
<label htmlFor="message" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Project Details</label>
<label htmlFor="message" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">
{isSupport ? 'Your Message' : 'Project Details'}
</label>
<textarea
name="message"
id="message"
placeholder="What are you building? Mention any specific constraints, timelines, or technologies..."
placeholder={isSupport
? "Describe the issue or suggestion. Include your device, OS version, and game version if reporting a bug..."
: "What are you building? Mention any specific constraints, timelines, or technologies..."}
required
aria-describedby={state?.errors?.message ? "message-error" : undefined}
aria-invalid={state?.errors?.message ? "true" : undefined}
@@ -104,7 +124,7 @@ export function ContactForm() {
</p>
</div>
<div className="w-full md:w-auto">
<SubmitButton />
<SubmitButton label={isSupport ? "Send Feedback" : "Send Message"} />
</div>
</div>
@@ -119,7 +139,7 @@ export function ContactForm() {
}`}
>
<div className="flex items-center gap-3">
<span className="text-xl" aria-hidden="true">{state.success ? "✓" : "⚠"}</span>
<span className="text-xl" aria-hidden="true">{state.success ? "\u2713" : "\u26A0"}</span>
<p className="font-medium">{state.message}</p>
</div>
</div>