Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/[locale]/cart/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export default async function CartPage({
customizationText: dict.common.customizationText,
increaseQuantity: dict.common.increaseQuantity,
decreaseQuantity: dict.common.decreaseQuantity,
customizationDesignImageAlt: dict.common.customizationDesignImageAlt,
price: dict.common.price,
}}
/>
);
Expand Down
35 changes: 19 additions & 16 deletions app/[locale]/checkout/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@
margin-bottom: 1.5rem;
}

.checkoutCard {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}

.sellerSection {
margin-bottom: 1.5rem;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding-bottom: var(--spacing-lg);
border-bottom: 1px solid var(--color-border);
}

.sellerName {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
border-bottom: 1px solid var(--color-border);
}

.itemRow {
Expand All @@ -46,22 +50,22 @@

.itemCustomization {
font-size: 0.8rem;
color: #888;
color: var(--color-text-secondary);
}

.itemCustomizationRemoved {
font-size: 0.8rem;
color: #c62828;
color: var(--color-danger);
font-style: italic;
}

.warning {
padding: 0.75rem 1rem;
margin-bottom: 1rem;
background: #fff3e0;
border: 1px solid #ff9800;
border-radius: 6px;
color: #e65100;
background: var(--color-cream);
border: 1px solid var(--color-warning);
border-radius: var(--radius-md);
color: var(--color-green-dark);
font-size: 0.9rem;
}

Expand All @@ -88,15 +92,14 @@
justify-content: space-between;
padding-top: 0.5rem;
margin-top: 0.5rem;
border-top: 1px solid #eee;
border-top: 1px solid var(--color-border);
font-weight: 600;
font-size: 0.95rem;
}

.totals {
margin: 1.5rem 0;
padding: 1rem;
background: #f9f9f9;
padding: var(--spacing-md);
background: var(--color-cream);
border-radius: var(--radius-md);
}

Expand All @@ -114,7 +117,7 @@
.grandTotal {
font-size: 1.2rem;
font-weight: 700;
border-top: 2px solid #ddd;
border-top: 2px solid var(--color-border-strong);
margin-top: 0.5rem;
padding-top: 0.75rem;
}
170 changes: 92 additions & 78 deletions app/[locale]/checkout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { ProductEntity } from '@/modules/products/domain/product-repository
import { Money } from '@/shared/kernel/domain/value-objects/money';
import { Currency } from '@/shared/kernel/domain/value-objects/currency';
import type { CustomizationSnapshot } from '@/modules/cart/domain/customization-lookup-port';
import { getDictionary } from '@/shared/i18n/get-dictionary';
import { Card } from '@/shared/ui/card';
import Image from 'next/image';
import styles from './page.module.css';

