mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
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:
@@ -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 & 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 & 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>
|
||||
|
||||
@@ -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">
|
||||
← Home
|
||||
</Link>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">Support & 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'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'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'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>
|
||||
|
||||
Reference in New Issue
Block a user