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>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
'use server';
|
|
|
|
import { redirect } from 'next/navigation';
|
|
import { prisma } from '@/lib/prisma';
|
|
import { sendProofEmail } from '@/lib/mail';
|
|
import { saveUploadedFile } from '@/lib/storage';
|
|
|
|
export async function createProof(formData: FormData) {
|
|
const productId = String(formData.get('productId') ?? '');
|
|
const customerName = String(formData.get('customerName') ?? '').trim();
|
|
const customerEmail = String(formData.get('customerEmail') ?? '').trim();
|
|
const color = String(formData.get('color') ?? '');
|
|
const quantity = Math.max(1, Number(formData.get('quantity') ?? 1));
|
|
const note = String(formData.get('note') ?? '').trim();
|
|
const priceOverride = formData.get('price');
|
|
const file = formData.get('image') as File | null;
|
|
|
|
if (!productId || !customerEmail || !color || !file || file.size === 0) {
|
|
throw new Error('Missing required fields: product, customer email, color, and an image are all required.');
|
|
}
|
|
|
|
const product = await prisma.product.findUnique({ where: { id: productId } });
|
|
if (!product) throw new Error('Product not found.');
|
|
|
|
const imageUrl = await saveUploadedFile(file, 'proofs');
|
|
|
|
const unitPrice = priceOverride && String(priceOverride).trim() !== ''
|
|
? Math.round(Number(priceOverride) * 100)
|
|
: product.basePrice;
|
|
|
|
const proof = await prisma.designProof.create({
|
|
data: {
|
|
productId,
|
|
customerName: customerName || null,
|
|
customerEmail,
|
|
color,
|
|
quantity,
|
|
unitPrice,
|
|
imageUrl,
|
|
note: note || null,
|
|
},
|
|
});
|
|
|
|
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000';
|
|
const link = `${siteUrl}/proofs/${proof.id}`;
|
|
|
|
const result = await sendProofEmail({
|
|
to: customerEmail,
|
|
customerName: customerName || null,
|
|
link,
|
|
productName: product.name,
|
|
note: note || null,
|
|
});
|
|
|
|
redirect(`/admin/proofs/${proof.id}/sent?emailed=${result.sent ? '1' : '0'}`);
|
|
}
|