Fix money rounding drift, low-stock hint spacing, spotlight CTA height mismatch
- Round subtotal/discountAmount/total to 2 decimals before persisting an order — float arithmetic on money was drifting into values like 84.30000000000001, invisible wherever a display already ran it through toFixed(2), but stored as-is and visible raw in the Payload admin's plain number field - Low-stock hint now uses gap-1 consistently (was gap-2) in both AddToCartButton/AddToCartInlineButton, for smaller/consistent spacing above it regardless of context - ProductSpotlight's CTA row now uses items-start at sm: — without it, default cross-axis stretch made "Mehr erfahren" grow to match AddToCartButton's height whenever the low-stock hint made that one taller, so the link visibly looked "fatter" than the actual button Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,18 @@ import { describeBundleContents } from "../../lib/bundleContents";
|
||||
import { sendCriticalAlert } from "../../lib/alertAdmin";
|
||||
import { sendOrderConfirmationEmail } from "../../lib/orderEmail";
|
||||
|
||||
// Plain float arithmetic on money (quantity × unitPrice summed across
|
||||
// lines, a percent discount, subtracting/adding those together) drifts
|
||||
// into results like 84.30000000000001 — cosmetically invisible wherever
|
||||
// formatPrice()'s toFixed(2) already rounds for display, but stored as-is
|
||||
// on the order otherwise, which is where it actually showed up (Payload's
|
||||
// admin list/edit view for a plain number field has no such formatting).
|
||||
// Rounded once here, right before persisting, rather than chasing it down
|
||||
// at every downstream display site.
|
||||
function roundMoney(amount: number): number {
|
||||
return Math.round(amount * 100) / 100;
|
||||
}
|
||||
|
||||
type CheckoutBody = {
|
||||
cart: CartItem[];
|
||||
shippingMethodId: number;
|
||||
@@ -160,7 +172,7 @@ export async function POST(request: Request) {
|
||||
variantName: variant?.name ?? null,
|
||||
});
|
||||
}
|
||||
const subtotal = items.reduce((sum, i) => sum + i.quantity * i.unitPrice, 0);
|
||||
const subtotal = roundMoney(items.reduce((sum, i) => sum + i.quantity * i.unitPrice, 0));
|
||||
|
||||
const shippingMethods = await getShippingMethods();
|
||||
const shippingMethod = shippingMethods.find((m) => m.id === body.shippingMethodId);
|
||||
@@ -178,10 +190,11 @@ export async function POST(request: Request) {
|
||||
if (!validation.valid) return NextResponse.json({ ok: false, reason: validation.reason }, { status: 400 });
|
||||
const redeemed = await redeemDiscountCode(validation.doc);
|
||||
if (!redeemed) return NextResponse.json({ ok: false, reason: "Rabattcode konnte nicht eingelöst werden." }, { status: 400 });
|
||||
discountAmount =
|
||||
validation.doc.type === "percent" ? (subtotal * validation.doc.value) / 100 : Math.min(validation.doc.value, subtotal);
|
||||
discountAmount = roundMoney(
|
||||
validation.doc.type === "percent" ? (subtotal * validation.doc.value) / 100 : Math.min(validation.doc.value, subtotal),
|
||||
);
|
||||
}
|
||||
const total = Math.max(0, subtotal - discountAmount) + shippingCost;
|
||||
const total = roundMoney(Math.max(0, subtotal - discountAmount) + shippingCost);
|
||||
|
||||
const order = await createOrder({
|
||||
customerId: customer.id,
|
||||
|
||||
@@ -81,7 +81,7 @@ export function AddToCartButton({
|
||||
const displayLabel = currentlyOutOfStock ? "Ausverkauft" : label;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
{variants.length > 0 && (
|
||||
<select
|
||||
value={selectedVariant}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function AddToCartInlineButton({
|
||||
: "border-border hover:border-brand";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
{variants.length > 0 && (
|
||||
<select
|
||||
value={selectedVariant}
|
||||
|
||||
@@ -91,7 +91,13 @@ export async function ProductSpotlight() {
|
||||
Lieferzeit: {shipping.totalDays.min}–{shipping.totalDays.max} Werktage innerhalb Deutschlands
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-3 w-full sm:w-auto">
|
||||
{/* items-start at sm: — without it, the default cross-axis
|
||||
stretch makes "Mehr erfahren" grow to match
|
||||
AddToCartButton's own height whenever that one gets taller
|
||||
(e.g. the low-stock hint line pushing its content down), so
|
||||
a plain text link visibly ends up "fatter" than the actual
|
||||
button next to it. */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-start gap-3 w-full sm:w-auto">
|
||||
{/* No className override — the section's bg is bg-bg-base now
|
||||
(matches Tools/Blog above/below), same as AddToCartButton's
|
||||
own default styling/ring-offset, so no override is needed
|
||||
|
||||
Reference in New Issue
Block a user