diff --git a/.github/workflows/deploy-storefrontcloud.yml b/.github/workflows/deploy-storefrontcloud.yml index ccc4bb8f29..7350476189 100644 --- a/.github/workflows/deploy-storefrontcloud.yml +++ b/.github/workflows/deploy-storefrontcloud.yml @@ -15,6 +15,11 @@ jobs: uses: actions/setup-node@v1 with: node-version: "10.x" + - name: Create config file + run: | + echo '{"server":{"useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true},"api":{"url":"https://demo.storefrontcloud.io"}}' > config/local-cloud-demo.json + echo '{"server":{"useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true},"api":{"url":"https://demo.storefrontcloud.io"}}' > config/local-cloud-next.json + echo '{"server":{"useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true},"api":{"url":"https://test.storefrontcloud.io"}}' > config/local-cloud-test.json - name: Build and publish docker image uses: elgohr/Publish-Docker-Github-Action@master with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 269442a60e..72dbd429b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ 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.4] - 2020.05.26 + +### Added + + +### Changed / Improved + +- use yarn in cli installer - @gibkigonzo (#4292) +- disable out of stock notification when config.stock.allowOutOfStockInCart is true - @gibigonzo (#4340) + + +### Fixed + +- Use LRU as object contructor based on newest changes in module - @gibkigonzo (#4242) +- Fixed ESC button action (minicart, wishlist) - @mdanilowicz (#4393) +- Fixes problems related to tax calculation and price filter in multistore setup - @juho-jaakkola (#4376) +- Blank order details page - @mdanilowicz (#4382) +- upadate cart hash after sync with backend - @gibkigonzo (#4387) +- exit from errorHandler after redirection - @gibkigonzo (#4246) +- add redirection in component for simple product related to configurable product - @gibkigonzo (#4359) +- disable sending carrier_code or method_code for virtual products, + adjust vue-carousel and vuelidate to newest versions api, + add aplha validators for register fields - @gibkigonzo (#4455, #4461) + ## [1.11.3] - 2020.04.27 ### Changed / Improved diff --git a/core/compatibility/components/blocks/Wishlist/Wishlist.js b/core/compatibility/components/blocks/Wishlist/Wishlist.js index dc474a6604..97d9235b75 100644 --- a/core/compatibility/components/blocks/Wishlist/Wishlist.js +++ b/core/compatibility/components/blocks/Wishlist/Wishlist.js @@ -13,7 +13,7 @@ export default { methods: { // theme-specific onEscapePress () { - this.closeWishlist() + this.$store.dispatch('ui/closeWishlist') } }, mixins: [ Wishlist, onEscapePress ] diff --git a/core/i18n/package.json b/core/i18n/package.json index af4ce35a14..2f260f8c43 100644 --- a/core/i18n/package.json +++ b/core/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@vue-storefront/i18n", - "version": "1.11.3", + "version": "1.11.4", "description": "Vue Storefront i18n", "license": "MIT", "main": "index.ts", diff --git a/core/lib/sync/index.ts b/core/lib/sync/index.ts index 7f57ef6916..999d37b7af 100644 --- a/core/lib/sync/index.ts +++ b/core/lib/sync/index.ts @@ -9,7 +9,7 @@ import Task from '@vue-storefront/core/lib/sync/types/Task' import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -/** Syncs given task. If user is offline requiest will be sent to the server after restored connection */ +/** Syncs given task. If user is offline request will be sent to the server after restored connection */ async function queue (task) { const tasksCollection = StorageManager.get('syncTasks') task = _prepareTask(task) diff --git a/core/lib/types.ts b/core/lib/types.ts index 507806fab7..ed2dc34b31 100644 --- a/core/lib/types.ts +++ b/core/lib/types.ts @@ -20,7 +20,9 @@ export interface StoreView { index: string }, tax: { - sourcePriceIncludesTax: boolean, + sourcePriceIncludesTax?: boolean, + finalPriceIncludesTax?: boolean, + deprecatedPriceFieldsSupport?: boolean, defaultCountry: string, defaultRegion: null | string, calculateServerSide: boolean, diff --git a/core/modules/cart/helpers/createShippingInfoData.ts b/core/modules/cart/helpers/createShippingInfoData.ts index e42b0f19ff..8395dcd816 100644 --- a/core/modules/cart/helpers/createShippingInfoData.ts +++ b/core/modules/cart/helpers/createShippingInfoData.ts @@ -6,8 +6,8 @@ const createShippingInfoData = (methodsData) => ({ billingAddress: { ...(methodsData.billingAddress ? methodsData.billingAddress : {}) }, - shippingCarrierCode: methodsData.carrier_code, - shippingMethodCode: methodsData.method_code + ...(methodsData.carrier_code ? { shippingCarrierCode: methodsData.carrier_code } : {}), + ...(methodsData.method_code ? { shippingMethodCode: methodsData.method_code } : {}) }); export default createShippingInfoData diff --git a/core/modules/cart/helpers/getProductConfiguration.ts b/core/modules/cart/helpers/getProductConfiguration.ts index 901a9edd55..df5a9b8f97 100644 --- a/core/modules/cart/helpers/getProductConfiguration.ts +++ b/core/modules/cart/helpers/getProductConfiguration.ts @@ -6,8 +6,8 @@ const ATTRIBUTES = ['color', 'size'] const getProductConfiguration = (product: CartItem): ProductConfiguration => { const options = getProductOptions(product) - const getAttributesFields = (attributeCode) => - options[attributeCode].find(c => c.id === parseInt(product[attributeCode])) + const getAttributesFields = (attributeCode) => + (options[attributeCode] || []).find(c => c.id === parseInt(product[attributeCode])) if (!options) { return null diff --git a/core/modules/cart/store/actions/itemActions.ts b/core/modules/cart/store/actions/itemActions.ts index fcaf2d6ddb..3ce9518e90 100644 --- a/core/modules/cart/store/actions/itemActions.ts +++ b/core/modules/cart/store/actions/itemActions.ts @@ -9,6 +9,7 @@ import { notifications } from '@vue-storefront/core/modules/cart/helpers' import { cartHooksExecutors } from './../../hooks' +import config from 'config' const itemActions = { async configureItem (context, { product, configuration }) { @@ -62,7 +63,7 @@ const itemActions = { if (errors.length === 0) { const { status, onlineCheckTaskId } = await dispatch('checkProductStatus', { product }) - if (status === 'volatile') { + if (status === 'volatile' && !config.stock.allowOutOfStockInCart) { diffLog.pushNotification(notifications.unsafeQuantity()) } if (status === 'out_of_stock') { diff --git a/core/modules/cart/store/actions/synchronizeActions.ts b/core/modules/cart/store/actions/synchronizeActions.ts index eb625f634f..da181d8950 100644 --- a/core/modules/cart/store/actions/synchronizeActions.ts +++ b/core/modules/cart/store/actions/synchronizeActions.ts @@ -75,9 +75,10 @@ const synchronizeActions = { Logger.error(result, 'cart') cartHooksExecutors.afterSync(result) + commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) return createDiffLog() }, - async stockSync ({ dispatch, commit }, stockTask) { + async stockSync ({ dispatch, commit, getters }, stockTask) { const product = { sku: stockTask.product_sku } const cartItem = await dispatch('getItem', { product }) @@ -102,6 +103,7 @@ const synchronizeActions = { product: { info: { stock: i18n.t('In stock!') }, sku: stockTask.product_sku, is_in_stock: true } }) EventBus.$emit('cart-after-itemchanged', { item: cartItem }) + commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) } } diff --git a/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts b/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts index ed2bfbfc80..3fbcf461f3 100644 --- a/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts +++ b/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts @@ -76,4 +76,17 @@ describe('Cart createShippingInfoData', () => { shippingMethodCode: 'YY' }); }); + + it('doesn\t add shippingCarrierCode or shippingMethodCode if missing carrier_code or method_code', async () => { + const methodsData = { + country: 'UK' + }; + const shippingInfoData = createShippingInfoData(methodsData); + expect(shippingInfoData).toEqual({ + billingAddress: {}, + shippingAddress: { + countryId: 'UK' + } + }); + }); }); diff --git a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts index 0ff0734020..ae866ecb0e 100644 --- a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts +++ b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts @@ -224,7 +224,11 @@ describe('Cart synchronizeActions', () => { config.cart = { synchronize: false } - const contextMock = createContextMock(); + const contextMock = createContextMock({ + getters: { + getCurrentCartHash: 'zyx' + } + }); (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => product) diff --git a/core/modules/catalog-next/store/category/getters.ts b/core/modules/catalog-next/store/category/getters.ts index 8b5bde50b8..57d2e79a54 100644 --- a/core/modules/catalog-next/store/category/getters.ts +++ b/core/modules/catalog-next/store/category/getters.ts @@ -14,7 +14,7 @@ import { getFiltersFromQuery } from '../../helpers/filterHelpers' import { Category } from '../../types/Category' import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers' import { _prepareCategoryPathIds, getSearchOptionsFromRouteParams } from '../../helpers/categoryHelpers'; -import { removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore' +import { currentStoreView, removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore' import cloneDeep from 'lodash-es/cloneDeep' function mapCategoryProducts (productsFromState, productsData) { @@ -77,8 +77,8 @@ const getters: GetterTree = { }); filters[attrToFilter] = filterOptions.sort(compareByLabel) } else { // special case is range filter for prices - const storeView = rootState.storeView - const currencySign = storeView.i18n.currencySign + const currencySign = currentStoreView().i18n.currencySign + if (aggregations['agg_range_' + attrToFilter]) { let index = 0 let count = aggregations['agg_range_' + attrToFilter].buckets.length diff --git a/core/modules/catalog/events.ts b/core/modules/catalog/events.ts index fe2f0ded64..f99f03716b 100644 --- a/core/modules/catalog/events.ts +++ b/core/modules/catalog/events.ts @@ -2,6 +2,12 @@ import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' import { PRODUCT_SET_CURRENT_CONFIGURATION, PRODUCT_SET_CURRENT } from './store/product/mutation-types' import omit from 'lodash-es/omit' import config from 'config' +import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader'; +import { currentStoreView } from '@vue-storefront/core/lib/multistore'; +import { formatProductLink } from '@vue-storefront/core/modules/url/helpers'; +import { Logger } from '@vue-storefront/core/lib/logger'; +import { isServer } from '@vue-storefront/core/helpers'; +import { router } from '@vue-storefront/core/app' // Listeners moved from Product.js @@ -110,3 +116,23 @@ export const onUserPricesRefreshed = async (store, router) => { }, { root: true }) } } + +export const checkParentRedirection = (currentProduct, parentProduct) => { + if (parentProduct && parentProduct.id !== currentProduct.id && config.products.preventConfigurableChildrenDirectAccess) { + Logger.log('Redirecting to parent, configurable product', parentProduct.sku)() + parentProduct.parentSku = parentProduct.sku + parentProduct.sku = currentProduct.sku + const parentUrl = formatProductLink(parentProduct, currentStoreView().storeCode) + if (isServer) { + AsyncDataLoader.push({ + execute: async ({ context }) => { + if (context && !context.url.includes(parentUrl)) { + context.server.response.redirect(301, parentUrl) + } + } + }) + } else { + router.replace(parentUrl as string) + } + } +} diff --git a/core/modules/catalog/store/product/actions.ts b/core/modules/catalog/store/product/actions.ts index 26a1363af7..0f0c7e9fef 100644 --- a/core/modules/catalog/store/product/actions.ts +++ b/core/modules/catalog/store/product/actions.ts @@ -32,6 +32,7 @@ import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' import { StorageManager } from '@vue-storefront/core/lib/storage-manager' import { quickSearchByQuery } from '@vue-storefront/core/lib/search' import { formatProductLink } from 'core/modules/url/helpers' +import { checkParentRedirection } from '@vue-storefront/core/modules/catalog/events' const PRODUCT_REENTER_TIMEOUT = 20000 @@ -640,7 +641,12 @@ const actions: ActionTree = { throw new Error(`Product query returned empty result product status = ${product.status}`) } if (product.visibility === 1) { // not visible individually (https://magento.stackexchange.com/questions/171584/magento-2-table-name-for-product-visibility) - throw new Error(`Product query returned empty result product visibility = ${product.visibility}`) + if (config.products.preventConfigurableChildrenDirectAccess) { + const parentProduct = await dispatch('findConfigurableParent', { product }) + checkParentRedirection(product, parentProduct) + } else { + throw new Error(`Product query returned empty result product visibility = ${product.visibility}`) + } } await dispatch('loadProductAttributes', { product }) @@ -651,12 +657,6 @@ const actions: ActionTree = { syncPromises.push(variantsFilter) syncPromises.push(gallerySetup) } - if (config.products.preventConfigurableChildrenDirectAccess) { - const parentChecker = dispatch('checkConfigurableParent', { product }) - if (isServer) { - syncPromises.push(parentChecker) - } - } await Promise.all(syncPromises) await EventBus.$emitFilter('product-after-load', { store: rootStore, route: route }) return product diff --git a/core/modules/catalog/store/tax/actions.ts b/core/modules/catalog/store/tax/actions.ts index 8847c2fa5e..72faad3ea1 100644 --- a/core/modules/catalog/store/tax/actions.ts +++ b/core/modules/catalog/store/tax/actions.ts @@ -11,6 +11,7 @@ import config from 'config' import { calculateProductTax } from '@vue-storefront/core/modules/catalog/helpers/taxCalc' import { doPlatformPricesSync } from '@vue-storefront/core/modules/catalog/helpers' import { catalogHooksExecutors } from './../../hooks' +import { currentStoreView } from '@vue-storefront/core/lib/multistore'; const actions: ActionTree = { async list ({ state, commit, dispatch }, { entityType = 'taxrule' }) { @@ -48,6 +49,8 @@ const actions: ActionTree = { return doPlatformPricesSync(mutatedProducts) } + let storeView = currentStoreView() + const tcs = await dispatch('list', {}) const { defaultCountry, @@ -55,7 +58,7 @@ const actions: ActionTree = { sourcePriceIncludesTax, finalPriceIncludesTax, deprecatedPriceFieldsSupport - } = rootState.storeView.tax + } = storeView.tax const recalculatedProducts = mutatedProducts.map(product => calculateProductTax({ diff --git a/core/modules/catalog/store/tax/getters.ts b/core/modules/catalog/store/tax/getters.ts index 1fc26780aa..aa65d66f74 100644 --- a/core/modules/catalog/store/tax/getters.ts +++ b/core/modules/catalog/store/tax/getters.ts @@ -1,6 +1,7 @@ import { GetterTree } from 'vuex' import RootState from '@vue-storefront/core/types/RootState' import TaxState from '../../types/TaxState' +import { currentStoreView } from '@vue-storefront/core/lib/multistore'; const getters: GetterTree = { getRules: (state) => state.rules, @@ -17,7 +18,7 @@ const getters: GetterTree = { return currentUser.group_id }, getIsUserGroupedTaxActive: (state, getters, rootState) => { - return typeof rootState.storeView.tax.userGroupId === 'number' + return typeof currentStoreView().tax.userGroupId === 'number' } } diff --git a/core/package.json b/core/package.json index c7c7654453..5f5cf8f6d2 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@vue-storefront/core", - "version": "1.11.3", + "version": "1.11.4", "description": "Vue Storefront Core", "license": "MIT", "main": "app.js", @@ -16,7 +16,7 @@ "lean-he": "^2.0.0", "localforage": "^1.7.2", "lodash-es": "^4.17", - "lru-cache": "^4.0.1", + "lru-cache": "^5.1.1", "query-string": "^6.2.0", "redis-tag-cache": "^1.2.1", "remove-accents": "^0.4.2", diff --git a/core/scripts/all.js b/core/scripts/all.js index 37dba7b24b..a7f6a39306 100644 --- a/core/scripts/all.js +++ b/core/scripts/all.js @@ -23,7 +23,7 @@ class Manager extends installer.Manager { */ initStorefront () { return this.storefront.goToDirectory() - .then(this.storefront.npmBuild.bind(this.storefront)) + .then(this.storefront.depBuild.bind(this.storefront)) .then(this.storefront.runDevEnvironment.bind(this.storefront)) } diff --git a/core/scripts/installer.js b/core/scripts/installer.js index e7a2f72ec8..21e6d35ad3 100644 --- a/core/scripts/installer.js +++ b/core/scripts/installer.js @@ -168,16 +168,16 @@ class Backend extends Abstract { } /** - * Run 'npm install' in backend directory + * Run 'yarn install' in backend directory * * @returns {Promise} */ - npmInstall () { + depInstall () { return new Promise((resolve, reject) => { - Message.info('Installing backend npm...') + Message.info('Installing backend dep...') - if (shell.exec(`npm i >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t install backend npm.')) + if (shell.exec(`yarn >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { + reject(new Error('Can\'t install backend dep.')) } resolve() @@ -280,7 +280,7 @@ class Backend extends Abstract { } /** - * Run 'npm run restore' + * Run 'yarn restore' * * @returns {Promise} */ @@ -288,7 +288,7 @@ class Backend extends Abstract { return new Promise((resolve, reject) => { Message.info('Restoring data for ElasticSearch...') - if (shell.exec(`npm run restore >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { + if (shell.exec(`yarn restore >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { reject(new Error('Can\'t restore data for ElasticSearch.')) } @@ -297,7 +297,7 @@ class Backend extends Abstract { } /** - * Run 'npm run migrate' + * Run 'yarn migrate' * * @returns {Promise} */ @@ -305,7 +305,7 @@ class Backend extends Abstract { return new Promise((resolve, reject) => { Message.info('Migrating data into ElasticSearch...') - if (shell.exec(`npm run migrate >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { + if (shell.exec(`yarn migrate >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { reject(new Error('Can\'t migrate data into ElasticSearch.')) } @@ -348,7 +348,7 @@ class Backend extends Abstract { } /** - * Start 'npm run dev' in background + * Start 'yarn dev' in background * * @returns {Promise} */ @@ -357,11 +357,11 @@ class Backend extends Abstract { Message.info('Starting backend server...') if (isWindows()) { - if (shell.exec(`start /min npm run dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { + if (shell.exec(`start /min yarn dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { reject(new Error('Can\'t start dev server.', VUE_STOREFRONT_BACKEND_LOG_FILE)) } } else { - if (shell.exec(`nohup npm run dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { + if (shell.exec(`nohup yarn dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { reject(new Error('Can\'t start dev server.', VUE_STOREFRONT_BACKEND_LOG_FILE)) } } @@ -472,16 +472,16 @@ class Storefront extends Abstract { } /** - * Run 'npm run build' on storefront + * Run 'yarn build' on storefront * * @returns {Promise} */ - npmBuild () { + depBuild () { return new Promise((resolve, reject) => { - Message.info('Build storefront npm...') + Message.info('Build storefront dep...') - if (shell.exec(`npm run build > ${Abstract.storefrontLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t build storefront npm.', VUE_STOREFRONT_LOG_FILE)) + if (shell.exec(`yarn build > ${Abstract.storefrontLogStream} 2>&1`).code !== 0) { + reject(new Error('Can\'t build storefront dep.', VUE_STOREFRONT_LOG_FILE)) } resolve() @@ -489,7 +489,7 @@ class Storefront extends Abstract { } /** - * Start 'npm run dev' in background + * Start 'yarn dev' in background * * @returns {Promise} */ @@ -498,11 +498,11 @@ class Storefront extends Abstract { Message.info('Starting storefront server...') if (isWindows()) { - if (shell.exec(`start /min npm run dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { + if (shell.exec(`start /min yarn dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { reject(new Error('Can\'t start storefront server.', VUE_STOREFRONT_LOG_FILE)) } } else { - if (shell.exec(`nohup npm run dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { + if (shell.exec(`nohup yarn dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { reject(new Error('Can\'t start storefront server.', VUE_STOREFRONT_LOG_FILE)) } } @@ -574,7 +574,7 @@ class Manager extends Abstract { return this.backend.validateM2Integration() .then(this.backend.cloneRepository.bind(this.backend)) .then(this.backend.goToDirectory.bind(this.backend)) - .then(this.backend.npmInstall.bind(this.backend)) + .then(this.backend.depInstall.bind(this.backend)) .then(this.backend.createConfig.bind(this.backend)) .then(this.backend.dockerComposeUp.bind(this.backend)) .then(this.backend.importElasticSearch.bind(this.backend)) @@ -582,7 +582,7 @@ class Manager extends Abstract { } else { return this.backend.cloneRepository() .then(this.backend.goToDirectory.bind(this.backend)) - .then(this.backend.npmInstall.bind(this.backend)) + .then(this.backend.depInstall.bind(this.backend)) .then(this.backend.createConfig.bind(this.backend)) .then(this.backend.dockerComposeUp.bind(this.backend)) .then(this.backend.restoreElasticSearch.bind(this.backend)) @@ -603,7 +603,7 @@ class Manager extends Abstract { initStorefront () { return this.storefront.goToDirectory() .then(this.storefront.createConfig.bind(this.storefront)) - .then(this.storefront.npmBuild.bind(this.storefront)) + .then(this.storefront.depBuild.bind(this.storefront)) .then(this.storefront.runDevEnvironment.bind(this.storefront)) } diff --git a/core/scripts/server.ts b/core/scripts/server.ts index 0976f2199e..c8dcf54d43 100755 --- a/core/scripts/server.ts +++ b/core/scripts/server.ts @@ -156,18 +156,16 @@ app.get('*', (req, res, next) => { const errorHandler = err => { if (err && err.code === 404) { if (NOT_ALLOWED_SSR_EXTENSIONS_REGEX.test(req.url)) { - apiStatus(res, 'Vue Storefront: Resource is not found', 404) console.error(`Resource is not found : ${req.url}`) - next() + return apiStatus(res, 'Vue Storefront: Resource is not found', 404) } else { - res.redirect('/page-not-found') console.error(`Redirect for resource not found : ${req.url}`) + return res.redirect('/page-not-found') } } else { - res.redirect('/error') console.error(`Error during render : ${req.url}`) console.error(err) - next() + return res.redirect('/error') } } @@ -221,10 +219,10 @@ app.get('*', (req, res, next) => { isProd }) - if (typeof afterOutputRenderedResponse.output === 'string') { - res.end(afterOutputRenderedResponse.output) - } else if (typeof afterOutputRenderedResponse === 'string') { + if (typeof afterOutputRenderedResponse === 'string') { res.end(afterOutputRenderedResponse) + } else if (typeof afterOutputRenderedResponse.output === 'string') { + res.end(afterOutputRenderedResponse.output) } else { res.end(output) } diff --git a/core/scripts/utils/ssr-renderer.js b/core/scripts/utils/ssr-renderer.js index ad3e2efc2b..8694fe9d72 100644 --- a/core/scripts/utils/ssr-renderer.js +++ b/core/scripts/utils/ssr-renderer.js @@ -10,11 +10,12 @@ const config = require('config') const minify = require('html-minifier').minify function createRenderer (bundle, clientManifest, template) { + const LRU = require('lru-cache') // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer return require('vue-server-renderer').createBundleRenderer(bundle, { clientManifest, // runInNewContext: false, - cache: require('lru-cache')({ + cache: new LRU({ max: 1000, maxAge: 1000 * 60 * 15 }) diff --git a/docs/guide/basics/recipes.md b/docs/guide/basics/recipes.md index d707b89c8d..1e42d6fa21 100644 --- a/docs/guide/basics/recipes.md +++ b/docs/guide/basics/recipes.md @@ -183,17 +183,6 @@ To make it work, you need have Magento 2 OAuth keys configured in your `vue-stor After this change, you need to restart the `yarn dev` command to take the config changes into consideration by the VS. All the cart actions (add to cart, remove from cart, modify the quantity) are now synchronized directly with Magento 2 for both guest and logged-in clients. -## How to prevent an error "Can’t build storefront npm" - -The error, "Can't build storefront npm", appears because npm can't automatically install required modules. To prevent this error, you should manually install those modules before running the installer. It's easy: - -```bash -git clone https://github.com/DivanteLtd/vue-storefront.git vue-storefront && cd vue-storefront -npm install -npm install vue-carousel vue-no-ssr -npm run build # check if no errors -npm run installer -``` ## How to integrate 3rd party platform? Do you think it could be used with a legacy bespoke PHP eCommerce? @@ -240,7 +229,7 @@ If you would like to have a Category filter working with configurable products, There is an SEO redirects generator for NGINX -> `https://serverfault.com/a/441517` available within the [vue-storefront-api](https://github.com/DivanteLtd/vue-storefront-api/commit/2c7e10b4c4294f222f7a1aae96627d6a0e23f30e). Now you can generate an SEO map redirecting users from the original Magento URLs to Vue Storefront URLs by running: ```bash -npm run seo redirects — —oldFormat=true | false +yarn seo redirects — —oldFormat=true | false ``` Please make sure that `vue-storefront/config/local.json` setting of `useMagentoUrlKeys` is set to `true` and you have ElasticSearch synchronized with the Magento2 instance using the current version of [mage2vuestorefront](https://github.com/DivanteLtd/mage2vuestorefront). diff --git a/docs/guide/basics/ssr-cache.md b/docs/guide/basics/ssr-cache.md index e79df35abb..7da4fbffe5 100644 --- a/docs/guide/basics/ssr-cache.md +++ b/docs/guide/basics/ssr-cache.md @@ -69,10 +69,10 @@ We strongly recommend you DO NOT USE output cache in development mode. By using You can manually clear the Redis cache for specific tags by running the following command: ```bash -npm run cache clear -npm run cache clear -- --tag=product,category -npm run cache clear -- --tag=P198 -npm run cache clear -- --tag=* +yarn cache clear +yarn cache clear -- --tag=product,category +yarn cache clear -- --tag=P198 +yarn cache clear -- --tag=* ``` **Note:** The commands presented above works exactly the same way in the `vue-storefront-api`. diff --git a/docs/guide/cookbook/data-import.md b/docs/guide/cookbook/data-import.md index 60fa01cbdb..f31590ab4a 100644 --- a/docs/guide/cookbook/data-import.md +++ b/docs/guide/cookbook/data-import.md @@ -22,7 +22,7 @@ Vue Storefront uses a data-migration mechanism based on [node-migrate](https://g ### 2. Recipe 1. Run a node script from **Vue Storefront API root path** which is configured out of the box. ```bash -npm run migrate +yarn migrate ``` which runs the migrations in `migrations` folder. @@ -370,7 +370,7 @@ We worked in the red rectangle part of the architecture as a preparation for dat What we did in a simple term, we taught Elasticsearch types and sorts of data(mapping, also known as schema) we will use for Vue Storefront API later on. -Upon running `npm run migrate`, it runs the pre-configured [migration scripts](https://github.com/DivanteLtd/vue-storefront-api/tree/master/migrations) using [node-migrate](https://github.com/tj/node-migrate). If you take a closer look into the migration scripts, you will notice the ultimate js file which is located at [`./src/lib/elastic.js`](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/lib/elastic.js) that does the actual labor for migration. +Upon running `yarn migrate`, it runs the pre-configured [migration scripts](https://github.com/DivanteLtd/vue-storefront-api/tree/master/migrations) using [node-migrate](https://github.com/tj/node-migrate). If you take a closer look into the migration scripts, you will notice the ultimate js file which is located at [`./src/lib/elastic.js`](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/lib/elastic.js) that does the actual labor for migration. If you take one more closer look in the `elastic.js` file, you will also find all the schema files are located under [`./config`](https://github.com/DivanteLtd/vue-storefront-api/tree/master/config) folder. What those scripts do can be divided into steps as per the file name. It first creates index from index schema, then import schema from `elastic.schema.[types].json` files. It will then reindex them, and delete temporary index. Finally it will work a few workarounds to deal with deprecated process. @@ -386,14 +386,14 @@ If you encountered with the exception as follows during the migration script : It means you don't have the temporary index `vue_storefront_catalog_temp` which is required. Solution is : ```bash -npm run restore +yarn restore ``` This will create the necessary temporary index, then the necessary temp index will be deleted by the steps mentioned [above](#_3-peep-into-the-kitchen-what-happens-internally) when the migration is finished #### Secret 2. Add a new migration script You might need to write your own migration script. In that case, you can do so by adding a file under the `./migrations` directory though this is not a recommended way. `node-migrate` provides you with the cli command for the purpose as follows : ```bash -npm run migrate create name-of-migration +yarn migrate create name-of-migration ``` This wil create a migration script template under `./migration` folder with the standard naming convention. [more info](https://github.com/tj/node-migrate#creating-migrations) @@ -438,7 +438,7 @@ module.exports.down = function(next) { ``` #### Secret 3. Execute migration multiple times -If you run a migration multiple times using `npm run migrate`, it will only run the migration once and subsequent execution will be ignored and only repeat the result as follows : +If you run a migration multiple times using `yarn migrate`, it will only run the migration once and subsequent execution will be ignored and only repeat the result as follows : ![migration complete](../images/npm-run-migrate-result.png) @@ -725,7 +725,7 @@ node --harmony cli.js pages 7. Finally, reindex the Elasticsearch making sure up-to-date with data source in **Vue Storefront API** root path. ```bash -npm run db rebuild +yarn db rebuild ``` ### 3. Peep into the kitchen (what happens internally) diff --git a/docs/guide/cookbook/internals.md b/docs/guide/cookbook/internals.md index 966ca78c5e..da8efdae3d 100644 --- a/docs/guide/cookbook/internals.md +++ b/docs/guide/cookbook/internals.md @@ -22,7 +22,7 @@ Ever wonder what happens when you enter into **Vue Storefront** shop? From gatew 1. Go to **Vue Storefront** root path and run the following : ```bash -npm run dev +yarn dev ``` 2. Open your browser and go to your development **Vue Storefront** store, for example, [_http://localhost:3000_](http://localhost:3000) if you run it by default. diff --git a/docs/guide/cookbook/setup.md b/docs/guide/cookbook/setup.md index 24c2e4fdf4..12b0eba888 100644 --- a/docs/guide/cookbook/setup.md +++ b/docs/guide/cookbook/setup.md @@ -1362,7 +1362,7 @@ Upon the release of 1.10, we also present a new way of setup and all its sorts f We will continuously add new features to [`CLI`](https://www.npmjs.com/package/@vue-storefront/cli) as the version goes up. ### 1. Preparation -- You need to have installed [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) on your machine. (or [`yarn`](https://yarnpkg.com/lang/en/docs/install/#debian-stable) if you chose it) +- You need to have installed [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) on your machine and [`yarn`](https://yarnpkg.com/lang/en/docs/install/#debian-stable). ### 2. Recipe 1. Install _Vue Storefront CLI_ package on your machine with `-g` flag as follows : diff --git a/docs/guide/data/data-migrations.md b/docs/guide/data/data-migrations.md index fa2080a43e..14019fefd2 100644 --- a/docs/guide/data/data-migrations.md +++ b/docs/guide/data/data-migrations.md @@ -11,7 +11,7 @@ Vue Storefront uses a data-migration mechanism based on [node-migrate](https://g We use node-migrate, which is pre-configured with npm, so we're using the following alias: ```bash -npm run migrate +yarn migrate ``` which runs the migrations against `migrations` folder. @@ -21,7 +21,7 @@ which runs the migrations against `migrations` folder. You can add a new migration by simply adding a file to the `migrations` directory (not recommended) or using the command line tool: ```bash -npm run migrate create name-of-my-migration +yarn migrate create name-of-my-migration ``` The tool automatically generates the file under the `migrations` folder. diff --git a/docs/guide/installation/production-setup.md b/docs/guide/installation/production-setup.md index 69ba382147..75a6ddcb7e 100644 --- a/docs/guide/installation/production-setup.md +++ b/docs/guide/installation/production-setup.md @@ -1,6 +1,6 @@ # Production setup -If you’d like to start developing sites using Vue Storefront, you should start with the [Installation guide](linux-mac.md). For development purposes, you'll likely use the `yarn install` / `npm run installer` sequence, which will set up Vue Storefront locally using the automated installer and prepared Docker images for having Elasticsearch and Redis support. +If you’d like to start developing sites using Vue Storefront, you should start with the [Installation guide](linux-mac.md). For development purposes, you'll likely use the `yarn install` sequence, which will set up Vue Storefront locally using the automated installer and prepared Docker images for having Elasticsearch and Redis support. Development mode means you're using a node.js-based server as HTTP service and running the app on the `3000` TCP port. As it's great for local testing, it's not recommended to use the installer and direct-user access to node.js in production configurations. @@ -412,7 +412,7 @@ You can easily dump your current VS index using the following command (your loca ```bash cd vue-storefront-api rm var/catalog.json -npm run dump +yarn dump ``` Now in the `var/catalog.json` you have your current database dump. Please transfer this file to the server—for example, using the following ssh command: @@ -426,9 +426,9 @@ Then, after logging in to your `prod.vuestorefront.io` server as a `vuestorefron ```bash cd vue-storefront-api -npm run db new -npm run restore2main -npm run db rebuild +yarn db new +yarn restore2main +yarn db rebuild ``` #### Running the Vue Storefront and Vue Storefront API diff --git a/docs/guide/integrations/direct-prices-sync.md b/docs/guide/integrations/direct-prices-sync.md index 176bbdf929..1db1b88f8d 100644 --- a/docs/guide/integrations/direct-prices-sync.md +++ b/docs/guide/integrations/direct-prices-sync.md @@ -28,4 +28,4 @@ To use this feature, you should also modify `config/local.json` within your `vue }, ``` -_Important note_: To use the dynamic Magento 2 prices sync, you should restore the database using `npm run restore` within the `vue-storefront-api` or re-run the `mage2vuestorefront` product sync, because an "ID" field has been added to the `configurable_children` products and it's required for the prices sync. +_Important note_: To use the dynamic Magento 2 prices sync, you should restore the database using `yarn restore` within the `vue-storefront-api` or re-run the `mage2vuestorefront` product sync, because an "ID" field has been added to the `configurable_children` products and it's required for the prices sync. diff --git a/docs/guide/integrations/multistore.md b/docs/guide/integrations/multistore.md index 09a3b58417..d7839c7796 100644 --- a/docs/guide/integrations/multistore.md +++ b/docs/guide/integrations/multistore.md @@ -57,9 +57,9 @@ In the result, you should get: Then, to use these indexes in Vue Storefront, you should index the database schema using the `vue-storefront-api` db tool: ```bash -npm run db rebuild -- --indexName=vue_storefront_catalog_it -npm run db rebuild -- --indexName=vue_storefront_catalog_de -npm run db rebuild -- --indexName=vue_storefront_catalog +yarn db rebuild -- --indexName=vue_storefront_catalog_it +yarn db rebuild -- --indexName=vue_storefront_catalog_de +yarn db rebuild -- --indexName=vue_storefront_catalog ``` ## Vue Storefront and Vue Storefront API configuration @@ -154,9 +154,9 @@ By default, the language / store is switched by the URL prefix: General URL format is: `http://localhost:3000/{storeCode}` -The storeCode may be switched by ENV variable set before running `npm run dev` / `npm start`: +The storeCode may be switched by ENV variable set before running `yarn dev` / `yarn start`: -- `export STORE_CODE=de && npm run dev` will run the shop with the `de` shop loaded +- `export STORE_CODE=de && yarn dev` will run the shop with the `de` shop loaded Another option, useful when using multistore mode with the NGINX/varnish mode, is to set the shop code by the `x-vs-store-code` http reqeuest header. diff --git a/docs/guide/integrations/totals-sync.md b/docs/guide/integrations/totals-sync.md index 330d6d055c..de95988d52 100644 --- a/docs/guide/integrations/totals-sync.md +++ b/docs/guide/integrations/totals-sync.md @@ -87,6 +87,6 @@ This process doesn't require much additional configuration: 1. You must have the Magento2 API access configures in the `config/local.json` file of `vue-storefront-api` 2. You must have the "Orders" section marked On within the "Permissions" section of Magento Integration ([see the previous tutorial for the reference on how to set it up](../installation/magento.md)). -3. After the configuration step You just run `npm run o2m` inside your `vue-storefront-api` directory. +3. After the configuration step You just run `yarn o2m` inside your `vue-storefront-api` directory. ![This is the output of o2m after successfull setup](../images/o2m-output.png) diff --git a/docs/guide/upgrade-notes/README.md b/docs/guide/upgrade-notes/README.md index c91a09a4ba..5034414a49 100644 --- a/docs/guide/upgrade-notes/README.md +++ b/docs/guide/upgrade-notes/README.md @@ -432,7 +432,7 @@ Now it mirrors `core/` folder structure, which is desired behaviour. We added the possibility to run the `vue-storefront-api` fully in Docker (previously, just the Elastic and Redis images were present in the `docker-compose.yml`. Please read the [README.md](https://github.com/DivanteLtd/vue-storefront-api) for more details. -**PLEASE NOTE:** We changed the structure of the `elasticsearch` section of the config files, moving `esIndexes` to `elasticsearch.indices` etc. There is an automatic migration that will update your config files automatically by running: `npm run migrate` in the `vue-storefront-api` folder. +**PLEASE NOTE:** We changed the structure of the `elasticsearch` section of the config files, moving `esIndexes` to `elasticsearch.indices` etc. There is an automatic migration that will update your config files automatically by running: `yarn migrate` in the `vue-storefront-api` folder. ### Default storage of the shopping carts and user data moved to localStorage diff --git a/docs/package.json b/docs/package.json index b7315a7711..f1a99307ae 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "@vue-storefront/docs", "private": true, - "version": "1.11.3", + "version": "1.11.4", "scripts": { "docs:dev": "vuepress dev", "docs:build": "vuepress build", diff --git a/package.json b/package.json index bf98970556..5d319408db 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-storefront", - "version": "1.11.3", + "version": "1.11.4", "description": "A Vue.js, PWA eCommerce frontend", "private": true, "engines": { diff --git a/src/themes/default-amp/package.json b/src/themes/default-amp/package.json index a04ddf3c35..dd8fc665db 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.3", + "version": "1.11.4", "description": "Default AMP theme for Vue Storefront", "main": "index.js", "scripts": { diff --git a/src/themes/default/components/core/ProductGalleryCarousel.vue b/src/themes/default/components/core/ProductGalleryCarousel.vue index 8232298bf0..88ca1bbe90 100644 --- a/src/themes/default/components/core/ProductGalleryCarousel.vue +++ b/src/themes/default/components/core/ProductGalleryCarousel.vue @@ -11,6 +11,7 @@ ref="carousel" :speed="carouselTransitionSpeed" @pageChange="pageChange" + :navigate-to="currentPage" > { result[attribute] = this.configuration[attribute].id @@ -137,8 +137,6 @@ export default { }, pageChange (index) { this.switchCarouselSpeed() - - this.currentPage = index this.hideImageAtIndex = null }, onVideoStarted (index) { diff --git a/src/themes/default/components/core/ProductGalleryZoomCarousel.vue b/src/themes/default/components/core/ProductGalleryZoomCarousel.vue index f35e85e910..0a93e61fcb 100644 --- a/src/themes/default/components/core/ProductGalleryZoomCarousel.vue +++ b/src/themes/default/components/core/ProductGalleryZoomCarousel.vue @@ -23,6 +23,7 @@ class="media-zoom-carousel__carousel" :speed="carouselTransitionSpeed" @pageChange="pageChange" + :navigate-to="currentPage" > @@ -61,10 +65,16 @@ v-model="lastName" @blur="$v.lastName.$touch()" :placeholder="$t('Last name *')" - :validations="[{ - condition: !$v.lastName.required && $v.lastName.$error, - text: $t('Field is required.') - }]" + :validations="[ + { + condition: !$v.lastName.required && $v.lastName.$error, + text: $t('Field is required.') + }, + { + condition: !$v.lastName.alpha && $v.lastName.$error, + text: $t('Accepts only alphabet characters.') + } + ]" /> {{ $t('I accept terms and conditions') }} * - + {{ $t('Register an account') }}
@@ -139,7 +149,7 @@ import Register from '@vue-storefront/core/compatibility/components/blocks/Auth/ import ButtonFull from 'theme/components/theme/ButtonFull.vue' import BaseCheckbox from 'theme/components/core/blocks/Form/BaseCheckbox.vue' import BaseInput from 'theme/components/core/blocks/Form/BaseInput.vue' -import { required, email, minLength, sameAs } from 'vuelidate/lib/validators' +import { required, email, minLength, sameAs, alpha } from 'vuelidate/lib/validators' export default { validations: { @@ -149,10 +159,12 @@ export default { }, firstName: { minLength: minLength(2), - required + required, + alpha }, lastName: { - required + required, + alpha }, password: { minLength: minLength(8), @@ -163,7 +175,7 @@ export default { sameAsPassword: sameAs('password') }, conditions: { - required + sameAs: sameAs(() => true) } }, mixins: [Register], diff --git a/src/themes/default/components/core/blocks/Checkout/OrderReview.vue b/src/themes/default/components/core/blocks/Checkout/OrderReview.vue index 0899de8bab..2731a02b45 100644 --- a/src/themes/default/components/core/blocks/Checkout/OrderReview.vue +++ b/src/themes/default/components/core/blocks/Checkout/OrderReview.vue @@ -42,7 +42,7 @@ @blur="$v.orderReview.terms.$touch()" v-model="orderReview.terms" :validations="[{ - condition: !$v.orderReview.terms.required && $v.orderReview.terms.$error, + condition: !$v.orderReview.terms.sameAs && $v.orderReview.terms.$error, text: $t('Field is required') }]" > @@ -108,7 +108,7 @@