From 5bd6f3de39a2a9a4c2f879624ec5b30f80223f7a Mon Sep 17 00:00:00 2001 From: Andymick Date: Mon, 20 Jul 2026 18:16:17 +0100 Subject: [PATCH] Feature: Implement context-aware pricing for product types Updates pricing logic to use correct price based on product type: - Updated getEffectivePrice to accept priceType parameter ('base', 'plain', 'personalised') - Updated toProductDTO to accept and use priceType when computing effective price - Updated made-to-order pages to use 'personalised' price type - Updated products catalog pages to use 'plain' price type - Updated checkout logic to determine price type based on design Pricing behavior: - Personalised orders (with design): use personalisedPrice if set, else basePrice - Plain/blank orders (no design): use plainPrice if set, else basePrice - Ready-made orders (no design, no plainPrice): use basePrice All prices now correctly reflect the product variant being purchased. Co-Authored-By: Claude Haiku 4.5 --- .claude/settings.local.json | 3 ++- src/app/api/checkout/route.ts | 12 +++++++++++- src/app/made-to-order/[slug]/page.tsx | 4 ++-- src/app/made-to-order/page.tsx | 2 +- src/app/products/[slug]/page.tsx | 4 ++-- src/app/products/page.tsx | 2 +- src/lib/pricing.ts | 10 ++++++++-- src/lib/product.ts | 5 +++-- src/lib/types.ts | 1 + 9 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d786477..1268589 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -134,7 +134,8 @@ "Bash(timeout 30 git push origin main --force)", "Bash(GIT_TRACE=1 git push origin main --force)", "Bash(pkill -f \"node.exe\")", - "PowerShell(Stop-Process -Name node -Force -ErrorAction SilentlyContinue)" + "PowerShell(Stop-Process -Name node -Force -ErrorAction SilentlyContinue)", + "Bash(find \"D:\\\\Craft2Prints\\\\src\\\\app\" -path \"*/\\\\[slug\\\\]/*\" -name \"page.tsx\" | head -10)" ] } } diff --git a/src/app/api/checkout/route.ts b/src/app/api/checkout/route.ts index 102f5e4..d71b776 100644 --- a/src/app/api/checkout/route.ts +++ b/src/app/api/checkout/route.ts @@ -110,9 +110,19 @@ export async function POST(req: Request) { const pricedItems = items.map((i) => { const design = typeof i.designJson === 'string' ? JSON.parse(i.designJson) : i.designJson; const isPersonalised = (design.front?.length ?? 0) > 0 || (design.back?.length ?? 0) > 0; + const product = productsById.get(i.productId)!; + + // Determine which price to use + let priceType: 'base' | 'plain' | 'personalised' = 'base'; + if (isPersonalised) { + priceType = 'personalised'; + } else if (product.plainPrice != null) { + priceType = 'plain'; + } + return { ...i, - unitPrice: getEffectivePrice(productsById.get(i.productId)!, activePromotions, new Date(), isPersonalised).price, + unitPrice: getEffectivePrice(product, activePromotions, new Date(), priceType).price, }; }); diff --git a/src/app/made-to-order/[slug]/page.tsx b/src/app/made-to-order/[slug]/page.tsx index a4e377e..fb11288 100644 --- a/src/app/made-to-order/[slug]/page.tsx +++ b/src/app/made-to-order/[slug]/page.tsx @@ -19,13 +19,13 @@ export default async function ProductDetailPage({ const row = await prisma.product.findUnique({ where: { slug: params.slug } }); if (!row || !row.showOnPersonalised) notFound(); - const product = toProductDTO(row, activePromotions); + const product = toProductDTO(row, activePromotions, 'personalised'); const relatedRows = await prisma.product.findMany({ where: { category: product.category, showOnPersonalised: true, NOT: { id: product.id } }, take: 4, }); - const related = relatedRows.map((p) => toProductDTO(p, activePromotions)); + const related = relatedRows.map((p) => toProductDTO(p, activePromotions, 'personalised')); // Fetch custom photo from request if customRequestId is provided let customPhoto: string | undefined; diff --git a/src/app/made-to-order/page.tsx b/src/app/made-to-order/page.tsx index 45609f3..c395bea 100644 --- a/src/app/made-to-order/page.tsx +++ b/src/app/made-to-order/page.tsx @@ -70,7 +70,7 @@ export default async function ProductsPage({ searchParams }: { searchParams: Sea const activePromotions = await fetchActivePromotions(); const rows = await prisma.product.findMany({ where, orderBy: { createdAt: 'asc' } }); - const products = rows.map((p) => toProductDTO(p, activePromotions)); + const products = rows.map((p) => toProductDTO(p, activePromotions, 'personalised')); const hasFilters = selectedCategories.length > 0 || diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index 79a951d..f0d89dc 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -11,13 +11,13 @@ export default async function StandardProductPage({ params }: { params: { slug: const row = await prisma.product.findUnique({ where: { slug: params.slug } }); if (!row || !row.showOnProducts) notFound(); - const product = toProductDTO(row, activePromotions); + const product = toProductDTO(row, activePromotions, 'plain'); const relatedRows = await prisma.product.findMany({ where: { category: product.category, showOnProducts: true, NOT: { id: product.id } }, take: 4, }); - const related = relatedRows.map((p) => toProductDTO(p, activePromotions)); + const related = relatedRows.map((p) => toProductDTO(p, activePromotions, 'plain')); return (
diff --git a/src/app/products/page.tsx b/src/app/products/page.tsx index 5c8616c..5366b80 100644 --- a/src/app/products/page.tsx +++ b/src/app/products/page.tsx @@ -69,7 +69,7 @@ export default async function ReadyMadeProductsPage({ searchParams }: { searchPa const activePromotions = await fetchActivePromotions(); const rows = await prisma.product.findMany({ where, orderBy: { createdAt: 'asc' } }); - const products = rows.map((p) => toProductDTO(p, activePromotions)); + const products = rows.map((p) => toProductDTO(p, activePromotions, 'plain')); const hasFilters = selectedCategories.length > 0 || diff --git a/src/lib/pricing.ts b/src/lib/pricing.ts index 9856e1a..56bf4cf 100644 --- a/src/lib/pricing.ts +++ b/src/lib/pricing.ts @@ -1,6 +1,7 @@ type SalePricingFields = { id: string; basePrice: number; + plainPrice: number | null; personalisedPrice: number | null; salePrice: number | null; saleStartsAt: Date | null; @@ -44,9 +45,14 @@ export function getEffectivePrice( product: SalePricingFields, activePromotions: ActivePromotion[] = [], now = new Date(), - usePersonalisedPrice = false, + priceType: 'base' | 'plain' | 'personalised' = 'base', ) { - const basePrice = usePersonalisedPrice && product.personalisedPrice != null ? product.personalisedPrice : product.basePrice; + let basePrice = product.basePrice; + if (priceType === 'plain' && product.plainPrice != null) { + basePrice = product.plainPrice; + } else if (priceType === 'personalised' && product.personalisedPrice != null) { + basePrice = product.personalisedPrice; + } const candidates = [basePrice]; const manualSaleActive = diff --git a/src/lib/product.ts b/src/lib/product.ts index 778d614..fdc6496 100644 --- a/src/lib/product.ts +++ b/src/lib/product.ts @@ -2,8 +2,8 @@ import type { Product as PrismaProduct, DesignProof as PrismaDesignProof } from import type { ProductDTO, ProofDTO } from './types'; import { getEffectivePrice, type ActivePromotion } from './pricing'; -export function toProductDTO(p: PrismaProduct, activePromotions: ActivePromotion[] = []): ProductDTO { - const { onSale, price, originalPrice } = getEffectivePrice(p, activePromotions); +export function toProductDTO(p: PrismaProduct, activePromotions: ActivePromotion[] = [], priceType: 'base' | 'plain' | 'personalised' = 'base'): ProductDTO { + const { onSale, price, originalPrice } = getEffectivePrice(p, activePromotions, new Date(), priceType); return { id: p.id, slug: p.slug, @@ -11,6 +11,7 @@ export function toProductDTO(p: PrismaProduct, activePromotions: ActivePromotion category: p.category, description: p.description, basePrice: p.basePrice, + plainPrice: p.plainPrice, personalisedPrice: p.personalisedPrice, colors: JSON.parse(p.colors), colorPhotos: p.colorPhotos ? JSON.parse(p.colorPhotos) : {}, diff --git a/src/lib/types.ts b/src/lib/types.ts index 8f087b9..cf8db79 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -35,6 +35,7 @@ export type ProductDTO = { category: string; // matches a Category.slug — categories are admin-managed, not a fixed set description: string; basePrice: number; + plainPrice: number | null; // price for plain/blank items; null = use basePrice personalisedPrice: number | null; // higher price for made-to-order items; null = use basePrice colors: string[]; colorPhotos: Record;