create-brainerce-store 1.50.1 → 1.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +171 -66
- package/messages/en.json +4 -1
- package/messages/he.json +4 -1
- package/package.json +1 -1
- package/templates/nextjs/base/.claude/commands/design.md +51 -0
- package/templates/nextjs/base/.env.local.ejs +5 -0
- package/templates/nextjs/base/.eslintrc.json +57 -3
- package/templates/nextjs/base/AGENTS.md +41 -0
- package/templates/nextjs/base/AI-GUIDE.md +94 -0
- package/templates/nextjs/base/CLAUDE.md +42 -0
- package/templates/nextjs/base/TRANSLATIONS.md +1 -1
- package/templates/nextjs/base/next.config.ts +18 -10
- package/templates/nextjs/base/package.json.ejs +5 -1
- package/templates/nextjs/base/src/app/account/page.tsx +5 -5
- package/templates/nextjs/base/src/app/api/auth/logout/route.ts +1 -1
- package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +2 -2
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +1 -1
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +4 -4
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +4 -4
- package/templates/nextjs/base/src/app/blog/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/cart/page.tsx +2 -173
- package/templates/nextjs/base/src/app/checkout/page.tsx +12 -11
- package/templates/nextjs/base/src/app/contact/page.tsx +3 -3
- package/templates/nextjs/base/src/app/faq/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/forgot-password/page.tsx +4 -4
- package/templates/nextjs/base/src/app/layout.tsx.ejs +16 -14
- package/templates/nextjs/base/src/app/login/page.tsx +4 -4
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +19 -12
- package/templates/nextjs/base/src/app/page.tsx +4 -4
- package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +3 -3
- package/templates/nextjs/base/src/app/payment-complete/page.tsx +1 -1
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -6
- package/templates/nextjs/base/src/app/products/page.tsx +4 -623
- package/templates/nextjs/base/src/app/register/page.tsx +4 -4
- package/templates/nextjs/base/src/app/reset-password/page.tsx +5 -5
- package/templates/nextjs/base/src/app/sitemap.ts +1 -1
- package/templates/nextjs/base/src/app/verify-email/page.tsx +5 -5
- package/templates/nextjs/base/src/components/account/address-book.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-customizations.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-history.tsx +13 -5
- package/templates/nextjs/base/src/components/account/order-payment-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-shipping-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +2 -2
- package/templates/nextjs/base/src/components/account/profile-section.tsx +3 -3
- package/templates/nextjs/base/src/components/auth/login-form.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/register-form.tsx +4 -4
- package/templates/nextjs/base/src/components/brainerce-bot.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +24 -3
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +4 -4
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +6 -6
- package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +3 -3
- package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +2 -2
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +3 -3
- package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +58 -0
- package/templates/nextjs/base/src/core/hooks/use-home-data.ts +51 -0
- package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +295 -0
- package/templates/nextjs/base/src/core/hooks/use-product-page.ts +278 -0
- package/templates/nextjs/base/src/{hooks → core/hooks}/use-search.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/brainerce.ts.ejs +1 -1
- package/templates/nextjs/base/src/core/lib/image-hosts.ts +7 -0
- package/templates/nextjs/base/src/core/lib/product-options.ts +121 -0
- package/templates/nextjs/base/src/{lib → core/lib}/resolve-currency.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/safe-redirect.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/sanitize-html.ts +12 -0
- package/templates/nextjs/base/src/{lib → core/lib}/sanitize.ts.ejs +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/use-currency.ts +1 -1
- package/templates/nextjs/base/src/{providers → core/providers}/store-provider.tsx.ejs +4 -4
- package/templates/nextjs/base/src/middleware.ts.ejs +2 -2
- package/templates/nextjs/base/src/{components → ui}/cart/cart-bundle-offer.tsx +5 -5
- package/templates/nextjs/base/src/{components → ui}/cart/cart-item.tsx +6 -6
- package/templates/nextjs/base/src/{components → ui}/cart/cart-nudges.tsx +1 -1
- package/templates/nextjs/base/src/{components → ui}/cart/cart-summary.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/cart-upgrade-banner.tsx +5 -5
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/base/src/{components → ui}/cart/coupon-input.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/free-shipping-bar.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/reservation-countdown.tsx +2 -2
- package/templates/nextjs/base/src/ui/home/discount-banner-strip.tsx +20 -0
- package/templates/nextjs/base/src/ui/home/featured-products-section.tsx +24 -0
- package/templates/nextjs/base/src/ui/home/hero-section.tsx +29 -0
- package/templates/nextjs/base/src/ui/home/home-client.tsx +29 -0
- package/templates/nextjs/base/src/{components/content → ui/layout}/faq-section.tsx.ejs +2 -2
- package/templates/nextjs/base/src/{components/content → ui/layout}/header-account.tsx +3 -3
- package/templates/nextjs/base/src/{components/content → ui/layout}/rich-text-block.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/content → ui/layout}/site-header.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/customization-fields.tsx +18 -51
- package/templates/nextjs/base/src/{components/products → ui/product}/discount-badge.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/frequently-bought-together.tsx +6 -6
- package/templates/nextjs/base/src/{components/products → ui/product}/modifier-group-selector.tsx +10 -77
- package/templates/nextjs/base/src/{components/shared → ui/product}/price-display.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/product-card.tsx +10 -10
- package/templates/nextjs/base/src/{app/products/[slug] → ui/product}/product-client-section.tsx +45 -206
- package/templates/nextjs/base/src/{components/products → ui/product}/product-grid.tsx +3 -3
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +429 -0
- package/templates/nextjs/base/src/{components/products → ui/product}/recommendation-section.tsx +4 -4
- package/templates/nextjs/base/src/{components/reviews → ui/product}/review-form.tsx +2 -2
- package/templates/nextjs/base/src/{components/reviews → ui/product}/reviews-section.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/stock-badge.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/variant-selector.tsx +3 -3
- package/templates/nextjs/base/src/ui/shared/cdn-image.tsx +26 -0
- package/templates/nextjs/base/src/{components → ui}/shared/loading-spinner.tsx +2 -2
- package/templates/nextjs/base/tailwind.config.ts +12 -0
- package/templates/nextjs/designs/passepartout/app-overlay/layout.tsx.ejs +253 -0
- package/templates/nextjs/designs/passepartout/globals.css +412 -0
- package/templates/nextjs/designs/passepartout/messages-patch/en.json +87 -0
- package/templates/nextjs/designs/passepartout/messages-patch/he.json +87 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-bundle-offer.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +39 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-upgrade-banner.tsx +141 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/designs/passepartout/ui/cart/free-shipping-bar.tsx +62 -0
- package/templates/nextjs/designs/passepartout/ui/cart/reservation-countdown.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +401 -0
- package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +139 -0
- package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +45 -0
- package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +159 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +155 -0
- package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +60 -0
- package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +34 -0
- package/templates/nextjs/designs/passepartout/ui/product/discount-badge.tsx +22 -0
- package/templates/nextjs/designs/passepartout/ui/product/frequently-bought-together.tsx +205 -0
- package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +265 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +667 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +35 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +427 -0
- package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +113 -0
- package/templates/nextjs/designs/passepartout/ui/product/review-form.tsx +294 -0
- package/templates/nextjs/designs/passepartout/ui/product/reviews-section.tsx.ejs +96 -0
- package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +52 -0
- package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +307 -0
- package/templates/nextjs/designs/passepartout/ui/shared/fly-to-cart.ts +85 -0
- package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +33 -0
- package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +87 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +121 -0
- package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +25 -0
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +97 -0
- package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +103 -0
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +106 -0
- package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +117 -0
- package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +52 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +79 -0
- package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +19 -0
- package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +23 -0
- package/templates/nextjs/ui-canvas/home/hero-section.tsx +21 -0
- package/templates/nextjs/ui-canvas/home/home-client.tsx +26 -0
- package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +101 -0
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +71 -0
- package/templates/nextjs/ui-canvas/layout/header-account.tsx +31 -0
- package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +74 -0
- package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +34 -0
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +82 -0
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +119 -0
- package/templates/nextjs/ui-canvas/product/customization-fields.tsx +443 -0
- package/templates/nextjs/ui-canvas/product/discount-badge.tsx +17 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +166 -0
- package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +163 -0
- package/templates/nextjs/ui-canvas/product/price-display.tsx +50 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +197 -0
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +352 -0
- package/templates/nextjs/ui-canvas/product/product-grid.tsx +27 -0
- package/templates/nextjs/ui-canvas/product/product-listing.tsx +224 -0
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +95 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +269 -0
- package/templates/nextjs/ui-canvas/product/reviews-section.tsx +78 -0
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +43 -0
- package/templates/nextjs/ui-canvas/product/variant-selector.tsx +210 -0
- package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +29 -0
- package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +25 -0
- package/templates/nextjs/base/src/app/home-client.tsx +0 -98
- package/templates/nextjs/base/src/components/products/allergen-chips.tsx +0 -78
- /package/templates/nextjs/base/src/{lib → core/lib}/auth.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/csrf.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/navigation.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/nonce.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/seo.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/store-info.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/translations.ts.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/utils.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/validation.ts +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/announcement-bar.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components → ui}/layout/language-switcher.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/site-footer.tsx.ejs +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"whatsappMessage": "Hi, I'd love more details about a product from your store"
|
|
4
|
+
},
|
|
5
|
+
"home": {
|
|
6
|
+
"heroOverline": "New collection · chosen with care",
|
|
7
|
+
"featuredKicker": "No. 02",
|
|
8
|
+
"viewExhibition": "View the full exhibition",
|
|
9
|
+
"trustShipping": "Fast home delivery",
|
|
10
|
+
"trustWarranty": "Full warranty on every item",
|
|
11
|
+
"trustGift": "Free gift wrapping",
|
|
12
|
+
"trustSecure": "Secure payment",
|
|
13
|
+
"ctaStory": "Our story",
|
|
14
|
+
"marqueeHandmade": "Chosen with care, piece by piece",
|
|
15
|
+
"marqueeOneOff": "Small, rotating editions",
|
|
16
|
+
"collectionsKicker": "No. 01",
|
|
17
|
+
"collections": "Collections",
|
|
18
|
+
"onWallKicker": "No. 03",
|
|
19
|
+
"onWall": "On the wall now",
|
|
20
|
+
"voicesKicker": "No. 04",
|
|
21
|
+
"voices": "Kind words",
|
|
22
|
+
"quote1": "“The order arrived in a box that is a gift in itself. Meticulous service from the first moment.”",
|
|
23
|
+
"name1": "Michal, Tel Aviv",
|
|
24
|
+
"quote2": "“I was looking for a special gift and found exactly what I wanted — and then some.”",
|
|
25
|
+
"name2": "Noa, Haifa",
|
|
26
|
+
"quote3": "“Quality you can feel in your hands, and quick, kind answers to every question.”",
|
|
27
|
+
"name3": "Dana, Jerusalem",
|
|
28
|
+
"storyOverline": "Behind the scenes",
|
|
29
|
+
"storyTitle": "Some things are not rushed.",
|
|
30
|
+
"storyBody": "Every piece here is chosen or made with intention — in small series, with good materials and plenty of patience. We believe good things are made slowly.",
|
|
31
|
+
"storyCta": "View the collection",
|
|
32
|
+
"visitTitle": "We'd love to meet",
|
|
33
|
+
"visitSub": "Personal service · quick answers to every question",
|
|
34
|
+
"visitCta": "Contact us",
|
|
35
|
+
"newsletterPlaceholder": "Your email",
|
|
36
|
+
"newsletterCta": "Join",
|
|
37
|
+
"newsletterHook": "Join the newsletter for 10% off your first order",
|
|
38
|
+
"newsletterSuccess": "Welcome ✨ Your discount is on its way to your inbox"
|
|
39
|
+
},
|
|
40
|
+
"productDetail": {
|
|
41
|
+
"itemNo": "Item no. {sku}",
|
|
42
|
+
"aboutWork": "About this piece",
|
|
43
|
+
"giftPanel": "Personalization",
|
|
44
|
+
"trustLine": "Fast shipping · 14-day returns · Gift wrapping",
|
|
45
|
+
"fbtKicker": "No. 02",
|
|
46
|
+
"upsellsKicker": "No. 03",
|
|
47
|
+
"relatedKicker": "No. 04",
|
|
48
|
+
"installments": "Up to 12 interest-free payments",
|
|
49
|
+
"inStockShip": "In stock · ships within a few business days",
|
|
50
|
+
"shippingReturnsTitle": "Shipping & returns",
|
|
51
|
+
"shippingReturnsBody": "Home delivery within a few business days. Exchange or return within 14 days of receiving your order, provided the item is unused and in its original packaging.",
|
|
52
|
+
"careWarrantyTitle": "Warranty & service",
|
|
53
|
+
"careWarrantyBody": "We stand behind every piece that leaves here. Ran into a problem? Write to us and we'll sort it out quickly — repair, replacement or refund."
|
|
54
|
+
},
|
|
55
|
+
"reviews": {
|
|
56
|
+
"title": "Reviews",
|
|
57
|
+
"noReviews": "No reviews yet — be the first to share your experience.",
|
|
58
|
+
"verifiedPurchase": "Verified purchase",
|
|
59
|
+
"loading": "Loading…",
|
|
60
|
+
"signIn": "Sign in",
|
|
61
|
+
"signInSuffix": "to leave a review for this product.",
|
|
62
|
+
"onlyPurchasers": "Only customers who purchased this product can leave a review.",
|
|
63
|
+
"reviewsDisabled": "Reviews are not enabled for this store.",
|
|
64
|
+
"cannotReview": "You cannot review this product right now.",
|
|
65
|
+
"confirmDelete": "Delete your review for this product?",
|
|
66
|
+
"deleteFailed": "Could not delete your review. Please try again.",
|
|
67
|
+
"startOver": "Want to start over? You can delete your review.",
|
|
68
|
+
"deleting": "Deleting…",
|
|
69
|
+
"deleteReview": "Delete review",
|
|
70
|
+
"alreadySubmitted": "You've already submitted a review for this product.",
|
|
71
|
+
"tooMany": "Too many submissions. Please try again in a few minutes.",
|
|
72
|
+
"updateFailed": "Could not update your review. Please try again.",
|
|
73
|
+
"submitFailed": "Could not submit your review. Please try again.",
|
|
74
|
+
"thanksUpdated": "Thanks! Your review has been updated.",
|
|
75
|
+
"thanksSubmitted": "Thanks! Your review has been submitted.",
|
|
76
|
+
"editYourReview": "Edit your review",
|
|
77
|
+
"writeReview": "Write a review",
|
|
78
|
+
"yourRating": "Your rating",
|
|
79
|
+
"starRating": "Star rating",
|
|
80
|
+
"yourReview": "Your review",
|
|
81
|
+
"optionalField": "optional",
|
|
82
|
+
"saving": "Saving…",
|
|
83
|
+
"submittingBtn": "Submitting…",
|
|
84
|
+
"saveChanges": "Save changes",
|
|
85
|
+
"submitReview": "Submit review"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common": {
|
|
3
|
+
"whatsappMessage": "היי, אשמח לפרטים על מוצר מהאתר"
|
|
4
|
+
},
|
|
5
|
+
"home": {
|
|
6
|
+
"heroOverline": "אוסף חדש · נבחר בקפידה",
|
|
7
|
+
"featuredKicker": "מס׳ 02",
|
|
8
|
+
"viewExhibition": "לכל התערוכה",
|
|
9
|
+
"trustShipping": "משלוח מהיר עד הבית",
|
|
10
|
+
"trustWarranty": "אחריות מלאה על כל פריט",
|
|
11
|
+
"trustGift": "אריזת מתנה חינם",
|
|
12
|
+
"trustSecure": "תשלום מאובטח",
|
|
13
|
+
"ctaStory": "הסיפור שלנו",
|
|
14
|
+
"marqueeHandmade": "נבחר בקפידה, פריט-פריט",
|
|
15
|
+
"marqueeOneOff": "מהדורות קטנות ומתחלפות",
|
|
16
|
+
"collectionsKicker": "מס׳ 01",
|
|
17
|
+
"collections": "האוספים",
|
|
18
|
+
"onWallKicker": "מס׳ 03",
|
|
19
|
+
"onWall": "על הקיר עכשיו",
|
|
20
|
+
"voicesKicker": "מס׳ 04",
|
|
21
|
+
"voices": "אומרים עלינו",
|
|
22
|
+
"quote1": "״ההזמנה הגיעה באריזה שהיא בעצמה מתנה. שירות מוקפד מהרגע הראשון.״",
|
|
23
|
+
"name1": "מיכל, תל אביב",
|
|
24
|
+
"quote2": "״חיפשתי מתנה מיוחדת ומצאתי בדיוק את מה שרציתי — ואפילו יותר.״",
|
|
25
|
+
"name2": "נועה, חיפה",
|
|
26
|
+
"quote3": "״איכות שמרגישים בידיים, ומענה מהיר ואדיב לכל שאלה.״",
|
|
27
|
+
"name3": "דנה, ירושלים",
|
|
28
|
+
"storyOverline": "מאחורי הקלעים",
|
|
29
|
+
"storyTitle": "יש דברים שלא ממהרים.",
|
|
30
|
+
"storyBody": "כל פריט אצלנו נבחר או נוצר בתשומת לב — בסדרות קטנות, עם חומרים טובים והרבה סבלנות. אנחנו מאמינים שדברים טובים נעשים לאט.",
|
|
31
|
+
"storyCta": "לצפייה באוסף",
|
|
32
|
+
"visitTitle": "נשמח להכיר",
|
|
33
|
+
"visitSub": "שירות אישי · מענה מהיר לכל שאלה",
|
|
34
|
+
"visitCta": "צרו קשר",
|
|
35
|
+
"newsletterPlaceholder": "האימייל שלכם",
|
|
36
|
+
"newsletterCta": "הצטרפו",
|
|
37
|
+
"newsletterHook": "הצטרפו לניוזלטר וקבלו 10% הנחה בקנייה הראשונה",
|
|
38
|
+
"newsletterSuccess": "ברוכים הבאים ✨ ההטבה בדרך לתיבת המייל שלכם"
|
|
39
|
+
},
|
|
40
|
+
"productDetail": {
|
|
41
|
+
"itemNo": "פריט מס׳ {sku}",
|
|
42
|
+
"aboutWork": "על הפריט",
|
|
43
|
+
"giftPanel": "התאמה אישית",
|
|
44
|
+
"trustLine": "משלוח מהיר · החזרות עד 14 יום · אריזת מתנה",
|
|
45
|
+
"fbtKicker": "מס׳ 02",
|
|
46
|
+
"upsellsKicker": "מס׳ 03",
|
|
47
|
+
"relatedKicker": "מס׳ 04",
|
|
48
|
+
"installments": "עד 12 תשלומים ללא ריבית",
|
|
49
|
+
"inStockShip": "במלאי · נשלח תוך ימי עסקים ספורים",
|
|
50
|
+
"shippingReturnsTitle": "משלוחים והחזרות",
|
|
51
|
+
"shippingReturnsBody": "משלוח עד הבית תוך ימי עסקים ספורים. החלפה או החזרה תוך 14 יום מקבלת ההזמנה, בתנאי שהפריט לא נעשה בו שימוש ובאריזתו המקורית.",
|
|
52
|
+
"careWarrantyTitle": "אחריות ושירות",
|
|
53
|
+
"careWarrantyBody": "אנחנו עומדים מאחורי כל פריט שיוצא מכאן. נתקלתם בבעיה? כתבו לנו ונדאג לפתרון מהיר — תיקון, החלפה או החזר."
|
|
54
|
+
},
|
|
55
|
+
"reviews": {
|
|
56
|
+
"title": "ביקורות",
|
|
57
|
+
"noReviews": "אין עדיין ביקורות — היו הראשונים לשתף.",
|
|
58
|
+
"verifiedPurchase": "רכישה מאומתת",
|
|
59
|
+
"loading": "טוען…",
|
|
60
|
+
"signIn": "התחברו",
|
|
61
|
+
"signInSuffix": "כדי לכתוב ביקורת על המוצר.",
|
|
62
|
+
"onlyPurchasers": "רק לקוחות שרכשו את המוצר יכולים לכתוב ביקורת.",
|
|
63
|
+
"reviewsDisabled": "ביקורות אינן פעילות בחנות זו.",
|
|
64
|
+
"cannotReview": "לא ניתן לכתוב ביקורת על המוצר כרגע.",
|
|
65
|
+
"confirmDelete": "למחוק את הביקורת שלך על המוצר?",
|
|
66
|
+
"deleteFailed": "לא ניתן היה למחוק את הביקורת. נסו שוב.",
|
|
67
|
+
"startOver": "רוצים להתחיל מחדש? אפשר למחוק את הביקורת.",
|
|
68
|
+
"deleting": "מוחק…",
|
|
69
|
+
"deleteReview": "מחיקת ביקורת",
|
|
70
|
+
"alreadySubmitted": "כבר שלחתם ביקורת על המוצר הזה.",
|
|
71
|
+
"tooMany": "יותר מדי נסיונות. נסו שוב בעוד כמה דקות.",
|
|
72
|
+
"updateFailed": "לא ניתן היה לעדכן את הביקורת. נסו שוב.",
|
|
73
|
+
"submitFailed": "לא ניתן היה לשלוח את הביקורת. נסו שוב.",
|
|
74
|
+
"thanksUpdated": "תודה! הביקורת עודכנה.",
|
|
75
|
+
"thanksSubmitted": "תודה! הביקורת נשלחה.",
|
|
76
|
+
"editYourReview": "עריכת הביקורת",
|
|
77
|
+
"writeReview": "כתיבת ביקורת",
|
|
78
|
+
"yourRating": "הדירוג שלכם",
|
|
79
|
+
"starRating": "דירוג כוכבים",
|
|
80
|
+
"yourReview": "הביקורת שלכם",
|
|
81
|
+
"optionalField": "לא חובה",
|
|
82
|
+
"saving": "שומר…",
|
|
83
|
+
"submittingBtn": "שולח…",
|
|
84
|
+
"saveChanges": "שמירת שינויים",
|
|
85
|
+
"submitReview": "שליחת ביקורת"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { CdnImage as Image } from '@/ui/shared/cdn-image';
|
|
5
|
+
import type { CartBundleOffer as CartBundleOfferType } from 'brainerce';
|
|
6
|
+
import { formatPrice } from 'brainerce';
|
|
7
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
8
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
9
|
+
import { cn } from '@/core/lib/utils';
|
|
10
|
+
|
|
11
|
+
interface CartBundleOfferCardProps {
|
|
12
|
+
offer: CartBundleOfferType;
|
|
13
|
+
cartId: string;
|
|
14
|
+
onAdd: () => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function CartBundleOfferCard({ offer, cartId, onAdd, className }: CartBundleOfferCardProps) {
|
|
19
|
+
const t = useTranslations('cart');
|
|
20
|
+
const currency = useCurrency();
|
|
21
|
+
const [adding, setAdding] = useState(false);
|
|
22
|
+
|
|
23
|
+
const offered = offer.offeredProducts;
|
|
24
|
+
|
|
25
|
+
const totalOriginal = parseFloat(offer.totalOriginalPrice);
|
|
26
|
+
const totalDiscounted = parseFloat(offer.totalDiscountedPrice);
|
|
27
|
+
const discountLabel =
|
|
28
|
+
offer.discountType === 'PERCENTAGE'
|
|
29
|
+
? `${offer.discountValue}%`
|
|
30
|
+
: (formatPrice(parseFloat(offer.discountValue), { currency }) as string);
|
|
31
|
+
|
|
32
|
+
async function handleAdd() {
|
|
33
|
+
if (adding) return;
|
|
34
|
+
try {
|
|
35
|
+
setAdding(true);
|
|
36
|
+
const { getClient } = await import('@/core/lib/brainerce');
|
|
37
|
+
const client = getClient();
|
|
38
|
+
// No variant selections in the default template — products with
|
|
39
|
+
// variants are best handled with a dedicated picker per offered item.
|
|
40
|
+
await client.addBundleToCart(cartId, offer.id);
|
|
41
|
+
onAdd();
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('Failed to add bundle:', err);
|
|
44
|
+
} finally {
|
|
45
|
+
setAdding(false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className={cn('bg-background border-border rounded-none border p-4', className)}>
|
|
51
|
+
<div className="mb-3">
|
|
52
|
+
<p className="text-foreground text-sm font-medium">{offer.name}</p>
|
|
53
|
+
{offer.description && (
|
|
54
|
+
<p className="text-muted-foreground mt-0.5 text-xs">{offer.description}</p>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<ul className="space-y-2">
|
|
59
|
+
{offered.map((p) => {
|
|
60
|
+
const firstImage = p.images?.[0];
|
|
61
|
+
const imageUrl = firstImage?.url ?? null;
|
|
62
|
+
return (
|
|
63
|
+
<li key={p.id} className="flex items-center gap-3">
|
|
64
|
+
<div className="bg-muted relative h-12 w-12 flex-shrink-0 overflow-hidden rounded">
|
|
65
|
+
{imageUrl ? (
|
|
66
|
+
<Image src={imageUrl} alt={p.name} fill sizes="48px" className="object-cover" />
|
|
67
|
+
) : null}
|
|
68
|
+
</div>
|
|
69
|
+
<div className="min-w-0 flex-1">
|
|
70
|
+
<p className="text-foreground truncate text-sm">{p.name}</p>
|
|
71
|
+
<div className="mt-0.5 flex items-center gap-2">
|
|
72
|
+
<span className="text-muted-foreground text-xs line-through">
|
|
73
|
+
{formatPrice(parseFloat(p.originalPrice), { currency }) as string}
|
|
74
|
+
</span>
|
|
75
|
+
<span className="text-foreground text-xs font-semibold">
|
|
76
|
+
{formatPrice(parseFloat(p.discountedPrice), { currency }) as string}
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</li>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
</ul>
|
|
84
|
+
|
|
85
|
+
<div className="border-border mt-3 flex items-center justify-between border-t pt-3">
|
|
86
|
+
<div className="flex items-center gap-2">
|
|
87
|
+
<span className="text-muted-foreground text-sm line-through">
|
|
88
|
+
{formatPrice(totalOriginal, { currency }) as string}
|
|
89
|
+
</span>
|
|
90
|
+
<span className="text-foreground text-sm font-semibold">
|
|
91
|
+
{formatPrice(totalDiscounted, { currency }) as string}
|
|
92
|
+
</span>
|
|
93
|
+
<span className="bg-destructive/10 text-destructive rounded px-1.5 py-0.5 text-xs font-medium">
|
|
94
|
+
-{discountLabel}
|
|
95
|
+
</span>
|
|
96
|
+
</div>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
onClick={handleAdd}
|
|
100
|
+
disabled={adding}
|
|
101
|
+
className={cn(
|
|
102
|
+
'bg-primary text-primary-foreground flex-shrink-0 rounded px-4 py-2 text-xs font-medium transition-opacity hover:opacity-90',
|
|
103
|
+
'disabled:cursor-not-allowed disabled:opacity-50'
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
{adding ? t('addingBundle') : t('addBundleItem')}
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { CartNudge } from 'brainerce';
|
|
4
|
+
import { cn } from '@/core/lib/utils';
|
|
5
|
+
|
|
6
|
+
interface CartNudgesProps {
|
|
7
|
+
nudges: CartNudge[];
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function CartNudges({ nudges, className }: CartNudgesProps) {
|
|
12
|
+
if (nudges.length === 0) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className={cn('space-y-2', className)}>
|
|
16
|
+
{nudges.map((nudge) => (
|
|
17
|
+
<div
|
|
18
|
+
key={nudge.ruleId}
|
|
19
|
+
className="bg-primary/5 border-primary/20 flex items-start gap-3 rounded-none border px-4 py-3"
|
|
20
|
+
>
|
|
21
|
+
<svg
|
|
22
|
+
className="text-primary mt-0.5 h-5 w-5 flex-shrink-0"
|
|
23
|
+
fill="none"
|
|
24
|
+
viewBox="0 0 24 24"
|
|
25
|
+
stroke="currentColor"
|
|
26
|
+
>
|
|
27
|
+
<path
|
|
28
|
+
strokeLinecap="round"
|
|
29
|
+
strokeLinejoin="round"
|
|
30
|
+
strokeWidth={2}
|
|
31
|
+
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
32
|
+
/>
|
|
33
|
+
</svg>
|
|
34
|
+
<p className="text-foreground flex-1 text-sm">{nudge.text}</p>
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { CdnImage as Image } from '@/ui/shared/cdn-image';
|
|
5
|
+
import type { CartUpgradeSuggestion, CartItem as CartItemType } from 'brainerce';
|
|
6
|
+
import { formatPrice } from 'brainerce';
|
|
7
|
+
import { getClient } from '@/core/lib/brainerce';
|
|
8
|
+
import { useCurrency } from '@/core/lib/use-currency';
|
|
9
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
10
|
+
import { cn } from '@/core/lib/utils';
|
|
11
|
+
|
|
12
|
+
interface CartUpgradeBannerProps {
|
|
13
|
+
suggestion: CartUpgradeSuggestion;
|
|
14
|
+
cartItem: CartItemType;
|
|
15
|
+
onUpgrade: () => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CartUpgradeBanner({
|
|
20
|
+
suggestion,
|
|
21
|
+
cartItem,
|
|
22
|
+
onUpgrade,
|
|
23
|
+
className,
|
|
24
|
+
}: CartUpgradeBannerProps) {
|
|
25
|
+
const t = useTranslations('cart');
|
|
26
|
+
const currency = useCurrency();
|
|
27
|
+
const [upgrading, setUpgrading] = useState(false);
|
|
28
|
+
const [dismissed, setDismissed] = useState(false);
|
|
29
|
+
|
|
30
|
+
const storageKey = `dismissed_upgrade_${suggestion.sourceProductId}`;
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
try {
|
|
34
|
+
if (sessionStorage.getItem(storageKey)) {
|
|
35
|
+
setDismissed(true);
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
/* ignore */
|
|
39
|
+
}
|
|
40
|
+
}, [storageKey]);
|
|
41
|
+
|
|
42
|
+
if (dismissed) return null;
|
|
43
|
+
|
|
44
|
+
const target = suggestion.targetProduct;
|
|
45
|
+
const firstImage = target.images?.[0];
|
|
46
|
+
const imageUrl = firstImage
|
|
47
|
+
? typeof firstImage === 'string'
|
|
48
|
+
? firstImage
|
|
49
|
+
: firstImage.url
|
|
50
|
+
: null;
|
|
51
|
+
const formattedDelta = formatPrice(parseFloat(suggestion.priceDelta), { currency }) as string;
|
|
52
|
+
|
|
53
|
+
function handleDismiss() {
|
|
54
|
+
try {
|
|
55
|
+
sessionStorage.setItem(storageKey, '1');
|
|
56
|
+
} catch {
|
|
57
|
+
/* ignore */
|
|
58
|
+
}
|
|
59
|
+
setDismissed(true);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function handleUpgrade() {
|
|
63
|
+
if (upgrading) return;
|
|
64
|
+
try {
|
|
65
|
+
setUpgrading(true);
|
|
66
|
+
const client = getClient();
|
|
67
|
+
await client.smartRemoveFromCart(cartItem.productId, cartItem.variantId || undefined);
|
|
68
|
+
await client.smartAddToCart({ productId: target.id, quantity: cartItem.quantity });
|
|
69
|
+
onUpgrade();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error('Failed to upgrade cart item:', err);
|
|
72
|
+
} finally {
|
|
73
|
+
setUpgrading(false);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div
|
|
79
|
+
className={cn(
|
|
80
|
+
'bg-primary/5 border-primary/20 relative flex items-center gap-3 rounded-none border px-4 py-3',
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{/* Dismiss button */}
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={handleDismiss}
|
|
88
|
+
className="text-muted-foreground hover:text-foreground absolute end-2 top-2 text-xs"
|
|
89
|
+
aria-label={t('dismissUpgrade')}
|
|
90
|
+
>
|
|
91
|
+
<svg
|
|
92
|
+
className="h-4 w-4"
|
|
93
|
+
fill="none"
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
stroke="currentColor"
|
|
96
|
+
strokeWidth={2}
|
|
97
|
+
>
|
|
98
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
99
|
+
</svg>
|
|
100
|
+
</button>
|
|
101
|
+
|
|
102
|
+
{/* Product image */}
|
|
103
|
+
<div className="bg-muted relative h-12 w-12 flex-shrink-0 overflow-hidden rounded">
|
|
104
|
+
{imageUrl ? (
|
|
105
|
+
<Image src={imageUrl} alt={target.name} fill sizes="48px" className="object-cover" />
|
|
106
|
+
) : (
|
|
107
|
+
<div className="text-muted-foreground flex h-full w-full items-center justify-center">
|
|
108
|
+
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
109
|
+
<path
|
|
110
|
+
strokeLinecap="round"
|
|
111
|
+
strokeLinejoin="round"
|
|
112
|
+
strokeWidth={1.5}
|
|
113
|
+
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
114
|
+
/>
|
|
115
|
+
</svg>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Text */}
|
|
121
|
+
<div className="min-w-0 flex-1">
|
|
122
|
+
<p className="text-foreground text-sm font-medium">
|
|
123
|
+
{t('upgradeFor', { name: target.name, amount: formattedDelta })}
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{/* Upgrade button */}
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={handleUpgrade}
|
|
131
|
+
disabled={upgrading}
|
|
132
|
+
className={cn(
|
|
133
|
+
'bg-primary text-primary-foreground flex-shrink-0 rounded px-3 py-1.5 text-xs font-medium transition-opacity hover:opacity-90',
|
|
134
|
+
'disabled:cursor-not-allowed disabled:opacity-50'
|
|
135
|
+
)}
|
|
136
|
+
>
|
|
137
|
+
{upgrading ? t('upgrading') : t('upgrade')}
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Link } from '@/core/lib/navigation';
|
|
4
|
+
import { useCartPage } from '@/core/hooks/use-cart-page';
|
|
5
|
+
import { CartItem } from '@/ui/cart/cart-item';
|
|
6
|
+
import { CartUpgradeBanner } from '@/ui/cart/cart-upgrade-banner';
|
|
7
|
+
import { CartBundleOfferCard } from '@/ui/cart/cart-bundle-offer';
|
|
8
|
+
import { CartSummary } from '@/ui/cart/cart-summary';
|
|
9
|
+
import { CouponInput } from '@/ui/cart/coupon-input';
|
|
10
|
+
import { CartNudges } from '@/ui/cart/cart-nudges';
|
|
11
|
+
import { FreeShippingBar } from '@/ui/cart/free-shipping-bar';
|
|
12
|
+
import { ReservationCountdown } from '@/ui/cart/reservation-countdown';
|
|
13
|
+
import { CartRecommendationSection } from '@/ui/product/recommendation-section';
|
|
14
|
+
import { LoadingSpinner } from '@/ui/shared/loading-spinner';
|
|
15
|
+
import { useTranslations } from '@/core/lib/translations';
|
|
16
|
+
|
|
17
|
+
export function CartView() {
|
|
18
|
+
const t = useTranslations('cart');
|
|
19
|
+
const tc = useTranslations('common');
|
|
20
|
+
const { cart, cartLoading, refreshCart, itemCount, cartRecs, upgrades, bundles } = useCartPage();
|
|
21
|
+
|
|
22
|
+
if (cartLoading) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex min-h-[60vh] items-center justify-center">
|
|
25
|
+
<LoadingSpinner size="lg" />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Empty cart state
|
|
31
|
+
if (!cart || cart.items.length === 0) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="mx-auto max-w-7xl px-4 py-16 text-center sm:px-6 lg:px-8">
|
|
34
|
+
<svg
|
|
35
|
+
className="text-muted-foreground mx-auto mb-4 h-16 w-16"
|
|
36
|
+
fill="none"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
>
|
|
40
|
+
<path
|
|
41
|
+
strokeLinecap="round"
|
|
42
|
+
strokeLinejoin="round"
|
|
43
|
+
strokeWidth={1.5}
|
|
44
|
+
d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
<h1 className="text-foreground text-2xl font-bold">{t('emptyTitle')}</h1>
|
|
48
|
+
<p className="text-muted-foreground mt-2">{t('emptySubtitle')}</p>
|
|
49
|
+
<Link
|
|
50
|
+
href="/products"
|
|
51
|
+
className="bg-primary text-primary-foreground mt-6 inline-flex items-center rounded px-6 py-3 font-medium transition-opacity hover:opacity-90"
|
|
52
|
+
>
|
|
53
|
+
{tc('continueShopping')}
|
|
54
|
+
</Link>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
|
61
|
+
<h1 className="text-foreground mb-6 text-2xl font-bold">
|
|
62
|
+
{t('title')} ({itemCount} {itemCount === 1 ? tc('item') : tc('items')})
|
|
63
|
+
</h1>
|
|
64
|
+
|
|
65
|
+
{/* Reservation countdown */}
|
|
66
|
+
{cart.reservation?.hasReservation && (
|
|
67
|
+
<ReservationCountdown reservation={cart.reservation} className="mb-6" />
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
|
|
71
|
+
{/* Cart Items */}
|
|
72
|
+
<div className="lg:col-span-2">
|
|
73
|
+
{/* Nudges */}
|
|
74
|
+
{cart.nudges && cart.nudges.length > 0 && (
|
|
75
|
+
<CartNudges nudges={cart.nudges} className="mb-4" />
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* Cart items */}
|
|
79
|
+
<div>
|
|
80
|
+
{cart.items.map((item) => (
|
|
81
|
+
<div key={item.id}>
|
|
82
|
+
<CartItem item={item} onUpdate={refreshCart} />
|
|
83
|
+
{upgrades?.upgrades?.[item.productId] && (
|
|
84
|
+
<CartUpgradeBanner
|
|
85
|
+
suggestion={upgrades.upgrades[item.productId]}
|
|
86
|
+
cartItem={item}
|
|
87
|
+
onUpgrade={refreshCart}
|
|
88
|
+
className="mb-2 ms-24"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Bundle offers */}
|
|
96
|
+
{bundles?.bundles && bundles.bundles.length > 0 && (
|
|
97
|
+
<div className="mt-6 space-y-3">
|
|
98
|
+
<h3 className="text-foreground text-sm font-semibold">{t('bundleOffers')}</h3>
|
|
99
|
+
{bundles.bundles.map((offer) => (
|
|
100
|
+
<CartBundleOfferCard
|
|
101
|
+
key={offer.id}
|
|
102
|
+
offer={offer}
|
|
103
|
+
cartId={cart.id}
|
|
104
|
+
onAdd={refreshCart}
|
|
105
|
+
/>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{/* Coupon input */}
|
|
111
|
+
<div className="border-border mt-6 border-t pt-4">
|
|
112
|
+
<CouponInput cart={cart} onUpdate={refreshCart} />
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Summary sidebar */}
|
|
117
|
+
<div className="lg:col-span-1">
|
|
118
|
+
<div className="bg-muted/50 border-border sticky top-24 rounded-none border p-6">
|
|
119
|
+
<FreeShippingBar className="mb-4" />
|
|
120
|
+
<CartSummary />
|
|
121
|
+
|
|
122
|
+
<Link
|
|
123
|
+
href="/checkout"
|
|
124
|
+
className="bg-primary text-primary-foreground mt-6 inline-flex w-full items-center justify-center rounded px-6 py-3 text-sm font-medium transition-opacity hover:opacity-90"
|
|
125
|
+
>
|
|
126
|
+
{t('proceedToCheckout')}
|
|
127
|
+
</Link>
|
|
128
|
+
|
|
129
|
+
<Link
|
|
130
|
+
href="/products"
|
|
131
|
+
className="text-muted-foreground hover:text-foreground mt-3 inline-flex w-full items-center justify-center px-6 py-2 text-sm transition-colors"
|
|
132
|
+
>
|
|
133
|
+
{tc('continueShopping')}
|
|
134
|
+
</Link>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Cross-sell recommendations */}
|
|
140
|
+
{cartRecs?.recommendations && cartRecs.recommendations.length > 0 && (
|
|
141
|
+
<CartRecommendationSection
|
|
142
|
+
title={t('youMightAlsoNeed')}
|
|
143
|
+
items={cartRecs.recommendations}
|
|
144
|
+
className="mt-10"
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|