Homepage was rendering 71MB of HTML because every uploaded image (gallery, products, proofs, custom requests, design previews, invoices, receipts) was stored as base64 in Postgres and double-embedded via RSC hydration payloads. Adds src/lib/storage.ts (sharp-based compression, disk storage under public/uploads/) and a new /api/design-uploads endpoint for images added inside the design tool, migrates existing rows, and drops the now-unused base64 columns and the dead ProductImage table. Also fixes a systemic bug from the Next.js 16 upgrade where dynamic pages across the app still destructured params/searchParams synchronously instead of awaiting them as Promises, which was silently breaking filters/params and crashing /products/[slug] outright. Updates README.md and SETUP_AND_BUILD.md to reflect Postgres + Next.js 16 and document the new image storage architecture and backup requirements. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import { getCurrentCustomer } from '@/lib/auth';
|
|
import { sendContactMessage } from './actions';
|
|
import Turnstile from '@/components/Turnstile';
|
|
|
|
const ERROR_MESSAGES: Record<string, string> = {
|
|
missing: 'Name, email, and a message are required.',
|
|
captcha: 'CAPTCHA verification failed — please try again.',
|
|
};
|
|
|
|
export default async function ContactPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ sent?: string; error?: string }>;
|
|
}) {
|
|
const params = await searchParams;
|
|
const customer = await getCurrentCustomer();
|
|
const error = params.error ? (ERROR_MESSAGES[params.error] ?? 'Something went wrong.') : null;
|
|
|
|
return (
|
|
<div className="mx-auto max-w-2xl px-6 py-12">
|
|
<p className="tag-label text-splash-pink">Contact</p>
|
|
<h1 className="mt-2 font-display text-3xl">Get in touch</h1>
|
|
<p className="mt-2 text-sm text-muted">
|
|
Question about an order, a template, or anything else? Send us a message and we'll get
|
|
back to you.
|
|
</p>
|
|
|
|
{params.sent === '1' ? (
|
|
<div className="mt-10 border border-splash-blue bg-splash-blue/5 p-6 text-sm">
|
|
Thanks — we've got your message and will be in touch soon.
|
|
</div>
|
|
) : (
|
|
<form action={sendContactMessage} className="mt-10 space-y-6">
|
|
{error && (
|
|
<p className="border border-splash-pink/40 bg-splash-pink/10 px-4 py-3 text-sm text-splash-pink">
|
|
{error}
|
|
</p>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label htmlFor="name" className="tag-label mb-2 block">
|
|
Your name
|
|
</label>
|
|
<input
|
|
id="name"
|
|
name="name"
|
|
required
|
|
defaultValue={customer?.name ?? ''}
|
|
className="w-full border border-line px-3 py-2 text-sm"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="email" className="tag-label mb-2 block">
|
|
Email
|
|
</label>
|
|
<input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
required
|
|
defaultValue={customer?.email ?? ''}
|
|
className="w-full border border-line px-3 py-2 text-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="message" className="tag-label mb-2 block">
|
|
Message
|
|
</label>
|
|
<textarea
|
|
id="message"
|
|
name="message"
|
|
required
|
|
rows={5}
|
|
className="w-full border border-line px-3 py-2 text-sm"
|
|
placeholder="How can we help?"
|
|
/>
|
|
</div>
|
|
|
|
<Turnstile />
|
|
|
|
<button type="submit" className="bg-clay px-6 py-3 text-sm font-medium text-paper hover:bg-clay-dark">
|
|
Send message
|
|
</button>
|
|
</form>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|