@viur/shop-components 0.15.1 → 0.15.2

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.
@@ -20,11 +20,9 @@ jobs:
20
20
  uses: actions/checkout@v4
21
21
  - uses: actions/setup-node@v4
22
22
  with:
23
- node-version: 22
23
+ node-version: 24
24
24
  registry-url: https://registry.npmjs.org/
25
25
 
26
- - name: Ensure npm supports trusted publishing
27
- run: npm install -g npm@latest
28
26
  - run: npm ci
29
27
 
30
28
  - name: Determine npm tag
@@ -42,6 +40,7 @@ jobs:
42
40
  else
43
41
  echo "tag=latest" >> $GITHUB_OUTPUT
44
42
  fi
43
+
45
44
  - name: Publish to npm (Trusted Publishing)
46
45
  run: npm publish --access public --tag ${{ steps.npm_tag.outputs.tag }}
47
46
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viur/shop-components",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Frontend Vue components for the shop module of ViUR",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,16 +3,34 @@
3
3
  <h2 class="viur-shop-cart-sidebar-headline headline" v-html="$t('viur.shop.summary_headline')"></h2>
4
4
  <div class="viur-shop-cart-sidebar-summary">
5
5
  <div class="viur-shop-cart-sidebar-summary-item" v-for="item in state.items">
6
- <template v-if="(!shopStore.state.showNodes && item.skel_type === 'leaf') || shopStore.state.showNodes">
7
- <div class="viur-shop-cart-sidebar-summary-item-amount" v-if="item.skel_type === 'leaf'">
8
- {{ item.quantity }}&nbsp;&times;
6
+ <template v-if="(!shopStore.state.showN1odes && item.skel_type === 'leaf') || shopStore.state.showNodes">
7
+ <div class="viur-shop-cart-sidebar-summary-item-row">
8
+ <div class="viur-shop-cart-sidebar-summary-item-amount" v-if="item.skel_type === 'leaf'">
9
+ {{ item.quantity }}&nbsp;&times;
10
+ </div>
11
+ <div class="viur-shop-cart-sidebar-summary-item-name" v-html="item.skel_type === 'node' ? item.name : item.shop_name"></div>
12
+ <div class="viur-shop-cart-sidebar-summary-item-price" v-if="getArticleDiscounts(item).length && item.price?.recommended > item.price?.current">
13
+ <sl-badge v-for="discount in getArticleDiscounts(item)" variant="danger" pill>
14
+ <template v-if="discount.discount_type === 'percentage'">-{{ discount.percentage }}%</template>
15
+ <template v-else>
16
+ -<sl-format-number lang="de" type="currency" currency="EUR" :value="discount.absolute"></sl-format-number>
17
+ </template>
18
+ </sl-badge>
19
+ <sl-format-number lang="de" type="currency" currency="EUR"
20
+ :value="(item.total ? item.total : item.price?.current) * (item.quantity || 1)">
21
+ </sl-format-number>
22
+ </div>
23
+ <div class="viur-shop-cart-sidebar-summary-item-price" v-else>
24
+ <sl-format-number lang="de" type="currency" currency="EUR"
25
+ :value="(item.total ? item.total : item.price?.current) * (item.quantity || 1)">
26
+ </sl-format-number>
27
+ </div>
9
28
  </div>
10
- <div class="viur-shop-cart-sidebar-summary-item-name" v-html="item.skel_type === 'node' ? item.name : item.shop_name"></div>
11
- <div class="viur-shop-cart-sidebar-summary-item-price">
12
- <sl-format-number lang="de" type="currency" currency="EUR"
13
- :value="item.total ? item.total : item.price?.current">
29
+ <span class="viur-shop-cart-sidebar-summary-item-price--uvp" v-if="getArticleDiscounts(item).length && item.price?.recommended > item.price?.current">
30
+ UVP: <sl-format-number lang="de" type="currency" currency="EUR"
31
+ :value="item.price.recommended * (item.quantity || 1)">
14
32
  </sl-format-number>
15
- </div>
33
+ </span>
16
34
  </template>
17
35
  </div>
18
36
  </div>
@@ -27,7 +45,7 @@
27
45
  <sl-format-number lang="de" type="currency" currency="EUR" :value="state.shippingTotal">
28
46
  </sl-format-number>
29
47
  </div>
30
- <div class="viur-shop-cart-sidebar-info" v-if="shopStore.state.cartRoot.discount">
48
+ <div class="viur-shop-cart-sidebar-info" v-if="shopStore.state.cartRoot.discount && isBasketDiscount(shopStore.state.cartRoot.discount.dest.key)">
31
49
  <span>{{ shopStore.state.cartRoot.discount.dest.name }}</span>
32
50
  <sl-format-number lang="de" type="currency" currency="EUR" :value="state.discount">
33
51
  </sl-format-number>