Expand Down Expand Up @@ -63,6 +65,7 @@ export default async function CheckoutPage({
}) {
const { locale } = await params;
const session = await getServerSession(authOptions);
const dict = await getDictionary(locale as 'es' | 'cat');

if (!session?.user?.id) {
redirect(`/${locale}/auth/signin?callbackUrl=/${locale}/checkout`);
Expand Down Expand Up @@ -92,7 +95,7 @@ export default async function CheckoutPage({
(item) => item.unitPriceSnapshot.currency !== currency,
);
if (hasMixedCurrencies) {
throw new Error('Checkout does not support mixed currencies');
throw new Error(dict.common.genericError);
}

const customer = await userRepository.findById(session.user.id);
Expand Down Expand Up @@ -132,10 +135,11 @@ export default async function CheckoutPage({
return {
id: item.id,
productId: item.productId.value,
productName: product?.translations?.[0]?.name ?? 'Unknown Product',
productName:
product?.translations?.[0]?.name ?? dict.common.unknownProduct,
productImageUrl: product?.images?.[0]?.url ?? null,
sellerId: item.sellerId.value,
sellerName: product?.sellerName ?? 'Unknown Seller',
sellerName: product?.sellerName ?? dict.common.unknownSeller,
quantity: item.quantity,
unitPrice: item.unitPriceSnapshot.amount,
lineTotal: +(item.unitPriceSnapshot.amount * item.quantity).toFixed(2),
Expand Down Expand Up @@ -185,92 +189,102 @@ export default async function CheckoutPage({

return (
<div className={styles.container}>
<h2 className={styles.title}>Checkout</h2>
<h2 className={styles.title}>{dict.common.checkout}</h2>

{hasMissingCustomizations && (
<div className={styles.warning} role="alert">
Some customizations are no longer available and will not be included
in your order.
</div>
)}
<Card className={styles.checkoutCard}>
{hasMissingCustomizations && (
<div className={styles.warning} role="alert">
{dict.common.checkoutMissingCustomizations}
</div>
)}

{sellerGroups.map((group) => (
<div key={group.sellerId} className={styles.sellerSection}>
<h3 className={styles.sellerName}>{group.sellerName}</h3>
{group.items.map((item) => (
<div key={item.id} className={styles.itemRow}>
<div className={styles.itemInfo}>
<span className={styles.itemName}>{item.productName}</span>
{item.customizations.length > 0 && (
<span className={styles.itemCustomization}>
{[
...item.customizations.flatMap((c) => [
c.size && `Size: ${c.size}`,
c.color && `Color: ${c.color}`,
c.text && `Text: ${c.text}`,
]),
]
.filter(Boolean)
.join(' · ')}
{sellerGroups.map((group) => (
<div key={group.sellerId} className={styles.sellerSection}>
<h3 className={styles.sellerName}>{group.sellerName}</h3>
{group.items.map((item) => (
<div key={item.id} className={styles.itemRow}>
<div className={styles.itemInfo}>
<span className={styles.itemName}>{item.productName}</span>
{item.customizations.length > 0 && (
<span className={styles.itemCustomization}>
{[
...item.customizations.flatMap((c) => [
c.size &&
`${dict.common.customizationSize}: ${c.size}`,
c.color &&
`${dict.common.customizationColor}: ${c.color}`,
c.text &&
`${dict.common.customizationText}: ${c.text}`,
]),
]
.filter(Boolean)
.join(' · ')}
</span>
)}
{item.customizations[0]?.imageUrl && (
<Image
src={item.customizations[0].imageUrl}
alt={dict.common.customizationPreview}
width={48}
height={48}
className={styles.itemCustomizationThumbnail}
/>
)}
{item.customizationIdList.length >
item.customizations.length && (
<span className={styles.itemCustomizationRemoved}>
{dict.common.customizationRemoved}
</span>
)}
</div>
<div className={styles.itemRight}>
<span className={styles.itemQty}>×{item.quantity}</span>
<span className={styles.itemLineTotal}>
{Money.format(item.lineTotal, item.currency)}
</span>
)}
{item.customizations[0]?.imageUrl && (
<Image
src={item.customizations[0].imageUrl}
alt="Customization preview"
width={48}
height={48}
className={styles.itemCustomizationThumbnail}
/>
)}
{item.customizationIdList.length >
item.customizations.length && (
<span className={styles.itemCustomizationRemoved}>
Customization removed
</span>
)}
</div>
<div className={styles.itemRight}>
<span className={styles.itemQty}>×{item.quantity}</span>
<span className={styles.itemLineTotal}>
{Money.format(item.lineTotal, item.currency)}
</span>
</div>
</div>
))}
<div className={styles.sellerSubtotal}>
<span>{dict.common.subtotal}</span>
<span>{Money.format(group.subtotal, currency)}</span>
</div>
))}
<div className={styles.sellerSubtotal}>
<span>Subtotal</span>
<span>{Money.format(group.subtotal, currency)}</span>
</div>
</div>
))}
))}

<div className={styles.totals}>
<div className={styles.totalRow}>
<span>Subtotal</span>
<span>{Money.format(subtotal, currency)}</span>
</div>
{isFirstPurchase && (
<div className={styles.totals}>
<div className={styles.totalRow}>
<span>
{FIRST_PURCHASE_DISCOUNT_RATE * 100}% first-purchase discount
</span>
<span className={styles.discount}>
−{Money.format(discount, currency)}
</span>
<span>{dict.common.subtotal}</span>
<span>{Money.format(subtotal, currency)}</span>
</div>
{isFirstPurchase && (
<div className={styles.totalRow}>
<span>
{dict.common.firstPurchaseDiscount.replace(
'{rate}',
String(FIRST_PURCHASE_DISCOUNT_RATE * 100),
)}
</span>
<span className={styles.discount}>
−{Money.format(discount, currency)}
</span>
</div>
)}
<div className={styles.totalRow}>
<span>{dict.common.shipping}</span>
<span>{Money.format(shipping, currency)}</span>
</div>
<div className={`${styles.totalRow} ${styles.grandTotal}`}>
<span>{dict.common.total}</span>
<span>{Money.format(total, currency)}</span>
</div>
)}
<div className={styles.totalRow}>
<span>Shipping</span>
<span>{Money.format(shipping, currency)}</span>
</div>
<div className={`${styles.totalRow} ${styles.grandTotal}`}>
<span>Total</span>
<span>{Money.format(total, currency)}</span>
</div>
</div>

<CheckoutConfirmButton locale={locale} initialAddress={initialAddress} />
<CheckoutConfirmButton
locale={locale}
initialAddress={initialAddress}
/>
</Card>
</div>
);
}
1 change: 1 addition & 0 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export default async function RootLayout({
unknownSeller: dict.common.unknownSeller,
increaseQuantity: dict.common.increaseQuantity,
decreaseQuantity: dict.common.decreaseQuantity,
close: dict.common.close,
}}
/>
</CartPopupProvider>
Expand Down
Loading
Loading