diff --git a/CHANGELOG.md b/CHANGELOG.md index 52be74608a..1298ff80b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.11.1] - 2020.02.05 + +### Added +- Add `ProductPrice` component with bundleOptions and customOptions prices - @gibkigonzo (#3978) +- Add lazy create cart token - @gibkigonzo (#3994) + +### Changed / Improved +- Set cache tag when loading a category - @haelbichalex (#3940) +- In development build `webpack.config.js` in theme folder is now called without the `default` key + +### Fixed +- Added Finnish translations - @mattiteraslahti and @alphpkeemik +- Updated Estonian translations to match 1.11 - @alphpkeemik +- CookieNotification CSR&SSR mismatch fixed - @Fifciu (#3922) +- The attribute filter in `attribute/list` was not filtering the already loaded attributes properly - @pkarw (#3964) +- Update `hasProductErrors` in Product component and support additional sku in custom options - @gibkigonzo (#3976) +- Fixed logic for generating ${lang}.json files in multi-store setup - @jpkempf +- Fixed logic for collecting valid locales in single-store, multi-lang setup - @jpkempf +- Make initial custom option value reactive - @gibkigonzo +- Fixed No image thumbnails leaded on 404 page - @andrzejewsky (#3955) +- Fixed Stock logic not working with manage_stock set to false - @andrzejewsky - (#3957) +- Support old price format in `ProductPrice` - @gibkigonzo (#3978) +- Fixed product bundle comparison condition - @gk-daniel (#4004) +- Add event callback for checkout load initial data - @gibkigonzo(#3985) +- Fixed `Processing order...` modal closing too early - @grimasod (#4021) +- Keep registered payment methods after `syncTotals` - @grimasod (#4020) +- Added status code to the cache content and use it in cache response - @resubaka (#4014) +- Fixed sku attribute is missing on compare page - @gibkigonzo (#4036) +- Fixed z-index for aside in compare list - @gibkigonzo (#4037) +- Disable checking max quantity when manage stock is set to false - @gibkigonzo (#4038) +- Add products quantity only when token is created - @gibkigonzo (#4017) +- Revert init filters in Vue app - add storeView to global/store and pass it to filters - @gibkigonzo (#3929) +- Fix v-model not working in BaseRadioButton - @lukeromanowicz (#4035) +- always keep filters values as array of object - @gibkigonzo (#4045) +- Fix ecosystem config to work with ts-node - @andrzejewsky (#3981) + ## [1.11.0] - 2019.12.20 ### Added diff --git a/config/default.json b/config/default.json index c11151aa3b..5399fe6cd8 100644 --- a/config/default.json +++ b/config/default.json @@ -270,7 +270,6 @@ "width": 150, "height": 150 }, - "bypassCartLoaderForAuthorizedUsers": true, "serverMergeByDefault": true, "serverSyncCanRemoveLocalItems": false, "serverSyncCanModifyLocalItems": false, diff --git a/core/app.ts b/core/app.ts index 0cde365b6e..4e82eeac59 100755 --- a/core/app.ts +++ b/core/app.ts @@ -77,16 +77,6 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu }) }) - // @todo remove this part when we'll get rid of global multistore mixin - if (isServer) { - Object.defineProperty(ssrContext, 'helpers', { - value: { - currentStoreView - }, - writable: true - }) - } - let vueOptions = { router: routerProxy, store, diff --git a/core/build/dev-server.js b/core/build/dev-server.js index 958c1b6dbf..b40d7554ff 100644 --- a/core/build/dev-server.js +++ b/core/build/dev-server.js @@ -2,14 +2,14 @@ const path = require('path') const webpack = require('webpack') const MFS = require('memory-fs') -let baseClientConfig = require('./webpack.client.config') -let baseServerConfig = require('./webpack.server.config') +let baseClientConfig = require('./webpack.client.config').default +let baseServerConfig = require('./webpack.server.config').default const themeRoot = require('./theme-path') const extendedConfig = require(path.join(themeRoot, '/webpack.config.js')) -let clientConfig = extendedConfig(baseClientConfig, { isClient: true, isDev: true }).default; -let serverConfig = extendedConfig(baseServerConfig, { isClient: false, isDev: true }).default; +let clientConfig = extendedConfig(baseClientConfig, { isClient: true, isDev: true }) +let serverConfig = extendedConfig(baseServerConfig, { isClient: false, isDev: true }) module.exports = function setupDevServer (app, cb) { let bundle diff --git a/core/filters/date.js b/core/filters/date.js index fee5d40065..1bcd01b9c2 100644 --- a/core/filters/date.js +++ b/core/filters/date.js @@ -12,9 +12,10 @@ once('__VUE_EXTEND_DAYJS_LOCALIZED_FORMAT__', () => { * @param {String} date * @param {String} format */ -export function date (date, format) { - const displayFormat = format || currentStoreView().i18n.dateFormat - let storeLocale = currentStoreView().i18n.defaultLocale.toLocaleLowerCase() +export function date (date, format, storeView) { + const _storeView = storeView || currentStoreView() + const displayFormat = format || _storeView.i18n.dateFormat + let storeLocale = _storeView.i18n.defaultLocale.toLocaleLowerCase() const separatorIndex = storeLocale.indexOf('-') const languageCode = (separatorIndex > -1) ? storeLocale.substr(0, separatorIndex) : storeLocale diff --git a/core/filters/price.js b/core/filters/price.js index c69462fb41..a7e6d858a4 100644 --- a/core/filters/price.js +++ b/core/filters/price.js @@ -13,15 +13,15 @@ const applyCurrencySign = (formattedPrice, { currencySign, priceFormat }) => { * Converts number to price string * @param {Number} value */ -export function price (value) { +export function price (value, storeView) { if (isNaN(value)) { return value; } - const storeView = currentStoreView(); - if (!storeView.i18n) { + const _storeView = storeView || currentStoreView(); + if (!_storeView.i18n) { return value; } - const { defaultLocale, currencySign, priceFormat } = storeView.i18n + const { defaultLocale, currencySign, priceFormat } = _storeView.i18n const formattedValue = formatValue(value, defaultLocale); const valueWithSign = applyCurrencySign(formattedValue, { currencySign, priceFormat }) diff --git a/core/i18n/helpers.ts b/core/i18n/helpers.ts index d8e3d17ad9..f816174c5c 100644 --- a/core/i18n/helpers.ts +++ b/core/i18n/helpers.ts @@ -6,7 +6,7 @@ export const currentBuildLocales = (): string[] => { ? Object.values(config.storeViews) .map((store: any) => store && typeof store === 'object' && store.i18n && store.i18n.defaultLocale) .filter(Boolean) - : [] + : config.i18n.availableLocale const locales = multistoreLocales.includes(defaultLocale) ? multistoreLocales : [defaultLocale, ...multistoreLocales] diff --git a/core/i18n/package.json b/core/i18n/package.json index 075a23c421..a853811690 100644 --- a/core/i18n/package.json +++ b/core/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@vue-storefront/i18n", - "version": "1.11.0", + "version": "1.11.1", "description": "Vue Storefront i18n", "license": "MIT", "main": "index.ts", diff --git a/core/i18n/resource/i18n/en-US.csv b/core/i18n/resource/i18n/en-US.csv index ebee752413..ae3ff06b7b 100644 --- a/core/i18n/resource/i18n/en-US.csv +++ b/core/i18n/resource/i18n/en-US.csv @@ -30,7 +30,7 @@ "No such configuration for the product. Please do choose another combination of attributes.","No such configuration for the product. Please do choose another combination of attributes." "OK","OK" "Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Or if you will stay on "Order confirmation" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Or if you will stay on "Order confirmation" page, the order will be placed automatically without confirmation, once the internet connection will be back." +"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back." "Out of stock!","Out of stock!" "Out of the stock!","Out of the stock!" "Payment Information","Payment Information" diff --git a/core/i18n/resource/i18n/et-EE.csv b/core/i18n/resource/i18n/et-EE.csv index 66f2749919..6a051e889c 100644 --- a/core/i18n/resource/i18n/et-EE.csv +++ b/core/i18n/resource/i18n/et-EE.csv @@ -1,77 +1,90 @@ -"Registering the account ...","Konto loomine…" +" is out of stock!","ei ole laos!" +"404 Page Not Found","404 Lehekülge ei leitud" +"Account data has successfully been updated","Konto andmed uuendatud" +"Add review","Lisa kommentaar" +"Adding a review ...","Lisan kommentaari ..." +"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Ostuvormistamisel sisestatud aadress sisaldab vigu! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." +"Allow notification about the order","Luba tellimusge seotud teadete edastamine" +"Are you sure you would like to remove this item from the shopping cart?","Olete kindel, et soovite antud toote ostukorvist eemaldada?" +"Compare Products","Võrdle tooteid" +"Compare products","Võrdle tooteid" +"Confirm your order","Kinnitage oma tellimus" +"Error refreshing user token. User is not authorized to access the resource","Kasutaja tokeni uuendamisel esineb probleeme. Kasutajal puuduvad õigused antud lehele sisenemiseks" +"Error with response - bad content-type!","Viga sisu laadimisel" +"Error: Error while adding products","Viga toote lisamisel." +"Extension developers would like to thank you for placing an order!","Mooduli loojad tänavad tellimuse tegemise eest!" +"Field is required",Kohustuslik +"Field is required.",Kohustuslik. +"Grand total",Kokku +"Home Page",Esileht +"In stock!",Laos +"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Tokeni värskendamisel esines viga. Palun tühjendage vahemälu ja uuendage lehekülge." +"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Viga! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." +"Must be greater than 0","Peab olema suurem, kui 0" +"My Account","Minu konto" +"Newsletter preferences have successfully been updated","Uudiskirjaga liitumine uuendatud" +"No available product variants","Toote valikvariandid puuduvad" "No products synchronized for this category. Please come back while online!","Kategooria on tühi." -"Shopping cart is empty. Please add some products before entering Checkout","Otsukorv on tühi." -"Out of stock!","Laost otsas!" -" is out of the stock!"," on laost otsas!" -"Some of the ordered products are not available!","Mõned tellitud tooted ei ole kahjuks enam saadaval." -"Please wait ...","Palun oota…" -"Stock check in progress, please wait while available stock quantities are checked","Palun oota, kontrollime laoseise." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Interneti ühendus puudub. Saad sellegi poolest tellimuse luua. Kontrollime interneti ühenduse taastudes tellitud toodete laoseisu üle. Anname märku, kui mõni tellitud toodetest vahepeal otsa on saanud. " "No such configuration for the product. Please do choose another combination of attributes.","Sellise kombinatsiooniga toodet ei saa tellida." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Antud toodet ostetakse väga palju ja me ei ole laoseisu osas kindlad. Toode on ostukorvi lisatud ja Teie jaoks broneeritud." -"This feature is not implemented yet! Please take a look at https://github.com/DivanteLtd/vue-storefront/issues for our Roadmap!","Sellist funktsionaalsust ei ole veel lisatud. Saad meie arendusplaanidega tutvuda https://github.com/DivanteLtd/vue-storefront/issues." -"The product is out of stock and cannot be added to the cart!","Toode on kahjuks laost otsa saanud." +"Only {maxQuantity} products of this type are available!","Ainult {maxQuantity} seda tüüpi toodet on saadaval!" +"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Või kui sa jäää ""Tellimuse kinnitus"" lehele, tellimus esitatakse internetiühenduse taastudes tellimus automaatselt." +"Out of stock!","Laost otsas!" +"Out of the stock!","Laost otsas" +"Payment Information","Makse informatsioon" +"Please configure product bundle options and fix the validation errors","Toode on valikutega. Palun valige sobivad valikud" +"Please configure product custom options and fix the validation errors","Palun valige sobiv toode" +"Please confirm order you placed when you was offline","Palun kinnitage oma tellimuse, mille ilma interneti ühenduseta varasemalt tegite" +"Please fix the validation errors","Palun parandage valideerimise vead" +"Please select the field which You like to sort by","Palun valige sorteerimise viis" +"Please wait ...","Palun oota ..." +"Proceed to checkout","Vormista ost" +"Processing order...","Tellimuse loomine ..." "Product has been added to the cart!","Toode lisati ostukorvi." +"Product price is unknown, product cannot be added to the cart!","Tootel puudub hind. Toodet ei saa ostukorvi lisada." "Product quantity has been updated!","Toote laoseis on uuendatud." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Viga! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Ostuvormistamisel sisestatud aadress sisaldab vigu! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." "Product {productName} has been added to the compare!","{productName} lisati võrdlusesse." -"Product {productName} has been removed from compare!","{productName} võrdlusest eemaldatud." "Product {productName} has been added to wishlist!","{productName} Lisati soovikorvi." -"Product {productName} has been removed from wishlit!","{productName} eemaldati soovikorvist" -"Account data has successfully been updated","Konto andmed uuendatud" -"Newsletter preferences have successfully been updated","Uudiskirjaga liitumine uuendatud" +"Product {productName} has been removed from compare!","{productName} võrdlusest eemaldatud." +"Product {productName} has been removed from wishlist!","{productName} on sooviloendist eemaldatud." +"Quantity available offline","Kogus on saadaval internetiühenduseta" +"Quantity available","Kogus on saadaval" +"Quantity must be above 0","Laokogus peab olema suurem, kui 0" +"Quantity must be below {quantity}","Laokogus peab olema alla {quantity}" +"Quantity must be positive integer","Laokogus peab olema positiivne täisarv" +"Registering the account ...","Konto loomine..." "Reset password feature does not work while offline!","Parooli ei saa kahjuks ilma interneti ühenduseta muuta." -"You are logged in!","Olete sisse logitud." -"Please fix the validation errors","Palun parandage valideerimise vead" -"Product price is unknown, product cannot be added to the cart!","Tootel puudub hind. Toodet ei saa ostukorvi lisada." -"My Account","Minu konto" -"Type what you are looking for...",Otsi… -"Home Page",Esileht -Checkout,Ostuvormistamine +"Select 0","Vali 0" +"Select 1","Vali 1" +"Shopping cart is empty. Please add some products before entering Checkout","Otsukorv on tühi." +"Some of the ordered products are not available!","Mõned tellitud tooted ei ole kahjuks enam saadaval." +"Stock check in progress, please wait while available stock quantities are checked","Palun oota, kontrollime laoseise." "Subtotal incl. tax","Kokku (sisaldab käibemaksu)" -"Grand total",Kokku -"Field is required",Kohustuslik -"Field is required.",Kohustuslik. -"You're logged out","Olete välja logitud" -"Compare Products","Võrdle tooteid" -"404 Page Not Found","404 Lehekülge ei leitud" -"Error with response - bad content-type!","Viga sisu laadimisel" +"The product is out of stock and cannot be added to the cart!","Toode on kahjuks laost otsa saanud." +"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Antud toode, kategooria või sisuleht ei ole kahjuks ilma internetiühenduseta saadaval. Suuname ümber esilehele." +"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Antud toodet ostetakse väga palju ja me ei ole laoseisu osas kindlad. Toode on ostukorvi lisatud ja Teie jaoks broneeritud." +"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Interneti ühendus puudub. Saad sellegi poolest tellimuse luua. Kontrollime interneti ühenduse taastudes tellitud toodete laoseisu üle. Anname märku, kui mõni tellitud toodetest vahepeal otsa on saanud. " +"This feature is not implemented yet! Please take a look at https://github.com/DivanteLtd/vue-storefront/issues for our Roadmap!","Sellist funktsionaalsust ei ole veel lisatud. Saad meie arendusplaanidega tutvuda https://github.com/DivanteLtd/vue-storefront/issues." +"Type what you are looking for...","Otsi ..." +"Unexpected authorization error. Check your Network conection.","Internetiühenduse viga. Palun kontrollige oma internetiühendust." "Unhandled error, wrong response format!","Vale päringu formaat" -"not authorized","Ligipääs puudub" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Tokeni värskendamisel esines viga. Palun tühjendage vahemälu ja uuendage lehekülge." -"Proceed to checkout","Vormista ost" -OK,Ok -"Out of the stock!","Laost otsas" -"In stock!",Laos -"Please configure product custom options and fix the validation errors","Palun valige sobiv toode" -"Error refreshing user token. User is not authorized to access the resource","Kasutaja tokeni uuendamisel esineb probleeme. Kasutajal puuduvad õigused antud lehele sisenemiseks" -"Must be greater than 0","Peab olema suurem, kui 0" -"Please select the field which You like to sort by","Palun valige sorteerimise viis" -"No available product variants","Toote valikvariandid puuduvad" -email,E-mail -password,Parool -"Confirm your order","Kinnitage oma tellimus" -"Please confirm order you placed when you was offline","Palun kinnitage oma tellimuse, mille ilma interneti ühenduseta varasemalt tegite" -"Payment Information","Makse informatsioon" +"Vue Storefront","Vue Storefront" +"You are going to pay for this order upon delivery.","Maksmine toimub tellimuse kättesaamisel." +"You are logged in!","Olete sisse logitud." "You are to pay for this order upon delivery.","Saate makse teostada tellimuse kätte saamisel." -"Allow notification about the order","Luba tellimusge seotud teadete edastamine" -"Extension developers would like to thank you for placing an order!","Mooduli loojad tänavad tellimuse tegemise eest!" -"most you may purchase","maksimum ostu kogus" +"You need to be logged in to see this page","Palun logige lehe nägemisesse sisse" +"You submitted your review for moderation.","Tagasiside on edastatud üle vaatamiseks." +"You're logged out","Olete välja logitud" "have as many","osta kuni" -"Compare products","Võrdle tooteid" -Reviews,Kommentaarid +"most you may purchase","maksimum ostu kogus" +"not authorized","Ligipääs puudub" +"to account",kontole +Checkout,Ostuvormistamine +Columns,Tulbad +OK,Ok Review,Kommentaar -"Add review","Lisa kommentaar" +Reviews,Kommentaarid Summary,Kokkuvõte +Thumbnail,Pisipilt +email,E-post login,"logi sisse" -"to account",kontole -"Are you sure you would like to remove this item from the shopping cart?","Olete kindel, et soovite antud toote ostukorvist eemaldada?" -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Antud toode, kategooria või sisuleht ei ole kahjuks ilma internetiühenduseta saadaval. Suuname ümber esilehele." -"Please configure product bundle options and fix the validation errors","Toode on valikutega. Palun valige sobivad valikud" -"Processing order...","Tellimuse loomine…" -"You need to be logged in to see this page","Palun logige lehe nägemisesse sisse" -"Quantity must be above 0","Laokogus peab olema suurem, kui 0" -"Error: Error while adding products","Viga toote lisamisel." -"Unexpected authorization error. Check your Network conection.","Internetiühenduse viga. Palun kontrollige oma internetiühendust." -Columns,Tulbad +password,Parool diff --git a/core/i18n/resource/i18n/fi-FI.csv b/core/i18n/resource/i18n/fi-FI.csv new file mode 100644 index 0000000000..40cd4fe51a --- /dev/null +++ b/core/i18n/resource/i18n/fi-FI.csv @@ -0,0 +1,90 @@ +" is out of stock!"," on loppu varastosta" +"404 Page Not Found","404 Sivua ei löydy" +"Account data has successfully been updated","Tilin tiedot on päivitetty" +"Add review","Lisää arvostelu" +"Adding a review ...","Lisätään arvostelua…" +"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Kassalla annettu osoite on virheellinen. Tarkista, että kaikki pakolliset kentät on täytetty. Tilauksesi on peruttu. Ongelman jatkuessa ota yhteyttä sähköpostilla osoitteeseen {email}." +"Allow notification about the order","Vastaanota ilmoitus tilauksesta" +"Are you sure you would like to remove this item from the shopping cart?","Haluatko varmasti poistaa tuotteen ostoskorista?" +"Compare Products","Vertaile tuotteita" +"Compare products","Vertaile tuotteita" +"Confirm your order","Vahvista tilaus" +"Error refreshing user token. User is not authorized to access the resource","Käyttöoikeus puuttuu." +"Error with response - bad content-type!","Virhe sivun latauksessa - väärä sisältötyyppi." +"Error: Error while adding products","Virhe tuotteiden lisäyksessä." +"Extension developers would like to thank you for placing an order!","Laajennuksen kehittäjät kiittävät tilauksesta!" +"Field is required","Vaadittu tieto" +"Field is required.","Vaadittu tieto" +"Grand total",Yhteensä +"Home Page",Etusivu +"In stock!",Varastossa +"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Virhe käyttöoikeuksien tarkistuksessa. Tyhjennä selaimen muisti ja lataa sivu uudelleen." +"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Tarkista, että kaikki pakolliset kentät on täytetty. Ongelman jatkuessa ota yhteyttä sähköpostilla osoitteeseen {email}." +"Must be greater than 0","Oltava suurempi kuin 0" +"My Account","Oma tili" +"Newsletter preferences have successfully been updated","Uutiskirjeen asetukset on päivitetty" +"No available product variants","Tuotevaihtoehtoja ei ole saatavilla." +"No products synchronized for this category. Please come back while online!","Kategoriassa ei ole tuotteita. Yritä uudelleen, kun verkkoyhteys on palautunut." +"No such configuration for the product. Please do choose another combination of attributes.","Valintaa vastaavaa tuotetta ei löydy." +"Only {maxQuantity} products of this type are available!","Vain {maxQuantity} saatavilla" +"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Tai, jos pysyt tilausvahvistussivulla, tilaus tehdään automaattisesti ilman vahvistusta yhteyden palautuessa." +"Out of stock!","Loppu varastosta" +"Out of the stock!","Loppu varastosta" +"Payment Information","Maksun tiedot" +"Please configure product bundle options and fix the validation errors","Tee valinnat" +"Please configure product custom options and fix the validation errors","Tee valinnat" +"Please confirm order you placed when you was offline","Vahvista yhteydettömässä tilassa tekemäsi tilaus" +"Please fix the validation errors","Korjaa virheelliset tiedot." +"Please select the field which You like to sort by","Valitse tieto, jonka mukaan haluat järjestää" +"Please wait ...","Odota hetki ..." +"Proceed to checkout",Kassalle +"Processing order...","Käsitellään tilausta..." +"Product has been added to the cart!","Tuote on lisätty ostoskoriin." +"Product price is unknown, product cannot be added to the cart!","Tuotteen hinta ei ole saatavilla ja sitä ei voida lisätä ostoskoriin." +"Product quantity has been updated!","Tuotteen varastosaldo on päivitetty." +"Product {productName} has been added to the compare!","{productName} on lisätty tuotevertailuun." +"Product {productName} has been added to wishlist!","{productName} on lisätty toivelistaan." +"Product {productName} has been removed from compare!","{productName} on poistettu tuotevertailusta." +"Product {productName} has been removed from wishlist!","{productName} on poistettu toivelistasta" +"Quantity available offline","Määrä saatavilla yhteydettömässä" +"Quantity available","Määrä saatavilla" +"Quantity must be above 0","Määrän pitää olla suurempi kuin 0" +"Quantity must be below {quantity}","Määrän pitää olla alle {quantity}" +"Quantity must be positive integer","Määrän pitää olla kokonaisluku" +"Registering the account ...","Luodaan tiliä ..." +"Reset password feature does not work while offline!","Salasanan uusiminen ei ole käytössä yhteydettömässä tilassa." +"Select 0","Valitse 0" +"Select 1","Valitse 1" +"Shopping cart is empty. Please add some products before entering Checkout","Ostoskori on tyhjä." +"Some of the ordered products are not available!","Jotkut tilatuista tuotteista eivät ole saatavilla." +"Stock check in progress, please wait while available stock quantities are checked","Odota hetki, varastosaldoja tarkistetaan" +"Subtotal incl. tax","Välisumma (sis. ALV)" +"The product is out of stock and cannot be added to the cart!","Tuotetta ei ole varastossa." +"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Sivu ei ole saatavilla yhteydettömässä tilassa. Ohjataan etusivulle." +"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Tuotteen saatavuus on epävarma. Tuote on lisätty ostoskoriin ennakkovarauksena." +"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Verkkoyhteys ei ole saatavilla. Voit silti tehdä tilauksen. Saat myöhemmin ilmoituksen, mikäli jokin tilaamistasi tuotteista ei ole saatavilla." +"This feature is not implemented yet! Please take a look at https://github.com/DivanteLtd/vue-storefront/issues for our Roadmap!","Tätä ominaisuutta ei ole vielä toteutettu. Järjestelmän roadmap löytyy osoitteesta https://github.com/DivanteLtd/vue-storefront/issues." +"Type what you are looking for...",Hae +"Unexpected authorization error. Check your Network conection.","Odottamaton virhe oikeuksien tarkistamisessa. Tarkista verkkoyhteys." +"Unhandled error, wrong response format!","Odottamaton virhe, väärä vastauksen formaatti." +"Vue Storefront","Vue Storefront" +"You are going to pay for this order upon delivery.","Maksat tilauksen toimituksen yhteydessä" +"You are logged in!","Olet kirjautuneena." +"You are to pay for this order upon delivery.","Maksu peritään toimituksen yhteydessä" +"You need to be logged in to see this page","Kirjaudu sisään nähdäksesi tämän sivun." +"You submitted your review for moderation.","Arvostelu on lähetetty tarkastettavaksi" +"You're logged out","Et ole kirjautuneena" +"have as many","määrä on" +"most you may purchase",Maksimitilausmäärä +"not authorized","ei sallittu" +"to account",tilille +Checkout,Kassa +Columns,Sarakkeet +OK,Ok +Review,Arvostelu +Reviews,Arvostelut +Summary,Tiivistelmä +Thumbnail,Pienoiskuva +email,sähköposti +login,"kirjaudu sisään" +password,salasana diff --git a/core/i18n/resource/i18n/it-IT.csv b/core/i18n/resource/i18n/it-IT.csv index 0eaa53d823..436144f0d0 100644 --- a/core/i18n/resource/i18n/it-IT.csv +++ b/core/i18n/resource/i18n/it-IT.csv @@ -30,7 +30,7 @@ "No such configuration for the product. Please do choose another combination of attributes.","Configurazione del prodotto inesistente. Scegli un'altra combinazione di attributi" "OK","OK" "Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Or if you will stay on "Order confirmation" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Oppure se rimarrai nella pagina di "Conferma ordine", l'ordine verrà automaticamente evaso senza conferma, una volta che la connessione sarà ripristinata." +"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Oppure se rimarrai nella pagina di ""Conferma ordine"", l'ordine verrà automaticamente evaso senza conferma, una volta che la connessione sarà ripristinata." "Out of stock!","Non disponibile" "Out of the stock!","Non disponibile" "Payment Information","Informazioni di pagamento" diff --git a/core/i18n/scripts/translation.preprocessor.js b/core/i18n/scripts/translation.preprocessor.js index 7164d39ed6..985944c4c3 100644 --- a/core/i18n/scripts/translation.preprocessor.js +++ b/core/i18n/scripts/translation.preprocessor.js @@ -55,7 +55,7 @@ module.exports = function (csvDirectories, config = null) { fs.writeFileSync(path.join(__dirname, '../resource/i18n', `multistoreLanguages.json`), JSON.stringify(bundledLanguages)) } else { currentLocales.forEach((language) => { - if (language !== fallbackLocale) return // it's already loaded + if (language === fallbackLocale) return // it's already loaded const filePath = path.join(__dirname, '../resource/i18n', `${language}.json`) console.debug(`Writing JSON file: ${language}.json`) fs.writeFileSync(filePath, JSON.stringify(messages[language])) diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts index 57e0e727c7..354ead2751 100644 --- a/core/lib/multistore.ts +++ b/core/lib/multistore.ts @@ -11,6 +11,9 @@ import { coreHooksExecutors } from '@vue-storefront/core/hooks' import { StorageManager } from '@vue-storefront/core/lib/storage-manager' import { LocalizedRoute, StoreView } from './types' import storeCodeFromRoute from './storeCodeFromRoute' +import cloneDeep from 'lodash-es/cloneDeep' +import get from 'lodash-es/get' +import { isServer } from '@vue-storefront/core/helpers' function getExtendedStoreviewConfig (storeView: StoreView): StoreView { if (storeView.extend) { @@ -30,20 +33,28 @@ function getExtendedStoreviewConfig (storeView: StoreView): StoreView { return storeView } +/** + * Returns base storeView object that can be created without storeCode + */ +function buildBaseStoreView (): StoreView { + return cloneDeep({ + tax: config.tax, + i18n: config.i18n, + elasticsearch: config.elasticsearch, + storeCode: null, + storeId: config.defaultStoreCode && config.defaultStoreCode !== '' ? config.storeViews[config.defaultStoreCode].storeId : 1, + seo: config.seo + }) +} + export function currentStoreView (): StoreView { - // TODO: Change to getter all along our code - return rootStore.state.storeView + const serverStoreView = get(global, 'process.storeView', undefined) + const clientStoreView = get(rootStore, 'state.storeView', undefined) + return (isServer ? serverStoreView : clientStoreView) || buildBaseStoreView() } export async function prepareStoreView (storeCode: string): Promise { - let storeView: StoreView = { // current, default store - tax: Object.assign({}, config.tax), - i18n: Object.assign({}, config.i18n), - elasticsearch: Object.assign({}, config.elasticsearch), - storeCode: null, - storeId: config.defaultStoreCode && config.defaultStoreCode !== '' ? config.storeViews[config.defaultStoreCode].storeId : 1, - seo: Object.assign({}, config.seo) - } + let storeView: StoreView = buildBaseStoreView() // current, default store if (config.storeViews.multistore === true) { storeView.storeCode = storeCode || config.defaultStoreCode || '' @@ -61,6 +72,11 @@ export async function prepareStoreView (storeCode: string): Promise { if (storeViewHasChanged) { storeView = coreHooksExecutors.beforeStoreViewChanged(storeView) rootStore.state.storeView = storeView + + if (global && isServer) { + (global.process as any).storeView = storeView + } + await loadLanguageAsync(storeView.i18n.defaultLocale) } if (storeViewHasChanged || StorageManager.currentStoreCode !== storeCode) { diff --git a/core/mixins/multistore.js b/core/mixins/multistore.js index 4f8135e186..8964566055 100644 --- a/core/mixins/multistore.js +++ b/core/mixins/multistore.js @@ -10,13 +10,7 @@ export const multistore = { * @param {Int} height */ localizedRoute (routeObj) { - let storeView - - if (isServer) { - storeView = this.$ssrContext.helpers.currentStoreView() - } else { - storeView = currentStoreView() - } + const storeView = currentStoreView() return localizedRouteHelper(routeObj, storeView.storeCode) }, @@ -27,13 +21,7 @@ export const multistore = { * @param {Int} height */ localizedDispatcherRoute (routeObj) { - let storeView - - if (isServer) { - storeView = this.$ssrContext.helpers.currentStoreView() - } else { - storeView = currentStoreView() - } + const storeView = currentStoreView() return localizedDispatcherRouteHelper(routeObj, storeView.storeCode) } diff --git a/core/modules/cart/helpers/createCartItemForUpdate.ts b/core/modules/cart/helpers/createCartItemForUpdate.ts index 8380fa896c..6c0224edae 100644 --- a/core/modules/cart/helpers/createCartItemForUpdate.ts +++ b/core/modules/cart/helpers/createCartItemForUpdate.ts @@ -1,11 +1,11 @@ import config from 'config' import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'; -const createCartItemForUpdate = (clientItem: CartItem, serverItem: any, updateIds: boolean = false): CartItem => { +const createCartItemForUpdate = (clientItem: CartItem, serverItem: any, updateIds: boolean = false, mergeQty: boolean = false): CartItem => { const sku = clientItem.parentSku && config.cart.setConfigurableProductOptions ? clientItem.parentSku : clientItem.sku const cartItem = { sku, - qty: clientItem.qty, + qty: mergeQty ? (clientItem.qty + serverItem.qty) : clientItem.qty, product_option: clientItem.product_option } as any as CartItem diff --git a/core/modules/cart/helpers/productsEquals.ts b/core/modules/cart/helpers/productsEquals.ts index 92e8870fc9..69156aee04 100644 --- a/core/modules/cart/helpers/productsEquals.ts +++ b/core/modules/cart/helpers/productsEquals.ts @@ -18,6 +18,7 @@ const getServerItemId = (product: CartItem): string | number => const isServerIdsEquals = (product1: CartItem, product2: CartItem): boolean => { const product1ItemId = getServerItemId(product1) const product2ItemId = getServerItemId(product2) + const areItemIdsDefined = product1ItemId !== undefined && product2ItemId !== undefined return areItemIdsDefined && product1ItemId === product2ItemId @@ -35,10 +36,12 @@ const productsEquals = (product1: CartItem, product2: CartItem): boolean => { const typeProduct2 = getProductType(product2) if (typeProduct1 === 'bundle' || typeProduct2 === 'bundle') { - return isServerIdsEquals(product1, product2) || isChecksumEquals(product1, product2) + if (isServerIdsEquals(product1, product2) || isChecksumEquals(product1, product2)) { + return true + } } - return String(product1.sku) === String(product2.sku) + return isServerIdsEquals(product1, product2) || String(product1.sku) === String(product2.sku) } export default productsEquals diff --git a/core/modules/cart/store/actions/connectActions.ts b/core/modules/cart/store/actions/connectActions.ts index d6ff2c9072..37b423a630 100644 --- a/core/modules/cart/store/actions/connectActions.ts +++ b/core/modules/cart/store/actions/connectActions.ts @@ -9,28 +9,20 @@ const connectActions = { toggleMicrocart ({ commit }) { commit(types.CART_TOGGLE_MICROCART) }, - async clear ({ commit, dispatch, getters }, options = { recreateAndSyncCart: true }) { + async clear ({ commit, dispatch, getters }) { await commit(types.CART_LOAD_CART, []) - if (options.recreateAndSyncCart && getters.isCartSyncEnabled) { - await commit(types.CART_LOAD_CART_SERVER_TOKEN, null) - await commit(types.CART_SET_ITEMS_HASH, null) - await dispatch('connect', { guestCart: !config.orders.directBackendSync }) // guest cart when not using directBackendSync because when the order hasn't been passed to Magento yet it will repopulate your cart - } + await commit(types.CART_LOAD_CART_SERVER_TOKEN, null) + await commit(types.CART_SET_ITEMS_HASH, null) }, async disconnect ({ commit }) { commit(types.CART_LOAD_CART_SERVER_TOKEN, null) }, async authorize ({ dispatch, getters }) { - const coupon = getters.getCoupon.code - const lastCartBypassTs = await StorageManager.get('user').getItem('last-cart-bypass-ts') - const timeBypassCart = config.orders.directBackendSync || (Date.now() - lastCartBypassTs) >= (1000 * 60 * 24) - - if (!config.cart.bypassCartLoaderForAuthorizedUsers || timeBypassCart) { - await dispatch('connect', { guestCart: false }) + await dispatch('connect', { guestCart: false }) - if (!getters.getCoupon) { - await dispatch('applyCoupon', coupon) - } + const coupon = getters.getCoupon.code + if (!getters.getCoupon) { + await dispatch('applyCoupon', coupon) } }, async connect ({ getters, dispatch, commit }, { guestCart = false, forceClientState = false }) { @@ -41,7 +33,7 @@ const connectActions = { Logger.info('Server cart token created.', 'cart', result)() commit(types.CART_LOAD_CART_SERVER_TOKEN, result) - return dispatch('sync', { forceClientState, dryRun: !config.cart.serverMergeByDefault }) + return dispatch('sync', { forceClientState, dryRun: !config.cart.serverMergeByDefault, mergeQty: true }) } if (resultCode === 401 && getters.bypassCounter < config.queues.maxCartBypassAttempts) { @@ -53,6 +45,17 @@ const connectActions = { Logger.warn('Cart sync is disabled by the config', 'cart')() return createDiffLog() + }, + /** + * Create cart token when there are products in cart and we don't have token already + */ + async create ({ dispatch, getters }) { + const storedItems = getters['getCartItems'] || [] + const cartToken = getters['getCartToken'] + if (storedItems.length && !cartToken) { + Logger.info('Creating server cart token', 'cart')() + await dispatch('connect', { guestCart: false }) + } } } diff --git a/core/modules/cart/store/actions/itemActions.ts b/core/modules/cart/store/actions/itemActions.ts index 3cc36d2f08..ec8e5399cc 100644 --- a/core/modules/cart/store/actions/itemActions.ts +++ b/core/modules/cart/store/actions/itemActions.ts @@ -80,6 +80,7 @@ const itemActions = { productIndex++ } } + await dispatch('create') if (getters.isCartSyncEnabled && getters.isCartConnected && !forceServerSilence) { return dispatch('sync', { forceClientState: true }) } diff --git a/core/modules/cart/store/actions/mergeActions.ts b/core/modules/cart/store/actions/mergeActions.ts index 448d87f0a2..0b024b500e 100644 --- a/core/modules/cart/store/actions/mergeActions.ts +++ b/core/modules/cart/store/actions/mergeActions.ts @@ -30,9 +30,9 @@ const mergeActions = { await dispatch('updateItem', { product }) EventBus.$emit('cart-after-itemchanged', { item: cartItem }) }, - async updateServerItem ({ getters, rootGetters, commit, dispatch }, { clientItem, serverItem, updateIds }) { + async updateServerItem ({ getters, rootGetters, commit, dispatch }, { clientItem, serverItem, updateIds, mergeQty }) { const diffLog = createDiffLog() - const cartItem = createCartItemForUpdate(clientItem, serverItem, updateIds) + const cartItem = createCartItemForUpdate(clientItem, serverItem, updateIds, mergeQty) const event = await CartService.updateItem(getters.getCartToken, cartItem) const wasUpdatedSuccessfully = event.resultCode === 200 Logger.debug('Cart item server sync' + event, 'cart')() @@ -65,7 +65,7 @@ const mergeActions = { return diffLog }, - async synchronizeServerItem ({ dispatch }, { serverItem, clientItem, forceClientState, dryRun }) { + async synchronizeServerItem ({ dispatch }, { serverItem, clientItem, forceClientState, dryRun, mergeQty }) { const diffLog = createDiffLog() if (!serverItem) { @@ -82,12 +82,12 @@ const mergeActions = { return diffLog } - if (serverItem.qty !== clientItem.qty) { + if (serverItem.qty !== clientItem.qty || mergeQty) { Logger.log('Wrong qty for ' + clientItem.sku, clientItem.qty, serverItem.qty)() diffLog.pushServerParty({ sku: clientItem.sku, status: 'wrong-qty', 'client-qty': clientItem.qty, 'server-qty': serverItem.qty }) if (dryRun) return diffLog if (forceClientState || !config.cart.serverSyncCanModifyLocalItems) { - const updateServerItemDiffLog = await dispatch('updateServerItem', { clientItem, serverItem, updateIds: true }) + const updateServerItemDiffLog = await dispatch('updateServerItem', { clientItem, serverItem, updateIds: true, mergeQty }) return diffLog.merge(updateServerItemDiffLog) } @@ -97,9 +97,9 @@ const mergeActions = { return diffLog }, - async mergeClientItem ({ dispatch }, { clientItem, serverItems, forceClientState, dryRun }) { + async mergeClientItem ({ dispatch }, { clientItem, serverItems, forceClientState, dryRun, mergeQty }) { const serverItem = serverItems.find(itm => productsEquals(itm, clientItem)) - const diffLog = await dispatch('synchronizeServerItem', { serverItem, clientItem, forceClientState, dryRun }) + const diffLog = await dispatch('synchronizeServerItem', { serverItem, clientItem, forceClientState, dryRun, mergeQty }) if (!diffLog.isEmpty()) return diffLog @@ -118,12 +118,12 @@ const mergeActions = { return diffLog }, - async mergeClientItems ({ dispatch }, { clientItems, serverItems, forceClientState, dryRun }) { + async mergeClientItems ({ dispatch }, { clientItems, serverItems, forceClientState, dryRun, mergeQty }) { const diffLog = createDiffLog() for (const clientItem of clientItems) { try { - const mergeClientItemDiffLog = await dispatch('mergeClientItem', { clientItem, serverItems, forceClientState, dryRun }) + const mergeClientItemDiffLog = await dispatch('mergeClientItem', { clientItem, serverItems, forceClientState, dryRun, mergeQty }) diffLog.merge(mergeClientItemDiffLog) } catch (e) { Logger.debug('Problem syncing clientItem', 'cart', clientItem)() @@ -186,7 +186,7 @@ const mergeActions = { commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) }, - async merge ({ getters, dispatch }, { serverItems, clientItems, dryRun = false, forceClientState = false }) { + async merge ({ getters, dispatch }, { serverItems, clientItems, dryRun = false, forceClientState = false, mergeQty = false }) { const hookResult = cartHooksExecutors.beforeSync({ clientItems, serverItems }) const diffLog = createDiffLog() @@ -194,7 +194,8 @@ const mergeActions = { clientItems: hookResult.clientItems, serverItems: hookResult.serverItems, forceClientState, - dryRun + dryRun, + mergeQty } const mergeClientItemsDiffLog = await dispatch('mergeClientItems', mergeParameters) const mergeServerItemsDiffLog = await dispatch('mergeServerItems', mergeParameters) diff --git a/core/modules/cart/store/actions/synchronizeActions.ts b/core/modules/cart/store/actions/synchronizeActions.ts index fc1fb9210d..066ea70eb2 100644 --- a/core/modules/cart/store/actions/synchronizeActions.ts +++ b/core/modules/cart/store/actions/synchronizeActions.ts @@ -36,17 +36,15 @@ const synchronizeActions = { Logger.info('Cart token received from cache.', 'cache', token)() Logger.info('Syncing cart with the server.', 'cart')() dispatch('sync', { forceClientState, dryRun: !serverMergeByDefault }) - } else { - Logger.info('Creating server cart token', 'cart')() - await dispatch('connect', { guestCart: false }) } + await dispatch('create') }, /** @deprecated backward compatibility only */ async serverPull ({ dispatch }, { forceClientState = false, dryRun = false }) { Logger.warn('The "cart/serverPull" action is deprecated and will not be supported with the Vue Storefront 1.11', 'cart')() return dispatch('sync', { forceClientState, dryRun }) }, - async sync ({ getters, rootGetters, commit, dispatch, state }, { forceClientState = false, dryRun = false }) { + async sync ({ getters, rootGetters, commit, dispatch, state }, { forceClientState = false, dryRun = false, mergeQty = false }) { const shouldUpdateClientState = rootGetters['checkout/isUserInCheckout'] || forceClientState const { getCartItems, canUpdateMethods, isSyncRequired, bypassCounter } = getters if (!canUpdateMethods || !isSyncRequired) return createDiffLog() @@ -59,7 +57,8 @@ const synchronizeActions = { dryRun, serverItems, clientItems, - forceClientState: shouldUpdateClientState + forceClientState: shouldUpdateClientState, + mergeQty }) cartHooksExecutors.afterSync(diffLog) return diffLog diff --git a/core/modules/cart/store/actions/totalsActions.ts b/core/modules/cart/store/actions/totalsActions.ts index 0e0ea350dc..d6e687136d 100644 --- a/core/modules/cart/store/actions/totalsActions.ts +++ b/core/modules/cart/store/actions/totalsActions.ts @@ -2,10 +2,12 @@ import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' import { Logger } from '@vue-storefront/core/lib/logger' import { CartService } from '@vue-storefront/core/data-resolver' import { + preparePaymentMethodsToSync, prepareShippingInfoForUpdateTotals, createOrderData, createShippingInfoData } from '@vue-storefront/core/modules/cart/helpers' +import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' const totalsActions = { async getTotals (context, { addressInformation, hasShippingInformation }) { @@ -15,7 +17,7 @@ const totalsActions = { return CartService.getTotals() }, - async overrideServerTotals ({ commit, getters, dispatch }, { addressInformation, hasShippingInformation }) { + async overrideServerTotals ({ commit, getters, rootGetters, dispatch }, { addressInformation, hasShippingInformation }) { const { resultCode, result } = await dispatch('getTotals', { addressInformation, hasShippingInformation }) if (resultCode === 200) { @@ -34,8 +36,12 @@ const totalsActions = { // we received payment methods as a result of this call, updating state if (result.payment_methods && getters.canUpdateMethods) { - const backendPaymentMethods = result.payment_methods.map(method => ({ ...method, is_server_method: true })) - dispatch('checkout/replacePaymentMethods', backendPaymentMethods, { root: true }) + const { uniqueBackendMethods, paymentMethods } = preparePaymentMethodsToSync( + result.payment_methods.map(method => ({ ...method, is_server_method: true })), + rootGetters['checkout/getNotServerPaymentMethods'] + ) + dispatch('checkout/replacePaymentMethods', paymentMethods, { root: true }) + EventBus.$emit('set-unique-payment-methods', uniqueBackendMethods) } return diff --git a/core/modules/cart/test/unit/helpers/productEquals.spec.ts b/core/modules/cart/test/unit/helpers/productEquals.spec.ts index d7612a0735..bb9fe4eab2 100644 --- a/core/modules/cart/test/unit/helpers/productEquals.spec.ts +++ b/core/modules/cart/test/unit/helpers/productEquals.spec.ts @@ -64,12 +64,12 @@ describe('Cart productEquals', () => { expect(productsEquals(product1, product2)).toBeTruthy() }); - - it('returns false because bundle products have not the same options selected', async () => { + + it('returns true because bundle products have not the same options selected', async () => { const product1 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'bundle', options: [2, 2, 5, 8] }) const product2 = createBundleProduct({ id: 2, sku: 'WG-001', type_id: 'bundle', options: [2, 4, 5, 8] }) - expect(productsEquals(product1, product2)).toBeFalsy() + expect(productsEquals(product1, product2)).toBeTruthy() }); it('returns true because bundle products have the same server id', async () => { @@ -79,7 +79,7 @@ describe('Cart productEquals', () => { expect(productsEquals(product1, product2)).toBeTruthy() }); - it('returns true because configurable products have the same eku', async () => { + it('returns true because configurable products have the same sku', async () => { const product1 = createConfigurableProduct({ id: 1, sku: 'WG-001' }) const product2 = createConfigurableProduct({ id: 2, sku: 'WG-001' }) diff --git a/core/modules/cart/test/unit/store/connectActions.spec.ts b/core/modules/cart/test/unit/store/connectActions.spec.ts index 107e52198e..7ebd24a6f1 100644 --- a/core/modules/cart/test/unit/store/connectActions.spec.ts +++ b/core/modules/cart/test/unit/store/connectActions.spec.ts @@ -63,7 +63,6 @@ describe('Cart connectActions', () => { expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_LOAD_CART_SERVER_TOKEN, null); expect(contextMock.commit).toHaveBeenNthCalledWith(3, types.CART_SET_ITEMS_HASH, null); - expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: true }); }) it('disconnects cart', async () => { @@ -85,10 +84,6 @@ describe('Cart connectActions', () => { } }) - config.cart = { - bypassCartLoaderForAuthorizedUsers: false - } - await (cartActions as any).authorize(contextMock) expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'connect', { guestCart: false }); }) @@ -110,7 +105,7 @@ describe('Cart connectActions', () => { await (cartActions as any).connect(contextMock, {}) expect(contextMock.commit).toBeCalledWith(types.CART_LOAD_CART_SERVER_TOKEN, 'server-cart-token') - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true }) + expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true, mergeQty: true }) }) it('attempts bypassing guest cart', async () => { @@ -136,4 +131,43 @@ describe('Cart connectActions', () => { expect(contextMock.commit).toBeCalledWith(types.CART_UPDATE_BYPASS_COUNTER, { counter: 1 }) expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: true }) }) + it('Create cart token when there are products in cart and we don\'t have token already', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { getCartItems: [{id: 1}], getCartToken: '' } + }; + + const wrapper = (actions: any) => actions.create(contextMock); + + await wrapper(cartActions); + + expect(contextMock.dispatch).toBeCalledWith('connect', {guestCart: false}); + }) + it('doesn\'t create cart token when there are NO products in cart', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { getCartItems: [], getCartToken: '' } + }; + + const wrapper = (actions: any) => actions.create(contextMock); + + await wrapper(cartActions); + + expect(contextMock.dispatch).toHaveBeenCalledTimes(0); + }) + it('doesn\'t create cart token when there are products in cart but we have token already', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { getCartItems: [{id: 1}], getCartToken: 'xyz' } + }; + + const wrapper = (actions: any) => actions.create(contextMock); + + await wrapper(cartActions); + + expect(contextMock.dispatch).toHaveBeenCalledTimes(0); + }) }) diff --git a/core/modules/cart/test/unit/store/itemActions.spec.ts b/core/modules/cart/test/unit/store/itemActions.spec.ts index dda55f99d1..2dfffbea87 100644 --- a/core/modules/cart/test/unit/store/itemActions.spec.ts +++ b/core/modules/cart/test/unit/store/itemActions.spec.ts @@ -145,7 +145,8 @@ describe('Cart itemActions', () => { await (cartActions as any).addItems(contextMock, { productsToAdd: [product] }) expect(contextMock.commit).toBeCalledWith(types.CART_ADD_ITEM, { product: { ...product, onlineStockCheckid: 1 } }) expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'checkProductStatus', { product }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'sync', { forceClientState: true }) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'create') + expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'sync', { forceClientState: true }) }) it('removes item from the cart', async () => { diff --git a/core/modules/cart/test/unit/store/mergeActions.spec.ts b/core/modules/cart/test/unit/store/mergeActions.spec.ts index 6342f7c4a1..fe3313afa4 100644 --- a/core/modules/cart/test/unit/store/mergeActions.spec.ts +++ b/core/modules/cart/test/unit/store/mergeActions.spec.ts @@ -77,9 +77,9 @@ jest.mock('@vue-storefront/core/helpers', () => ({ describe('Cart mergeActions', () => { it('updates client item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -99,16 +99,16 @@ describe('Cart mergeActions', () => { product_option: 'a', server_cart_id: undefined, server_item_id: 1, - sku: 1, + sku: '1', type_id: 'b' } }); }); it('updates server item - removes when updating was not successful', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -132,9 +132,9 @@ describe('Cart mergeActions', () => { }) it('updates server item - restoring quantity', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -159,9 +159,9 @@ describe('Cart mergeActions', () => { }) it('updates server item - deletes non confirmed item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -187,9 +187,9 @@ describe('Cart mergeActions', () => { }) it('updates server item - apply changes for client item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -219,9 +219,9 @@ describe('Cart mergeActions', () => { }) it('synchronizes item with server when there is no given server item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -242,9 +242,9 @@ describe('Cart mergeActions', () => { }) it('synchronizes item with server when there quantities are different', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -266,9 +266,9 @@ describe('Cart mergeActions', () => { }) it('merges client item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -284,13 +284,13 @@ describe('Cart mergeActions', () => { await (cartActions as any).mergeClientItem(contextMock, { clientItem, serverItems: [serverItem], forceClientState: false, dryRun: false }); expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'synchronizeServerItem', { serverItem, clientItem, forceClientState: false, dryRun: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { product: { product_option: 'a', server_cart_id: undefined, server_item_id: 1, sku: 1, type_id: 'b' } }) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { product: { product_option: 'a', server_cart_id: undefined, server_item_id: 1, sku: '1', type_id: 'b' } }) }) it('merges server item', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -311,7 +311,7 @@ describe('Cart mergeActions', () => { }) it('updates totals after merge', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const contextMock = createContextMock({ getters: { @@ -325,9 +325,9 @@ describe('Cart mergeActions', () => { }) it('merges client and server cart', async () => { - const clientItem = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 }; + const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; const serverItem = { - sku: 1, + sku: '1', name: 'product1', server_item_id: 1, server_cart_id: 12, @@ -351,8 +351,8 @@ describe('Cart mergeActions', () => { (createDiffLog as jest.Mock).mockImplementation(() => diffLog) await (cartActions as any).merge(contextMock, { clientItems: [clientItem], serverItems: [serverItem] }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'mergeClientItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'mergeServerItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false }) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'mergeClientItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false, mergeQty: false }) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'mergeServerItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false, mergeQty: false }) expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'updateTotalsAfterMerge', { clientItems: [clientItem], dryRun: false }) }) }); diff --git a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts index 918acc9785..0ff0734020 100644 --- a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts +++ b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts @@ -119,7 +119,7 @@ describe('Cart synchronizeActions', () => { await (cartActions as any).synchronizeCart(contextMock, { forceClientState: false }); expect(contextMock.commit).not.toHaveBeenNthCalledWith(1, types.CART_SET_ITEMS_HASH, 'hash-token') expect(contextMock.commit).not.toHaveBeenNthCalledWith(2, types.CART_LOAD_CART_SERVER_TOKEN, 'hash-token') - expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: false }) + expect(contextMock.dispatch).toBeCalledWith('create') }) it('merges current cart', async () => { @@ -144,7 +144,8 @@ describe('Cart synchronizeActions', () => { clientItems: [], dryRun: false, forceClientState: true, - serverItems: [] + serverItems: [], + mergeQty: false }) }) diff --git a/core/modules/catalog-next/helpers/filterHelpers.ts b/core/modules/catalog-next/helpers/filterHelpers.ts index 9981aba2bb..700669d130 100644 --- a/core/modules/catalog-next/helpers/filterHelpers.ts +++ b/core/modules/catalog-next/helpers/filterHelpers.ts @@ -1,5 +1,6 @@ import config from 'config' import FilterVariant from 'core/modules/catalog-next/types/FilterVariant' +import { Filters } from '../types/Category' export const getSystemFilterNames: string[] = config.products.systemFilterNames @@ -32,27 +33,25 @@ export const changeFilterQuery = ({currentQuery = {}, filterVariant}: {currentQu return newQuery } -export const getFiltersFromQuery = ({filtersQuery = {}, availableFilters = {}} = {}) => { +export const getFiltersFromQuery = ({filtersQuery = {}, availableFilters = {}} = {}): { filters: Filters } => { const searchQuery = { filters: {} } Object.keys(filtersQuery).forEach(filterKey => { const filter = availableFilters[filterKey] - const queryValue = filtersQuery[filterKey] + let queryValue = filtersQuery[filterKey] if (!filter) return + // keep original value for system filters - for example sort if (getSystemFilterNames.includes(filterKey)) { searchQuery[filterKey] = queryValue - } else if (Array.isArray(queryValue)) { + } else { + queryValue = [].concat(filtersQuery[filterKey]) queryValue.map(singleValue => { const variant = filter.find(filterVariant => filterVariant.id === singleValue) if (!variant) return - if (!searchQuery.filters[filterKey] || !Array.isArray(searchQuery.filters[filterKey])) searchQuery.filters[filterKey] = [] + if (!Array.isArray(searchQuery.filters[filterKey])) searchQuery.filters[filterKey] = [] searchQuery.filters[filterKey].push({...variant, attribute_code: filterKey}) }) - } else { - const variant = filter.find(filterVariant => filterVariant.id === queryValue) - if (!variant) return - searchQuery.filters[filterKey] = {...variant, attribute_code: filterKey} } }) return searchQuery diff --git a/core/modules/catalog-next/store/category/actions.ts b/core/modules/catalog-next/store/category/actions.ts index 2d6cd06508..a81a9fe2a0 100644 --- a/core/modules/catalog-next/store/category/actions.ts +++ b/core/modules/catalog-next/store/category/actions.ts @@ -1,4 +1,4 @@ -// import Vue from 'vue' +import Vue from 'vue' import { ActionTree } from 'vuex' import * as types from './mutation-types' import RootState from '@vue-storefront/core/types/RootState' @@ -143,6 +143,11 @@ const actions: ActionTree = { if (!searchingByIds || categorySearchOptions.filters.id.length) { categorySearchOptions.filters = Object.assign(cloneDeep(config.entities.category.filterFields), categorySearchOptions.filters ? cloneDeep(categorySearchOptions.filters) : {}) const categories = await CategoryService.getCategories(categorySearchOptions) + if (Vue.prototype.$cacheTags) { + categories.forEach(category => { + Vue.prototype.$cacheTags.add(`C${category.id}`) + }) + } const notFoundCategories = searchedIds.filter(categoryId => !categories.some(cat => cat.id === parseInt(categoryId))) commit(types.CATEGORY_ADD_CATEGORIES, categories) @@ -154,6 +159,9 @@ const actions: ActionTree = { async loadCategory ({ commit }, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { const categories: Category[] = await CategoryService.getCategories(categorySearchOptions) const category: Category = categories && categories.length ? categories[0] : null + if (Vue.prototype.$cacheTags) { + Vue.prototype.$cacheTags.add(`C${category.id}`) + } commit(types.CATEGORY_ADD_CATEGORY, category) return category }, diff --git a/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts b/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts index 610f9c0e91..2b432cad16 100644 --- a/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts +++ b/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts @@ -94,12 +94,14 @@ describe('getFiltersFromQuery method', () => { const result = getFiltersFromQuery({availableFilters, filtersQuery}) expect(result).toEqual({ filters: { - color: { - 'id': '49', - 'label': 'Black', - 'type': 'color', - 'attribute_code': 'color' - } + color: [ + { + 'id': '49', + 'label': 'Black', + 'type': 'color', + 'attribute_code': 'color' + } + ] } }) }); diff --git a/core/modules/catalog-next/types/Category.d.ts b/core/modules/catalog-next/types/Category.d.ts index b7d22eb495..c1b24106f3 100644 --- a/core/modules/catalog-next/types/Category.d.ts +++ b/core/modules/catalog-next/types/Category.d.ts @@ -1,3 +1,5 @@ +import FilterVariant from './FilterVariant' + export interface ChildrenData { id: number | string, children_data?: ChildrenData[], @@ -20,3 +22,7 @@ export interface Category { children_data: ChildrenData[], slug: string } + +export interface Filters { + [key: string]: FilterVariant[] +} diff --git a/core/modules/catalog/components/ProductCustomOptions.ts b/core/modules/catalog/components/ProductCustomOptions.ts index 821eecde03..412e06fcb2 100644 --- a/core/modules/catalog/components/ProductCustomOptions.ts +++ b/core/modules/catalog/components/ProductCustomOptions.ts @@ -54,9 +54,9 @@ export const ProductCustomOptions = { setupInputFields () { for (const customOption of this.product.custom_options) { const fieldName = customOptionFieldName(customOption) - this['inputValues'][fieldName] = defaultCustomOptionValue(customOption) + this.$set(this.inputValues, fieldName, defaultCustomOptionValue(customOption)) if (customOption.is_require) { // validation rules are very basic - this.validation.rules[fieldName] = 'required' // TODO: add custom validators for the custom options + this.$set(this.validation.rules, fieldName, 'required') // TODO: add custom validators for the custom options } this.optionChanged(customOption) } diff --git a/core/modules/catalog/helpers/bundleOptions.ts b/core/modules/catalog/helpers/bundleOptions.ts new file mode 100644 index 0000000000..51234dc88e --- /dev/null +++ b/core/modules/catalog/helpers/bundleOptions.ts @@ -0,0 +1,39 @@ +import get from 'lodash-es/get' + +const calculateBundleOptionProductPrice = ({ price = 1, priceInclTax = 1, qty = '1' }) => { + const product = { + price: 0, + priceInclTax: 0 + } + if (parseInt(qty) >= 0) { + product.price += price * parseInt(qty) + product.priceInclTax += priceInclTax * parseInt(qty) + } + return product +} + +export const getBundleOptionPrice = (bundleOptionValues) => bundleOptionValues + .map(bundleOptionValue => { + const product = get(bundleOptionValue, 'product', {}) + return calculateBundleOptionProductPrice({ + price: product.price, + priceInclTax: product.price_incl_tax || product.priceInclTax, + qty: bundleOptionValue.qty + }) + }) + .reduce( + (priceDelta, currentPriceDelta) => ({ + price: currentPriceDelta.price + priceDelta.price, + priceInclTax: currentPriceDelta.priceInclTax + priceDelta.priceInclTax + }), + { price: 0, priceInclTax: 0 } + ) + +export const getBundleOptionsValues = (selectedBundleOptions, allBundeOptions) => selectedBundleOptions + .map(selectedBundleOption => { + const { + product_links: productLinks = [] + } = allBundeOptions.find(bundleOption => bundleOption.option_id === selectedBundleOption.option_id) || {} + const value = productLinks.find(productLink => String(productLink.id) === String(selectedBundleOption.option_selections[0])) || {} + return { ...value, qty: selectedBundleOption.option_qty } + }) diff --git a/core/modules/catalog/helpers/customOption.ts b/core/modules/catalog/helpers/customOption.ts index 04cade1a91..23a007ec5e 100644 --- a/core/modules/catalog/helpers/customOption.ts +++ b/core/modules/catalog/helpers/customOption.ts @@ -1,4 +1,5 @@ -import { CustomOption, OptionValue, InputValue } from './../types/CustomOption'; +import Product from '@vue-storefront/core/modules/catalog/types/Product'; +import { CustomOption, OptionValue, InputValue, SelectedCustomOption } from '@vue-storefront/core/modules/catalog/types/CustomOption'; export const defaultCustomOptionValue = (customOption: CustomOption): InputValue => { switch (customOption.type) { @@ -43,3 +44,34 @@ export const selectedCustomOptionValue = (optionType: string, optionValues: Opti } } } + +export const getCustomOptionValues = (selectedCustomOptions: SelectedCustomOption[], allCustomOptions: CustomOption[]): OptionValue[] => selectedCustomOptions + .filter(customOptionIds => customOptionIds.option_value) // remove null | undefined values + .map(customOptionIds => { + const { values = [] } = allCustomOptions.find( + customOption => String(customOption.option_id) === String(customOptionIds.option_id) // get all custom option values based on 'option_id' + ) + const customOptionValues = customOptionIds.option_value + .split(',') // split ids, because there can be '1,2' for checkbox + .map(optionValueId => values.find(value => String(value.option_type_id) === optionValueId)) // get custom option value based on selected option value id + .filter(Boolean) // remove falsy results + + return customOptionValues + }) + .reduce((allCustomOptionValues, customOptionValue) => allCustomOptionValues.concat(customOptionValue), []) // merge all values in one array + +export const getCustomOptionPriceDelta = (customOptionValues: OptionValue[], { price, priceInclTax }: Pick) => customOptionValues + .reduce((delta, customOptionValue) => { + if (customOptionValue.price_type === 'fixed' && customOptionValue.price !== 0) { + delta.price += customOptionValue.price + delta.priceInclTax += customOptionValue.price + } + if (customOptionValue.price_type === 'percent' && customOptionValue.price !== 0) { + delta.price += ((customOptionValue.price / 100) * price) + delta.priceInclTax += ((customOptionValue.price / 100) * priceInclTax) + } + return delta + }, { + price: 0, + priceInclTax: 0 + }) diff --git a/core/modules/catalog/helpers/areAttributesAlreadyLoaded.ts b/core/modules/catalog/helpers/filterAttributes.ts similarity index 74% rename from core/modules/catalog/helpers/areAttributesAlreadyLoaded.ts rename to core/modules/catalog/helpers/filterAttributes.ts index 967578df43..ff3b26013b 100644 --- a/core/modules/catalog/helpers/areAttributesAlreadyLoaded.ts +++ b/core/modules/catalog/helpers/filterAttributes.ts @@ -1,6 +1,6 @@ import config from 'config' -const areAttributesAlreadyLoaded = ({ +export default function filterAttributes ({ filterValues, filterField, blacklist, @@ -12,27 +12,19 @@ const areAttributesAlreadyLoaded = ({ blacklist: string[], idsList: any, codesList: any -}): boolean => { +}) { return filterValues.filter(fv => { - if (config.entities.product.standardSystemFields.indexOf(fv) >= 0) { - return false - } - if (fv.indexOf('.') >= 0) { return false } - if (blacklist !== null && blacklist.includes(fv)) { return false } - if (filterField === 'attribute_id') { return (typeof idsList[fv] === 'undefined' || idsList[fv] === null) } if (filterField === 'attribute_code') { return (typeof codesList[fv] === 'undefined' || codesList[fv] === null) } - }).length === 0 + }) } - -export default areAttributesAlreadyLoaded diff --git a/core/modules/catalog/store/attribute/actions.ts b/core/modules/catalog/store/attribute/actions.ts index 260fa14e4b..38a9aed071 100644 --- a/core/modules/catalog/store/attribute/actions.ts +++ b/core/modules/catalog/store/attribute/actions.ts @@ -8,9 +8,9 @@ import config from 'config' import { Logger } from '@vue-storefront/core/lib/logger' import { entityKeyName } from '@vue-storefront/core/lib/store/entities' import { prefetchCachedAttributes } from '../../helpers/prefetchCachedAttributes' -import areAttributesAlreadyLoaded from './../../helpers/areAttributesAlreadyLoaded' import createAttributesListQuery from './../../helpers/createAttributesListQuery' import reduceAttributesLists from './../../helpers/reduceAttributesLists' +import filterAttributes from '../../helpers/filterAttributes' const actions: ActionTree = { async updateAttributes ({ commit, getters }, { attributes }) { @@ -63,7 +63,8 @@ const actions: ActionTree = { await dispatch('loadCachedAttributes', { filterField, filterValues }) - if (areAttributesAlreadyLoaded({ filterValues, filterField, blacklist, idsList, codesList })) { + filterValues = filterAttributes({ filterValues, filterField, blacklist, idsList, codesList }) + if (filterValues.length === 0) { Logger.info('Skipping attribute load - attributes already loaded', 'attr', { orgFilterValues, filterField })() return { items: Object.values(codesList) } } diff --git a/core/modules/catalog/store/product/getters.ts b/core/modules/catalog/store/product/getters.ts index cc4431a7ca..e8d9d4948f 100644 --- a/core/modules/catalog/store/product/getters.ts +++ b/core/modules/catalog/store/product/getters.ts @@ -11,7 +11,8 @@ const getters: GetterTree = { getProductsSearchResult: state => state.list, getProducts: (state, getters) => getters.getProductsSearchResult.items, getProductGallery: state => state.productGallery, - getProductRelated: state => state.related + getProductRelated: state => state.related, + getCurrentCustomOptions: state => state.current_custom_options } export default getters diff --git a/core/modules/catalog/store/stock/actions.ts b/core/modules/catalog/store/stock/actions.ts index 85ba6cf5a4..5989318f19 100644 --- a/core/modules/catalog/store/stock/actions.ts +++ b/core/modules/catalog/store/stock/actions.ts @@ -34,9 +34,11 @@ const actions: ActionTree = { async check (context, { product }) { if (config.stock.synchronize) { const { result, task_id } = await StockService.check(product.sku) + return { qty: result ? result.qty : 0, status: getStatus(result, 'ok'), + isManageStock: result.manage_stock, onlineCheckTaskId: task_id } } diff --git a/core/modules/catalog/types/CustomOption.ts b/core/modules/catalog/types/CustomOption.ts index d9c6c9a399..ad41b66ab3 100644 --- a/core/modules/catalog/types/CustomOption.ts +++ b/core/modules/catalog/types/CustomOption.ts @@ -22,3 +22,8 @@ export interface OptionValue { } export type InputValue = string | number | number[] + +export interface SelectedCustomOption { + option_id: number, + option_value?: string +} diff --git a/core/modules/catalog/types/Product.ts b/core/modules/catalog/types/Product.ts index 70cda94e34..7bb815d3cc 100644 --- a/core/modules/catalog/types/Product.ts +++ b/core/modules/catalog/types/Product.ts @@ -1,3 +1,5 @@ +import { CustomOption } from './CustomOption'; + export default interface Product { category: Record[], category_ids: string[], @@ -9,6 +11,7 @@ export default interface Product { description: string, errors?: Record, final_price: number, + finalPrice: number, gift_message_available: string, has_options?: string, id?: number | string, @@ -28,7 +31,9 @@ export default interface Product { pattern?: string, price: number, price_incl_tax?: number, + priceInclTax?: number, price_tax?: number, + priceTax?: number, product_links?: Record[], product_option?: Record, regular_price: number, @@ -43,6 +48,9 @@ export default interface Product { special_price_incl_tax?: any, special_price_tax?: any, special_price?: number, + specialPriceInclTax?: any, + specialPriceTax?: any, + specialPrice?: number, status: number, stock: Record, style_general?: string, @@ -53,5 +61,6 @@ export default interface Product { url_key: string, visibility: number, _score?: number, - qty?: number + qty?: number, + custom_options?: CustomOption } diff --git a/core/modules/checkout/components/Payment.ts b/core/modules/checkout/components/Payment.ts index 72c8f530d5..f22cf22a29 100644 --- a/core/modules/checkout/components/Payment.ts +++ b/core/modules/checkout/components/Payment.ts @@ -1,6 +1,7 @@ import { mapState, mapGetters } from 'vuex' import RootState from '@vue-storefront/core/types/RootState' import toString from 'lodash-es/toString' +import debounce from 'lodash-es/debounce' const Countries = require('@vue-storefront/i18n/resource/countries.json') export const Payment = { @@ -37,6 +38,9 @@ export const Payment = { this.payment.paymentMethod = this.paymentMethods.length > 0 ? this.paymentMethods[0].code : 'cashondelivery' } }, + beforeMount () { + this.$bus.$on('checkout-after-load', this.onCheckoutLoad) + }, mounted () { if (this.payment.firstName) { this.initializeBillingAddress() @@ -47,6 +51,9 @@ export const Payment = { } this.changePaymentMethod() }, + beforeDestroy () { + this.$bus.$off('checkout-after-load', this.onCheckoutLoad) + }, watch: { shippingDetails: { handler () { @@ -70,6 +77,11 @@ export const Payment = { handler () { this.useGenerateInvoice() } + }, + paymentMethods: { + handler: debounce(function () { + this.changePaymentMethod() + }, 500) } }, methods: { @@ -237,6 +249,9 @@ export const Payment = { changeCountry () { this.$store.dispatch('checkout/updatePaymentDetails', { country: this.payment.country }) this.$store.dispatch('cart/syncPaymentMethods', { forceServerSync: true }) + }, + onCheckoutLoad () { + this.payment = this.$store.getters['checkout/getPaymentDetails'] } } } diff --git a/core/modules/checkout/components/PersonalDetails.ts b/core/modules/checkout/components/PersonalDetails.ts index d5358f8a0e..ef3ba7a1f5 100644 --- a/core/modules/checkout/components/PersonalDetails.ts +++ b/core/modules/checkout/components/PersonalDetails.ts @@ -58,6 +58,9 @@ export const PersonalDetails = { }, gotoAccount () { this.$bus.$emit('modal-show', 'modal-signup') + }, + onCheckoutLoad () { + this.personalDetails = this.$store.state.checkout.personalDetails } }, updated () { @@ -72,9 +75,11 @@ export const PersonalDetails = { } }, beforeMount () { + this.$bus.$on('checkout-after-load', this.onCheckoutLoad) this.$bus.$on('user-after-loggedin', this.onLoggedIn) }, - destroyed () { + beforeDestroy () { + this.$bus.$off('checkout-after-load', this.onCheckoutLoad) this.$bus.$off('user-after-loggedin', this.onLoggedIn) } } diff --git a/core/modules/checkout/components/Shipping.ts b/core/modules/checkout/components/Shipping.ts index a2e59bd927..4ba41bb131 100644 --- a/core/modules/checkout/components/Shipping.ts +++ b/core/modules/checkout/components/Shipping.ts @@ -12,10 +12,12 @@ export const Shipping = { } }, beforeDestroy () { + this.$bus.$off('checkout-after-load', this.onCheckoutLoad) this.$bus.$off('checkout-after-personalDetails', this.onAfterPersonalDetails) this.$bus.$off('checkout-after-shippingset', this.onAfterShippingSet) }, beforeMount () { + this.$bus.$on('checkout-after-load', this.onCheckoutLoad) this.$bus.$on('checkout-after-personalDetails', this.onAfterPersonalDetails) this.$bus.$on('checkout-after-shippingset', this.onAfterShippingSet) }, @@ -185,6 +187,9 @@ export const Shipping = { return false } return true + }, + onCheckoutLoad () { + this.shipping = this.$store.state.checkout.shippingDetails } } } diff --git a/core/modules/checkout/store/checkout/actions.ts b/core/modules/checkout/store/checkout/actions.ts index 4ab73b02a2..bd26d0807d 100644 --- a/core/modules/checkout/store/checkout/actions.ts +++ b/core/modules/checkout/store/checkout/actions.ts @@ -11,7 +11,7 @@ const actions: ActionTree = { const result = await dispatch('order/placeOrder', order, { root: true }) if (!result.resultCode || result.resultCode === 200) { await dispatch('updateOrderTimestamp') - await dispatch('cart/clear', { recreateAndSyncCart: true }, { root: true }) + await dispatch('cart/clear', null, { root: true }) await dispatch('dropPassword') } } catch (e) { @@ -41,9 +41,15 @@ const actions: ActionTree = { }, async load ({ commit }) { const checkoutStorage = StorageManager.get('checkout') - const personalDetails = await checkoutStorage.getItem('personal-details') - const shippingDetails = await checkoutStorage.getItem('shipping-details') - const paymentDetails = await checkoutStorage.getItem('payment-details') + const [ + personalDetails, + shippingDetails, + paymentDetails + ] = await Promise.all([ + checkoutStorage.getItem('personal-details'), + checkoutStorage.getItem('shipping-details'), + checkoutStorage.getItem('payment-details') + ]) if (personalDetails) { commit(types.CHECKOUT_LOAD_PERSONAL_DETAILS, personalDetails) diff --git a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts b/core/modules/checkout/test/unit/store/checkout/actions.spec.ts index cba2de367e..273f6bbadc 100644 --- a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts +++ b/core/modules/checkout/test/unit/store/checkout/actions.spec.ts @@ -43,7 +43,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(4); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { recreateAndSyncCart: true }, { root: true }); + expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -54,7 +54,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(4); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { recreateAndSyncCart: true }, { root: true }); + expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -65,7 +65,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(1); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { recreateAndSyncCart: true }, { root: true }); + expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -76,7 +76,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(1); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { recreateAndSyncCart: true }, { root: true }); + expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); expect(Logger.error).toHaveBeenCalled(); }); diff --git a/core/modules/order/store/actions.ts b/core/modules/order/store/actions.ts index 145483c48c..4c141a7175 100644 --- a/core/modules/order/store/actions.ts +++ b/core/modules/order/store/actions.ts @@ -34,7 +34,7 @@ const actions: ActionTree = { EventBus.$emit('order-before-placed', { order: preparedOrder }) const order = orderHooksExecutors.beforePlaceOrder(preparedOrder) - if (!config.orders.directBackendSync || !isOnline()) { + if (!isOnline()) { dispatch('enqueueOrder', { newOrder: order }) EventBus.$emit('order-after-placed', { order }) orderHooksExecutors.beforePlaceOrder({ order, task: { resultCode: 200 } }) @@ -53,7 +53,6 @@ const actions: ActionTree = { async processOrder ({ commit, dispatch }, { newOrder, currentOrderHash }) { const order = { ...newOrder, transmited: true } const task = await OrderService.placeOrder(order) - EventBus.$emit('notification-progress-stop') if (task.resultCode === 200) { dispatch('enqueueOrder', { newOrder: order }) @@ -61,7 +60,7 @@ const actions: ActionTree = { commit(types.ORDER_LAST_ORDER_WITH_CONFIRMATION, { order, confirmation: task.result }) orderHooksExecutors.afterPlaceOrder({ order, task }) EventBus.$emit('order-after-placed', { order, confirmation: task.result }) - + EventBus.$emit('notification-progress-stop') return task } @@ -71,10 +70,10 @@ const actions: ActionTree = { Logger.error('Internal validation error; Order entity is not compliant with the schema: ' + JSON.stringify(task.result), 'orders')() dispatch('notification/spawnNotification', notifications.internalValidationError(), { root: true }) dispatch('enqueueOrder', { newOrder: order }) - + EventBus.$emit('notification-progress-stop') return task } - + EventBus.$emit('notification-progress-stop') throw new Error('Unhandled place order request error') }, handlePlacingOrderFailed ({ commit, dispatch }, { newOrder, currentOrderHash }) { diff --git a/core/modules/order/test/unit/store/actions.spec.ts b/core/modules/order/test/unit/store/actions.spec.ts index 1955db08ae..47401ac81c 100644 --- a/core/modules/order/test/unit/store/actions.spec.ts +++ b/core/modules/order/test/unit/store/actions.spec.ts @@ -980,7 +980,7 @@ describe('Order actions', () => { expect(contextMock.commit).not.toBeCalledWith(types.ORDER_ADD_SESSION_STAMPS); }) - it('should dispatch enqueueOrder', async () => { + it('should dispatch processOrder', async () => { const contextMock = createContextMock({ getters: { getSessionOrderHashes: 'current-order-hash' } }); @@ -1036,7 +1036,7 @@ describe('Order actions', () => { await (orderActions as any).placeOrder(contextMock, order) - expect(contextMock.dispatch).toBeCalledWith('enqueueOrder', { newOrder: newOrder }) + expect(contextMock.dispatch).toBeCalledWith('processOrder', { newOrder: newOrder, currentOrderHash }) }) it('should dispatch processOrder', async () => { diff --git a/core/modules/user/store/actions.ts b/core/modules/user/store/actions.ts index 6e5af68765..d5899743eb 100644 --- a/core/modules/user/store/actions.ts +++ b/core/modules/user/store/actions.ts @@ -226,7 +226,7 @@ const actions: ActionTree = { await dispatch('cart/disconnect', {}, { root: true }) await dispatch('clearCurrentUser') EventBus.$emit('user-after-logout') - await dispatch('cart/clear', { recreateAndSyncCart: true }, { root: true }) + await dispatch('cart/clear', null, { root: true }) if (!silent) { await dispatch('notification/spawnNotification', { diff --git a/core/modules/user/test/unit/store/actions.spec.ts b/core/modules/user/test/unit/store/actions.spec.ts index c1718ec3be..f4cee6dd77 100644 --- a/core/modules/user/test/unit/store/actions.spec.ts +++ b/core/modules/user/test/unit/store/actions.spec.ts @@ -414,7 +414,7 @@ describe('User actions', () => { expect(contextMock.commit).toBeCalledWith(types.USER_END_SESSION) expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'cart/disconnect', {}, {root: true}) expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'clearCurrentUser') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', {recreateAndSyncCart: true}, {root: true}) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, {root: true}) expect(contextMock.dispatch).toHaveBeenNthCalledWith(4, 'notification/spawnNotification', { type: 'success', message: "You're logged out", diff --git a/core/package.json b/core/package.json index ca87dcdc2d..6b524ba401 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@vue-storefront/core", - "version": "1.11.0", + "version": "1.11.1", "description": "Vue Storefront Core", "license": "MIT", "main": "app.js", diff --git a/core/pages/Checkout.js b/core/pages/Checkout.js index 19040a24b6..c2687d4a02 100644 --- a/core/pages/Checkout.js +++ b/core/pages/Checkout.js @@ -44,8 +44,9 @@ export default { isThankYouPage: 'checkout/isThankYouPage' }) }, - beforeMount () { - this.$store.dispatch('checkout/load') + async beforeMount () { + await this.$store.dispatch('checkout/load') + this.$bus.$emit('checkout-after-load') this.$store.dispatch('checkout/setModifiedAt', Date.now()) // TODO: Use one event with name as apram this.$bus.$on('cart-after-update', this.onCartAfterUpdate) diff --git a/core/scripts/server.ts b/core/scripts/server.ts index 361e745300..de6a8dc2ab 100755 --- a/core/scripts/server.ts +++ b/core/scripts/server.ts @@ -208,7 +208,7 @@ app.get('*', (req, res, next) => { if (config.server.useOutputCache && cache) { cache.set( 'page:' + req.url, - { headers: res.getHeaders(), body: output }, + { headers: res.getHeaders(), body: output, httpCode: res.statusCode }, tagsArray ).catch(errorHandler) } @@ -249,6 +249,11 @@ app.get('*', (req, res, next) => { } } res.setHeader('X-VS-Cache', 'Hit') + + if (output.httpCode) { + res.status(output.httpCode) + } + if (output.body) { res.end(output.body) } else { diff --git a/docs/guide/basics/configuration.md b/docs/guide/basics/configuration.md index 4382698a95..64c5fa9a0d 100644 --- a/docs/guide/basics/configuration.md +++ b/docs/guide/basics/configuration.md @@ -328,13 +328,6 @@ Starting with Vue Storefront 1.7, we added a configuration option `config.entiti ## Cart -```json -"cart": { - "bypassCartLoaderForAuthorizedUsers": true, -``` - -The cart-loader bypass feature is there because we're posting orders to Magento asynchronously. It may happen that directly after placing an order, the Magento’s user still has the same quote ID, and after browsing through the VS store, old items will be restored to the shopping cart. Now you can disable this behavior by setting `bypassCartLoaderForAuthorizedUsers` option to `false` - ```json "cart": { "serverMergeByDefault": true, diff --git a/docs/guide/cookbook/setup.md b/docs/guide/cookbook/setup.md index 412e72d922..24c2e4fdf4 100644 --- a/docs/guide/cookbook/setup.md +++ b/docs/guide/cookbook/setup.md @@ -1092,7 +1092,6 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor } }, "cart": { - "bypassCartLoaderForAuthorizedUsers": true, "serverMergeByDefault": true, "serverSyncCanRemoveLocalItems": false, "serverSyncCanModifyLocalItems": false, diff --git a/docs/package.json b/docs/package.json index b3328ed706..2b5a28f2e4 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "@vue-storefront/docs", "private": true, - "version": "1.11.0", + "version": "1.11.1", "scripts": { "docs:dev": "vuepress dev", "docs:build": "vuepress build", diff --git a/ecosystem.json b/ecosystem.json index caf130c5e3..7098247e71 100644 --- a/ecosystem.json +++ b/ecosystem.json @@ -10,7 +10,8 @@ "NODE_ENV": "production" }, "interpreter": "./node_modules/.bin/ts-node", - "script": "./core/scripts/server.ts", + "script": "./node_modules/.bin/ts-node", + "args": "-P tsconfig-build.json ./core/scripts/server.ts", "node_args": "--max_old_space_size=1024", "log_date_format": "YYYY-MM-DD HH:mm:ss", "ignore_watch": [ diff --git a/package.json b/package.json index 10db51c87a..4aee6da6c8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-storefront", - "version": "1.11.0", + "version": "1.11.1", "description": "A Vue.js, PWA eCommerce frontend", "private": true, "engines": { @@ -76,7 +76,7 @@ "redis-tag-cache": "^1.2.1", "reflect-metadata": "^0.1.12", "register-service-worker": "^1.5.2", - "ts-node": "^7.0.1", + "ts-node": "^8.6.2", "vue": "^2.6.6", "vue-analytics": "^5.16.1", "vue-apollo": "^3.0.0-beta.19", diff --git a/packages/cli/boilerplates/module/package.json b/packages/cli/boilerplates/module/package.json index 97c5aecac0..4c814aa428 100644 --- a/packages/cli/boilerplates/module/package.json +++ b/packages/cli/boilerplates/module/package.json @@ -12,13 +12,13 @@ "license": "MIT", "dependencies": {}, "devDependencies": { - "@vue-storefront/core": "^1.11.0", + "@vue-storefront/core": "^1.11.1", "ts-loader": "^6.0.4", "typescript": "^3.5.2", "webpack": "^4.35.2", "webpack-cli": "^3.3.5" }, "peerDependencies": { - "@vue-storefront/core": "^1.11.0" + "@vue-storefront/core": "^1.11.1" } } diff --git a/src/modules/instant-checkout/components/InstantCheckout.vue b/src/modules/instant-checkout/components/InstantCheckout.vue index 6987182579..9060feaffe 100644 --- a/src/modules/instant-checkout/components/InstantCheckout.vue +++ b/src/modules/instant-checkout/components/InstantCheckout.vue @@ -149,7 +149,7 @@ export default { this.$store.dispatch('checkout/setThankYouPage', true) this.$store.commit('ui/setMicrocart', false) this.$router.push(this.localizedRoute('/checkout')) - this.$store.dispatch('cart/clear', { recreateAndSyncCart: true }, {root: true}) + this.$store.dispatch('cart/clear', null, {root: true}) } }) }) diff --git a/src/themes/default-amp/components/core/ProductTile.vue b/src/themes/default-amp/components/core/ProductTile.vue index deb9dbde2d..d2daa0a3b9 100755 --- a/src/themes/default-amp/components/core/ProductTile.vue +++ b/src/themes/default-amp/components/core/ProductTile.vue @@ -28,21 +28,21 @@ class="price-original mr5 lh30 cl-secondary" v-if="product.special_price && parseFloat(product.original_price_incl_tax) > 0 && !onlyImage" > - {{ product.original_price_incl_tax | price }} + {{ product.original_price_incl_tax | price(storeView) }} - {{ product.price_incl_tax | price }} + {{ product.price_incl_tax | price(storeView) }} - {{ product.price_incl_tax | price }} + {{ product.price_incl_tax | price(storeView) }} @@ -52,6 +52,7 @@ import rootStore from '@vue-storefront/core/store' import { ProductTile } from '@vue-storefront/core/modules/catalog/components/ProductTile.ts' import config from 'config' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' export default { mixins: [ProductTile], @@ -65,6 +66,11 @@ export default { default: false } }, + computed: { + storeView () { + return currentStoreView() + } + }, methods: { onProductPriceUpdate (product) { if (product.sku === this.product.sku) { diff --git a/src/themes/default-amp/package.json b/src/themes/default-amp/package.json index 8464590059..041aaf4c87 100755 --- a/src/themes/default-amp/package.json +++ b/src/themes/default-amp/package.json @@ -1,6 +1,6 @@ { "name": "@vue-storefront/theme-default-amp", - "version": "1.11.0", + "version": "1.11.1", "description": "Default AMP theme for Vue Storefront", "main": "index.js", "scripts": { diff --git a/src/themes/default-amp/pages/Product.vue b/src/themes/default-amp/pages/Product.vue index 58b2de643d..6753b018be 100755 --- a/src/themes/default-amp/pages/Product.vue +++ b/src/themes/default-amp/pages/Product.vue @@ -44,17 +44,17 @@ v-if="product.special_price && product.price_incl_tax && product.original_price_incl_tax" > - {{ product.price_incl_tax * product.qty | price }} + {{ product.price_incl_tax * product.qty | price(storeView) }}   - {{ product.original_price_incl_tax * product.qty | price }} + {{ product.original_price_incl_tax * product.qty | price(storeView) }}
- {{ product.price_incl_tax * product.qty | price }} + {{ product.price_incl_tax * product.qty | price(storeView) }}
currentStoreView().i18n.currencyCode }, directives: { focusClean }, diff --git a/src/themes/default/components/core/CookieNotification.vue b/src/themes/default/components/core/CookieNotification.vue index 6014a89b8e..2716d39280 100644 --- a/src/themes/default/components/core/CookieNotification.vue +++ b/src/themes/default/components/core/CookieNotification.vue @@ -60,7 +60,7 @@ export default { this.$store.dispatch('claims/set', {claimCode: 'cookiesAccepted', value: true}) } }, - created () { + mounted () { this.$store.dispatch('claims/check', {claimCode: 'cookiesAccepted'}).then((cookieClaim) => { if (!cookieClaim) { this.isOpen = true diff --git a/src/themes/default/components/core/ProductLinks.vue b/src/themes/default/components/core/ProductLinks.vue index 5f66771428..317a81cd46 100644 --- a/src/themes/default/components/core/ProductLinks.vue +++ b/src/themes/default/components/core/ProductLinks.vue @@ -8,11 +8,11 @@

- {{ productLink.product.price_incl_tax | price }}  - {{ productLink.product.original_price_incl_tax | price }} + {{ productLink.product.price_incl_tax | price(storeView) }}  + {{ productLink.product.original_price_incl_tax | price(storeView) }}
- {{ productLink.product.price_incl_tax | price }} + {{ productLink.product.price_incl_tax | price(storeView) }}
@@ -35,12 +35,18 @@ diff --git a/src/themes/default/components/core/ProductPrice.vue b/src/themes/default/components/core/ProductPrice.vue new file mode 100644 index 0000000000..d38d5ece2a --- /dev/null +++ b/src/themes/default/components/core/ProductPrice.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/themes/default/components/core/ProductQuantity.vue b/src/themes/default/components/core/ProductQuantity.vue index a9e463e1f7..ef1af65288 100644 --- a/src/themes/default/components/core/ProductQuantity.vue +++ b/src/themes/default/components/core/ProductQuantity.vue @@ -45,7 +45,11 @@ export default { }, maxQuantity: { type: Number, - required: true + default: undefined + }, + checkMaxQuantity: { + type: Boolean, + default: true }, loading: { type: Boolean, @@ -64,7 +68,7 @@ export default { return this.isOnline ? this.maxQuantity : null }, disabled () { - return this.isOnline ? !this.maxQuantity : false + return this.isOnline ? !this.maxQuantity && this.checkMaxQuantity : false }, name () { if (this.isSimpleOrConfigurable && !this.loading && this.showQuantity) { diff --git a/src/themes/default/components/core/ProductTile.vue b/src/themes/default/components/core/ProductTile.vue index 101b4dbf71..d7e1497103 100644 --- a/src/themes/default/components/core/ProductTile.vue +++ b/src/themes/default/components/core/ProductTile.vue @@ -45,17 +45,17 @@ {{ product.original_price_incl_tax | price }} + >{{ product.original_price_incl_tax | price(storeView) }} {{ product.price_incl_tax | price }} + >{{ product.price_incl_tax | price(storeView) }} {{ product.price_incl_tax | price }} + >{{ product.price_incl_tax | price(storeView) }} @@ -69,6 +69,7 @@ import AddToWishlist from 'theme/components/core/blocks/Wishlist/AddToWishlist' import AddToCompare from 'theme/components/core/blocks/Compare/AddToCompare' import { IsOnWishlist } from '@vue-storefront/core/modules/wishlist/components/IsOnWishlist' import { IsOnCompare } from '@vue-storefront/core/modules/compare/components/IsOnCompare' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' export default { mixins: [ProductTile, IsOnWishlist, IsOnCompare], @@ -96,6 +97,9 @@ export default { }, favoriteIcon () { return this.isOnWishlist ? 'favorite' : 'favorite_border' + }, + storeView () { + return currentStoreView() } }, methods: { diff --git a/src/themes/default/components/core/blocks/Checkout/CartSummary.vue b/src/themes/default/components/core/blocks/Checkout/CartSummary.vue index 2cac035558..0d02305458 100644 --- a/src/themes/default/components/core/blocks/Checkout/CartSummary.vue +++ b/src/themes/default/components/core/blocks/Checkout/CartSummary.vue @@ -11,7 +11,7 @@ {{ segment.title }}
- {{ segment.value | price }} + {{ segment.value | price(storeView) }}
@@ -20,7 +20,7 @@ {{ segment.title }}
- {{ segment.value | price }} + {{ segment.value | price(storeView) }}
@@ -53,12 +53,18 @@ diff --git a/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue b/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue index e710b545cd..720a285e0d 100644 --- a/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue +++ b/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue @@ -34,13 +34,13 @@ - {{ product.price_incl_tax | price }} + {{ product.price_incl_tax | price(storeView) }} {{ product.qty }} - {{ product.price_incl_tax * product.qty | price }} + {{ product.price_incl_tax * product.qty | price(storeView) }} @@ -65,6 +65,7 @@ import { ConfirmOrders } from '@vue-storefront/core/modules/offline-order/compon import { CancelOrders } from '@vue-storefront/core/modules/offline-order/components/CancelOrders' import Modal from 'theme/components/core/Modal' import ButtonFull from 'theme/components/theme/ButtonFull.vue' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' export default { props: { @@ -79,6 +80,11 @@ export default { this.$bus.$emit('modal-show', 'modal-order-confirmation') }) }, + computed: { + storeView () { + return currentStoreView() + } + }, methods: { confirmOrders () { ConfirmOrders.methods.confirmOrders.call(this) diff --git a/src/themes/default/components/core/blocks/Checkout/Product.vue b/src/themes/default/components/core/blocks/Checkout/Product.vue index bca6719ee6..78bac119ac 100644 --- a/src/themes/default/components/core/blocks/Checkout/Product.vue +++ b/src/themes/default/components/core/blocks/Checkout/Product.vue @@ -42,14 +42,14 @@
- {{ product.totals.row_total - product.totals.discount_amount + product.totals.tax_amount | price }} - {{ product.totals.row_total_incl_tax | price }} - {{ product.totals.row_total_incl_tax | price }} + {{ product.totals.row_total - product.totals.discount_amount + product.totals.tax_amount | price(storeView) }} + {{ product.totals.row_total_incl_tax | price(storeView) }} + {{ product.totals.row_total_incl_tax | price(storeView) }}
- {{ product.price_incl_tax * product.qty | price }} - {{ product.original_price_incl_tax * product.qty | price }} - {{ product.price_incl_tax * product.qty | price }} + {{ product.price_incl_tax * product.qty | price(storeView) }} + {{ product.original_price_incl_tax * product.qty | price(storeView) }} + {{ product.price_incl_tax * product.qty | price(storeView) }}
@@ -61,9 +61,13 @@ import { Product } from '@vue-storefront/core/modules/checkout/components/Product' import { onlineHelper } from '@vue-storefront/core/helpers' import ProductImage from 'theme/components/core/ProductImage' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' export default { computed: { + storeView () { + return currentStoreView() + }, isOnline () { return onlineHelper.isOnline }, diff --git a/src/themes/default/components/core/blocks/Checkout/Shipping.vue b/src/themes/default/components/core/blocks/Checkout/Shipping.vue index a093fc0c0d..fef1da91c7 100644 --- a/src/themes/default/components/core/blocks/Checkout/Shipping.vue +++ b/src/themes/default/components/core/blocks/Checkout/Shipping.vue @@ -184,7 +184,7 @@ {{ $t('Shipping method') }}
-
-
- {{ segment.value | price }} + {{ segment.value | price(storeView) }}
@@ -94,7 +94,7 @@ {{ segment.title }}
- {{ segment.value | price }} + {{ segment.value | price(storeView) }}
@@ -127,6 +127,7 @@ import { mapGetters, mapActions } from 'vuex' import i18n from '@vue-storefront/i18n' import { isModuleRegistered } from '@vue-storefront/core/lib/modules' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' import VueOfflineMixin from 'vue-offline/mixin' import onEscapePress from '@vue-storefront/core/mixins/onEscapePress' @@ -184,7 +185,10 @@ export default { appliedCoupon: 'cart/getCoupon', totals: 'cart/getTotals', isOpen: 'cart/getIsMicroCartOpen' - }) + }), + storeView () { + return currentStoreView() + } }, methods: { ...mapActions({ @@ -227,7 +231,7 @@ export default { action1: { label: i18n.t('Cancel'), action: 'close' }, action2: { label: i18n.t('OK'), action: async () => { - await this.$store.dispatch('cart/clear', { recreateAndSyncCart: false }) // just clear the items without sync + await this.$store.dispatch('cart/clear') // just clear the items without sync await this.$store.dispatch('cart/sync', { forceClientState: true }) } }, diff --git a/src/themes/default/components/core/blocks/Microcart/Product.vue b/src/themes/default/components/core/blocks/Microcart/Product.vue index 8f183698b7..0489553725 100644 --- a/src/themes/default/components/core/blocks/Microcart/Product.vue +++ b/src/themes/default/components/core/blocks/Microcart/Product.vue @@ -63,29 +63,29 @@
- {{ product.price_incl_tax * product.qty | price }} + {{ product.price_incl_tax * product.qty | price(storeView) }} - {{ product.original_price_incl_tax * product.qty | price }} + {{ product.original_price_incl_tax * product.qty | price(storeView) }} - {{ (product.original_price_incl_tax ? product.original_price_incl_tax : product.price_incl_tax) * product.qty | price }} + {{ (product.original_price_incl_tax ? product.original_price_incl_tax : product.price_incl_tax) * product.qty | price(storeView) }}
- {{ product.totals.row_total - product.totals.discount_amount + product.totals.tax_amount | price }} + {{ product.totals.row_total - product.totals.discount_amount + product.totals.tax_amount | price(storeView) }} - {{ product.totals.row_total_incl_tax | price }} + {{ product.totals.row_total_incl_tax | price(storeView) }} - {{ product.totals.row_total_incl_tax | price }} + {{ product.totals.row_total_incl_tax | price(storeView) }}
- {{ (product.regular_price || product.price_incl_tax) * product.qty | price }} + {{ (product.regular_price || product.price_incl_tax) * product.qty | price(storeView) }}
@@ -183,7 +183,9 @@ export default { return this.product.info && Object.keys(this.product.info).length > 0 }, hasProductErrors () { - return this.product.errors && Object.keys(this.product.errors).length > 0 + const errors = this.product.errors || {} + const errorsValuesExists = Object.keys(errors).filter(errorKey => errors[errorKey]).length > 0 + return errorsValuesExists }, isTotalsActive () { return this.isOnline && !this.editMode && this.product.totals && this.product.totals.options @@ -225,6 +227,9 @@ export default { return this.quantityError || this.isStockInfoLoading || (this.isOnline && !this.maxQuantity && this.isSimpleOrConfigurable) + }, + storeView () { + return currentStoreView() } }, methods: { diff --git a/src/themes/default/components/core/blocks/MyAccount/MyOrder.vue b/src/themes/default/components/core/blocks/MyAccount/MyOrder.vue index 4e46ba6d0e..e34a73aeab 100644 --- a/src/themes/default/components/core/blocks/MyAccount/MyOrder.vue +++ b/src/themes/default/components/core/blocks/MyAccount/MyOrder.vue @@ -17,7 +17,7 @@
-

{{ order.created_at | date('LLL') }}

+

{{ order.created_at | date('LLL', storeView) }}

{{ $t('Remake order') }}

@@ -58,13 +58,13 @@ {{ item.sku }} - {{ item.price_incl_tax | price }} + {{ item.price_incl_tax | price(storeView) }} {{ item.qty_ordered }} - {{ item.row_total_incl_tax | price }} + {{ item.row_total_incl_tax | price(storeView) }} @@ -76,31 +76,31 @@ {{ $t('Subtotal') }} - {{ order.subtotal | price }} + {{ order.subtotal | price(storeView) }} {{ $t('Shipping') }} - {{ order.shipping_amount | price }} + {{ order.shipping_amount | price(storeView) }} {{ $t('Tax') }} - {{ order.tax_amount + order.discount_tax_compensation_amount | price }} + {{ order.tax_amount + order.discount_tax_compensation_amount | price(storeView) }} {{ $t('Discount') }} - {{ order.discount_amount | price }} + {{ order.discount_amount | price(storeView) }} {{ $t('Grand total') }} - {{ order.grand_total | price }} + {{ order.grand_total | price(storeView) }} @@ -148,6 +148,7 @@ import MyOrder from '@vue-storefront/core/compatibility/components/blocks/MyAcco import ReturnIcon from 'theme/components/core/blocks/Header/ReturnIcon' import ProductImage from 'theme/components/core/ProductImage' import { getThumbnailPath, productThumbnailPath } from '@vue-storefront/core/helpers' +import { currentStoreView } from '@vue-storefront/core/lib/multistore' import { mapActions } from 'vuex' export default { @@ -161,6 +162,11 @@ export default { itemThumbnail: [] } }, + computed: { + storeView () { + return currentStoreView() + } + }, methods: { ...mapActions({ getProduct: 'product/single' diff --git a/src/themes/default/components/core/blocks/MyAccount/MyOrders.vue b/src/themes/default/components/core/blocks/MyAccount/MyOrders.vue index 8d4f0b1cee..bf0006b0ad 100644 --- a/src/themes/default/components/core/blocks/MyAccount/MyOrders.vue +++ b/src/themes/default/components/core/blocks/MyAccount/MyOrders.vue @@ -43,13 +43,13 @@ #{{ order.increment_id }} - {{ order.created_at | date }} + {{ order.created_at | date(null, storeView) }} {{ order.customer_firstname }} {{ order.customer_lastname }} - {{ order.grand_total | price }} + {{ order.grand_total | price(storeView) }} {{ $t('Purchase') }} @@ -81,9 +81,15 @@ diff --git a/src/themes/default/components/core/blocks/Wishlist/Product.vue b/src/themes/default/components/core/blocks/Wishlist/Product.vue index 531b510dbb..af1bb9c086 100644 --- a/src/themes/default/components/core/blocks/Wishlist/Product.vue +++ b/src/themes/default/components/core/blocks/Wishlist/Product.vue @@ -31,11 +31,11 @@
- {{ product.price_incl_tax | price }}  - {{ product.original_price_incl_tax | price }} + {{ product.price_incl_tax | price(storeView) }}  + {{ product.original_price_incl_tax | price(storeView) }} - {{ product.price_incl_tax | price }} + {{ product.price_incl_tax | price(storeView) }}
@@ -80,6 +80,9 @@ export default { loading: this.thumbnail, src: this.thumbnail } + }, + storeView () { + return currentStoreView() } }, methods: { diff --git a/src/themes/default/components/theme/blocks/Reviews/ReviewsList.vue b/src/themes/default/components/theme/blocks/Reviews/ReviewsList.vue index 3fd1cac16c..d5cc2880ce 100644 --- a/src/themes/default/components/theme/blocks/Reviews/ReviewsList.vue +++ b/src/themes/default/components/theme/blocks/Reviews/ReviewsList.vue @@ -9,7 +9,7 @@ {{ item.title }}

- {{ item.nickname }}, {{ item.created_at | date }} + {{ item.nickname }}, {{ item.created_at | date(null, storeView) }}

{{ item.detail }} @@ -35,6 +35,7 @@