@@ -133,6 +151,34 @@ const state = reactive({
133
151
  loading:false
134
152
  })
135
153
 
154
+ function isBasketDiscount(discountKey) {
155
+ const item = state.items.find(i => i.price?.cart_discounts?.length)
156
+ if (!item) return false
157
+ const discount = item.price.cart_discounts.find(d => d.key === discountKey)
158
+ if (!discount) return false
159
+ return !(discount.condition || []).some(c => c.dest?.application_domain === 'article')
160
+ }
161
+
162
+ function getArticleDiscounts(item) {
163
+ if (!item.price?.cart_discounts) return []
164
+ return item.price.cart_discounts.filter(discount =>
165
+ (discount.condition || []).some(c => c.dest?.application_domain === 'article')
166
+ )
167
+ }
168
+
169
+ function calcDiscountValue(discount, item) {
170
+ const quantity = item.quantity || 1
171
+ const recommended = item.price?.recommended || 0
172
+ const current = item.price?.current || 0
173
+ if (recommended > current) {
174
+ return (recommended - current) * quantity
175
+ }
176
+ if (discount.discount_type === 'percentage') {
177
+ return recommended * quantity * discount.percentage / 100
178
+ }
179
+ return (discount.absolute || 0) * quantity
180
+ }
181
+
136
182
  onBeforeMount(() => {
137
183
  state.loading=true
138
184
  if (!shopStore.state.cartList.length) {
@@ -210,13 +256,38 @@ function calc_percent(val){
210
256
  }
211
257
 
212
258
  .viur-shop-cart-sidebar-summary-item {
259
+ display: flex;
260
+ flex-direction: column;
261
+ }
262
+
263
+ .viur-shop-cart-sidebar-summary-item-row {
213
264
  display: flex;
214
265
  flex-direction: row;
215
266
  flex-wrap: nowrap;
216
267
  gap: var(--sl-spacing-medium);
268
+ align-items: baseline;
217
269
  }
218
270
 
219
271
  .viur-shop-cart-sidebar-summary-item-name {
220
272
  margin-right: auto;
273
+ min-width: 0;
274
+ overflow: hidden;
275
+ text-overflow: ellipsis;
276
+ white-space: nowrap;
277
+ }
278
+
279
+ .viur-shop-cart-sidebar-summary-item-price {
280
+ flex-shrink: 0;
281
+ display: flex;
282
+ gap: var(--sl-spacing-x-small);
283
+ align-items: baseline;
284
+ }
285
+
286
+ .viur-shop-cart-sidebar-summary-item-price--uvp {
287
+ color: var(--sl-color-neutral-400);
288
+ font-size: var(--sl-font-size-small);
289
+ white-space: nowrap;
290
+ text-align: right;
291
+ display: block;
221
292
  }
222
293
  </style>
@@ -35,26 +35,31 @@ export function useCart() {
35
35
  }
36
36
 
37
37
 
38
+ let _fetchCartPromise = null;
38
39
  function fetchCart() {
39
- //first fetch root then fetchItems for this root
40
+ // Deduplicate parallel calls - return existing promise if one is in flight
41
+ if (_fetchCartPromise) return _fetchCartPromise;
42
+
40
43
  shopStore.state.cartIsLoading = true;
44
+ let promise;
41
45
  if (shopStore.state.order != null && shopStore.state.order?.cart?.dest.key) {
42
- // shopStore.state.cartRoot = {};
43
46
  shopStore.state.cartRoot = shopStore.state.order.cart.dest;
44
-
45
- return fetchCartItems(shopStore.state.cartRoot["key"]).then(() => { // TODO: duplicate code
46
- shopStore.state.cartIsLoading = false;
47
- shopStore.state.cartReady = true;
47
+ promise = fetchCartItems(shopStore.state.cartRoot["key"]);
48
+ } else {
49
+ promise = fetchCartRoot().then(() => {
50
+ if (!shopStore.state.cartRoot?.["key"]) return 0;
51
+ return fetchCartItems(shopStore.state.cartRoot["key"]);
48
52
  });
49
53
  }
50
- shopStore.state.discounts = {}
51
- return fetchCartRoot().then(() => {
52
- if (!shopStore.state.cartRoot?.["key"]) return 0;
53
- fetchCartItems(shopStore.state.cartRoot["key"]).then(() => { // TODO: duplicate code
54
- shopStore.state.cartIsLoading = false;
55
- shopStore.state.cartReady = true;
56
- });
54
+
55
+ _fetchCartPromise = promise.then(() => {
56
+ shopStore.state.cartIsLoading = false;
57
+ shopStore.state.cartReady = true;
58
+ }).finally(() => {
59
+ _fetchCartPromise = null;
57
60
  });
61
+
62
+ return _fetchCartPromise;
58
63
  }
59
64
 
60
65
  function fetchCartRoot(){
@@ -69,32 +74,30 @@ export function useCart() {
69
74
  })
70
75
  }
71
76
 
72
- function fetchCartItems(key, parentKey=null){
73
- //fetch cart items
74
- if (key === shopStore.state.cartRoot["key"]){ // initial
75
- shopStore.state.cartList = []
76
- }
77
- return Request.get(`${shopStore.state.shopApiUrl}/cart_list`,{dataObj:{
77
+ async function _collectCartItems(key, leafs, discounts){
78
+ let resp = await Request.get(`${shopStore.state.shopApiUrl}/cart_list`,{dataObj:{
78
79
  cart_key:key
79
- }}).then(async( resp) =>{
80
- let data = await resp.clone().json()
81
-
82
- let currentLeafs = []
83
- for (const item of data){
84
- if (item["skel_type"]==="leaf"){
85
- currentLeafs.push(item)
86
- }else{
87
- if(item.discount){
88
- shopStore.state.discounts[item.discount.dest.key] = item.discount
89
- }
90
- await fetchCartItems(item['key'], parentKey=true)
91
- }
92
- }
93
- if (parentKey){
94
- shopStore.state.cartList=shopStore.state.cartList.concat(currentLeafs)
95
- }else{
96
- shopStore.state.cartList=currentLeafs
80
+ }})
81
+ let data = await resp.clone().json()
82
+ for (const item of data){
83
+ if (item["skel_type"]==="leaf"){
84
+ leafs.push(item)
85
+ }else{
86
+ if(item.discount){
87
+ discounts[item.discount.dest.key] = item.discount
97
88
  }
89
+ await _collectCartItems(item['key'], leafs, discounts)
90
+ }
91
+ }
92
+ return resp
93
+ }
94
+
95
+ function fetchCartItems(key){
96
+ let leafs = []
97
+ let discounts = {}
98
+ return _collectCartItems(key, leafs, discounts).then((resp) => {
99
+ shopStore.state.cartList = leafs
100
+ Object.assign(shopStore.state.discounts, discounts)
98
101
 
99
102
  return resp
100
103
  })
@@ -128,7 +131,7 @@ export function useCart() {
128
131
  return Request.post(`${shopStore.state.shopApiUrl}/cart_update`, {
129
132
  dataObj: removeUndefinedValues(data)
130
133
  }).then(async (resp)=>{
131
- fetchCart()
134
+ await fetchCart()
132
135
  return resp
133
136
  })
134
137
  }
@@ -143,7 +146,7 @@ export function useCart() {
143
146
  quantity_mode:quantity_mode
144
147
  }}).then(async (resp)=>{
145
148
  shopStore.state.cartIsUpdating=false
146
- fetchCart()
149
+ await fetchCart()
147
150
  })
148
151
 
149
152
  }
@@ -154,46 +157,26 @@ export function useCart() {
154
157
  parent_cart_key:cart?cart:shopStore.state.cartRoot['key']
155
158
  }}).then(async (resp)=>{
156
159
  shopStore.state.cartIsUpdating=false
157
- fetchCart()
160
+ await fetchCart()
158
161
  })
159
162
  }
160
163
 
161
- function addDiscount(code) {
162
- return new Promise((resolve, reject) => {
163
- Request.securePost(`${shopStore.state.shopApiUrl}/discount_add`, {
164
- dataObj: {
165
- code: code,
166
- },
167
- })
168
- .then(async (resp) => {
169
- let data = await resp.json();
170
- fetchCart()
171
- console.log("discount debug", data);
172
- resolve()
164
+ async function addDiscount(code) {
165
+ let resp = await Request.securePost(`${shopStore.state.shopApiUrl}/discount_add`, {
166
+ dataObj: { code: code },
173
167
  })
174
- .catch((error) => {
175
- reject(error);
176
- });
177
- });
168
+ let data = await resp.json();
169
+ await fetchCart()
170
+ return data
178
171
  }
179
172
 
180
- function removeDiscount(key) {
181
- return new Promise((resolve, reject) => {
182
- Request.securePost(`${shopStore.state.shopApiUrl}/discount_remove`, {
183
- dataObj: {
184
- discount_key: key,
185
- },
186
- })
187
- .then(async (resp) => {
188
- let data = await resp.json();
189
- fetchCart()
190
- console.log("discount debug", data);
191
- resolve()
173
+ async function removeDiscount(key) {
174
+ let resp = await Request.securePost(`${shopStore.state.shopApiUrl}/discount_remove`, {
175
+ dataObj: { discount_key: key },
192
176
  })
193
- .catch((error) => {
194
- reject(error);
195
- });
196
- });
177
+ let data = await resp.json();
178
+ await fetchCart()
179
+ return data
197
180
  }
198
181
 
199
182
  const shippingAddressKey = computed(() => shopStore.state.cartRoot?.['shipping_address']?.['dest']?.['key']);