Skip to content

Commit 1159a57

Browse files
committed
Add minicart
1 parent b42a7f6 commit 1159a57

3 files changed

Lines changed: 287 additions & 0 deletions

File tree

etc/frontend/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<item name="product.info.details" xsi:type="string">LumaTabs</item>
2929
<item name="form.subscribe" xsi:type="string">LumaNewsletter</item>
3030
<item name="catalog.topnav" xsi:type="string">LumaMainMenu</item>
31+
<item name="minicart" xsi:type="string">LumaMiniCart</item>
3132
</argument>
3233
</arguments>
3334
</type>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php declare(strict_types=1);
2+
3+
use Magento\Framework\View\Element\Template;
4+
5+
/** @var Template $block */
6+
?>
7+
<template x-if="hasCartItems">
8+
<div id="minicart-content-wrapper">
9+
<div class="block-title">
10+
<strong>
11+
<span class="text"><?= __('My Cart') ?></span>
12+
<span
13+
class="qty"
14+
:class="qtyClass"
15+
title="<?= __('Items in Cart') ?>"
16+
x-html="cart.summary_count">0</span>
17+
</strong>
18+
</div>
19+
20+
<div class="block-content">
21+
<button
22+
type="button"
23+
id="btn-minicart-close"
24+
class="action close"
25+
@click="closeContent"
26+
title="Close">
27+
<span>Close</span>
28+
</button>
29+
30+
<div class="items-total">
31+
<span class="count" x-html="cart.summary_count">0</span>
32+
<span><?= __('Items in Cart') ?></span>
33+
</div>
34+
35+
<div class="subtotal">
36+
<span class="label">
37+
<span><?= __('Cart Subtotal') ?></span>
38+
</span>
39+
40+
<div class="amount price-container">
41+
<span class="price-wrapper">
42+
<span class="price" x-html="cart.subtotal">-</span>
43+
</span>
44+
</div>
45+
</div>
46+
47+
<div class="actions">
48+
<div class="primary">
49+
<button id="top-cart-btn-checkout" type="button"
50+
class="action primary checkout"
51+
@click="proceedToCheckout"
52+
title="<?= __('Proceed to Checkout') ?>"><?= __('Proceed to Checkout') ?>
53+
</button>
54+
</div>
55+
</div>
56+
57+
<strong class="subtitle"><?= __('Recently added item(s)') ?></strong>
58+
<div class="minicart-items-wrapper">
59+
<ol id="mini-cart" class="minicart-items">
60+
<template x-for="item in cart.items">
61+
<li class="item product product-item odd last"
62+
data-role="product-item">
63+
<div class="product">
64+
<a
65+
tabindex="-1" class="product-item-photo"
66+
:href="item.product_url"
67+
:title="item.product_name">
68+
<span class="product-image-container">
69+
<span class="product-image-wrapper">
70+
<img class="product-image-photo" loading="lazy"
71+
:src="item.product_image.src"
72+
:alt="item.product_image.alt"
73+
:width="item.product_image.width"
74+
:height="item.product_image.height">
75+
</span>
76+
</span>
77+
</a>
78+
79+
<div class="product-item-details">
80+
<strong class="product-item-name">
81+
<a
82+
:href="item.product_url"
83+
x-html="item.product_name"
84+
></a>
85+
</strong>
86+
87+
<div class="product-item-pricing">
88+
<div class="price-container">
89+
<span class="price-wrapper" x-html="item.product_price"></span>
90+
</div>
91+
92+
<div class="details-qty qty">
93+
<label class="label"
94+
:for="'cart-item-' + item.item_id + '-qty'"><?= __('Qty') ?></label>
95+
<input type="number" min="0" size="4"
96+
class="item-qty cart-item-qty"
97+
:id="'cart-item-' + item.item_id + '-qty'"
98+
:value="item.qty"
99+
:data-cart-item="item.item_id"
100+
:data-item-qty="item.qty"
101+
:data-cart-item-id="item.product_sku">
102+
<button class="update-cart-item"
103+
:data-cart-item="item.item_id"
104+
@click="updateCartItemQty"
105+
title="<?= __('Update') ?>">
106+
<span><?= __('Update') ?></span>
107+
</button>
108+
</div>
109+
</div>
110+
111+
<div class="product actions">
112+
<div class="primary">
113+
<a class="action edit"
114+
:href="item.configure_url"
115+
title="<?= __('Edit item') ?>">
116+
<span><?= __('Edit') ?></span>
117+
</a>
118+
</div>
119+
<div class="secondary">
120+
<a @click="removeCartItem"
121+
class="action delete"
122+
:data-cart-item="item.item_id"
123+
title="<?= __('Remove item') ?>">
124+
<span><?= __('Remove') ?></span>
125+
</a>
126+
</div>
127+
</div>
128+
</div>
129+
</div>
130+
</li>
131+
</template>
132+
</ol>
133+
</div>
134+
135+
<div class="actions">
136+
<div class="secondary">
137+
<a class="action viewcart"
138+
href="<?= $block->getUrl('checkout/cart') ?>">
139+
<span><?= __('View and Edit Cart') ?></span>
140+
</a>
141+
</div>
142+
</div>
143+
144+
<div id="minicart-widgets" class="minicart-widgets">
145+
</div>
146+
</div>
147+
</div>
148+
</template>
149+
150+
<template x-if="hasNoCartItems">
151+
<div>
152+
<?= __('Your cart is empty') ?>
153+
</div>
154+
</template>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Magento\Framework\View\Element\Template;
5+
6+
/** @version 0.0.2 */
7+
/** @var Template $block */
8+
?>
9+
<script>
10+
document.addEventListener('alpine:init', () => {
11+
Alpine.data('LumaMiniCart', () => ({
12+
cart: {},
13+
init() {
14+
const anchor = this.$el.querySelector('a.action.showcart');
15+
if (anchor) {
16+
anchor.addEventListener('click', (e) => {
17+
e.preventDefault();
18+
const minicartBlock = this.getMinicartBlock();
19+
minicartBlock.style.display = minicartBlock.style.display === 'block' ? 'none' : 'block';
20+
});
21+
}
22+
23+
Alpine.effect(() => {
24+
this.cart = Alpine.store('LumaLocalStorage').get('cart');
25+
26+
const qty = this.$el.querySelector('span.counter.qty');
27+
if (qty) {
28+
if (this.cart) {
29+
qty.classList.remove('empty');
30+
} else {
31+
qty.classList.add('empty');
32+
}
33+
}
34+
35+
const counterNumber = this.$el.querySelector('span.counter-number');
36+
if (counterNumber) {
37+
if (this.cart) {
38+
counterNumber.innerHTML = this.cart.summary_count;
39+
} else {
40+
counterNumber.innerHTML = 0;
41+
}
42+
}
43+
});
44+
},
45+
getMinicartBlock() {
46+
return this.$el.querySelector('div.block.block-minicart');
47+
},
48+
qtyClass() {
49+
return this.summaryCount > 0 ? '' : 'empty';
50+
},
51+
closeContent() {
52+
console.log('Close content via button');
53+
const minicartBlock = this.getMinicartBlock();
54+
minicartBlock.style.display = 'none';
55+
},
56+
proceedToCheckout() {
57+
window.location = '<?= $block->getUrl('checkout') ?>';
58+
},
59+
updateCartItemQty(event) {
60+
const cartItemId = this.$el.getAttribute('data-cart-item');
61+
const cartItemInput = document.getElementById('cart-item-' + cartItemId + '-qty');
62+
const cartItemQty = parseInt(cartItemInput.value);
63+
const cartItem = this.cart.items.find((item) => item.item_id === cartItemId);
64+
const currentQty = parseInt(cartItem.qty);
65+
66+
if (currentQty === cartItemQty) {
67+
return;
68+
}
69+
70+
const ajaxUpdateUrl = '<?= $block->getUrl(
71+
'checkout/sidebar/updateItemQty'
72+
) ?>';
73+
74+
fetch(ajaxUpdateUrl, {
75+
method: 'POST',
76+
headers: {
77+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
78+
},
79+
body: new URLSearchParams({
80+
'item_id': cartItemId,
81+
'item_qty': cartItemQty,
82+
'form_key': window.FORM_KEY
83+
})
84+
})
85+
.then(response => response.json())
86+
.then(data => {
87+
if (data.success) {
88+
Alpine.store('LumaLocalStorage').refresh('cart');
89+
}
90+
91+
Alpine.store('LumaMessageStore').addNoticeMessage('<?= __('Updated quantity') ?>');
92+
});
93+
},
94+
hasCartItems() {
95+
if (!this.cart || !this.cart.items) {
96+
return false;
97+
}
98+
99+
return this.cart.items.length > 0;
100+
},
101+
hasNoCartItems() {
102+
return !this.hasCartItems();
103+
},
104+
removeCartItem(event) {
105+
const cartItemId = this.$el.getAttribute('data-cart-item');
106+
107+
const ajaxUpdateUrl = '<?= $block->getUrl(
108+
'checkout/sidebar/removeItem'
109+
) ?>';
110+
111+
fetch(ajaxUpdateUrl, {
112+
method: 'POST',
113+
headers: {
114+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
115+
},
116+
body: new URLSearchParams({
117+
'item_id': cartItemId,
118+
'form_key': window.FORM_KEY
119+
})
120+
})
121+
.then(response => response.json())
122+
.then(data => {
123+
if (data.success) {
124+
Alpine.store('LumaLocalStorage').refresh('cart');
125+
}
126+
127+
Alpine.store('LumaMessageStore').addNoticeMessage('<?= __('Removed item') ?>');
128+
});
129+
}
130+
}));
131+
});
132+
</script>

0 commit comments

Comments
 (0)