Add design approval workflow and per-image dimension tracking
- Add DesignApproval and DesignApprovalMessage models for design review workflow - Add design approval admin interface (list, detail, chat, approve/reject) - Add per-image dimension tracking to OrderItem - Add design submission API endpoint - Add AdminDesignEditor for admin-side design modifications - Add DesignApprovalChat component for customer-admin communication - Update design specification generation with image dimension details - Add design persistence across login with next parameter redirect - Add font properties UI with font size and color display Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
10764f3a5b
commit
5c4774be84
@@ -0,0 +1,41 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { sendDesignApprovedEmail } from '@/lib/mail';
|
||||
|
||||
export async function POST(
|
||||
req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const design = await prisma.designApproval.findUnique({
|
||||
where: { id: params.id },
|
||||
include: { product: true },
|
||||
});
|
||||
|
||||
if (!design) {
|
||||
return NextResponse.json({ error: 'Design not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const updated = await prisma.designApproval.update({
|
||||
where: { id: params.id },
|
||||
data: { status: 'APPROVED' },
|
||||
include: { product: true },
|
||||
});
|
||||
|
||||
const checkoutLink = `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/made-to-order/${design.product.slug}?design=${design.id}`;
|
||||
await sendDesignApprovedEmail({
|
||||
to: design.customerEmail,
|
||||
customerName: design.customerName,
|
||||
productName: design.product.name,
|
||||
checkoutLink,
|
||||
});
|
||||
|
||||
return NextResponse.json(updated);
|
||||
} catch (err) {
|
||||
console.error('Error approving design:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to approve design' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import type { DesignApprovalMessage } from '@prisma/client';
|
||||
|
||||
export async function GET(
|
||||
req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const messages = await prisma.designApprovalMessage.findMany({
|
||||
where: { designId: params.id },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
|
||||
return NextResponse.json(messages);
|
||||
} catch (err) {
|
||||
console.error('Error fetching messages:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch messages' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const { sender, body } = await req.json();
|
||||
|
||||
if (!sender || !body) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const message = await prisma.designApprovalMessage.create({
|
||||
data: {
|
||||
designId: params.id,
|
||||
sender,
|
||||
body,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(message);
|
||||
} catch (err) {
|
||||
console.error('Error creating message:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create message' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function POST(
|
||||
req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const {
|
||||
designJson,
|
||||
designPreviewUrl,
|
||||
designPreviewUrlBack,
|
||||
placementPreviewUrl,
|
||||
placementPreviewUrlBack,
|
||||
designWidthCm,
|
||||
designHeightCm,
|
||||
} = await req.json();
|
||||
|
||||
const design = await prisma.designApproval.findUnique({
|
||||
where: { id: params.id },
|
||||
});
|
||||
|
||||
if (!design) {
|
||||
return NextResponse.json({ error: 'Design not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const updated = await prisma.designApproval.update({
|
||||
where: { id: params.id },
|
||||
data: {
|
||||
...(designJson && { designJson }),
|
||||
...(designPreviewUrl && { designPreviewUrl }),
|
||||
...(designPreviewUrlBack && { designPreviewUrlBack }),
|
||||
...(placementPreviewUrl && { placementPreviewUrl }),
|
||||
...(placementPreviewUrlBack && { placementPreviewUrlBack }),
|
||||
...(designWidthCm !== undefined && { designWidthCm }),
|
||||
...(designHeightCm !== undefined && { designHeightCm }),
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(updated);
|
||||
} catch (err) {
|
||||
console.error('Error modifying design:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to modify design' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function GET(
|
||||
req: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const design = await prisma.designApproval.findUnique({
|
||||
where: { id: params.id },
|
||||
include: { product: true, messages: { orderBy: { createdAt: 'asc' } } },
|
||||
});
|
||||
|
||||
if (!design) {
|
||||
return NextResponse.json({ error: 'Design not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return NextResponse.json(design);
|
||||
} catch (err) {
|
||||
console.error('Error fetching design:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch design' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const designs = await prisma.designApproval.findMany({
|
||||
where: { status: 'PENDING' },
|
||||
include: { product: true, messages: { orderBy: { createdAt: 'desc' }, take: 1 } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
return NextResponse.json(designs);
|
||||
} catch (err) {
|
||||
console.error('Error fetching pending designs:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch pending designs' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { sendDesignSubmissionNotification } from '@/lib/mail';
|
||||
import { getCurrentCustomer } from '@/lib/auth';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const customer = await getCurrentCustomer();
|
||||
if (!customer) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Must be logged in to submit design' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
productId,
|
||||
designJson,
|
||||
designPreviewUrl,
|
||||
designPreviewUrlBack,
|
||||
placementPreviewUrl,
|
||||
placementPreviewUrlBack,
|
||||
designWidthCm,
|
||||
designHeightCm,
|
||||
designImageDimensions,
|
||||
color,
|
||||
size,
|
||||
} = await req.json();
|
||||
|
||||
if (!productId || !designJson) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Missing required fields' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id: productId },
|
||||
});
|
||||
|
||||
if (!product) {
|
||||
return NextResponse.json({ error: 'Product not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const design = await prisma.designApproval.create({
|
||||
data: {
|
||||
customerId: customer.id,
|
||||
customerEmail: customer.email,
|
||||
customerName: customer.name,
|
||||
productId,
|
||||
designJson,
|
||||
designPreviewUrl,
|
||||
designPreviewUrlBack,
|
||||
placementPreviewUrl,
|
||||
placementPreviewUrlBack,
|
||||
designWidthCm: designWidthCm || null,
|
||||
designHeightCm: designHeightCm || null,
|
||||
designImageDimensions: designImageDimensions && designImageDimensions.length > 0 ? JSON.stringify(designImageDimensions) : null,
|
||||
color,
|
||||
size,
|
||||
},
|
||||
});
|
||||
|
||||
const adminLink = `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/admin/designs/${design.id}`;
|
||||
await sendDesignSubmissionNotification({
|
||||
customerName,
|
||||
productName: product.name,
|
||||
adminLink,
|
||||
});
|
||||
|
||||
return NextResponse.json(design);
|
||||
} catch (err) {
|
||||
console.error('Error submitting design:', err);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to submit design' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user