diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..2e81d7877
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,8 @@
+[submodule "src/modules/vsf-cache-nginx"]
+ path = src/modules/vsf-cache-nginx
+ url = https://github.com/new-fantastic/vsf-cache-nginx.git
+ branch = master
+[submodule "src/modules/vsf-cache-varnish"]
+ path = src/modules/vsf-cache-varnish
+ url = https://github.com/new-fantastic/vsf-cache-varnish.git
+ branch = master
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 000000000..acf0da7d5
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,19 @@
+tasks:
+ - init: |
+ yarn install
+ echo '{ "api": { "url": "https://next.storefrontcloud.io" }}' > config/local.json
+ yarn build
+ command: yarn dev
+
+ports:
+ - port: 3000
+ onOpen: open-preview
+
+vscode:
+ extensions:
+ - octref.vetur@0.23.0:TEzauMObB6f3i2JqlvrOpA==
+ - dbaeumer.vscode-eslint@2.0.15:/v3eRFwBI38JLZJv5ExY5g==
+ - eg2.vscode-npm-script@0.3.11:peDPJqeL8FmmJiabU4fAJQ==
+ - formulahendry.auto-close-tag@0.5.6:oZ/8R2VhZEhkHsoeO57hSw==
+ - formulahendry.auto-rename-tag@0.1.1:lKCmLIZAiCM0M8AjDnwCLQ==
+ - dariofuzinato.vue-peek@1.0.2:oYJg0oZA/6FBnFfW599HRg==
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..f599e28b8
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+10
diff --git a/.travis.yml b/.travis.yml
index 6364ccfcd..da072b925 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,7 @@ cache:
- node_modules
install:
+ - git clone --quiet --single-branch --branch master https://github.com/DivanteLtd/vsf-default.git ./src/themes/default
- yarn
jobs:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72dbd429b..a9c6900c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,86 @@ 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.12.0] - 2020.06.01
+
+### Added
+
+- Add `vsf-capybara` support as a dependency and extend CLI to support customization - @psmyrek (#4209)
+- Support theme configuration via CLI - @psmyrek (#4395)
+- Allow parent_ids field on product as an alternative to urlpath based breadcrumb navigation (#4219)
+- Pass the original item_id when updating/deleting a cart entry @carlokok (#4218)
+- Separating endpoints for CSR/SSR - @Fifciu (#2861)
+- Added short hands for version and help flags - @jamesgeorge007 (#3946)
+- Add `or` operator for Elasticsearch filters in `quickSearchByQuery` and use exists if value is `null` - @cewald (#3960)
+- Add unified fetch in mappingFallback for all searched entities - @gibkigonzo (#3942)
+- add npm-run-all for parallel build - @gibkigonzo (#3819)
+- Add OutputCaching support for x-vs-store-code - @benjick (#3979)
+- The new search adapter `api-search-query` has been added. When you switch to it, by setting the `config.server.api = "api-search-query"` the ElasticSearch query is being built in the [`vue-storefront-api`](https://github.com/DivanteLtd/vue-storefront-api/pull/390) which saves around 400kB in the bundle size as `bodybuilder` is no longer needed in the frontend - @pkarw - #2167
+- This new `api-search-query` adapter supports the `response_format` query parameter which now is sent to the `/api/catalog` endpoint. Currently there is just one additional format supported: `response_format=compact`. When used, the response format got optimized by: a) remapping the results, removing the `_source` from the `hits.hits`; b) compressing the JSON fields names according to the `config.products.fieldsToCompact`; c) removing the JSON fields from the `product.configurable_children` when their values === parent product values; overall response size reduced over -70% - @pkarw
+- The `amp-renderer` module has been disabled by default to save the bundle size; If you'd like to enable it uncomment the module from the `src/modules` and uncomment the `product-amp` and `category-amp` links that are added to the `
` section in the `src/themes/default/Product.vue` and `src/themes/default/Category.vue`
+- Reset Password confirmation page - @Fifciu (#2576)
+- Add `Intl.NumberFormat()`/`toLocaleString()` via polyfill support in NodeJs - @cewald (#3836, #4040)
+- Added `saveBandwidthOverCache` parameter for skipping caching for products data - @andrzejewsky (#3706)
+- New zoom effect for product gallery images - @Michal-Dziedzinski (#2755)
+- Add custom currency separators and amount of fraction digits - @EndPositive (#3553)
+- Product Page Schema implementation as JSON-LD - @Michal-Dziedzinski (#3704)
+- Add `/cache-version.json` route to get current cache version
+- Built-in module for detecting device type based on UserAgent with SSR support - @Fifciu
+- Update to `storefront-query-builder` version `1.0.0` - @cewald (#4234)
+- Move generating files from webpack config to script @gibkigonzo (#4236)
+- Add correct type matching to `getConfigurationMatchLevel` - @cewald (#4241)
+- Support `useSpecificImagePaths` with `useExactUrlsNoProxy` - @cewald (#4243)
+- Adds module which handles cache invalidation for Fastly. - @gibkigonzo (#4096)
+- Add vsf-cache-nginx and vsf-cache-varnish modules - @gibkigonzo (#4096)
+- Added meta info for CMS pages from Magento @mdanilowicz (#4392)
+- Add useful core events to server & logger - @cewald (#4419)
+
+### Fixed
+
+- Fixed `resultPorcessor` typo - @psmyrek
+- Negative price has doubled minus sign - @psmyrek (#4353)
+- Fixed Search product fails for category filter when categoryId is string - @adityasharma7 (#3929)
+- Revert init filters in Vue app - @gibkigonzo (#3929)
+- All categories disappearing if you add the child category name to includeFields - @1070rik (#4015)
+- Fix overlapping text in PersonalDetails component - @jakubmakielkowski (#4024)
+- Redirect from checkout to home with a proper store code - @Fifciu
+- Added back error notification when user selects invalid configuration - @1070rik (#4033)
+- findConfigurableChildAsync - return best match for configurable variant - @gibkigonzo, @cewald (#4042, #4216)
+- use storeCode for mappingFallback url - @gibkigonzo (#4050)
+- `getVariantWithLowestPrice` uses inexistent `final_price` property - @cewald (#4091)
+- Fixed `NOT_ALLOWED_SSR_EXTENSIONS_REGEX` to only match with file extensions having a dot - @haelbichalex (#4100)
+- Fixed problem with not showing error message when placing an order fails - @qiqqq
+- Invoking afterCacheInvalidated server hook in a proper moment - @Fifciu (#4176)
+- Fixed `cart/isVirtualCart` to return `false` when cart is empty - @haelbichalex(#4182)
+- Use `setProductGallery` in `product/setCurrent` to use logic of the action - @cewald (#4153)
+- Use same data format in getConfigurationMatchLevel - @gibkigonzo (#4208)
+- removed possible memory leak in ssr - @resubaka (#4247)
+- Bugfix for reactivity of `current_configuration` in `populateProductConfigurationAsync` - @cewald (#4258)
+- Bugfix for build exception in Node v13.13+ - @cewald (#4249)
+- Convert option ids to string while comparing them in `getProductConfiguration` - @gibkigonzo (#4484)
+- change value to number in price filter - @gibkigonzo (#4478)
+
+### Changed / Improved
+
+- Optimized `translation.processor` to process only enabled locale CSV files - @pkarw (#3950)
+- Remove commit register mapping - @gibkigonzo (#3875)
+- Improved method `findConfigurableChildAsync` - find variant with lowest price - @gibkigonzo (#3939)
+- Removed `product/loadConfigurableAttributes` calls - @andrzejewsky (#3336)
+- Removed unused locales in disabled multistore - @gibkigonzo (#4072)
+- Optimized attributes loading - @andrzejewsky (#3948)
+- Cart optimization can now be used regardless if entity optimization is enabled - @juho-jaakkola (#4198)
+- Improve typescript support for test utils - @resubaka (#4067)
+- Removed `product/loadConfigurableAttributes` calls - @andrzejewsky, @gibkigonzo (#3336)
+- Disable `mapFallback` url by default - @gibkigonzo(#4092)
+- Include token in pricing sync - @carlokok (#4156)
+- Move 'graphql' search adapter from core to src (deprecated) - @gibkigonzo (#4214)
+- Homepage, new products query, uses now `new` attribute - @mdanilwoicz
+- Refactor product module, more info in upgrade notes- @gibkigonzo (#3952, #4459)
+- Move default theme to separate repository https://github.com/DivanteLtd/vsf-default - @gibkigonzo (#4255)
+- add two numbers after dot to price by default, calculate default price for bundle or grouped main product, update typing, add fallback to attribute options - @gibkigonzo (#4476)
+- udpate yarn and filter shipping methods for instant checkout - @gibkigonzo (#4480)
+- add attribute metadata search query, add parentId - @gibkigonzo (#4491)
+
## [1.11.4] - 2020.05.26
### Added
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 00130e0de..635fac2bf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,6 +3,10 @@
Already a JavaScript/Vue.js developer? Pick an issue, push a pull request (PR) and instantly become a member of the vue-storefront contributors community.
We've marked some issues as "Easy first pick" to make it easier for newcomers to begin!
+You can start a ready-to-code development environment in your browser, by clicking the button below:
+
+[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)
+
Thank you for your interest in, and engagement!
Before you type an issue please read about out [release lifecycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html).
diff --git a/README.md b/README.md
index 0855fdce5..8c44f38e3 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
![Branch Develop](https://img.shields.io/badge/community%20chat-slack-FF1493.svg)
+[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/)
Vue Storefront is a standalone PWA storefront for your eCommerce, possible to connect with any eCommerce backend (eg. Magento, Pimcore/CoreShop, [BigCommerce](https://github.com/DivanteLtd/bigcommerce2vuestorefront), Prestashop or Shopware) through the API.
diff --git a/config/default.json b/config/default.json
index bfb3c8a57..73bd7cc22 100644
--- a/config/default.json
+++ b/config/default.json
@@ -68,6 +68,20 @@
"config": {}
}
},
+ "initialResources": [
+ {
+ "filters": ["vsf-newsletter-modal", "vsf-languages-modal", "vsf-layout-empty", "vsf-layout-minimal", "vsf-order-confirmation", "vsf-search-panel"],
+ "type": "script",
+ "onload": true,
+ "rel": "prefetch"
+ },
+ {
+ "filters": ["vsf-category", "vsf-home", "vsf-not-found", "vsf-error", "vsf-product", "vsf-cms", "vsf-checkout", "vsf-compare", "vsf-my-account", "vsf-static", "vsf-reset-password"],
+ "type": "script",
+ "onload": true,
+ "rel": "prefetch"
+ }
+ ],
"staticPages": {
"updateOnRequest": true,
"destPath": "static"
@@ -91,7 +105,8 @@
"port": 8080
},
"api": {
- "url": "http://localhost:8080"
+ "url": "http://localhost:8080",
+ "saveBandwidthOverCache": true
},
"elasticsearch": {
"httpAuth": "",
@@ -284,12 +299,14 @@
"is_comparable",
"options",
"tier_prices"
- ]
+ ],
+ "loadByAttributeMetadata": false
},
"productList": {
"sort": "updated_at:desc",
"includeFields": [
"activity",
+ "configurable_children.attributes",
"configurable_children.id",
"configurable_children.final_price",
"configurable_children.color",
@@ -328,18 +345,37 @@
"*small_image"
],
"excludeFields": [
+ "attribute_set_id",
"configurable_options",
"description",
"sgn",
"*.sgn",
"msrp_display_actual_price_type",
"*.msrp_display_actual_price_type",
- "required_options"
+ "required_options",
+ "media_gallery",
+ "stock.use_config_min_qty",
+ "stock.use_config_notify_stock_qty",
+ "stock.stock_id",
+ "stock.use_config_backorders",
+ "stock.use_config_enable_qty_inc",
+ "stock.enable_qty_increments",
+ "stock.use_config_manage_stock",
+ "stock.use_config_min_sale_qty",
+ "stock.notify_stock_qty",
+ "stock.use_config_max_sale_qty",
+ "stock.use_config_max_sale_qty",
+ "stock.qty_increments",
+ "stock.stock_status_changed_auto",
+ "stock.show_default_notification_message",
+ "stock.use_config_qty_increments",
+ "stock.is_decimal_divided"
]
},
"productListWithChildren": {
"includeFields": [
"activity",
+ "configurable_children.attributes",
"configurable_children.image",
"configurable_children.sku",
"configurable_children.price",
@@ -380,12 +416,30 @@
"url_key"
],
"excludeFields": [
+ "attribute_set_id",
"description",
"sgn",
"*.sgn",
"msrp_display_actual_price_type",
"*.msrp_display_actual_price_type",
- "required_options"
+ "required_options",
+ "media_gallery",
+ "stock.use_config_min_qty",
+ "stock.use_config_notify_stock_qty",
+ "stock.stock_id",
+ "stock.use_config_backorders",
+ "stock.use_config_enable_qty_inc",
+ "stock.enable_qty_increments",
+ "stock.use_config_manage_stock",
+ "stock.use_config_min_sale_qty",
+ "stock.notify_stock_qty",
+ "stock.use_config_max_sale_qty",
+ "stock.use_config_max_sale_qty",
+ "stock.qty_increments",
+ "stock.stock_status_changed_auto",
+ "stock.show_default_notification_message",
+ "stock.use_config_qty_increments",
+ "stock.is_decimal_divided"
]
},
"review": {
@@ -519,6 +573,34 @@
"disablePersistentAttributesCache": false
},
"products": {
+ "fieldsToCompact": {
+ "minimal_price": "mp",
+ "has_options": "ho",
+ "url_key": "u",
+ "status": "s",
+ "required_options": "ro",
+ "name": "nm",
+ "tax_class_id": "tci",
+ "description": "desc",
+ "minimal_regular_price": "mrp",
+ "final_price": "fp",
+ "price": "p",
+ "special_price": "sp",
+ "original_final_price": "ofp",
+ "original_price": "op",
+ "original_special_price": "osp",
+ "final_price_incl_tax": "fpit",
+ "original_price_incl_tax": "opit",
+ "price_incl_tax": "pit",
+ "special_price_incl_tax": "spit",
+ "final_price_tax": "fpt",
+ "price_tax": "pt",
+ "special_price_tax": "spt",
+ "original_price_tax": "opt",
+ "image": "i",
+ "small_image": "si",
+ "thumbnail": "t"
+ },
"disablePersistentProductsCache": true,
"useMagentoUrlKeys": true,
"setFirstVarianAsDefaultInURL": false,
@@ -529,6 +611,7 @@
"listOutOfStockProducts": true,
"preventConfigurableChildrenDirectAccess": true,
"alwaysSyncPlatformPricesOver": false,
+ "alwaysSyncPricesClientSide": false,
"clearPricesBeforePlatformSync": false,
"waitForPlatformSync": false,
"setupVariantByAttributeCode": true,
@@ -625,9 +708,11 @@
},
"users": {
"autoRefreshTokens": true,
+ "loginAfterCreatePassword": true,
"endpoint": "/api/user",
"history_endpoint": "/api/user/order-history?token={{token}}&pageSize={{pageSize}}¤tPage={{currentPage}}",
"resetPassword_endpoint": "/api/user/reset-password",
+ "createPassword_endpoint": "http://localhost:8080/api/user/create-password",
"changePassword_endpoint": "/api/user/change-password?token={{token}}",
"login_endpoint": "/api/user/login",
"create_endpoint": "/api/user/create",
@@ -688,6 +773,9 @@
"defaultLocale": "en-US",
"currencyCode": "USD",
"currencySign": "$",
+ "currencyDecimal": "",
+ "currencyGroup": "",
+ "fractionDigits": 2,
"priceFormat": "{sign}{amount}",
"dateFormat": "HH:mm D/M/YYYY",
"fullCountryName": "United States",
@@ -756,8 +844,8 @@
"newProducts": {
"filter": [
{
- "key": "category.name",
- "value": { "eq": "Tees" }
+ "key": "new",
+ "value": { "eq": 1 }
}
]
},
@@ -769,5 +857,19 @@
}
]
}
+ },
+ "urlModule": {
+ "enableMapFallbackUrl": false,
+ "endpoint": "/api/url",
+ "map_endpoint": "/api/url/map"
+ },
+ "fastly": {
+ "enabled":false
+ },
+ "nginx": {
+ "enabled":false
+ },
+ "varnish": {
+ "enabled":false
}
}
diff --git a/core/app.ts b/core/app.ts
index 8dfb969e1..72fc80ed6 100755
--- a/core/app.ts
+++ b/core/app.ts
@@ -25,9 +25,10 @@ import { enabledModules } from './modules-entry'
import globalConfig from 'config'
import { injectReferences } from '@vue-storefront/core/lib/modules'
import { coreHooksExecutors } from '@vue-storefront/core/hooks'
-import { registerClientModules } from 'src/modules/client';
+import { registerClientModules } from 'src/modules/client'
import initialStateFactory from '@vue-storefront/core/helpers/initialStateFactory'
-import { createRouter, createRouterProxy } from '@vue-storefront/core/helpers/router';
+import { createRouter, createRouterProxy } from '@vue-storefront/core/helpers/router'
+import { checkForIntlPolyfill } from '@vue-storefront/i18n/intl'
const stateFactory = initialStateFactory(store.state)
@@ -49,7 +50,12 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
store.state.__DEMO_MODE__ = (config.demomode === true)
if (ssrContext) {
// @deprecated - we shouldn't share server context between requests
- Vue.prototype.$ssrRequestContext = { output: { cacheTags: ssrContext.output.cacheTags } }
+ Vue.prototype.$ssrRequestContext = {
+ output: {
+ cacheTags: ssrContext.output.cacheTags
+ },
+ userAgent: ssrContext.server.request.headers['user-agent']
+ }
Vue.prototype.$cacheTags = ssrContext.output.cacheTags
}
@@ -101,6 +107,8 @@ const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vu
registerModules(enabledModules, appContext)
registerTheme(globalConfig.theme, app, routerProxy, store, globalConfig, ssrContext)
+ await checkForIntlPolyfill(storeView)
+
coreHooksExecutors.afterAppInit()
// @deprecated from 2.0
EventBus.$emit('application-after-init', app)
diff --git a/core/build/theme-path.js b/core/build/theme-path.js
index 9e291c1b5..209ed6a32 100644
--- a/core/build/theme-path.js
+++ b/core/build/theme-path.js
@@ -12,7 +12,15 @@ if (detectInstalled.sync(config.theme, { local: true })) {
else {
themeName = themeName.replace('@vue-storefront/theme-', '')
themePath = path.resolve(__dirname, '../../src/themes/' + themeName)
- if(!fs.existsSync(themePath)) themePath = path.resolve(__dirname, '../../packages/theme-' + themeName)
+ if(!fs.existsSync(themePath)) {
+ console.error(`
+ The theme you want to use does not exist.
+ Please use 'vsf init' or install manualy one of our official themes:
+ - https://github.com/DivanteLtd/vsf-capybara#--installation
+ - https://github.com/DivanteLtd/vsf-default#--installation
+ `)
+ process.exit(1)
+ }
}
module.exports = themePath
diff --git a/core/build/webpack.base.config.ts b/core/build/webpack.base.config.ts
index 1cf00cb3e..a5002d33c 100644
--- a/core/build/webpack.base.config.ts
+++ b/core/build/webpack.base.config.ts
@@ -1,25 +1,19 @@
import { buildLocaleIgnorePattern } from './../i18n/helpers';
import path from 'path';
-import config from 'config';
import fs from 'fs';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import VueLoaderPlugin from 'vue-loader/lib/plugin';
import autoprefixer from 'autoprefixer';
import HTMLPlugin from 'html-webpack-plugin';
-// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
import webpack from 'webpack';
import dayjs from 'dayjs';
-fs.writeFileSync(
- path.resolve(__dirname, './config.json'),
- JSON.stringify(config)
-)
+// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// eslint-disable-next-line import/first
import themeRoot from './theme-path';
const themesRoot = '../../src/themes'
-const moduleRoot = path.resolve(__dirname, '../../src/modules')
const themeResources = themeRoot + '/resource'
const themeCSS = themeRoot + '/css'
const themeApp = themeRoot + '/App.vue'
@@ -28,23 +22,6 @@ const themedIndexMinimal = path.join(themeRoot, '/templates/index.minimal.templa
const themedIndexBasic = path.join(themeRoot, '/templates/index.basic.template.html')
const themedIndexAmp = path.join(themeRoot, '/templates/index.amp.template.html')
-const csvDirectories = [
- path.resolve(__dirname, '../../node_modules/@vue-storefront/i18n/resource/i18n/')
-]
-
-fs.readdirSync(moduleRoot).forEach(directory => {
- const dirName = moduleRoot + '/' + directory + '/resource/i18n'
-
- if (fs.existsSync(dirName)) {
- csvDirectories.push(dirName);
- }
-});
-
-csvDirectories.push(path.resolve(__dirname, themeResources + '/i18n/'));
-
-const translationPreprocessor = require('@vue-storefront/i18n/scripts/translation.preprocessor.js')
-translationPreprocessor(csvDirectories, config)
-
const postcssConfig = {
loader: 'postcss-loader',
options: {
@@ -63,9 +40,9 @@ export default {
plugins: [
new webpack.ContextReplacementPlugin(/dayjs[/\\]locale$/, buildLocaleIgnorePattern()),
new webpack.ProgressPlugin(),
- // new BundleAnalyzerPlugin({
- // generateStatsFile: true
- // }),
+ /* new BundleAnalyzerPlugin({
+ generateStatsFile: true
+ }), */
new CaseSensitivePathsPlugin(),
new VueLoaderPlugin(),
// generate output HTML
diff --git a/core/build/webpack.client.config.ts b/core/build/webpack.client.config.ts
index 6a3d71fea..553d7d8ec 100644
--- a/core/build/webpack.client.config.ts
+++ b/core/build/webpack.client.config.ts
@@ -9,9 +9,11 @@ const config = merge(base, {
splitChunks: {
cacheGroups: {
commons: {
- test: /[\\/]node_modules[\\/](vue|vuex|vue-router|vue-meta|vue-i18n|vuex-router-sync|localforage|lean-he|vue-lazyload|js-sha3|dayjs|core-js|whatwg-fetch|vuelidate)[\\/]/,
+ // create 'vendor' group from initial packages from node_modules
+ test: /node_modules/,
name: 'vendor',
- chunks: 'all'
+ chunks: 'initial',
+ priority: 1
}
}
},
diff --git a/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js b/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js
index 5084b2c7a..a92416ec2 100644
--- a/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js
+++ b/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js
@@ -8,7 +8,10 @@ export default {
name: 'SidebarMenu',
mixins: [onEscapePress, CompareButton],
computed: {
- ...mapGetters('category', ['getCategories']),
+ ...mapGetters('category-next', ['getMenuCategories']),
+ getCategories () {
+ return this.getMenuCategories
+ },
categories () {
return this.getCategories.filter((op) => {
return op.level === (config.entities.category.categoriesDynamicPrefetchLevel >= 0 ? config.entities.category.categoriesDynamicPrefetchLevel : 2) // display only the root level (level =1 => Default Category), categoriesDynamicPrefetchLevel = 2 by default
diff --git a/core/data-resolver/CartService.ts b/core/data-resolver/CartService.ts
index 97b9b9333..770d2a729 100644
--- a/core/data-resolver/CartService.ts
+++ b/core/data-resolver/CartService.ts
@@ -1,3 +1,4 @@
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
import { DataResolver } from './types/DataResolver'
import Task from '@vue-storefront/core/lib/sync/types/Task'
import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'
@@ -7,7 +8,7 @@ import config from 'config';
const setShippingInfo = async (addressInformation: any): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.shippinginfo_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'shippinginfo_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -19,7 +20,7 @@ const setShippingInfo = async (addressInformation: any): Promise =>
const getTotals = async (): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.totals_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'totals_endpoint')),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -30,8 +31,8 @@ const getTotals = async (): Promise =>
const getCartToken = async (guestCart: boolean = false, forceClientState: boolean = false): Promise => {
const url = processLocalizedURLAddress(guestCart
- ? config.cart.create_endpoint.replace('{{token}}', '')
- : config.cart.create_endpoint)
+ ? getApiEndpointUrl(config.cart, 'create_endpoint').replace('{{token}}', '')
+ : getApiEndpointUrl(config.cart, 'create_endpoint'))
return TaskQueue.execute({
url,
@@ -47,7 +48,7 @@ const getCartToken = async (guestCart: boolean = false, forceClientState: boolea
const updateItem = async (cartServerToken: string, cartItem: CartItem): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.updateitem_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'updateitem_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -63,7 +64,7 @@ const updateItem = async (cartServerToken: string, cartItem: CartItem): Promise<
const deleteItem = async (cartServerToken: string, cartItem: CartItem): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.deleteitem_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'deleteitem_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -80,7 +81,7 @@ const deleteItem = async (cartServerToken: string, cartItem: CartItem): Promise<
const getPaymentMethods = async (): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.paymentmethods_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'paymentmethods_endpoint')),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -91,7 +92,7 @@ const getPaymentMethods = async (): Promise =>
const getShippingMethods = async (address: any): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.shippingmethods_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'shippingmethods_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -105,7 +106,7 @@ const getShippingMethods = async (address: any): Promise =>
const getItems = async (): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.pull_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'pull_endpoint')),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -115,7 +116,7 @@ const getItems = async (): Promise =>
});
const applyCoupon = async (couponCode: string): Promise => {
- const url = processLocalizedURLAddress(config.cart.applycoupon_endpoint.replace('{{coupon}}', couponCode))
+ const url = processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'applycoupon_endpoint').replace('{{coupon}}', couponCode))
return TaskQueue.execute({
url,
@@ -130,7 +131,7 @@ const applyCoupon = async (couponCode: string): Promise => {
const removeCoupon = async (): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.cart.deletecoupon_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'deletecoupon_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
diff --git a/core/data-resolver/CategoryService.ts b/core/data-resolver/CategoryService.ts
index c1146f488..1505f8eda 100644
--- a/core/data-resolver/CategoryService.ts
+++ b/core/data-resolver/CategoryService.ts
@@ -1,5 +1,5 @@
import { quickSearchByQuery } from '@vue-storefront/core/lib/search';
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery';
+import { SearchQuery } from 'storefront-query-builder'
import config from 'config';
import { DataResolver } from './types/DataResolver';
import { Category } from 'core/modules/catalog-next/types/Category';
diff --git a/core/data-resolver/NewsletterService.ts b/core/data-resolver/NewsletterService.ts
index 4d1969dc9..7aaee8e32 100644
--- a/core/data-resolver/NewsletterService.ts
+++ b/core/data-resolver/NewsletterService.ts
@@ -2,10 +2,11 @@ import config from 'config';
import { DataResolver } from './types/DataResolver';
import { processURLAddress } from '@vue-storefront/core/helpers';
import { TaskQueue } from '@vue-storefront/core/lib/sync'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
const isSubscribed = (email: string): Promise =>
TaskQueue.execute({
- url: processURLAddress(config.newsletter.endpoint) + '?email=' + encodeURIComponent(email),
+ url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')) + '?email=' + encodeURIComponent(email),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -16,7 +17,7 @@ const isSubscribed = (email: string): Promise =>
const subscribe = (email: string): Promise =>
TaskQueue.execute({
- url: processURLAddress(config.newsletter.endpoint),
+ url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -27,7 +28,7 @@ const subscribe = (email: string): Promise =>
const unsubscribe = (email: string): Promise =>
TaskQueue.execute({
- url: processURLAddress(config.newsletter.endpoint),
+ url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')),
payload: {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
diff --git a/core/data-resolver/OrderService.ts b/core/data-resolver/OrderService.ts
index c77bc73f6..96054296e 100644
--- a/core/data-resolver/OrderService.ts
+++ b/core/data-resolver/OrderService.ts
@@ -3,9 +3,10 @@ import { DataResolver } from './types/DataResolver';
import { Order } from '@vue-storefront/core/modules/order/types/Order'
import { TaskQueue } from '@vue-storefront/core/lib/sync'
import Task from '@vue-storefront/core/lib/sync/types/Task'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
const placeOrder = (order: Order): Promise =>
- TaskQueue.execute({ url: config.orders.endpoint, // sync the order
+ TaskQueue.execute({ url: getApiEndpointUrl(config.orders, 'endpoint'), // sync the order
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
diff --git a/core/data-resolver/ProductService.ts b/core/data-resolver/ProductService.ts
new file mode 100644
index 000000000..2b839e8fc
--- /dev/null
+++ b/core/data-resolver/ProductService.ts
@@ -0,0 +1,196 @@
+import { getOptimizedFields } from '@vue-storefront/core/modules/catalog/helpers/search';
+import { canCache, storeProductToCache } from './../modules/catalog/helpers/search';
+import { doPlatformPricesSync } from '@vue-storefront/core/modules/catalog/helpers';
+import { isServer } from '@vue-storefront/core/helpers';
+import { quickSearchByQuery, isOnline } from '@vue-storefront/core/lib/search';
+import { SearchQuery } from 'storefront-query-builder'
+import config from 'config';
+import { DataResolver } from './types/DataResolver';
+import { currentStoreView } from '@vue-storefront/core/lib/multistore'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
+import { TaskQueue } from '@vue-storefront/core/lib/sync'
+import { entityKeyName } from '@vue-storefront/core/lib/store/entities'
+import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
+import { Logger } from '@vue-storefront/core/lib/logger';
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import { prepareProducts } from '@vue-storefront/core/modules/catalog/helpers/prepare';
+import { configureProducts } from '@vue-storefront/core/modules/catalog/helpers/configure';
+
+const getProducts = async ({
+ query,
+ start = 0,
+ size = 50,
+ sort = '',
+ excludeFields = null,
+ includeFields = null,
+ configuration = null,
+ options: {
+ prefetchGroupProducts = !isServer,
+ fallbackToDefaultWhenNoAvailable = true,
+ setProductErrors = false,
+ setConfigurableProductOptions = config.cart.setConfigurableProductOptions,
+ filterUnavailableVariants = config.products.filterUnavailableVariants,
+ assignProductConfiguration = false,
+ separateSelectedVariant = false
+ } = {}
+}: DataResolver.ProductSearchOptions): Promise => {
+ const isCacheable = canCache({ includeFields, excludeFields })
+ const { excluded, included } = getOptimizedFields({ excludeFields, includeFields })
+ let {
+ items: products = [],
+ attributeMetadata = [],
+ aggregations = [],
+ total,
+ perPage
+ } = await quickSearchByQuery({
+ query,
+ start,
+ size,
+ entityType: 'product',
+ sort,
+ excludeFields: excluded,
+ includeFields: included
+ })
+
+ products = prepareProducts(products)
+
+ for (let product of products) { // we store each product separately in cache to have offline access to products/single method
+ if (isCacheable) { // store cache only for full loads
+ storeProductToCache(product, 'sku')
+ }
+ }
+
+ const configuredProducts = await configureProducts({
+ products,
+ attributes_metadata: attributeMetadata,
+ configuration,
+ options: {
+ prefetchGroupProducts,
+ fallbackToDefaultWhenNoAvailable,
+ setProductErrors,
+ setConfigurableProductOptions,
+ filterUnavailableVariants,
+ assignProductConfiguration,
+ separateSelectedVariant
+ },
+ excludeFields: excluded,
+ includeFields: included
+ })
+
+ return {
+ items: configuredProducts,
+ perPage,
+ start,
+ total,
+ aggregations,
+ attributeMetadata
+ }
+}
+
+const getProductRenderList = async ({
+ skus,
+ isUserGroupedTaxActive,
+ userGroupId,
+ token
+}): Promise => {
+ const { i18n, storeId } = currentStoreView()
+ let url = [
+ `${getApiEndpointUrl(config.products, 'endpoint')}/render-list`,
+ `?skus=${encodeURIComponent(skus.join(','))}`,
+ `¤cyCode=${encodeURIComponent(i18n.currencyCode)}`,
+ `&storeId=${encodeURIComponent(storeId)}`
+ ].join('')
+ if (isUserGroupedTaxActive) {
+ url = `${url}&userGroupId=${userGroupId}`
+ }
+
+ if (token) {
+ url = `${url}&token=${token}`
+ }
+
+ try {
+ const task = await TaskQueue.execute({ url, // sync the cart
+ payload: {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json' },
+ mode: 'cors'
+ },
+ callback_event: 'prices-after-sync'
+ })
+ return task.result as DataResolver.ProductsListResponse
+ } catch (err) {
+ console.error(err)
+ return { items: [] }
+ }
+}
+
+const getProduct = async (options: { [key: string]: string }, key: string): Promise => {
+ let searchQuery = new SearchQuery()
+ searchQuery = searchQuery.applyFilter({ key: key, value: { 'eq': options[key] } })
+ const { items = [] } = await getProducts({
+ query: searchQuery,
+ size: 1,
+ configuration: { sku: options.childSku },
+ options: {
+ prefetchGroupProducts: true,
+ assignProductConfiguration: true
+ }
+ })
+ return items[0] || null
+}
+
+const getProductFromCache = async (options: { [key: string]: string }, key: string): Promise => {
+ try {
+ const cacheKey = entityKeyName(key, options[key])
+ const cache = StorageManager.get('elasticCache')
+ const result = await cache.getItem(cacheKey)
+ if (result !== null) {
+ if (config.products.alwaysSyncPlatformPricesOver) {
+ if (!config.products.waitForPlatformSync) {
+ await doPlatformPricesSync([result])
+ } else {
+ doPlatformPricesSync([result])
+ }
+ }
+ const { excluded, included } = getOptimizedFields({ excludeFields: null, includeFields: null })
+ const [product] = await configureProducts({
+ products: [result],
+ attributes_metadata: [],
+ configuration: { [key]: options.childSku || options.sku || options[key] },
+ options: {
+ prefetchGroupProducts: true,
+ setConfigurableProductOptions: config.cart.setConfigurableProductOptions,
+ filterUnavailableVariants: config.products.filterUnavailableVariants,
+ assignProductConfiguration: true
+ },
+ excludeFields: excluded,
+ includeFields: included
+ })
+ return product
+ } else {
+ return getProduct(options, key)
+ }
+ } catch (err) {
+ // report errors
+ if (err) {
+ Logger.error(err, 'product')()
+ }
+ return getProduct(options, key)
+ }
+}
+
+const getProductByKey = async ({ options, key, skipCache }: DataResolver.ProductByKeySearchOptions): Promise => {
+ if (!isOnline()) {
+ return getProductFromCache(options, key)
+ }
+ const result = skipCache
+ ? await getProduct(options, key)
+ : await getProductFromCache(options, key)
+ return result
+}
+
+export const ProductService: DataResolver.ProductService = {
+ getProducts,
+ getProductRenderList,
+ getProductByKey
+}
diff --git a/core/data-resolver/ReviewsService.ts b/core/data-resolver/ReviewsService.ts
index 4cfcaeb0a..da3fa3e88 100644
--- a/core/data-resolver/ReviewsService.ts
+++ b/core/data-resolver/ReviewsService.ts
@@ -3,10 +3,11 @@ import { TaskQueue } from '@vue-storefront/core/lib/sync'
import { processLocalizedURLAddress } from '@vue-storefront/core/helpers'
import config from 'config'
import Review from 'core/modules/review/types/Review';
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
const createReview = (review: Review): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.reviews.create_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.reviews, 'create_endpoint')),
payload: {
method: 'POST',
mode: 'cors',
diff --git a/core/data-resolver/StockService.ts b/core/data-resolver/StockService.ts
index fa88961d0..7c89bc596 100644
--- a/core/data-resolver/StockService.ts
+++ b/core/data-resolver/StockService.ts
@@ -3,10 +3,11 @@ import { DataResolver } from './types/DataResolver';
import { TaskQueue } from '@vue-storefront/core/lib/sync';
import Task from '@vue-storefront/core/lib/sync/types/Task';
import { processURLAddress } from '@vue-storefront/core/helpers';
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
const queueCheck = (sku: string, actionName: string): Promise =>
TaskQueue.queue({
- url: processURLAddress(`${config.stock.endpoint}/check?sku=${encodeURIComponent(sku)}`),
+ url: processURLAddress(`${getApiEndpointUrl(config.stock, 'endpoint')}/check?sku=${encodeURIComponent(sku)}`),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -19,7 +20,7 @@ const queueCheck = (sku: string, actionName: string): Promise =>
const check = (sku: string): Promise =>
TaskQueue.execute({
- url: processURLAddress(`${config.stock.endpoint}/check?sku=${encodeURIComponent(sku)}`),
+ url: processURLAddress(`${getApiEndpointUrl(config.stock, 'endpoint')}/check?sku=${encodeURIComponent(sku)}`),
payload: {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
@@ -32,7 +33,7 @@ const check = (sku: string): Promise =>
const list = (skuList: string[]): Promise =>
TaskQueue.execute({
url: processURLAddress(
- `${config.stock.endpoint}/list?skus=${encodeURIComponent(
+ `${getApiEndpointUrl(config.stock, 'endpoint')}/list?skus=${encodeURIComponent(
skuList.join(',')
)}`
),
diff --git a/core/data-resolver/UserService.ts b/core/data-resolver/UserService.ts
index afbf9691a..97ad0aa49 100644
--- a/core/data-resolver/UserService.ts
+++ b/core/data-resolver/UserService.ts
@@ -4,6 +4,7 @@ import { TaskQueue } from '@vue-storefront/core/lib/sync'
import Task from '@vue-storefront/core/lib/sync/types/Task'
import { processLocalizedURLAddress } from '@vue-storefront/core/helpers'
import config from 'config'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
const headers = {
'Accept': 'application/json, text/plain, */*',
@@ -12,7 +13,7 @@ const headers = {
const resetPassword = async (email: string): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.users.resetPassword_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'resetPassword_endpoint')),
payload: {
method: 'POST',
mode: 'cors',
@@ -21,9 +22,20 @@ const resetPassword = async (email: string): Promise =>
}
})
+const createPassword = async (email: string, newPassword: string, resetToken: string): Promise =>
+ TaskQueue.execute({
+ url: processLocalizedURLAddress(config.users.createPassword_endpoint),
+ payload: {
+ method: 'POST',
+ mode: 'cors',
+ headers,
+ body: JSON.stringify({ email, newPassword, resetToken })
+ }
+ })
+
const login = async (username: string, password: string): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.users.login_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'login_endpoint')),
payload: {
method: 'POST',
mode: 'cors',
@@ -34,7 +46,7 @@ const login = async (username: string, password: string): Promise =>
const register = async (customer: DataResolver.Customer, password: string): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.users.create_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'create_endpoint')),
payload: {
method: 'POST',
headers,
@@ -44,7 +56,7 @@ const register = async (customer: DataResolver.Customer, password: string): Prom
const updateProfile = async (userProfile: UserProfile, actionName: string): Promise =>
TaskQueue.queue({
- url: processLocalizedURLAddress(config.users.me_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'me_endpoint')),
payload: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -56,7 +68,7 @@ const updateProfile = async (userProfile: UserProfile, actionName: string): Prom
const getProfile = async () =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.users.me_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'me_endpoint')),
payload: {
method: 'GET',
mode: 'cors',
@@ -67,7 +79,7 @@ const getProfile = async () =>
const getOrdersHistory = async (pageSize = 20, currentPage = 1): Promise =>
TaskQueue.execute({
url: processLocalizedURLAddress(
- config.users.history_endpoint.replace('{{pageSize}}', pageSize).replace('{{currentPage}}', currentPage)
+ getApiEndpointUrl(config.users, 'history_endpoint').replace('{{pageSize}}', pageSize + '').replace('{{currentPage}}', currentPage + '')
),
payload: {
method: 'GET',
@@ -78,7 +90,7 @@ const getOrdersHistory = async (pageSize = 20, currentPage = 1): Promise =
const changePassword = async (passwordData: DataResolver.PasswordData): Promise =>
TaskQueue.execute({
- url: processLocalizedURLAddress(config.users.changePassword_endpoint),
+ url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'changePassword_endpoint')),
payload: {
method: 'POST',
mode: 'cors',
@@ -88,7 +100,7 @@ const changePassword = async (passwordData: DataResolver.PasswordData): Promise<
})
const refreshToken = async (refreshToken: string): Promise =>
- fetch(processLocalizedURLAddress(config.users.refresh_endpoint), {
+ fetch(processLocalizedURLAddress(getApiEndpointUrl(config.users, 'refresh_endpoint')), {
method: 'POST',
mode: 'cors',
headers,
@@ -98,6 +110,7 @@ const refreshToken = async (refreshToken: string): Promise =>
export const UserService: DataResolver.UserService = {
resetPassword,
+ createPassword,
login,
register,
updateProfile,
diff --git a/core/data-resolver/types/DataResolver.d.ts b/core/data-resolver/types/DataResolver.d.ts
index 40ff960c8..88631409d 100644
--- a/core/data-resolver/types/DataResolver.d.ts
+++ b/core/data-resolver/types/DataResolver.d.ts
@@ -1,9 +1,12 @@
-import { Category } from 'core/modules/catalog-next/types/Category';
-import { UserProfile } from 'core/modules/user/types/UserProfile'
+import { AttributesMetadata } from '@vue-storefront/core/modules/catalog/types/Attribute';
+import { Category } from '@vue-storefront/core/modules/catalog-next/types/Category';
+import { UserProfile } from '@vue-storefront/core/modules/user/types/UserProfile'
import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'
import { Order } from '@vue-storefront/core/modules/order/types/Order'
import Task from '@vue-storefront/core/lib/sync/types/Task'
-import Review from 'core/modules/review/types/Review';
+import Review from '@vue-storefront/core/modules/review/types/Review';
+import { SearchQuery } from 'storefront-query-builder';
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
declare namespace DataResolver {
@@ -21,6 +24,38 @@ declare namespace DataResolver {
reloadAll?: boolean
}
+ interface ProductSearchOptions {
+ query: SearchQuery,
+ size?: number,
+ start?: number,
+ sort?: string,
+ includeFields?: string[],
+ excludeFields?: string[],
+ configuration?: { [key: string]: string[] | string },
+ options?: {
+ prefetchGroupProducts?: boolean,
+ fallbackToDefaultWhenNoAvailable?: boolean,
+ setProductErrors?: boolean,
+ setConfigurableProductOptions?: boolean,
+ filterUnavailableVariants?: boolean,
+ assignProductConfiguration?: boolean,
+ separateSelectedVariant?: boolean
+ }
+ }
+
+ interface ProductRenderListSearchOptions {
+ skus: string[],
+ isUserGroupedTaxActive?: boolean,
+ userGroupId?: string,
+ token?: string
+ }
+
+ interface ProductByKeySearchOptions {
+ options: { [key: string]: string },
+ key?: string,
+ skipCache?: boolean
+ }
+
interface Customer {
email: string,
firstname: string,
@@ -33,12 +68,28 @@ declare namespace DataResolver {
newPassword: string
}
+ interface ProductsListResponse {
+ items: Product[],
+ perPage?: number,
+ start?: number,
+ total?: number,
+ aggregations?: any[],
+ attributeMetadata?: AttributesMetadata[]
+ }
+
+ interface ProductService {
+ getProducts: (searchRequest: ProductSearchOptions) => Promise,
+ getProductRenderList: (searchRequest: ProductRenderListSearchOptions) => Promise,
+ getProductByKey: (searchRequest: ProductByKeySearchOptions) => Promise
+ }
+
interface CategoryService {
getCategories: (searchRequest?: CategorySearchOptions) => Promise
}
interface UserService {
resetPassword: (email: string) => Promise,
+ createPassword: (email: string, newPassword: string, resetToken: string) => Promise,
login: (username: string, password: string) => Promise,
register: (customer: Customer, pssword: string) => Promise,
updateProfile: (userProfile: UserProfile, actionName: string) => Promise,
diff --git a/core/filters/price.js b/core/filters/price.js
index a7e6d858a..b17c204cf 100644
--- a/core/filters/price.js
+++ b/core/filters/price.js
@@ -1,13 +1,21 @@
-import { currentStoreView } from '@vue-storefront/core/lib/multistore';
-
-const formatValue = (value, locale) => {
- const price = Math.abs(parseFloat(value));
- return price.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
-};
+import { currentStoreView } from '@vue-storefront/core/lib/multistore'
const applyCurrencySign = (formattedPrice, { currencySign, priceFormat }) => {
return priceFormat.replace('{sign}', currencySign).replace('{amount}', formattedPrice)
-}
+};
+
+const getLocaleSeparators = (defaultLocale) => {
+ return {
+ decimal: (0.01).toLocaleString(defaultLocale).replace(/[0-9]/g, ''),
+ group: (1000).toLocaleString(defaultLocale).replace(/[0-9]/g, '')
+ }
+};
+
+const replaceSeparators = (formattedPrice, currencySeparators, separators) => {
+ if (currencySeparators.decimal) formattedPrice = formattedPrice.replace(separators.decimal, currencySeparators.decimal);
+ if (currencySeparators.group) formattedPrice = formattedPrice.replace(separators.group, currencySeparators.group);
+ return formattedPrice;
+};
/**
* Converts number to price string
@@ -15,20 +23,24 @@ const applyCurrencySign = (formattedPrice, { currencySign, priceFormat }) => {
*/
export function price (value, storeView) {
if (isNaN(value)) {
- return value;
+ return value
}
const _storeView = storeView || currentStoreView();
if (!_storeView.i18n) {
- return value;
+ return Number(value).toFixed(2)
}
- const { defaultLocale, currencySign, priceFormat } = _storeView.i18n
- const formattedValue = formatValue(value, defaultLocale);
- const valueWithSign = applyCurrencySign(formattedValue, { currencySign, priceFormat })
+ const { defaultLocale, currencySign, currencyDecimal, currencyGroup, fractionDigits, priceFormat } = _storeView.i18n;
+
+ const options = { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits };
- if (value >= 0) {
- return valueWithSign;
- } else {
- return '-' + valueWithSign;
+ let localePrice = Math.abs(Number(value).toLocaleString(defaultLocale, options)).toFixed(2);
+
+ if (currencyDecimal !== '' || currencyGroup !== '') {
+ localePrice = replaceSeparators(localePrice, { decimal: currencyDecimal, group: currencyGroup }, getLocaleSeparators(defaultLocale));
}
+
+ const valueWithSign = applyCurrencySign(localePrice, { currencySign, priceFormat });
+
+ return value >= 0 ? valueWithSign : '-' + valueWithSign;
}
diff --git a/core/helpers/getApiEndpointUrl.ts b/core/helpers/getApiEndpointUrl.ts
new file mode 100644
index 000000000..86b5a4f0c
--- /dev/null
+++ b/core/helpers/getApiEndpointUrl.ts
@@ -0,0 +1,11 @@
+import { isServer } from '@vue-storefront/core/helpers';
+
+// object - parent object in the config, e.g. config.cart
+// field - field inside the object, e.g. create_endpoint
+
+// returns - object.[field]_ssr if it exists and it is a server,
+// object.field otherwise
+
+export default (object: Record, field: string): string => {
+ return isServer && object[`${field}_ssr`] ? object[`${field}_ssr`] : object[field]
+}
diff --git a/core/helpers/index.ts b/core/helpers/index.ts
index 10df4ce9d..7cd2e1ede 100644
--- a/core/helpers/index.ts
+++ b/core/helpers/index.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import { remove as removeAccents } from 'remove-accents'
import { formatCategoryLink } from '@vue-storefront/core/modules/url/helpers'
import Vue from 'vue'
@@ -7,10 +7,11 @@ import { sha3_224 } from 'js-sha3'
import store from '@vue-storefront/core/store'
import { adjustMultistoreApiUrl } from '@vue-storefront/core/lib/multistore'
import { coreHooksExecutors } from '@vue-storefront/core/hooks';
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
import omit from 'lodash-es/omit'
export const processURLAddress = (url: string = '') => {
- if (url.startsWith('/')) return `${config.api.url}${url}`
+ if (url.startsWith('/')) return `${getApiEndpointUrl(config.api, 'url')}${url}`
return url
}
@@ -47,14 +48,14 @@ export function slugify (text) {
* @returns {string}
*/
export function getThumbnailPath (relativeUrl: string, width: number = 0, height: number = 0, pathType: string = 'product'): string {
+ if (config.images.useSpecificImagePaths) {
+ const path = config.images.paths[pathType] !== undefined ? config.images.paths[pathType] : ''
+ relativeUrl = path + relativeUrl
+ }
+
if (config.images.useExactUrlsNoProxy) {
- return coreHooksExecutors.afterProductThumbnailPathGenerate({ path: relativeUrl, sizeX: width, sizeY: height }).path // this is exact url mode
+ return coreHooksExecutors.afterProductThumbnailPathGenerate({ path: relativeUrl, sizeX: width, sizeY: height, pathType }).path // this is exact url mode
} else {
- if (config.images.useSpecificImagePaths) {
- const path = config.images.paths[pathType] !== undefined ? config.images.paths[pathType] : ''
- relativeUrl = path + relativeUrl
- }
-
let resultUrl
if (relativeUrl && (relativeUrl.indexOf('://') > 0 || relativeUrl.indexOf('?') > 0 || relativeUrl.indexOf('&') > 0)) relativeUrl = encodeURIComponent(relativeUrl)
// proxyUrl is not a url base path but contains {{url}} parameters and so on to use the relativeUrl as a template value and then do the image proxy opertions
@@ -69,7 +70,7 @@ export function getThumbnailPath (relativeUrl: string, width: number = 0, height
}
const path = relativeUrl && relativeUrl.indexOf('no_selection') < 0 ? resultUrl : config.images.productPlaceholder || ''
- return coreHooksExecutors.afterProductThumbnailPathGenerate({ path, sizeX: width, sizeY: height }).path
+ return coreHooksExecutors.afterProductThumbnailPathGenerate({ path, sizeX: width, sizeY: height, pathType }).path
}
}
@@ -261,3 +262,97 @@ export function extendStore (moduleName: string | string[], module: any) {
store.unregisterModule(moduleName)
store.registerModule(moduleName, extendedModule)
}
+
+export function reviewJsonLd (reviews, { name, category, mpn, url_path, price, stock, is_in_stock, sku, image, description }, priceCurrency) {
+ return reviews.map(({ title, detail, nickname, created_at }) => (
+ {
+ '@context': 'http://schema.org/',
+ '@type': 'Review',
+ reviewAspect: title,
+ reviewBody: detail,
+ datePublished: created_at,
+ author: nickname,
+ itemReviewed: {
+ '@type': 'Product',
+ name,
+ sku,
+ image,
+ description,
+ offers: {
+ '@type': 'Offer',
+ category: category
+ ? category
+ .map(({ name }) => name || null)
+ .filter(name => name !== null)
+ : null,
+ mpn,
+ url: url_path,
+ priceCurrency,
+ price,
+ itemCondition: 'https://schema.org/NewCondition',
+ availability: stock && is_in_stock ? 'InStock' : 'OutOfStock'
+ }
+ }
+ }
+ )
+ )
+}
+
+function getMaterials (material, customAttributes) {
+ const materialsArr = []
+ if (customAttributes && customAttributes.length && customAttributes.length > 0 && material && material.length && material.length > 0) {
+ const materialOptions = customAttributes.find(({ attribute_code }) => attribute_code === 'material').options
+ if (Array.isArray(material)) {
+ for (let key in materialOptions) {
+ material.forEach(el => {
+ if (String(el) === materialOptions[key].value) {
+ materialsArr.push(materialOptions[key].label)
+ }
+ })
+ }
+ } else {
+ for (let key in materialOptions) {
+ if (material === materialOptions[key].value) {
+ materialsArr.push(materialOptions[key].label)
+ }
+ }
+ }
+ }
+ return materialsArr
+}
+
+export function productJsonLd ({ category, image, name, id, sku, mpn, description, price, url_path, stock, is_in_stock, material }, color, priceCurrency, customAttributes) {
+ return {
+ '@context': 'http://schema.org',
+ '@type': 'Product',
+ category: category
+ ? category
+ .map(({ name }) => name || null)
+ .filter(name => name !== null)
+ : null,
+ color,
+ description,
+ image,
+ itemCondition: 'http://schema.org/NewCondition',
+ material: getMaterials(material, customAttributes),
+ name,
+ productID: id,
+ sku,
+ mpn,
+ offers: {
+ '@type': 'Offer',
+ category: category
+ ? category
+ .map(({ name }) => name || null)
+ .filter(name => name !== null)
+ : null,
+ mpn,
+ url: url_path,
+ priceCurrency,
+ price,
+ itemCondition: 'https://schema.org/NewCondition',
+ availability: stock && is_in_stock ? 'InStock' : 'OutOfStock',
+ sku
+ }
+ }
+}
diff --git a/core/hooks.ts b/core/hooks.ts
index 8e0c08368..8d4152b2b 100644
--- a/core/hooks.ts
+++ b/core/hooks.ts
@@ -23,7 +23,12 @@ const {
const {
hook: afterProductThumbnailPathGeneratedHook,
executor: afterProductThumbnailPathGeneratedExecutor
-} = createMutatorHook<{ path: string, sizeX: number, sizeY: number }, { path: string }>()
+} = createMutatorHook<{ path: string, pathType: string, sizeX: number, sizeY: number }, { path: string }>()
+
+const {
+ hook: beforeLogRenderedHook,
+ executor: beforeLogRenderedExecutor
+} = createMutatorHook<{ type: string, message: any, tag: any, context: any, noDefaultOutput?: boolean }, { message: any, tag: any, context: any, noDefaultOutput?: boolean }>()
/** Only for internal usage in core */
const coreHooksExecutors = {
@@ -31,7 +36,8 @@ const coreHooksExecutors = {
beforeStoreViewChanged: beforeStoreViewChangedExecutor,
afterStoreViewChanged: afterStoreViewChangedExecutor,
beforeHydrated: beforeHydratedExecutor,
- afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedExecutor
+ afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedExecutor,
+ beforeLogRendered: beforeLogRenderedExecutor
}
const coreHooks = {
@@ -46,7 +52,8 @@ const coreHooks = {
*/
afterStoreViewChanged: afterStoreViewChangedHook,
beforeHydrated: beforeHydratedHook,
- afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedHook
+ afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedHook,
+ beforeLogRendered: beforeLogRenderedHook
}
export {
diff --git a/core/i18n/intl.ts b/core/i18n/intl.ts
new file mode 100644
index 000000000..05a548f95
--- /dev/null
+++ b/core/i18n/intl.ts
@@ -0,0 +1,13 @@
+import areIntlLocalesSupported from 'intl-locales-supported'
+
+export const importIntlPolyfill = async () => {
+ const IntlPolyfill = await import('intl')
+ global.Intl = IntlPolyfill.default
+}
+
+export const checkForIntlPolyfill = async (storeView) => {
+ const globDTO = typeof window !== 'undefined' ? window : global
+ if (!globDTO.hasOwnProperty('Intl') || !areIntlLocalesSupported(storeView.i18n.defaultLocale)) {
+ await importIntlPolyfill()
+ }
+}
diff --git a/core/i18n/package.json b/core/i18n/package.json
index 2f260f8c4..290d0f99c 100644
--- a/core/i18n/package.json
+++ b/core/i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/i18n",
- "version": "1.11.4",
+ "version": "1.12.0",
"description": "Vue Storefront i18n",
"license": "MIT",
"main": "index.ts",
diff --git a/core/i18n/resource/i18n/ar-SA.csv b/core/i18n/resource/i18n/ar-SA.csv
new file mode 100644
index 000000000..03997aa6f
--- /dev/null
+++ b/core/i18n/resource/i18n/ar-SA.csv
@@ -0,0 +1,77 @@
+Registering the account ...,جاري تسجيل الحساب
+No products synchronized for this category. Please come back while online!,.لا يوجد منتجات تمت مزامنتها لهذا التصنيف. يرجى العودة مرة أخرى إذا تم الاتصال بالإنترنت
+Shopping cart is empty. Please add some products before entering Checkout,سلة التسوق فارغة. يرجى إضافة بعض المنتجات للانتقال إلى صفحة إتمام الطلب
+Out of stock!,غير متوفر!
+ is out of the stock!,غير متوفر بالمخزون!
+Some of the ordered products are not available!,بعض المنتجات المطلوبة غير متاحة
+Please wait ...,انتظر من فضلك…
+"Stock check in progress, please wait while available stock quantities are checked",جاري فحص المخزون، يرجى الانتظار حتى يتم التأكد من التوفر
+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.,لا يوجد اتصال بالإنترنت. مازال بإمكانك إتمام الطلب. وسنقوم بإخطارك إذا كان أي من المنتجات المطلوبة غير متوفر، لايمكننا التحقق الآن.
+No such configuration for the product. Please do choose another combination of attributes.,لا يوجد خيارات منطبقة لهذا المنتج من فضلك قم بتعديل الخيارات.
+The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.,الكمية غير مؤكدة (volatile). تمت إضافة المنتج للسلة للحجز المسبق.
+This feature is not implemented yet! Please take a look at https://github.com/DivanteLtd/vue-storefront/issues for our Roadmap!,This feature is not implemented yet! Please take a look at https://github.com/DivanteLtd/vue-storefront/issues for our Roadmap!
+The product is out of stock and cannot be added to the cart!,المنتج غير متوفر بالمخزون ولا يمكن إضافته للسلة
+Product has been added to the cart!,تمت إضافة المنتج للسلة!
+Product quantity has been updated!,تم تحديث الكمية!
+Internal validation error. Please check if all required fields are filled in. Please contact us on {email},خطأ تحقق داخلي. يرجى التأكد من تعبئة جميع الحقول المطلوبة. فضلاً تواصل معنا على {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.,العنوان المسجل في إنهاء الطلب يحتوي معلومات غير صالحة. يرجى التأكد من تعبئة جميع الحقول المطلوبة أيضاً يمكنك التواص معنا عبر {email} للمساعدة. تم إلغاء الطلب.
+Product {productName} has been added to the compare!,تمت إضافة المنتج {productName} إلى المقارنة!
+Product {productName} has been removed from compare!,تمت إزالة المنتج {productName} من المقارنة!
+Product {productName} has been added to wishlist!,تمت إضافة المنتج {productName} إلى قائمة الأمنيات بنجاح!
+Product {productName} has been removed from wishlit!,تمت إزالة المنتج {productName} من قائمة الأمنيات بنجاح!
+Account data has successfully been updated,تم تحديث الحساب بنجاح
+Newsletter preferences have successfully been updated,تم تحديث تفضيلات النشرة البريدية
+Reset password feature does not work while offline!,لا يمكن إستعادة كلمة المرور بدون اتصال بالشبكة!
+You are logged in!,تم تسجيل دخولك!
+Please fix the validation errors,يرجى إصلاح خطأ التحقق
+"Product price is unknown, product cannot be added to the cart!",سعر المنتج غير معلوم، لايمكن إضافته للسلة.
+My Account,حسابي
+Type what you are looking for...,اكتب ما تبحث عنه…
+Home Page,الصفحة الرئيسية
+Checkout,إتمام الطلب
+Subtotal incl. tax,المجموع متضمن الضريبة
+Grand total,المجموع النهائي
+Field is required,حقل مطلوب
+Field is required.,حقل مطلوب.
+You're logged out,تم تسجيل خروجك
+Compare Products,مقارنة المنتجات
+404 Page Not Found,404 الصفحة غير متوفرة
+Error with response - bad content-type!,خطأ بالرد - محتوى غير صالح!
+"Unhandled error, wrong response format!","Unhandled error, wrong response format!"
+not authorized,غير مصرح
+Internal Application error while refreshing the tokens. Please clear the storage and refresh page.,Internal Application error while refreshing the tokens. Please clear the storage and refresh page.
+Proceed to checkout,إتمام الطلب
+OK,موافق
+Out of the stock!,غير متوفر بالمخزون!
+In stock!,متوفر!
+Please configure product custom options and fix the validation errors,من فضلك حدد خيارات المنتج لإصلاح خطأ التحقق
+Error refreshing user token. User is not authorized to access the resource,خطأ في تحديث التشفير. المستخدم غير مصرح له بالدخول
+Must be greater than 0,يجب أن يكون أكثر من 0
+Please select the field which You like to sort by,اختر حقل للترتيب به
+No available product variants,لا يوجد منتجات متوفرة
+email,البريد الإلكتروني
+password,كلمة المرور
+Confirm your order,أكّد طلبك
+Please confirm order you placed when you was offline,فضلاً أكد الطلب الذي أنشأته بدون اتصال إنترنت
+Payment Information,معلومات الدفع
+You are to pay for this order upon delivery.,ستقوم بالدفع لهذا الطلب عند الاستلام.
+Allow notification about the order,السماح بتنبيهات الطلب
+Extension developers would like to thank you for placing an order!,Extension developers would like to thank you for placing an order!
+most you may purchase,most you may purchase
+have as many,have as many
+Compare products,مقارنة المنتجات
+Reviews,مراجعات
+Review,مراجعة
+Add review,أضف مراجعة
+Summary,ملخص
+login,تسجيل الدخول
+to account,إلى الحساب
+Are you sure you would like to remove this item from the shopping cart?,هل أنت متأكد من إزالة هذا المنتج من سلة التسوق
+"The product, category or CMS page is not available in Offline mode. Redirecting to Home.",المنتج أو التصنيف أو الصفحة غير متوفرة بدون اتصال إنترنت. جاري التحويل للصفحة الرئيسية.
+Please configure product bundle options and fix the validation errors,يرجى تحديد الخيارات وإصلاح خطأ التحقق
+Processing order...,جاري معالجة الطلب…
+You need to be logged in to see this page,يجب تسجيل الدخول لعرض الصفحة
+Quantity must be above 0,يجب أن تكون الكمية أكثر من 0
+Error: Error while adding products,خطأ: خطأ أثناء إضافة المنتجات
+Unexpected authorization error. Check your Network conection.,خطأ تحقق غير متوقع. تحقق من اتصالك بالشبكة.
+Columns,أعمدة
diff --git a/core/i18n/scripts/translation.preprocessor.js b/core/i18n/scripts/translation.preprocessor.js
index 985944c4c..03c92c6c3 100644
--- a/core/i18n/scripts/translation.preprocessor.js
+++ b/core/i18n/scripts/translation.preprocessor.js
@@ -28,13 +28,15 @@ module.exports = function (csvDirectories, config = null) {
const extName = path.extname(fullFileName)
const baseName = path.posix.basename(file, extName)
- if (extName === '.csv') {
- const fileContent = fs.readFileSync(fullFileName, 'utf8')
- if (languages.indexOf(baseName) === -1) {
- languages.push(baseName)
+ if (currentLocales.indexOf(baseName) !== -1) {
+ if (extName === '.csv') {
+ const fileContent = fs.readFileSync(fullFileName, 'utf8')
+ if (languages.indexOf(baseName) === -1) {
+ languages.push(baseName)
+ }
+ console.debug(`Processing translation file: ${fullFileName}`)
+ messages[baseName] = Object.assign(messages[baseName] ? messages[baseName] : {}, convertToObject(dsv.parseRows(fileContent)))
}
- console.debug(`Processing translation file: ${fullFileName}`)
- messages[baseName] = Object.assign(messages[baseName] ? messages[baseName] : {}, convertToObject(dsv.parseRows(fileContent)))
}
})
})
diff --git a/core/lib/logger.ts b/core/lib/logger.ts
index 588e7c987..9c0e3e931 100644
--- a/core/lib/logger.ts
+++ b/core/lib/logger.ts
@@ -1,4 +1,5 @@
import { isServer } from '@vue-storefront/core/helpers'
+import { coreHooksExecutors } from '@vue-storefront/core/hooks'
import buildTimeConfig from 'config'
const bgColorStyle = (color) => `color: white; background: ${color}; padding: 4px; font-weight: bold; font-size: 0.8em'`
@@ -71,6 +72,12 @@ class Logger {
return () => {}
}
+ let noDefaultOutput
+ ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'debug', message, tag, context }))
+ if (noDefaultOutput === true) {
+ return () => {}
+ }
+
if (isServer) {
return console.debug.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context)
}
@@ -107,6 +114,12 @@ class Logger {
return () => {}
}
+ let noDefaultOutput
+ ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'info', message, tag, context }))
+ if (noDefaultOutput === true) {
+ return () => {}
+ }
+
if (isServer) {
return console.log.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context)
}
@@ -130,6 +143,13 @@ class Logger {
if (!this.canPrint('warn')) {
return () => {}
}
+
+ let noDefaultOutput
+ ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'warn', message, tag, context }))
+ if (noDefaultOutput === true) {
+ return () => {}
+ }
+
if (isServer) {
return console.warn.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context)
}
@@ -150,6 +170,12 @@ class Logger {
* @param context meaningful data related to this message
*/
public error (message: any, tag: string = null, context: any = null): () => void {
+ let noDefaultOutput
+ ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'error', message, tag, context }))
+ if (noDefaultOutput === true) {
+ return () => {}
+ }
+
if (isServer) { // always show errors in SSR
return console.error.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context)
}
diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts
index cd163375e..3b832b766 100644
--- a/core/lib/multistore.ts
+++ b/core/lib/multistore.ts
@@ -55,7 +55,6 @@ export function currentStoreView (): StoreView {
export async function prepareStoreView (storeCode: string): Promise {
let storeView: StoreView = buildBaseStoreView() // current, default store
-
if (config.storeViews.multistore === true) {
storeView.storeCode = storeCode || config.defaultStoreCode || ''
} else {
@@ -83,6 +82,7 @@ export async function prepareStoreView (storeCode: string): Promise {
initializeSyncTaskStorage()
StorageManager.currentStoreCode = storeView.storeCode
}
+
coreHooksExecutors.afterStoreViewChanged(storeView)
return storeView
@@ -128,7 +128,7 @@ export function adjustMultistoreApiUrl (url: string): string {
return url
}
-export function localizedDispatcherRoute (routeObj: LocalizedRoute | string, storeCode: string): LocalizedRoute | string {
+export function localizedDispatcherRoute (routeObj: LocalizedRoute | string, storeCode?: string): LocalizedRoute | string {
const { storeCode: currentStoreCode, appendStoreCode } = currentStoreView()
if (!storeCode || !config.storeViews[storeCode]) {
storeCode = currentStoreCode
diff --git a/core/lib/search.ts b/core/lib/search.ts
index e0a5cacea..6dd580076 100644
--- a/core/lib/search.ts
+++ b/core/lib/search.ts
@@ -71,7 +71,7 @@ export const quickSearchByQuery = async ({ query = {}, start = 0, size = 50, ent
return
}
} catch (err) {
- console.error('Cannot read cache for ' + cacheKey + ', ' + err)
+ Logger.error('Cannot read cache for ' + cacheKey + ', ' + err)()
}
/* use only for cache */
@@ -91,7 +91,7 @@ export const quickSearchByQuery = async ({ query = {}, start = 0, size = 50, ent
const res = searchAdapter.entities[Request.type].resultProcessor(resp, start, size)
if (res) { // otherwise it can be just a offline mode
- cache.setItem(cacheKey, res, null, config.elasticsearch.disablePersistentQueriesCache).catch((err) => { console.error('Cannot store cache for ' + cacheKey + ', ' + err) })
+ cache.setItem(cacheKey, res, null, config.elasticsearch.disablePersistentQueriesCache).catch((err) => { Logger.error('Cannot store cache for ' + cacheKey + ', ' + err)() })
if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ...
Logger.debug('Result from ES for ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))()
res.cache = false
diff --git a/core/lib/search/adapter/api-search-query/searchAdapter.ts b/core/lib/search/adapter/api-search-query/searchAdapter.ts
new file mode 100644
index 000000000..5357c2571
--- /dev/null
+++ b/core/lib/search/adapter/api-search-query/searchAdapter.ts
@@ -0,0 +1,160 @@
+import map from 'lodash-es/map'
+import fetch from 'isomorphic-fetch'
+import { slugify, processURLAddress } from '@vue-storefront/core/helpers'
+import queryString from 'query-string'
+import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore'
+import { SearchQuery } from 'storefront-query-builder'
+import HttpQuery from '@vue-storefront/core/types/search/HttpQuery'
+import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse'
+import config from 'config'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
+
+export class SearchAdapter {
+ public entities: any
+
+ public constructor () {
+ this.entities = []
+ this.initBaseTypes()
+ }
+
+ protected decompactItem (item, fieldsToCompact) {
+ for (let key in fieldsToCompact) {
+ const value = fieldsToCompact[key]
+ if (typeof item[value] !== 'undefined') {
+ item[key] = item[value]
+ delete item[value]
+ }
+ }
+ return item
+ }
+
+ public async search (Request) {
+ const rawQueryObject = Request.searchQuery
+ if (!this.entities[Request.type]) {
+ throw new Error('No entity type registered for ' + Request.type)
+ }
+ if (!(Request.searchQuery instanceof SearchQuery)) {
+ throw new Error('The only supported type of the "Request.searchQuery" is "SearchQuery"')
+ }
+ if (Request.hasOwnProperty('groupId') && Request.groupId !== null) {
+ rawQueryObject['groupId'] = Request.groupId
+ }
+ if (Request.hasOwnProperty('groupToken') && Request.groupToken !== null) {
+ rawQueryObject['groupToken'] = Request.groupToken
+ }
+ if (Request.sort) {
+ const [ field, options ] = Request.sort.split(':')
+ rawQueryObject.applySort({ field, options })
+ delete Request.sort
+ }
+ const storeView = (Request.store === null) ? currentStoreView() : await prepareStoreView(Request.store)
+ Request.index = storeView.elasticsearch.index
+
+ let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host'))
+
+ if (this.entities[Request.type].url) {
+ url = getApiEndpointUrl(this.entities[Request.type], 'url')
+ }
+
+ const httpQuery: HttpQuery = {
+ size: Request.size,
+ from: Request.from,
+ sort: Request.sort,
+ request_format: 'search-query',
+ response_format: 'compact'
+ }
+
+ if (Request._sourceExclude) {
+ httpQuery._source_exclude = Request._sourceExclude.join(',')
+ }
+ if (Request._sourceInclude) {
+ httpQuery._source_include = Request._sourceInclude.join(',')
+ }
+ if (Request.q) {
+ httpQuery.q = Request.q
+ }
+
+ if (!Request.index || !Request.type) {
+ throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query')
+ }
+ if (config.elasticsearch.queryMethod === 'GET') {
+ httpQuery.request = JSON.stringify(rawQueryObject)
+ }
+ url = url + '/' + encodeURIComponent(Request.index) + '/' + encodeURIComponent(Request.type) + '/_search'
+ url = url + '?' + queryString.stringify(httpQuery)
+ return fetch(url, { method: config.elasticsearch.queryMethod,
+ mode: 'cors',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: config.elasticsearch.queryMethod === 'POST' ? JSON.stringify(rawQueryObject) : null
+ })
+ .then(resp => { return resp.json() })
+ .catch(error => {
+ throw new Error('FetchError in request to API: ' + error.toString())
+ })
+ }
+
+ public handleResult (resp, type, start = 0, size = 50): SearchResponse {
+ if (resp === null) {
+ throw new Error('Invalid API result - null not exepcted')
+ }
+ if (resp.hasOwnProperty('hits')) {
+ return {
+ items: map(resp.hits, hit => {
+ if (type === 'product') {
+ hit = this.decompactItem(hit, config.products.fieldsToCompact)
+ if (hit.configurable_children) {
+ hit.configurable_children = hit.configurable_children.map(childItem => {
+ return this.decompactItem(childItem, config.products.fieldsToCompact)
+ })
+ }
+ }
+ return Object.assign(hit, { slug: hit.slug ? hit.slug : ((hit.hasOwnProperty('url_key') && config.products.useMagentoUrlKeys) ? hit.url_key : (hit.hasOwnProperty('name') ? slugify(hit.name) + '-' + hit.id : '')) }) // TODO: assign slugs server side
+ }), // TODO: add scoring information
+ total: resp.total,
+ start: start,
+ perPage: size,
+ aggregations: resp.aggregations,
+ attributeMetadata: resp.attribute_metadata,
+ suggestions: resp.suggest
+ }
+ } else {
+ if (resp.error) {
+ throw new Error(JSON.stringify(resp.error))
+ } else {
+ throw new Error('Unknown error with API catalog result in resultProcessor for entity type \'' + type + '\'')
+ }
+ }
+ }
+
+ public registerEntityType (entityType, { url = '', url_ssr = '', queryProcessor, resultProcessor }) {
+ this.entities[entityType] = {
+ queryProcessor: queryProcessor,
+ resultProcessor: resultProcessor
+ }
+ if (url !== '') {
+ this.entities[entityType]['url'] = url
+ }
+ if (url_ssr !== '') {
+ this.entities[entityType]['url_ssr'] = url_ssr
+ }
+ return this
+ }
+
+ public initBaseTypes () {
+ const baseTypes = ['product', 'attribute', 'category', 'taxrule', 'review', 'cms_page', 'cms_block', 'cms_hierarchy']
+ baseTypes.forEach(type => {
+ this.registerEntityType(type, {
+ queryProcessor: (query) => {
+ // function that can modify the query each time before it's being executed
+ return query
+ },
+ resultProcessor: (resp, start, size) => {
+ return this.handleResult(resp, type, start, size)
+ }
+ })
+ })
+ }
+}
diff --git a/core/lib/search/adapter/api/elasticsearch/boost.js b/core/lib/search/adapter/api/elasticsearch/boost.js
deleted file mode 100644
index 946a97497..000000000
--- a/core/lib/search/adapter/api/elasticsearch/boost.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import config from 'config'
-
-export default function getBoosts (attribute = '') {
- let searchableAttributes = [
- ]
-
- if (config.elasticsearch.hasOwnProperty('searchableAttributes') && config.elasticsearch.searchableAttributes[attribute]) {
- searchableAttributes = config.elasticsearch.searchableAttributes[attribute]
- }
-
- if (searchableAttributes.hasOwnProperty('boost')) {
- return searchableAttributes['boost']
- }
-
- return 1
-}
diff --git a/core/lib/search/adapter/api/elasticsearch/mapping.js b/core/lib/search/adapter/api/elasticsearch/mapping.js
deleted file mode 100644
index 3fc8ee7ee..000000000
--- a/core/lib/search/adapter/api/elasticsearch/mapping.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import config from 'config'
-export default function getMapping (attribute, entityType = 'products') {
- let mapping = [
- ]
-
- if (config.hasOwnProperty(entityType) && config[entityType].hasOwnProperty('filterFieldMapping')) {
- mapping = config[entityType].filterFieldMapping
- }
-
- if (mapping.hasOwnProperty(attribute)) {
- return mapping[attribute]
- }
-
- return attribute
-}
diff --git a/core/lib/search/adapter/api/elasticsearch/multimatch.js b/core/lib/search/adapter/api/elasticsearch/multimatch.js
deleted file mode 100644
index 1abd481c3..000000000
--- a/core/lib/search/adapter/api/elasticsearch/multimatch.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import config from 'config'
-
-function getConfig (queryText) {
- let scoringConfig = config.elasticsearch.hasOwnProperty('searchScoring') ? config.elasticsearch.searchScoring : {}
- let minimumShouldMatch = scoringConfig.hasOwnProperty('minimum_should_match') ? scoringConfig.minimum_should_match : '75%'
- if (config.elasticsearch.queryMethod === 'GET') {
- // minimum_should_match param must be have a "%" suffix, which is an illegal char while sending over query string
- minimumShouldMatch = encodeURIComponent(minimumShouldMatch)
- }
- // Create config for multi match query
- let multiMatchConfig = {
- 'query': queryText,
- 'operator': scoringConfig.operator ? scoringConfig.operator : 'or',
- 'fuzziness': scoringConfig.fuzziness ? scoringConfig.fuzziness : '2',
- 'cutoff_frequency': scoringConfig.cutoff_frequency ? scoringConfig.cutoff_frequency : '0.01',
- 'max_expansions': scoringConfig.max_expansions ? scoringConfig.max_expansions : '3',
- 'prefix_length': scoringConfig.prefix_length ? scoringConfig.prefix_length : '1',
- 'minimum_should_match': minimumShouldMatch,
- 'tie_breaker': scoringConfig.tie_breaker ? scoringConfig.tie_breaker : '1'
- }
- if (scoringConfig.hasOwnProperty('analyzer')) {
- multiMatchConfig['analyzer'] = scoringConfig.analyzer
- }
- return multiMatchConfig
-}
-
-export default function getMultiMatchConfig (queryText) {
- return getConfig(queryText)
-}
diff --git a/core/lib/search/adapter/api/elasticsearch/score.js b/core/lib/search/adapter/api/elasticsearch/score.js
deleted file mode 100644
index c3d0b2a04..000000000
--- a/core/lib/search/adapter/api/elasticsearch/score.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import config from 'config'
-export default function getFunctionScores () {
- if (!config.elasticsearch.hasOwnProperty('searchScoring')) {
- return false
- }
- let filter = []
- let esScoringAttributes = config.elasticsearch.searchScoring.attributes
-
- if (!Object.keys(esScoringAttributes).length) {
- return false
- }
- for (const attribute of Object.keys(esScoringAttributes)) {
- for (const scoreValue of Object.keys(esScoringAttributes[attribute].scoreValues)) {
- let data = {
- 'filter': {
- 'match': {
- [attribute]: scoreValue
- }
- },
- 'weight': esScoringAttributes[attribute].scoreValues[scoreValue].weight
- }
- filter.push(data)
- }
- }
- if (filter.length) {
- return { 'functions': filter,
- 'score_mode': config.score_mode ? config.score_mode : 'multiply',
- 'boost_mode': config.boost_mode ? config.boost_mode : 'multiply',
- 'max_boost': config.max_boost ? config.max_boost : 100,
- 'min_score': config.function_min_score ? config.function_min_score : 1
- }
- }
- return false
-}
diff --git a/core/lib/search/adapter/api/elasticsearchQuery.js b/core/lib/search/adapter/api/elasticsearchQuery.js
deleted file mode 100644
index fbcf64eb4..000000000
--- a/core/lib/search/adapter/api/elasticsearchQuery.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import getFunctionScores from './elasticsearch/score'
-import getMultiMatchConfig from './elasticsearch/multimatch'
-import getBoosts from './elasticsearch/boost'
-import getMapping from './elasticsearch/mapping'
-import cloneDeep from 'lodash-es/cloneDeep'
-import config from 'config'
-
-export async function prepareElasticsearchQueryBody (searchQuery) {
- const bodybuilder = await import(/* webpackChunkName: "bodybuilder" */ 'bodybuilder')
- const optionsPrefix = '_options'
- const queryText = searchQuery.getSearchText()
- const rangeOperators = ['gt', 'lt', 'gte', 'lte', 'moreq', 'from', 'to']
- let query = bodybuilder.default()
-
- // process applied filters
- const appliedFilters = cloneDeep(searchQuery.getAppliedFilters()) // copy as function below modifies the object
- if (appliedFilters.length > 0) {
- let hasCatalogFilters = false
-
- // apply default filters
- appliedFilters.forEach(filter => {
- if (filter.scope === 'default') {
- if (Object.keys(filter.value).every(v => rangeOperators.includes(v))) {
- // process range filters
- query = query.filter('range', filter.attribute, filter.value)
- } else {
- // process terms filters
- filter.value = filter.value[Object.keys(filter.value)[0]]
- if (!Array.isArray(filter.value)) {
- filter.value = [filter.value]
- }
- query = query.filter('terms', getMapping(filter.attribute), filter.value)
- }
- } else if (filter.scope === 'catalog') {
- hasCatalogFilters = true
- }
- })
-
- // apply catalog scope filters
- let attrFilterBuilder = (filterQr, attrPostfix = '') => {
- appliedFilters.forEach(catalogfilter => {
- const valueKeys = Object.keys(catalogfilter.value)
- if (catalogfilter.scope === 'catalog' && valueKeys.length) {
- const isRange = valueKeys.filter(value => rangeOperators.indexOf(value) !== -1)
- if (isRange.length) {
- let rangeAttribute = catalogfilter.attribute
- // filter by product fiunal price
- if (rangeAttribute === 'price') {
- rangeAttribute = config.products.priceFilterKey
- }
- // process range filters
- filterQr = filterQr.andFilter('range', rangeAttribute, catalogfilter.value)
- } else {
- // process terms filters
- let newValue = catalogfilter.value[Object.keys(catalogfilter.value)[0]]
- if (!Array.isArray(newValue)) {
- newValue = [newValue]
- }
- if (attrPostfix === '') {
- filterQr = filterQr.andFilter('terms', getMapping(catalogfilter.attribute), newValue)
- } else {
- filterQr = filterQr.andFilter('terms', catalogfilter.attribute + attrPostfix, newValue)
- }
- }
- }
- })
- return filterQr
- }
-
- if (hasCatalogFilters) {
- query = query.filterMinimumShouldMatch(1).orFilter('bool', attrFilterBuilder)
- .orFilter('bool', (b) => attrFilterBuilder(b, optionsPrefix).filter('match', 'type_id', 'configurable')) // the queries can vary based on the product type
- }
- }
-
- // Add aggregations for catalog filters
- const allFilters = searchQuery.getAvailableFilters()
- if (allFilters.length > 0) {
- for (let attrToFilter of allFilters) {
- if (attrToFilter.scope === 'catalog') {
- if (attrToFilter.field !== 'price') {
- let aggregationSize = { size: config.products.filterAggregationSize[attrToFilter.field] || config.products.filterAggregationSize.default }
- query = query.aggregation('terms', getMapping(attrToFilter.field), aggregationSize)
- query = query.aggregation('terms', attrToFilter.field + optionsPrefix, aggregationSize)
- } else {
- query = query.aggregation('terms', attrToFilter.field)
- query.aggregation('range', 'price', config.products.priceFilters)
- }
- }
- }
- }
- // Get searchable fields based on user-defined config.
- let getQueryBody = function (b) {
- let searchableAttributes = config.elasticsearch.hasOwnProperty('searchableAttributes') ? config.elasticsearch.searchableAttributes : { 'name': { 'boost': 1 } }
- let searchableFields = [
- ]
- for (const attribute of Object.keys(searchableAttributes)) {
- searchableFields.push(attribute + '^' + getBoosts(attribute))
- }
- return b.orQuery('multi_match', 'fields', searchableFields, getMultiMatchConfig(queryText))
- .orQuery('bool', b => b.orQuery('terms', 'configurable_children.sku', queryText.split('-'))
- .orQuery('match_phrase', 'sku', { query: queryText, boost: 1 })
- .orQuery('match_phrase', 'configurable_children.sku', { query: queryText, boost: 1 })
- )
- }
- if (queryText !== '') {
- let functionScore = getFunctionScores()
- // Build bool or function_scrre accordingly
- if (functionScore) {
- query = query.query('function_score', functionScore, getQueryBody)
- } else {
- query = query.query('bool', getQueryBody)
- }
- }
- const queryBody = query.build()
- if (searchQuery.suggest) {
- queryBody.suggest = searchQuery.suggest
- }
-
- return queryBody
-}
diff --git a/core/lib/search/adapter/api/searchAdapter.ts b/core/lib/search/adapter/api/searchAdapter.ts
index c64fe343f..48c29a7b3 100644
--- a/core/lib/search/adapter/api/searchAdapter.ts
+++ b/core/lib/search/adapter/api/searchAdapter.ts
@@ -1,13 +1,14 @@
import map from 'lodash-es/map'
-import { prepareElasticsearchQueryBody } from '@vue-storefront/core/lib/search/adapter/api/elasticsearchQuery'
+import { elasticsearch } from 'storefront-query-builder'
import fetch from 'isomorphic-fetch'
import { slugify, processURLAddress } from '@vue-storefront/core/helpers'
import queryString from 'query-string'
import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import HttpQuery from '@vue-storefront/core/types/search/HttpQuery'
import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse'
import config from 'config'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
export class SearchAdapter {
public entities: any
@@ -23,7 +24,8 @@ export class SearchAdapter {
}
let ElasticsearchQueryBody = {}
if (Request.searchQuery instanceof SearchQuery) {
- ElasticsearchQueryBody = await prepareElasticsearchQueryBody(Request.searchQuery)
+ const bodybuilder = await import(/* webpackChunkName: "bodybuilder" */ 'bodybuilder')
+ ElasticsearchQueryBody = await elasticsearch.buildQueryBodyFromSearchQuery({ config, queryChain: bodybuilder.default(), searchQuery: Request.searchQuery })
if (Request.searchQuery.getSearchText() !== '') {
ElasticsearchQueryBody['min_score'] = config.elasticsearch.min_score
}
@@ -42,10 +44,10 @@ export class SearchAdapter {
Request.index = storeView.elasticsearch.index
- let url = processURLAddress(storeView.elasticsearch.host)
+ let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host'))
if (this.entities[Request.type].url) {
- url = this.entities[Request.type].url
+ url = getApiEndpointUrl(this.entities[Request.type], 'url')
}
const httpQuery: HttpQuery = {
@@ -72,7 +74,9 @@ export class SearchAdapter {
}
url = url + '/' + encodeURIComponent(Request.index) + '/' + encodeURIComponent(Request.type) + '/_search'
url = url + '?' + queryString.stringify(httpQuery)
- return fetch(url, { method: config.elasticsearch.queryMethod,
+
+ return fetch(url, {
+ method: config.elasticsearch.queryMethod,
mode: 'cors',
headers: {
'Accept': 'application/json',
@@ -99,6 +103,7 @@ export class SearchAdapter {
start: start,
perPage: size,
aggregations: resp.aggregations,
+ attributeMetadata: resp.attribute_metadata,
suggestions: resp.suggest
}
} else {
@@ -111,7 +116,7 @@ export class SearchAdapter {
}
}
- public registerEntityType (entityType, { url = '', queryProcessor, resultProcessor }) {
+ public registerEntityType (entityType, { url = '', url_ssr = '', queryProcessor, resultProcessor }) {
this.entities[entityType] = {
queryProcessor: queryProcessor,
resultProcessor: resultProcessor
@@ -119,6 +124,9 @@ export class SearchAdapter {
if (url !== '') {
this.entities[entityType]['url'] = url
}
+ if (url_ssr !== '') {
+ this.entities[entityType]['url_ssr'] = url_ssr
+ }
return this
}
diff --git a/core/lib/search/adapter/searchAdapterFactory.js b/core/lib/search/adapter/searchAdapterFactory.js
index 9a322ca33..9895d2eb6 100644
--- a/core/lib/search/adapter/searchAdapterFactory.js
+++ b/core/lib/search/adapter/searchAdapterFactory.js
@@ -1,5 +1,4 @@
import { server } from 'config'
-import { Logger } from '@vue-storefront/core/lib/logger'
let instances = {}
const isImplementingSearchAdapterInterface = (obj) => {
@@ -10,12 +9,12 @@ export const getSearchAdapter = async (adapterName = server.api) => {
let SearchAdapterModule
try {
- SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-" */ `src/search/adapter/${adapterName}/searchAdapter`)
+ SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-[request]" */ `src/search/adapter/${adapterName}/searchAdapter`)
} catch {}
if (!SearchAdapterModule) {
try {
- SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-" */ `./${adapterName}/searchAdapter`)
+ SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-[request]" */ `./${adapterName}/searchAdapter`)
} catch {}
}
diff --git a/core/lib/search/searchQuery.js b/core/lib/search/searchQuery.js
deleted file mode 100644
index 8eec238a8..000000000
--- a/core/lib/search/searchQuery.js
+++ /dev/null
@@ -1,80 +0,0 @@
-class SearchQuery {
- /**
- */
- constructor () {
- this._availableFilters = []
- this._appliedFilters = []
- this._searchText = ''
- }
-
- /**
- * @return {Array} array of all available filters objects
- */
- getAvailableFilters () {
- return this._availableFilters
- }
-
- /**
- * @return {Array} array of applied filters objects
- */
- getAppliedFilters () {
- return this._appliedFilters
- }
-
- /**
- * @return {String}
- */
- getSearchText () {
- return this._searchText
- }
-
- /**
- * @param {Object}
- * @return {Object}
- */
- applyFilter ({ key, value, scope = 'default', options = Object }) {
- this._appliedFilters.push({
- attribute: key,
- value: value,
- scope: scope,
- options: options
- })
-
- return this
- }
-
- /**
- * @param {Object}
- * @return {Object}
- */
- addAvailableFilter ({ field, scope = 'default', options = {} }) {
- // value can has only String, Array or numeric type
- this._availableFilters.push({
- field: field,
- scope: scope,
- options: options
- })
-
- return this
- }
-
- /**
- * @param {Array} filters
- * @return {Object}
- */
- setAvailableFilters (filters) {
- this._availableFilters = filters
- return this
- }
-
- /**
- * @param {String} searchText
- * @return {Object}
- */
- setSearchText (searchText) {
- this._searchText = searchText
- return this
- }
-}
-
-export default SearchQuery
diff --git a/core/lib/types.ts b/core/lib/types.ts
index ed2dc34b3..1791391ea 100644
--- a/core/lib/types.ts
+++ b/core/lib/types.ts
@@ -37,6 +37,10 @@ export interface StoreView {
defaultLocale: string,
currencyCode: string,
currencySign: string,
+ currencyDecimal: string,
+ currencyGroup: string,
+ fractionDigits: number,
+ priceFormat: string,
dateFormat: string
},
seo: {
diff --git a/core/modules/cart/helpers/cartCacheHandler.ts b/core/modules/cart/helpers/cartCacheHandler.ts
index 7041e16fb..683d0cedc 100644
--- a/core/modules/cart/helpers/cartCacheHandler.ts
+++ b/core/modules/cart/helpers/cartCacheHandler.ts
@@ -1,4 +1,5 @@
-import * as types from '../store/mutation-types';
+import * as types from '../store/mutation-types'
+import { Logger } from '@vue-storefront/core/lib/logger'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
@@ -15,19 +16,19 @@ export function cartCacheHandlerFactory (Vue) {
type.endsWith(types.CART_UPD_ITEM_PROPS)
) {
return StorageManager.get('cart').setItem('current-cart', state.cart.cartItems).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
} else if (
type.endsWith(types.CART_LOAD_CART_SERVER_TOKEN)
) {
return StorageManager.get('cart').setItem('current-cart-token', state.cart.cartServerToken).catch((reason) => {
- console.error(reason)
+ Logger.error(reason)()
})
} else if (
type.endsWith(types.CART_SET_ITEMS_HASH)
) {
return StorageManager.get('cart').setItem('current-cart-hash', state.cart.cartItemsHash).catch((reason) => {
- console.error(reason)
+ Logger.error(reason)()
})
}
}
diff --git a/core/modules/cart/helpers/createCartItemForUpdate.ts b/core/modules/cart/helpers/createCartItemForUpdate.ts
index 6c0224eda..6b13be97a 100644
--- a/core/modules/cart/helpers/createCartItemForUpdate.ts
+++ b/core/modules/cart/helpers/createCartItemForUpdate.ts
@@ -5,6 +5,7 @@ const createCartItemForUpdate = (clientItem: CartItem, serverItem: any, updateId
const sku = clientItem.parentSku && config.cart.setConfigurableProductOptions ? clientItem.parentSku : clientItem.sku
const cartItem = {
sku,
+ ...((serverItem && serverItem.item_id) ? { item_id: serverItem.item_id } : {}),
qty: mergeQty ? (clientItem.qty + serverItem.qty) : clientItem.qty,
product_option: clientItem.product_option
} as any as CartItem
diff --git a/core/modules/cart/helpers/getProductConfiguration.ts b/core/modules/cart/helpers/getProductConfiguration.ts
index df5a9b8f9..d45200279 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 => String(c.id) === String(product[attributeCode]))
if (!options) {
return null
diff --git a/core/modules/cart/helpers/optimizeProduct.ts b/core/modules/cart/helpers/optimizeProduct.ts
index 20d6326e8..16370a516 100644
--- a/core/modules/cart/helpers/optimizeProduct.ts
+++ b/core/modules/cart/helpers/optimizeProduct.ts
@@ -4,7 +4,7 @@ import omit from 'lodash-es/omit'
import pullAll from 'lodash-es/pullAll'
const optimizeProduct = (product: CartItem): CartItem => {
- if (!config.entities.optimize || !config.entities.optimizeShoppingCart) {
+ if (!config.entities.optimizeShoppingCart) {
return product
}
diff --git a/core/modules/cart/helpers/productsEquals.ts b/core/modules/cart/helpers/productsEquals.ts
index ad95e4989..7af6cb287 100644
--- a/core/modules/cart/helpers/productsEquals.ts
+++ b/core/modules/cart/helpers/productsEquals.ts
@@ -60,6 +60,7 @@ const makeCheck = (product1: CartItem, product2: CartItem, checks: string[]): bo
return true
}
}
+ return false
}
const productsEquals = (product1: CartItem, product2: CartItem): boolean => {
diff --git a/core/modules/cart/store/actions/itemActions.ts b/core/modules/cart/store/actions/itemActions.ts
index 3ce9518e9..02f0625fb 100644
--- a/core/modules/cart/store/actions/itemActions.ts
+++ b/core/modules/cart/store/actions/itemActions.ts
@@ -1,6 +1,5 @@
import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'
import { Logger } from '@vue-storefront/core/lib/logger'
-import { configureProductAsync } from '@vue-storefront/core/modules/catalog/helpers'
import {
prepareProductsToAdd,
productsEquals,
@@ -14,11 +13,11 @@ import config from 'config'
const itemActions = {
async configureItem (context, { product, configuration }) {
const { commit, dispatch, getters } = context
- const variant = configureProductAsync(context, {
+ const variant = await dispatch('product/getProductVariant', {
product,
- configuration,
- selectDefaultVariant: false
- })
+ configuration
+ }, { root: true })
+
const itemWithSameSku = getters.getCartItems.find(item => item.sku === variant.sku)
if (itemWithSameSku && product.sku !== variant.sku) {
diff --git a/core/modules/cart/store/actions/productActions.ts b/core/modules/cart/store/actions/productActions.ts
index f3f4ec28d..fa70ec7ee 100644
--- a/core/modules/cart/store/actions/productActions.ts
+++ b/core/modules/cart/store/actions/productActions.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const productActions = {
async findProductOption ({ dispatch }, { serverItem }) {
@@ -6,7 +6,15 @@ const productActions = {
let query = new SearchQuery()
query = query.applyFilter({ key: 'configurable_children.sku', value: { 'eq': serverItem.sku } })
- const { items } = await dispatch('product/list', { query, start: 0, size: 1, updateState: false }, { root: true })
+ const { items } = await dispatch('product/findProducts', {
+ query,
+ start: 0,
+ size: 1,
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
+ }, { root: true })
return items.length >= 1 ? { sku: items[0].sku, childSku: serverItem.sku } : null
}
@@ -16,7 +24,7 @@ const productActions = {
async getProductVariant ({ dispatch }, { serverItem }) {
try {
const options = await dispatch('findProductOption', { serverItem })
- const singleProduct = await dispatch('product/single', { options, assignDefaultVariant: true, setCurrentProduct: false, selectDefaultVariant: false }, { root: true })
+ const singleProduct = await dispatch('product/single', { options }, { root: true })
return {
...singleProduct,
diff --git a/core/modules/cart/store/getters.ts b/core/modules/cart/store/getters.ts
index d88157147..523381646 100644
--- a/core/modules/cart/store/getters.ts
+++ b/core/modules/cart/store/getters.ts
@@ -30,7 +30,7 @@ const getters: GetterTree = {
getItemsTotalQuantity: ({ cartItems }) => config.cart.minicartCountType === 'items' ? cartItems.length : sumBy(cartItems, p => p.qty),
getCoupon: ({ platformTotals }): AppliedCoupon | false =>
!(platformTotals && platformTotals.hasOwnProperty('coupon_code')) ? false : { code: platformTotals.coupon_code, discount: platformTotals.discount_amount },
- isVirtualCart: ({ cartItems }) => cartItems.every(itm => itm.type_id === 'downloadable' || itm.type_id === 'virtual'),
+ isVirtualCart: ({ cartItems }) => cartItems.length ? cartItems.every(itm => itm.type_id === 'downloadable' || itm.type_id === 'virtual') : false,
canUpdateMethods: (state, getters) => getters.isCartSyncEnabled && getters.isCartConnected,
canSyncTotals: (state, getters) => getters.isTotalsSyncEnabled && getters.isCartConnected,
isCartEmpty: state => state.cartItems.length === 0,
diff --git a/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts b/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts
index c53ee9c6e..b6a50c185 100644
--- a/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts
+++ b/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts
@@ -2,6 +2,7 @@ import Vue from 'vue'
import Vuex from 'vuex'
import * as types from '../../../store/mutation-types'
+import { Logger } from '@vue-storefront/core/lib/logger'
const StorageManager = {
cart: {
@@ -20,6 +21,12 @@ jest.mock('@vue-storefront/core/helpers', () => ({
jest.mock('@vue-storefront/core/app', () => ({ createApp: jest.fn() }))
jest.mock('@vue-storefront/i18n', () => ({ loadLanguageAsync: jest.fn() }))
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ error: () => () => {}
+ }
+}))
+
Vue.use(Vuex);
describe('Cart afterRegistration', () => {
@@ -55,7 +62,7 @@ describe('Cart afterRegistration', () => {
}
};
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementationOnce(() => {});
+ const consoleErrorSpy = jest.spyOn(Logger, 'error');
StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo'));
@@ -86,7 +93,7 @@ describe('Cart afterRegistration', () => {
}
};
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementationOnce(() => {});
+ const consoleErrorSpy = jest.spyOn(Logger, 'error');
StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo'));
@@ -102,7 +109,7 @@ describe('Cart afterRegistration', () => {
}
};
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementationOnce(() => {});
+ const consoleErrorSpy = jest.spyOn(Logger, 'error');
StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo'));
diff --git a/core/modules/cart/test/unit/helpers/productEquals.spec.ts b/core/modules/cart/test/unit/helpers/productEquals.spec.ts
index d54957961..9e8e40bd1 100644
--- a/core/modules/cart/test/unit/helpers/productEquals.spec.ts
+++ b/core/modules/cart/test/unit/helpers/productEquals.spec.ts
@@ -110,6 +110,12 @@ describe('Cart productEquals', () => {
expect(productsEquals(product1, product2)).toBeTruthy()
});
+ it('returns false 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()
+ });
+
it('returns true because products have the same server id', async () => {
const product1 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: null })
const product2 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: [2, 4, 5, 8] })
diff --git a/core/modules/cart/test/unit/store/connectActions.spec.ts b/core/modules/cart/test/unit/store/connectActions.spec.ts
index 4679d9a9b..5c12c7255 100644
--- a/core/modules/cart/test/unit/store/connectActions.spec.ts
+++ b/core/modules/cart/test/unit/store/connectActions.spec.ts
@@ -34,7 +34,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn());
jest.mock('@vue-storefront/core/helpers', () => ({
get isServer () {
return true
diff --git a/core/modules/cart/test/unit/store/couponActions.spec.ts b/core/modules/cart/test/unit/store/couponActions.spec.ts
index 61a8d9922..eced8f89c 100644
--- a/core/modules/cart/test/unit/store/couponActions.spec.ts
+++ b/core/modules/cart/test/unit/store/couponActions.spec.ts
@@ -31,7 +31,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn());
jest.mock('@vue-storefront/core/helpers', () => ({
get isServer () {
return true
diff --git a/core/modules/cart/test/unit/store/itemActions.spec.ts b/core/modules/cart/test/unit/store/itemActions.spec.ts
index a68bd0444..89b1517b8 100644
--- a/core/modules/cart/test/unit/store/itemActions.spec.ts
+++ b/core/modules/cart/test/unit/store/itemActions.spec.ts
@@ -3,6 +3,7 @@ import { configureProductAsync } from '@vue-storefront/core/modules/catalog/help
import { prepareProductsToAdd, productsEquals, validateProduct } from '@vue-storefront/core/modules/cart/helpers'
import cartActions from '@vue-storefront/core/modules/cart/store/actions';
import { createContextMock } from '@vue-storefront/unit-tests/utils';
+import config from 'config';
jest.mock('@vue-storefront/core/store', () => ({
dispatch: jest.fn(),
@@ -34,7 +35,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }))
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn())
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
@@ -61,20 +61,25 @@ jest.mock('@vue-storefront/core/helpers', () => ({
},
processLocalizedURLAddress: (url) => url
}));
+jest.mock('config', () => ({}));
describe('Cart itemActions', () => {
it('configures item and deletes when there is same sku', async () => {
const product1 = { sku: 1, name: 'product1', server_item_id: 1 }
const product2 = { sku: 2, name: 'product2', server_item_id: 2 }
- const configureProductAsyncMock = configureProductAsync as jest.Mock
- configureProductAsyncMock.mockImplementation(() => product2)
-
const contextMock = createContextMock({
getters: {
isCartSyncEnabled: true,
getCartItems: [product2]
- }
+ },
+ dispatch: jest.fn((actionName) => {
+ switch (actionName) {
+ case 'product/getProductVariant': {
+ return product2
+ }
+ }
+ })
})
await (cartActions as any).configureItem(contextMock, { product: product1, configuration: {} })
@@ -87,14 +92,18 @@ describe('Cart itemActions', () => {
const product1 = { sku: 1, name: 'product1', server_item_id: 1 }
const product2 = { sku: 2, name: 'product2', server_item_id: 2 }
- const configureProductAsyncMock = configureProductAsync as jest.Mock
- configureProductAsyncMock.mockImplementation(() => product2)
-
const contextMock = createContextMock({
getters: {
isCartSyncEnabled: true,
getCartItems: [product1]
- }
+ },
+ dispatch: jest.fn((actionName) => {
+ switch (actionName) {
+ case 'product/getProductVariant': {
+ return product2
+ }
+ }
+ })
})
await (cartActions as any).configureItem(contextMock, { product: product1, configuration: {} })
diff --git a/core/modules/cart/test/unit/store/mergeActions.spec.ts b/core/modules/cart/test/unit/store/mergeActions.spec.ts
index fe3313afa..61ed92c67 100644
--- a/core/modules/cart/test/unit/store/mergeActions.spec.ts
+++ b/core/modules/cart/test/unit/store/mergeActions.spec.ts
@@ -42,7 +42,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn());
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
diff --git a/core/modules/cart/test/unit/store/methodsActions.spec.ts b/core/modules/cart/test/unit/store/methodsActions.spec.ts
index 3fa70c4ce..8fe7dc93a 100644
--- a/core/modules/cart/test/unit/store/methodsActions.spec.ts
+++ b/core/modules/cart/test/unit/store/methodsActions.spec.ts
@@ -39,7 +39,7 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn());
+jest.mock('storefront-query-builder', () => jest.fn());
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
diff --git a/core/modules/cart/test/unit/store/productActions.spec.ts b/core/modules/cart/test/unit/store/productActions.spec.ts
index 21491f416..24395aaaf 100644
--- a/core/modules/cart/test/unit/store/productActions.spec.ts
+++ b/core/modules/cart/test/unit/store/productActions.spec.ts
@@ -36,7 +36,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn(() => ({ applyFilter: jest.fn() })));
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
@@ -77,7 +76,7 @@ describe('Cart productActions', () => {
(contextMock.dispatch as jest.Mock).mockImplementationOnce(() => ({ items: [serverItem] }));
const result = await (cartActions as any).findProductOption(contextMock, { serverItem });
- expect(contextMock.dispatch).toBeCalledWith('product/list', { start: 0, size: 1, updateState: false }, { root: true })
+ expect(contextMock.dispatch).toBeCalledWith('product/findProducts', { query: { _appliedFilters: [{ attribute: 'configurable_children.sku', options: Object, scope: 'default', value: { eq: 1 } }], _availableFilters: [], _appliedSort: [], _searchText: '' }, size: 1, start: 0, options: { populateRequestCacheTags: false, prefetchGroupProducts: false } }, { root: true })
expect(result).toEqual({ childSku: 1, sku: 1 })
});
@@ -90,7 +89,7 @@ describe('Cart productActions', () => {
const result = await (cartActions as any).getProductVariant(contextMock, { serverItem });
expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProductOption', { serverItem })
- expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'product/single', { options: {}, assignDefaultVariant: true, setCurrentProduct: false, selectDefaultVariant: false }, { root: true })
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'product/single', { options: {} }, { root: true })
expect(result).toEqual({
name: 'product1',
opt1: 1,
diff --git a/core/modules/cart/test/unit/store/quantityActions.spec.ts b/core/modules/cart/test/unit/store/quantityActions.spec.ts
index f41a9c3ff..ca6436e3f 100644
--- a/core/modules/cart/test/unit/store/quantityActions.spec.ts
+++ b/core/modules/cart/test/unit/store/quantityActions.spec.ts
@@ -37,7 +37,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn(() => ({ applyFilter: jest.fn() })));
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
diff --git a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts
index ae866ecb0..27c184079 100644
--- a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts
+++ b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts
@@ -41,7 +41,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn(() => ({ applyFilter: jest.fn() })));
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
diff --git a/core/modules/cart/test/unit/store/totalsActions.spec.ts b/core/modules/cart/test/unit/store/totalsActions.spec.ts
index c461cb8c0..331970394 100644
--- a/core/modules/cart/test/unit/store/totalsActions.spec.ts
+++ b/core/modules/cart/test/unit/store/totalsActions.spec.ts
@@ -43,7 +43,6 @@ jest.mock('@vue-storefront/core/lib/storage-manager', () => ({
}
}));
jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() }));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => jest.fn(() => ({ applyFilter: jest.fn() })));
jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
configureProductAsync: jest.fn()
}));
diff --git a/core/modules/catalog-next/store/category/CategoryState.ts b/core/modules/catalog-next/store/category/CategoryState.ts
index d67909ec5..d3af9e7a3 100644
--- a/core/modules/catalog-next/store/category/CategoryState.ts
+++ b/core/modules/catalog-next/store/category/CategoryState.ts
@@ -6,5 +6,6 @@ export default interface CategoryState {
notFoundCategoryIds: string[],
filtersMap: { [id: string]: any },
products: Product[],
- searchProductsStats: any
+ searchProductsStats: any,
+ menuCategories: Category[]
}
diff --git a/core/modules/catalog-next/store/category/actions.ts b/core/modules/catalog-next/store/category/actions.ts
index 01dbfea9d..05dcb3e43 100644
--- a/core/modules/catalog-next/store/category/actions.ts
+++ b/core/modules/catalog-next/store/category/actions.ts
@@ -4,25 +4,24 @@ import * as types from './mutation-types'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from './CategoryState'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
-import { buildFilterProductsQuery, isServer } from '@vue-storefront/core/helpers'
+import { buildFilterProductsQuery } from '@vue-storefront/core/helpers'
import { router } from '@vue-storefront/core/app'
-import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'
+import { localizedDispatcherRoute } from '@vue-storefront/core/lib/multistore'
import FilterVariant from '../../types/FilterVariant'
import { CategoryService } from '@vue-storefront/core/data-resolver'
import { changeFilterQuery } from '../../helpers/filterHelpers'
import { products, entities } from 'config'
-import { configureProductAsync } from '@vue-storefront/core/modules/catalog/helpers'
import { DataResolver } from 'core/data-resolver/types/DataResolver';
import { Category } from '../../types/Category';
import { _prepareCategoryPathIds } from '../../helpers/categoryHelpers';
import { prefetchStockItems } from '../../helpers/cacheProductsHelper';
-import { preConfigureProduct } from '@vue-storefront/core/modules/catalog/helpers/search'
import chunk from 'lodash-es/chunk'
-import Product from 'core/modules/catalog/types/Product';
import omit from 'lodash-es/omit'
import cloneDeep from 'lodash-es/cloneDeep'
import config from 'config'
import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers'
+import createCategoryListQuery from '@vue-storefront/core/modules/catalog/helpers/createCategoryListQuery'
+import { transformCategoryUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl';
const actions: ActionTree = {
async loadCategoryProducts ({ commit, getters, dispatch, rootState }, { route, category, pageSize = 50 } = {}) {
@@ -34,17 +33,30 @@ const actions: ActionTree = {
}
const searchQuery = getters.getCurrentFiltersFrom(route[products.routerFiltersSource], categoryMappedFilters)
let filterQr = buildFilterProductsQuery(searchCategory, searchQuery.filters)
- const { items, perPage, start, total, aggregations } = await quickSearchByQuery({
+ const { items, perPage, start, total, aggregations, attributeMetadata } = await dispatch('product/findProducts', {
query: filterQr,
sort: searchQuery.sort || `${products.defaultSortBy.attribute}:${products.defaultSortBy.order}`,
includeFields: entities.productList.includeFields,
excludeFields: entities.productList.excludeFields,
- size: pageSize
+ size: pageSize,
+ configuration: searchQuery.filters,
+ options: {
+ populateRequestCacheTags: true,
+ prefetchGroupProducts: false,
+ setProductErrors: false,
+ fallbackToDefaultWhenNoAvailable: true,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ }, { root: true })
+ await dispatch('loadAvailableFiltersFrom', {
+ aggregations,
+ attributeMetadata,
+ category: searchCategory,
+ filters: searchQuery.filters
})
- await dispatch('loadAvailableFiltersFrom', { aggregations, category: searchCategory, filters: searchQuery.filters })
commit(types.CATEGORY_SET_SEARCH_PRODUCTS_STATS, { perPage, start, total })
- const configuredProducts = await dispatch('processCategoryProducts', { products: items, filters: searchQuery.filters })
- commit(types.CATEGORY_SET_PRODUCTS, configuredProducts)
+ commit(types.CATEGORY_SET_PRODUCTS, items)
return items
},
@@ -55,33 +67,49 @@ const actions: ActionTree = {
const searchQuery = getters.getCurrentSearchQuery
let filterQr = buildFilterProductsQuery(getters.getCurrentCategory, searchQuery.filters)
- const searchResult = await quickSearchByQuery({
+ const searchResult = await dispatch('product/findProducts', {
query: filterQr,
sort: searchQuery.sort || `${products.defaultSortBy.attribute}:${products.defaultSortBy.order}`,
start: start + perPage,
size: perPage,
includeFields: entities.productList.includeFields,
- excludeFields: entities.productList.excludeFields
- })
+ excludeFields: entities.productList.excludeFields,
+ configuration: searchQuery.filters,
+ options: {
+ populateRequestCacheTags: true,
+ prefetchGroupProducts: false,
+ setProductErrors: false,
+ fallbackToDefaultWhenNoAvailable: true,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ }, { root: true })
commit(types.CATEGORY_SET_SEARCH_PRODUCTS_STATS, {
perPage: searchResult.perPage,
start: searchResult.start,
total: searchResult.total
})
- const configuredProducts = await dispatch('processCategoryProducts', { products: searchResult.items, filters: searchQuery.filters })
- commit(types.CATEGORY_ADD_PRODUCTS, configuredProducts)
+
+ commit(types.CATEGORY_ADD_PRODUCTS, searchResult.items)
return searchResult.items
},
async cacheProducts ({ commit, getters, dispatch, rootState }, { route } = {}) {
+ if (config.api.saveBandwidthOverCache) {
+ return
+ }
+
const searchCategory = getters.getCategoryFrom(route.path) || {}
const searchQuery = getters.getCurrentFiltersFrom(route[products.routerFiltersSource])
let filterQr = buildFilterProductsQuery(searchCategory, searchQuery.filters)
- const cachedProductsResponse = await dispatch('product/list', { // configure and calculateTaxes is being executed in the product/list - we don't need another call in here
+ const cachedProductsResponse = await dispatch('product/findProducts', {
query: filterQr,
sort: searchQuery.sort,
- updateState: false // not update the product listing - this request is only for caching
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
}, { root: true })
if (products.filterUnavailableVariants) { // prefetch the stock items
const skus = prefetchStockItems(cachedProductsResponse, rootState.stock.cache)
@@ -91,40 +119,6 @@ const actions: ActionTree = {
}
}
},
- /**
- * Calculates products taxes
- * Registers URLs
- * Configures products
- */
- async processCategoryProducts ({ dispatch, rootState }, { products = [], filters = {} } = {}) {
- const configuredProducts = await dispatch('configureProducts', { products, filters })
- dispatch('registerCategoryProductsMapping', products) // we don't need to wait for this
- return dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true })
- },
- /**
- * Configure configurable products to have first available options selected
- * so they can be added to cart/wishlist/compare without manual configuring
- */
- async configureProducts ({ rootState }, { products = [], filters = {}, populateRequestCacheTags = config.server.useOutputCacheTagging } = {}) {
- return products.map(product => {
- product = Object.assign({}, preConfigureProduct({ product, populateRequestCacheTags }))
- const configuredProductVariant = configureProductAsync({ rootState, state: { current_configuration: {} } }, { product, configuration: filters, selectDefaultVariant: false, fallbackToDefaultWhenNoAvailable: true, setProductErorrs: false })
- return Object.assign(product, omit(configuredProductVariant, ['visibility']))
- })
- },
- async registerCategoryProductsMapping ({ dispatch }, products = []) {
- const { storeCode, appendStoreCode } = currentStoreView()
- await Promise.all(products.map(product => {
- const { url_path, sku, slug, type_id } = product
- return dispatch('url/registerMapping', {
- url: localizedDispatcherRoute(url_path, storeCode),
- routeData: {
- params: { parentSku: product.sku, slug },
- 'name': localizedDispatcherRouteName(type_id + '-product', storeCode, appendStoreCode)
- }
- }, { root: true })
- }))
- },
async findCategories (context, categorySearchOptions: DataResolver.CategorySearchOptions): Promise {
return CategoryService.getCategories(categorySearchOptions)
},
@@ -148,7 +142,7 @@ const actions: ActionTree = {
Vue.prototype.$cacheTags.add(`C${category.id}`)
})
}
- const notFoundCategories = searchedIds.filter(categoryId => !categories.some(cat => cat.id === parseInt(categoryId)))
+ const notFoundCategories = searchedIds.filter(categoryId => !categories.some(cat => cat.id === parseInt(categoryId) || cat.id === categoryId))
commit(types.CATEGORY_ADD_CATEGORIES, categories)
commit(types.CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS, notFoundCategories)
@@ -171,14 +165,17 @@ const actions: ActionTree = {
async loadCategoryFilters ({ dispatch, getters }, category) {
const searchCategory = category || getters.getCurrentCategory
let filterQr = buildFilterProductsQuery(searchCategory)
- const { aggregations } = await quickSearchByQuery({
+ const { aggregations, attributeMetadata } = await quickSearchByQuery({
query: filterQr,
size: config.products.maxFiltersQuerySize,
excludeFields: ['*']
})
- await dispatch('loadAvailableFiltersFrom', { aggregations, category })
+ await dispatch('loadAvailableFiltersFrom', { aggregations, attributeMetadata: attributeMetadata, category })
},
- async loadAvailableFiltersFrom ({ commit, getters }, { aggregations, category, filters = {} }) {
+ async loadAvailableFiltersFrom ({ commit, getters, dispatch }, { aggregations, attributeMetadata, category, filters = {} }) {
+ if (config.entities.attribute.loadByAttributeMetadata) {
+ await dispatch('attribute/loadCategoryAttributes', { attributeMetadata }, { root: true })
+ }
const aggregationFilters = getters.getAvailableFiltersFrom(aggregations)
const currentCategory = category || getters.getCurrentCategory
const categoryMappedFilters = getters.getFiltersMap[currentCategory.id]
@@ -189,6 +186,7 @@ const actions: ActionTree = {
}
commit(types.CATEGORY_SET_CATEGORY_FILTERS, { category, filters: resultFilters })
},
+
async switchSearchFilters ({ dispatch }, filterVariants: FilterVariant[] = []) {
let currentQuery = router.currentRoute[products.routerFiltersSource]
filterVariants.forEach(filterVariant => {
@@ -204,7 +202,7 @@ const actions: ActionTree = {
},
async loadCategoryBreadcrumbs ({ dispatch, getters }, { category, currentRouteName, omitCurrent = false }) {
if (!category) return
- const categoryHierarchyIds = _prepareCategoryPathIds(category) // getters.getCategoriesHierarchyMap.find(categoryMapping => categoryMapping.includes(category.id))
+ const categoryHierarchyIds = category.parent_ids ? [...category.parent_ids, category.id] : _prepareCategoryPathIds(category) // getters.getCategoriesHierarchyMap.find(categoryMapping => categoryMapping.includes(category.id))
const categoryFilters = Object.assign({ 'id': categoryHierarchyIds }, cloneDeep(config.entities.category.breadcrumbFilterFields))
const categories = await dispatch('loadCategories', { filters: categoryFilters, reloadAll: Object.keys(config.entities.category.breadcrumbFilterFields).length > 0 })
const sorted = []
@@ -216,7 +214,55 @@ const actions: ActionTree = {
}
await dispatch('breadcrumbs/set', { current: currentRouteName, routes: parseCategoryPath(sorted) }, { root: true })
return sorted
- }
+ },
+ /**
+ * Load categories within specified parent
+ * @param {Object} commit promise
+ * @param {Object} parent parent category
+ */
+ async fetchMenuCategories ({ commit, getters, dispatch }, {
+ parent = null,
+ key = null,
+ value = null,
+ level = null,
+ onlyActive = true,
+ onlyNotEmpty = false,
+ size = 4000,
+ start = 0,
+ sort = 'position:asc',
+ includeFields = (config.entities.optimize ? config.entities.category.includeFields : null),
+ excludeFields = (config.entities.optimize ? config.entities.category.excludeFields : null),
+ skipCache = false
+ }) {
+ const { searchQuery, isCustomizedQuery } = createCategoryListQuery({ parent, level, key, value, onlyActive, onlyNotEmpty })
+ const shouldLoadCategories = skipCache || isCustomizedQuery
+
+ if (shouldLoadCategories) {
+ const resp = await quickSearchByQuery({ entityType: 'category', query: searchQuery, sort, size, start, includeFields, excludeFields })
+
+ await dispatch('registerCategoryMapping', { categories: resp.items })
+
+ commit(types.CATEGORY_UPD_MENU_CATEGORIES, { items: resp.items })
+
+ return resp
+ }
+
+ const list = { items: getters.getMenuCategories, total: getters.getMenuCategories.length }
+
+ return list
+ },
+ async registerCategoryMapping ({ dispatch }, { categories }) {
+ for (let category of categories) {
+ if (category.url_path) {
+ await dispatch('url/registerMapping', {
+ url: localizedDispatcherRoute(category.url_path),
+ routeData: transformCategoryUrl(category)
+ }, { root: true })
+ }
+ }
+ },
+ /** Below actions are not used from 1.12 and can be removed to reduce bundle */
+ ...require('./deprecatedActions').default
}
export default actions
diff --git a/core/modules/catalog-next/store/category/deprecatedActions.ts b/core/modules/catalog-next/store/category/deprecatedActions.ts
new file mode 100644
index 000000000..f6701570e
--- /dev/null
+++ b/core/modules/catalog-next/store/category/deprecatedActions.ts
@@ -0,0 +1,47 @@
+import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'
+import { preConfigureProduct } from '@vue-storefront/core/modules/catalog/helpers/search'
+import omit from 'lodash-es/omit'
+import config from 'config'
+const { configureProductAsync } = require('@vue-storefront/core/modules/catalog/helpers')
+
+const actions = {
+ /**
+ * Calculates products taxes
+ * Registers URLs
+ * Configures products
+ */
+ async processCategoryProducts ({ dispatch, rootState }, { products = [], filters = {} } = {}) {
+ dispatch('registerCategoryProductsMapping', products) // we don't need to wait for this
+ const configuredProducts = await dispatch('configureProducts', { products, filters })
+ return dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true })
+ },
+ /**
+ * Configure configurable products to have first available options selected
+ * so they can be added to cart/wishlist/compare without manual configuring
+ */
+ async configureProducts ({ rootState }, { products = [], filters = {}, populateRequestCacheTags = config.server.useOutputCacheTagging } = {}) {
+ return products.map(product => {
+ product = Object.assign({}, preConfigureProduct({ product, populateRequestCacheTags }))
+ const configuredProductVariant = configureProductAsync({ rootState, state: { current_configuration: {} } }, { product, configuration: filters, selectDefaultVariant: false, fallbackToDefaultWhenNoAvailable: true, setProductErorrs: false })
+ return Object.assign(product, omit(configuredProductVariant, ['visibility']))
+ })
+ },
+ async registerCategoryProductsMapping ({ dispatch }, products = []) {
+ const { storeCode, appendStoreCode } = currentStoreView()
+ await Promise.all(products.map(product => {
+ const { url_path, sku, slug, type_id } = product
+ return dispatch('url/registerMapping', {
+ url: localizedDispatcherRoute(url_path, storeCode),
+ routeData: {
+ params: {
+ parentSku: product.parentSku || product.sku,
+ slug
+ },
+ 'name': localizedDispatcherRouteName(type_id + '-product', storeCode, appendStoreCode)
+ }
+ }, { root: true })
+ }))
+ }
+}
+
+export default actions
diff --git a/core/modules/catalog-next/store/category/getters.ts b/core/modules/catalog-next/store/category/getters.ts
index 57d2e79a5..924ea644b 100644
--- a/core/modules/catalog-next/store/category/getters.ts
+++ b/core/modules/catalog-next/store/category/getters.ts
@@ -5,7 +5,7 @@ import CategoryState from './CategoryState'
import { compareByLabel } from '../../helpers/categoryHelpers'
import { products } from 'config'
import FilterVariant from '../../types/FilterVariant'
-import { optionLabel } from '../../helpers/optionLabel'
+import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers'
import trim from 'lodash-es/trim'
import toString from 'lodash-es/toString'
import forEach from 'lodash-es/forEach'
@@ -16,6 +16,7 @@ import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/help
import { _prepareCategoryPathIds, getSearchOptionsFromRouteParams } from '../../helpers/categoryHelpers';
import { currentStoreView, removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore'
import cloneDeep from 'lodash-es/cloneDeep'
+import config from 'config';
function mapCategoryProducts (productsFromState, productsData) {
return productsFromState.map(prodState => {
@@ -44,7 +45,7 @@ const getters: GetterTree = {
}) || {}
},
getCurrentCategory: (state, getters, rootState, rootGetters) => {
- return getters.getCategoryByParams(rootState.route.params)
+ return getters.getCategoryByParams({ ...rootGetters['url/getCurrentRoute'].params })
},
getAvailableFiltersFrom: (state, getters, rootState) => (aggregations) => {
const filters = {}
@@ -139,6 +140,9 @@ const getters: GetterTree = {
const totalValue = typeof total === 'object' ? total.value : total
return totalValue || 0
+ },
+ getMenuCategories (state, getters, rootState, rootGetters) {
+ return state.menuCategories || rootGetters['category/getCategories']
}
}
diff --git a/core/modules/catalog-next/store/category/index.ts b/core/modules/catalog-next/store/category/index.ts
index 8dd910ea1..6d2f9f7ea 100644
--- a/core/modules/catalog-next/store/category/index.ts
+++ b/core/modules/catalog-next/store/category/index.ts
@@ -12,7 +12,8 @@ export const categoryModule: Module = {
notFoundCategoryIds: [],
filtersMap: {},
products: [],
- searchProductsStats: {}
+ searchProductsStats: {},
+ menuCategories: []
},
getters,
actions,
diff --git a/core/modules/catalog-next/store/category/mutation-types.ts b/core/modules/catalog-next/store/category/mutation-types.ts
index 5e0ed5da0..3234f97a9 100644
--- a/core/modules/catalog-next/store/category/mutation-types.ts
+++ b/core/modules/catalog-next/store/category/mutation-types.ts
@@ -6,3 +6,4 @@ export const CATEGORY_ADD_CATEGORIES = `${SN_CATEGORY}/ADD_CATEGORIES`
export const CATEGORY_ADD_CATEGORY = `${SN_CATEGORY}/ADD_CATEGORY`
export const CATEGORY_SET_CATEGORY_FILTERS = `${SN_CATEGORY}/SET_CATEGORY_FILTERS`
export const CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS = `${SN_CATEGORY}/ADD_NOT_FOUND_CATEGORY_IDS`
+export const CATEGORY_UPD_MENU_CATEGORIES = `${SN_CATEGORY}/UPD_MENU_CATEGORIES`
diff --git a/core/modules/catalog-next/store/category/mutations.ts b/core/modules/catalog-next/store/category/mutations.ts
index 4580128ad..fcee77454 100644
--- a/core/modules/catalog-next/store/category/mutations.ts
+++ b/core/modules/catalog-next/store/category/mutations.ts
@@ -6,6 +6,7 @@ import * as types from './mutation-types'
import CategoryState from './CategoryState'
import { Category } from '../../types/Category'
import cloneDeep from 'lodash-es/cloneDeep'
+import slugifyCategories from '@vue-storefront/core/modules/catalog/helpers/slugifyCategories'
const mutations: MutationTree = {
[types.CATEGORY_SET_PRODUCTS] (state, products = []) {
@@ -38,6 +39,24 @@ const mutations: MutationTree = {
},
[types.CATEGORY_SET_SEARCH_PRODUCTS_STATS] (state, stats = {}) {
state.searchProductsStats = stats
+ },
+ [types.CATEGORY_UPD_MENU_CATEGORIES] (state, categories) {
+ for (let category of categories.items) {
+ category = slugifyCategories(category)
+ const catExist = state.menuCategories.find(existingCat => existingCat.id === category.id)
+
+ if (!catExist) {
+ state.menuCategories.push(category)
+ }
+ }
+
+ state.menuCategories.sort((catA, catB) => {
+ if (catA.position && catB.position) {
+ if (catA.position < catB.position) return -1
+ if (catA.position > catB.position) return 1
+ }
+ return 0
+ })
}
}
diff --git a/core/modules/catalog-next/types/Category.d.ts b/core/modules/catalog-next/types/Category.d.ts
index c1b24106f..a5bc993eb 100644
--- a/core/modules/catalog-next/types/Category.d.ts
+++ b/core/modules/catalog-next/types/Category.d.ts
@@ -20,7 +20,8 @@ export interface Category {
url_path: string,
url_key: string,
children_data: ChildrenData[],
- slug: string
+ slug: string,
+ position?: number
}
export interface Filters {
diff --git a/core/modules/catalog/components/Search.ts b/core/modules/catalog/components/Search.ts
index 7758b807d..f22343331 100644
--- a/core/modules/catalog/components/Search.ts
+++ b/core/modules/catalog/components/Search.ts
@@ -42,40 +42,58 @@ export const Search = {
let searchQuery = prepareQuickSearchQuery(queryText)
return searchQuery
},
- makeSearch () {
+ async makeSearch () {
if (this.search !== '' && this.search !== undefined) {
let query = this.buildSearchQuery(this.search)
let startValue = 0;
this.start = startValue
this.readMore = true
- this.$store.dispatch('product/list', { query, start: this.start, configuration: {}, size: this.size, updateState: false }).then(resp => {
- this.products = resp.items
+ try {
+ const { items } = await this.$store.dispatch('product/findProducts', {
+ query,
+ start: this.start,
+ size: this.size,
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
+ })
+ this.products = items
this.start = startValue + this.size
- this.emptyResults = resp.items.length < 1
- }).catch((err) => {
+ this.emptyResults = items.length < 1
+ } catch (err) {
Logger.error(err, 'components-search')()
- })
+ }
} else {
this.products = []
this.emptyResults = 0
}
},
- seeMore () {
+ async seeMore () {
if (this.search !== '' && this.search !== undefined) {
let query = this.buildSearchQuery(this.search)
let startValue = this.start;
- this.$store.dispatch('product/list', { query, start: startValue, size: this.size, updateState: false }).then((resp) => {
- let page = Math.floor(resp.total / this.size)
- let exceeed = resp.total - this.size * page
- if (resp.start === resp.total - exceeed) {
+ try {
+ const { items, total, start } = await this.$store.dispatch('product/findProducts', {
+ query,
+ start: startValue,
+ size: this.size,
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
+ })
+ let page = Math.floor(total / this.size)
+ let exceeed = total - this.size * page
+ if (start === total - exceeed) {
this.readMore = false
}
- this.products = this.products.concat(resp.items)
+ this.products = this.products.concat(items)
this.start = startValue + this.size
this.emptyResults = this.products.length < 1
- }).catch((err) => {
+ } catch (err) {
Logger.error(err, 'components-search')()
- })
+ }
} else {
this.products = []
this.emptyResults = 0
diff --git a/core/modules/catalog/events.ts b/core/modules/catalog/events.ts
index f99f03716..da1443376 100644
--- a/core/modules/catalog/events.ts
+++ b/core/modules/catalog/events.ts
@@ -1,7 +1,9 @@
import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
-import { PRODUCT_SET_CURRENT_CONFIGURATION, PRODUCT_SET_CURRENT } from './store/product/mutation-types'
+import { PRODUCT_SET_CURRENT } from './store/product/mutation-types'
import omit from 'lodash-es/omit'
import config from 'config'
+import i18n from '@vue-storefront/core/i18n';
+import { SearchQuery } from 'storefront-query-builder'
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';
@@ -22,36 +24,44 @@ export const productAfterPriceupdate = async (product, store) => {
export const filterChangedProduct = async (filterOption, store, router) => {
EventBus.$emit('product-before-configure', { filterOption: filterOption, configuration: store.getters['product/getCurrentProductConfiguration'] })
- const prevOption = store.getters['product/getCurrentProductConfiguration'][filterOption.attribute_code]
- let changedConfig = Object.assign({}, store.getters['product/getCurrentProductConfiguration'], { [filterOption.attribute_code]: filterOption })
- const selectedVariant = await store.dispatch('product/configure', {
- product: store.getters['product/getCurrentProduct'],
+ const currentProductConfiguration = store.getters['product/getCurrentProductConfiguration']
+ const changedConfig = Object.assign({}, currentProductConfiguration, { [filterOption.attribute_code]: filterOption })
+ let searchQuery = new SearchQuery()
+ searchQuery = searchQuery.applyFilter({ key: 'sku', value: { 'eq': store.getters['product/getCurrentProduct'].parentSku } })
+ const { items: [newProductVariant] } = await store.dispatch('product/findProducts', {
+ query: searchQuery,
+ size: 1,
configuration: changedConfig,
- selectDefaultVariant: true,
- fallbackToDefaultWhenNoAvailable: false,
- setProductErorrs: true
+ options: {
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: true,
+ assignProductConfiguration: true,
+ separateSelectedVariant: true
+ }
}, { root: true })
- if (config.products.setFirstVarianAsDefaultInURL) {
- router.push({ params: { childSku: selectedVariant.sku } })
+ const { configuration, selectedVariant, options, product_option } = newProductVariant
+ if (config.products.setFirstVarianAsDefaultInURL && selectedVariant) {
+ const routeProp = config.seo.useUrlDispatcher ? 'params' : 'query'
+ router.push({ [routeProp]: { childSku: selectedVariant.sku } })
}
- if (!selectedVariant) {
- if (prevOption) {
- store.commit(prefixMutation(PRODUCT_SET_CURRENT_CONFIGURATION), Object.assign(
- {},
- store.getters['product/getCurrentProductConfiguration'],
- {
- [filterOption.attribute_code]: prevOption
- }
- ), { root: true })
- } else {
- store.commit(prefixMutation(PRODUCT_SET_CURRENT_CONFIGURATION), Object.assign(
- {},
- store.getters['product/getCurrentProductConfiguration'],
- {
- [filterOption.attribute_code]: undefined
- }
- ), { root: true })
- }
+ if (selectedVariant) {
+ const newProductConfiguration = Object.assign(
+ {},
+ store.getters['product/getCurrentProduct'],
+ selectedVariant,
+ { configuration, options, product_option }
+ )
+ await store.dispatch('product/setCurrent', newProductConfiguration)
+ EventBus.$emit('product-after-configure', { product: newProductConfiguration, configuration: configuration, selectedVariant: selectedVariant })
+ return selectedVariant
+ } else {
+ store.dispatch('notification/spawnNotification', {
+ type: 'warning',
+ message: i18n.t(
+ 'No such configuration for the product. Please do choose another combination of attributes.'
+ ),
+ action1: { label: i18n.t('OK') }
+ })
}
}
@@ -66,8 +76,8 @@ export const productAfterCustomoptions = async (payload, store) => {
priceDeltaInclTax += optionValue.price
}
if (optionValue.price_type === 'percent' && optionValue.price !== 0) {
- priceDelta += ((optionValue.price / 100) * store.getters['product/getOriginalProduct'].price)
- priceDeltaInclTax += ((optionValue.price / 100) * store.getters['product/getOriginalProduct'].price_incl_tax)
+ priceDelta += ((optionValue.price / 100) * store.getters['product/getCurrentProduct'].price)
+ priceDeltaInclTax += ((optionValue.price / 100) * store.getters['product/getCurrentProduct'].price_incl_tax)
}
}
})
@@ -76,8 +86,8 @@ export const productAfterCustomoptions = async (payload, store) => {
{},
store.getters['product/getCurrentProduct'],
{
- price: store.getters['product/getOriginalProduct'].price + priceDelta,
- price_incl_tax: store.getters['product/getOriginalProduct'].price_incl_tax + priceDeltaInclTax
+ price: store.getters['product/getCurrentProduct'].price + priceDelta,
+ price_incl_tax: store.getters['product/getCurrentProduct'].price_incl_tax + priceDeltaInclTax
}
), { root: true })
}
diff --git a/core/modules/catalog/helpers/associatedProducts/buildQuery.ts b/core/modules/catalog/helpers/associatedProducts/buildQuery.ts
new file mode 100644
index 000000000..5a409f655
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/buildQuery.ts
@@ -0,0 +1,16 @@
+import config from 'config'
+import { SearchQuery } from 'storefront-query-builder'
+
+/**
+ * Creates simple query that will search product by skus list
+ */
+export default function buildQuery (skus: string[]): SearchQuery {
+ let productsQuery = new SearchQuery()
+ productsQuery = productsQuery
+ .applyFilter({ key: 'sku', value: { 'in': skus } })
+ .applyFilter({ key: 'status', value: { 'in': [1] } })
+ if (config.products.listOutOfStockProducts === false) {
+ productsQuery = productsQuery.applyFilter({ key: 'stock.is_in_stock', value: { 'eq': true } })
+ }
+ return productsQuery
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts b/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts
new file mode 100644
index 000000000..5ceb6b738
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts
@@ -0,0 +1,15 @@
+import {
+ getBundleOptionsValues,
+ getBundleOptionPrice,
+ getSelectedBundleOptions
+} from '@vue-storefront/core/modules/catalog/helpers/bundleOptions'
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+export default function getBundleProductPrice (product: Product) {
+ const selectedBundleOptions = getSelectedBundleOptions(product)
+ const { price, priceInclTax } = getBundleOptionPrice(
+ getBundleOptionsValues(selectedBundleOptions, product.bundle_options)
+ )
+
+ return { price, priceInclTax }
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts b/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts
new file mode 100644
index 000000000..ac50a373f
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts
@@ -0,0 +1,8 @@
+import Product, { ProductLink } from '@vue-storefront/core/modules/catalog/types/Product';
+import { getProductLinkPrice } from './getProductLinkPrice';
+
+export default function getGroupedProductPrice (product: Product) {
+ const productLinks: ProductLink[] = (product.product_links || [])
+
+ return getProductLinkPrice(productLinks)
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts b/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts
new file mode 100644
index 000000000..34339f690
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts
@@ -0,0 +1,36 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+interface BaseProductLink {
+ product?: Product,
+ qty?: number
+}
+
+export const calculateProductLinkPrice = ({ price = 1, priceInclTax = 1, qty = 1 }) => {
+ const product = {
+ price: 0,
+ priceInclTax: 0
+ }
+ const qtyNum = typeof qty === 'string' ? parseInt(qty) : qty
+ if (qtyNum >= 0) {
+ product.price += price * qtyNum
+ product.priceInclTax += priceInclTax * qtyNum
+ }
+ return product
+}
+
+export const getProductLinkPrice = (productLinks: BaseProductLink[]) => productLinks
+ .map((productLink) => {
+ const product = productLink.product || { price: 1, price_incl_tax: 1, priceInclTax: 1 }
+ return calculateProductLinkPrice({
+ price: product.price,
+ priceInclTax: product.price_incl_tax || product.priceInclTax,
+ qty: productLink.qty
+ })
+ })
+ .reduce(
+ (priceDelta, currentPriceDelta) => ({
+ price: currentPriceDelta.price + priceDelta.price,
+ priceInclTax: currentPriceDelta.priceInclTax + priceDelta.priceInclTax
+ }),
+ { price: 0, priceInclTax: 0 }
+ )
diff --git a/core/modules/catalog/helpers/associatedProducts/index.ts b/core/modules/catalog/helpers/associatedProducts/index.ts
new file mode 100644
index 000000000..eaaac8392
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/index.ts
@@ -0,0 +1,7 @@
+import setGroupedProduct from './setGroupedProduct'
+import setBundleProducts from './setBundleProducts'
+
+export {
+ setGroupedProduct,
+ setBundleProducts
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts b/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts
new file mode 100644
index 000000000..1a0858add
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts
@@ -0,0 +1,45 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import { isBundleProduct } from './..';
+import buildQuery from './buildQuery'
+import setProductLink from './setProductLink'
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import getBundleProductPrice from './getBundleProductPrice'
+
+/**
+ * This function prepare all product_links for bundle products.
+ * It fetches products by sku.
+ */
+export default async function setBundleProducts (product: Product, { includeFields = null, excludeFields = null } = {}) {
+ if (isBundleProduct(product) && product.bundle_options) {
+ const skus = product.bundle_options
+ .map((bundleOption) => bundleOption.product_links.map((productLink) => productLink.sku))
+ .reduce((acc, next) => acc.concat(next), [])
+
+ const query = buildQuery(skus)
+ const { items } = await ProductService.getProducts({
+ query,
+ excludeFields,
+ includeFields,
+ options: {
+ prefetchGroupProducts: false,
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: false,
+ setConfigurableProductOptions: false,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ })
+
+ for (const bundleOption of product.bundle_options) {
+ for (const productLink of bundleOption.product_links) {
+ const associatedProduct = items.find((associatedProduct) => associatedProduct.sku === productLink.sku)
+ setProductLink(productLink, associatedProduct)
+ }
+ }
+
+ const { price, priceInclTax } = getBundleProductPrice(product)
+ product.price = price
+ product.priceInclTax = priceInclTax
+ product.price_incl_tax = priceInclTax
+ }
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts b/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts
new file mode 100644
index 000000000..af48700d3
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts
@@ -0,0 +1,42 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import { isGroupedProduct } from './..';
+import buildQuery from './buildQuery'
+import setProductLink from './setProductLink'
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import getGroupedProductPrice from './getGroupedProductPrice'
+
+/**
+ * This function prepare all product_links for grouped products.
+ * It fetches products by sku.
+ */
+export default async function setGroupedProduct (product: Product, { includeFields = null, excludeFields = null } = {}) {
+ if (isGroupedProduct(product) && product.product_links) {
+ const productLinks = product.product_links.filter((productLink) => productLink.link_type === 'associated' && productLink.linked_product_type === 'simple')
+ const skus = productLinks.map((productLink) => productLink.linked_product_sku)
+
+ const query = buildQuery(skus)
+ const { items } = await ProductService.getProducts({
+ query,
+ excludeFields,
+ includeFields,
+ options: {
+ prefetchGroupProducts: false,
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: false,
+ setConfigurableProductOptions: false,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ })
+
+ for (const productLink of productLinks) {
+ const associatedProduct = items.find((associatedProduct) => associatedProduct.sku === productLink.linked_product_sku)
+ setProductLink(productLink, associatedProduct)
+ }
+
+ const { price, priceInclTax } = getGroupedProductPrice(product)
+ product.price = price
+ product.priceInclTax = priceInclTax
+ product.price_incl_tax = priceInclTax
+ }
+}
diff --git a/core/modules/catalog/helpers/associatedProducts/setProductLink.ts b/core/modules/catalog/helpers/associatedProducts/setProductLink.ts
new file mode 100644
index 000000000..bb9b0da7f
--- /dev/null
+++ b/core/modules/catalog/helpers/associatedProducts/setProductLink.ts
@@ -0,0 +1,17 @@
+import Product, { ProductLink } from '@vue-storefront/core/modules/catalog/types/Product';
+import { preConfigureProduct } from '../prepare';
+import { Logger } from '@vue-storefront/core/lib/logger';
+import { BundleOptionsProductLink } from '@vue-storefront/core/modules/catalog/types/BundleOption';
+
+/**
+ * Set associated product to product link object
+ */
+
+export default async function setProductLink (productLink: BundleOptionsProductLink | ProductLink, associatedProduct: Product) {
+ if (associatedProduct) {
+ productLink.product = preConfigureProduct(associatedProduct)
+ productLink.product.qty = Number((productLink as BundleOptionsProductLink).qty || '1')
+ } else {
+ Logger.error('Product not found', (productLink as ProductLink).linked_product_sku || productLink.sku)()
+ }
+}
diff --git a/core/modules/catalog/helpers/bundleOptions.ts b/core/modules/catalog/helpers/bundleOptions.ts
index 51234dc88..b54561390 100644
--- a/core/modules/catalog/helpers/bundleOptions.ts
+++ b/core/modules/catalog/helpers/bundleOptions.ts
@@ -1,39 +1,35 @@
+import { getProductLinkPrice } from './associatedProducts/getProductLinkPrice';
import get from 'lodash-es/get'
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import { BundleOption, BundleOptionsProductLink, SelectedBundleOption } from '@vue-storefront/core/modules/catalog/types/BundleOption';
-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 getBundleOptionPrice = (bundleOptionValues: BundleOptionsProductLink[]) => getProductLinkPrice(bundleOptionValues)
-export const getBundleOptionsValues = (selectedBundleOptions, allBundeOptions) => selectedBundleOptions
+export const getBundleOptionsValues = (selectedBundleOptions: SelectedBundleOption[], allBundeOptions: BundleOption[]): BundleOptionsProductLink[] => 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])) || {}
+ const value = productLinks.find(productLink => String(productLink.id) === String(selectedBundleOption.option_selections[0]))
return { ...value, qty: selectedBundleOption.option_qty }
})
+
+export const getSelectedBundleOptions = (product: Product): SelectedBundleOption[] => {
+ const selectedBundleOptions = Object.values(get(product, 'product_option.extension_attributes.bundle_options', {}))
+ if (selectedBundleOptions.length) {
+ return selectedBundleOptions as any as SelectedBundleOption[]
+ }
+
+ // get default options
+ const allBundeOptions = product.bundle_options || []
+ return allBundeOptions.map((bundleOption) => {
+ const productLinks = bundleOption.product_links || []
+ const defaultLink = productLinks.find((productLink) => productLink.is_default) || productLinks[0]
+ const qty = (typeof defaultLink.qty === 'string' ? parseInt(defaultLink.qty) : defaultLink.qty) || 1
+ return {
+ option_id: bundleOption.option_id,
+ option_qty: qty,
+ option_selections: [Number(defaultLink.id)]
+ }
+ })
+}
diff --git a/core/modules/catalog/helpers/configure/configureProductAsync.ts b/core/modules/catalog/helpers/configure/configureProductAsync.ts
new file mode 100644
index 000000000..45032161e
--- /dev/null
+++ b/core/modules/catalog/helpers/configure/configureProductAsync.ts
@@ -0,0 +1,93 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import cloneDeep from 'lodash/cloneDeep'
+import { getSelectedVariant, omitSelectedVariantFields } from '../variant';
+import { getProductConfiguration, setProductConfigurableOptions } from '../productOptions';
+import { filterOutUnavailableVariants } from '../stock';
+import { setGroupedProduct, setBundleProducts } from '../associatedProducts';
+import { hasConfigurableChildren } from './..'
+
+interface ConfigureProductAsyncParams {
+ product: Product,
+ configuration: any,
+ attribute: any,
+ options?: {
+ fallbackToDefaultWhenNoAvailable?: boolean,
+ setProductErrors?: boolean,
+ setConfigurableProductOptions?: boolean,
+ filterUnavailableVariants?: boolean,
+ assignProductConfiguration?: boolean,
+ separateSelectedVariant?: boolean,
+ prefetchGroupProducts?: boolean
+ },
+ stockItems: any[],
+ excludeFields?: string[],
+ includeFields?: string[]
+}
+
+/**
+ * This function configure product for 'configurable', 'bundle' or 'group' product.
+ */
+export default async function configureProductAsync ({
+ product,
+ configuration,
+ attribute,
+ options: {
+ fallbackToDefaultWhenNoAvailable = true,
+ setProductErrors = true,
+ setConfigurableProductOptions = true,
+ filterUnavailableVariants = false,
+ assignProductConfiguration = false,
+ separateSelectedVariant = false,
+ prefetchGroupProducts = false
+ } = {},
+ stockItems = [],
+ excludeFields,
+ includeFields
+}: ConfigureProductAsyncParams) {
+ // it not only filter variants but also it apply stock object
+ if (filterUnavailableVariants) {
+ filterOutUnavailableVariants(product, stockItems)
+ }
+
+ // setup bundle or group product. Product is filled with productLinks
+ if (prefetchGroupProducts) {
+ await setGroupedProduct(product, { includeFields, excludeFields })
+ await setBundleProducts(product, { includeFields, excludeFields })
+ }
+
+ // setup configurable product
+ if (hasConfigurableChildren(product)) {
+ // we don't want to modify configuration object
+ let _configuration = cloneDeep(configuration)
+
+ // find selected variant by configuration
+ let selectedVariant = getSelectedVariant(product, _configuration, { fallbackToDefaultWhenNoAvailable })
+
+ if (selectedVariant) {
+ // if there is selectedVariant we want to get configuration based on that variant
+ _configuration = getProductConfiguration({ product, selectedVariant, attribute })
+
+ // here we add product_options with selected configuration. It only applies to configurable product
+ setProductConfigurableOptions({ product, configuration: _configuration, setConfigurableProductOptions }) // set the custom options
+
+ product.is_configured = true
+
+ // remove props from variant that we don't want need to override in base product
+ selectedVariant = omitSelectedVariantFields(selectedVariant)
+ }
+ if (!selectedVariant && setProductErrors) { // can not find variant anyway, even the default one
+ product.errors.variants = 'No available product variants'
+ }
+
+ const configuredProduct = {
+ ...product,
+ ...(assignProductConfiguration ? { configuration: _configuration } : {}) // we can need configuration as separate object
+ }
+ return {
+ ...configuredProduct,
+ ...(separateSelectedVariant ? { selectedVariant } : selectedVariant) // we can need selected variant as separate object
+ }
+ }
+
+ return product
+}
diff --git a/core/modules/catalog/helpers/configure/configureProducts.ts b/core/modules/catalog/helpers/configure/configureProducts.ts
new file mode 100644
index 000000000..f8d0589a4
--- /dev/null
+++ b/core/modules/catalog/helpers/configure/configureProducts.ts
@@ -0,0 +1,59 @@
+import { AttributesMetadata } from './../../types/Attribute';
+import { getStockItems } from '../stock';
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import transformMetadataToAttributes from '../transformMetadataToAttributes';
+import configureProductAsync from './configureProductAsync';
+
+interface ConfigureProductsParams {
+ products: Product[],
+ attributes_metadata: AttributesMetadata[],
+ configuration: any,
+ options?: {
+ fallbackToDefaultWhenNoAvailable?: boolean,
+ setProductErrors?: boolean,
+ setConfigurableProductOptions?: boolean,
+ filterUnavailableVariants?: boolean,
+ assignProductConfiguration?: boolean,
+ separateSelectedVariant?: boolean,
+ prefetchGroupProducts?: boolean
+ },
+ excludeFields?: string[],
+ includeFields?: string[]
+}
+
+/**
+ * Prepare all data needed to make product configuration.
+ * After common data is setup this function map through every product and configure it based on 'configuration' object
+ */
+export default async function configureProducts ({
+ products,
+ attributes_metadata = [],
+ configuration = {},
+ options = {},
+ excludeFields = null,
+ includeFields = null
+}: ConfigureProductsParams) {
+ const productAttributesMetadata = products.map((product) => product.attributes_metadata || [])
+ const attribute = transformMetadataToAttributes([attributes_metadata, ...productAttributesMetadata])
+ const attributeStateFormat = { list_by_code: attribute.attrHashByCode, list_by_id: attribute.attrHashById }
+
+ let stockItems = []
+ if (options.filterUnavailableVariants) {
+ stockItems = await getStockItems(products)
+ }
+
+ const configuredProducts = await Promise.all((products as Product[]).map(async (product) => {
+ const configuredProduct = await configureProductAsync({
+ product,
+ configuration,
+ attribute: attributeStateFormat,
+ options: options,
+ stockItems,
+ excludeFields,
+ includeFields
+ })
+ return configuredProduct as Product
+ }))
+
+ return configuredProducts
+}
diff --git a/core/modules/catalog/helpers/configure/index.ts b/core/modules/catalog/helpers/configure/index.ts
new file mode 100644
index 000000000..258ce9637
--- /dev/null
+++ b/core/modules/catalog/helpers/configure/index.ts
@@ -0,0 +1,5 @@
+import configureProducts from './configureProducts'
+
+export {
+ configureProducts
+}
diff --git a/core/modules/catalog/helpers/createAttributesListQuery.ts b/core/modules/catalog/helpers/createAttributesListQuery.ts
index dedeabc16..7883c6c7d 100644
--- a/core/modules/catalog/helpers/createAttributesListQuery.ts
+++ b/core/modules/catalog/helpers/createAttributesListQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createAttributesListQuery = ({
filterValues,
diff --git a/core/modules/catalog/helpers/createCategoryListQuery.ts b/core/modules/catalog/helpers/createCategoryListQuery.ts
index 0ba89ff8e..5e6db47db 100644
--- a/core/modules/catalog/helpers/createCategoryListQuery.ts
+++ b/core/modules/catalog/helpers/createCategoryListQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import { isServer } from '@vue-storefront/core/helpers'
import config from 'config'
diff --git a/core/modules/catalog/helpers/customOption.ts b/core/modules/catalog/helpers/customOption.ts
index 23a007ec5..2ff7a837b 100644
--- a/core/modules/catalog/helpers/customOption.ts
+++ b/core/modules/catalog/helpers/customOption.ts
@@ -60,7 +60,7 @@ export const getCustomOptionValues = (selectedCustomOptions: SelectedCustomOptio
})
.reduce((allCustomOptionValues, customOptionValue) => allCustomOptionValues.concat(customOptionValue), []) // merge all values in one array
-export const getCustomOptionPriceDelta = (customOptionValues: OptionValue[], { price, priceInclTax }: Pick) => customOptionValues
+export const getCustomOptionPriceDelta = (customOptionValues: OptionValue[], { price, priceInclTax, price_incl_tax }: Pick) => customOptionValues
.reduce((delta, customOptionValue) => {
if (customOptionValue.price_type === 'fixed' && customOptionValue.price !== 0) {
delta.price += customOptionValue.price
@@ -68,7 +68,7 @@ export const getCustomOptionPriceDelta = (customOptionValues: OptionValue[], { p
}
if (customOptionValue.price_type === 'percent' && customOptionValue.price !== 0) {
delta.price += ((customOptionValue.price / 100) * price)
- delta.priceInclTax += ((customOptionValue.price / 100) * priceInclTax)
+ delta.priceInclTax += ((customOptionValue.price / 100) * (priceInclTax || price_incl_tax))
}
return delta
}, {
diff --git a/core/modules/catalog/helpers/deprecatedHelpers.ts b/core/modules/catalog/helpers/deprecatedHelpers.ts
new file mode 100644
index 000000000..2917769e9
--- /dev/null
+++ b/core/modules/catalog/helpers/deprecatedHelpers.ts
@@ -0,0 +1,145 @@
+import Vue from 'vue'
+import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
+import omit from 'lodash-es/omit'
+import i18n from '@vue-storefront/i18n'
+import { Logger } from '@vue-storefront/core/lib/logger'
+import { optionLabel } from './optionLabel'
+import { setProductConfigurableOptions } from './productOptions'
+import config from 'config'
+import { findConfigurableVariant } from './variant'
+import { hasImage } from './'
+
+export function populateProductConfigurationAsync (context, { product, selectedVariant }) {
+ Logger.warn('deprecated, will be not used from 1.12')()
+ if (product.configurable_options) {
+ for (let option of product.configurable_options) {
+ let attribute_code
+ let attribute_label
+ if (option.attribute_code) {
+ attribute_code = option.attribute_code
+ attribute_label = option.label ? option.label : (option.frontend_label ? option.frontend_label : option.default_frontend_label)
+ } else {
+ if (option.attribute_id) {
+ let attr = context.rootState.attribute.list_by_id[option.attribute_id]
+ if (!attr) {
+ Logger.error('Wrong attribute given in configurable_options - can not find by attribute_id', option)()
+ continue
+ } else {
+ attribute_code = attr.attribute_code
+ attribute_label = attr.frontend_label ? attr.frontend_label : attr.default_frontend_label
+ }
+ } else {
+ Logger.error('Wrong attribute given in configurable_options - no attribute_code / attribute_id', option)()
+ }
+ }
+ let selectedOption = null
+ if (selectedVariant.custom_attributes) {
+ selectedOption = selectedVariant.custom_attributes.find((a) => { // this is without the "label"
+ return (a.attribute_code === attribute_code)
+ })
+ } else {
+ selectedOption = {
+ attribute_code: attribute_code,
+ value: selectedVariant[attribute_code]
+ }
+ }
+ if (option.values && option.values.length) {
+ const selectedOptionMeta = option.values.find(ov => { return ov.value_index === selectedOption.value })
+ if (selectedOptionMeta) {
+ selectedOption.label = selectedOptionMeta.label ? selectedOptionMeta.label : selectedOptionMeta.default_label
+ selectedOption.value_data = selectedOptionMeta.value_data
+ }
+ }
+
+ const confVal = {
+ attribute_code: attribute_code,
+ id: selectedOption.value,
+ label: selectedOption.label ? selectedOption.label : /* if not set - find by attribute */optionLabel(context.rootState.attribute, { attributeKey: selectedOption.attribute_code, searchBy: 'code', optionId: selectedOption.value })
+ }
+ Vue.set(context.state.current_configuration, attribute_code, confVal)
+ }
+ setProductConfigurableOptions({
+ product,
+ configuration: context.state.current_configuration,
+ setConfigurableProductOptions: config.cart.setConfigurableProductOptions
+ }) // set the custom options
+ }
+ return selectedVariant
+}
+
+export function configureProductAsync (context, { product, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = true, setProductErorrs = false }) {
+ Logger.warn('deprecated, will be not used from 1.12')()
+ // use current product if product wasn't passed
+ if (product === null) product = context.getters.getCurrentProduct
+ const hasConfigurableChildren = (product.configurable_children && product.configurable_children.length > 0)
+
+ if (hasConfigurableChildren) {
+ // handle custom_attributes for easier comparing in the future
+ product.configurable_children.forEach((child) => {
+ let customAttributesAsObject = {}
+ if (child.custom_attributes) {
+ child.custom_attributes.forEach((attr) => {
+ customAttributesAsObject[attr.attribute_code] = attr.value
+ })
+ // add values from custom_attributes in a different form
+ Object.assign(child, customAttributesAsObject)
+ }
+ })
+ // find selected variant
+ let desiredProductFound = false
+ let selectedVariant = findConfigurableVariant({ product, configuration, availabilityCheck: true })
+ if (!selectedVariant) {
+ if (fallbackToDefaultWhenNoAvailable) {
+ selectedVariant = findConfigurableVariant({ product, selectDefaultChildren: true, availabilityCheck: true }) // return first available child
+ desiredProductFound = false
+ } else {
+ desiredProductFound = false
+ }
+ } else {
+ desiredProductFound = true
+ }
+
+ if (selectedVariant) {
+ if (!desiredProductFound && selectDefaultVariant /** don't change the state when no selectDefaultVariant is set */) { // update the configuration
+ populateProductConfigurationAsync(context, { product: product, selectedVariant: selectedVariant })
+ configuration = context.state.current_configuration
+ }
+ if (setProductErorrs) {
+ product.errors = {} // clear the product errors
+ }
+ product.is_configured = true
+
+ if (config.cart.setConfigurableProductOptions && !selectDefaultVariant && !(Object.keys(configuration).length === 1 && configuration.sku)) {
+ // the condition above: if selectDefaultVariant - then "setCurrent" is seeting the configurable options; if configuration = { sku: '' } -> this is a special case when not configuring the product but just searching by sku
+ setProductConfigurableOptions({
+ product,
+ configuration,
+ setConfigurableProductOptions: config.cart.setConfigurableProductOptions
+ }) // set the custom options
+ }/* else {
+ Logger.debug('Skipping configurable options setup', configuration)()
+ } */
+ const fieldsToOmit = ['name']
+ if (!hasImage(selectedVariant)) fieldsToOmit.push('image')
+ selectedVariant = omit(selectedVariant, fieldsToOmit) // We need to send the parent SKU to the Magento cart sync but use the child SKU internally in this case
+ // use chosen variant for the current product
+ if (selectDefaultVariant) {
+ context.dispatch('setCurrent', selectedVariant)
+ }
+ EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })
+ }
+ if (!selectedVariant && setProductErorrs) { // can not find variant anyway, even the default one
+ product.errors.variants = i18n.t('No available product variants')
+ if (selectDefaultVariant) {
+ context.dispatch('setCurrent', product) // without the configuration
+ }
+ }
+ return selectedVariant
+ } else {
+ if (fallbackToDefaultWhenNoAvailable) {
+ return product
+ } else {
+ return null
+ }
+ }
+}
diff --git a/core/modules/catalog/helpers/getProductGallery.ts b/core/modules/catalog/helpers/getProductGallery.ts
new file mode 100644
index 000000000..87709f7f2
--- /dev/null
+++ b/core/modules/catalog/helpers/getProductGallery.ts
@@ -0,0 +1,21 @@
+import config from 'config'
+import {
+ getMediaGallery,
+ configurableChildrenImages,
+ attributeImages
+} from './'
+import uniqBy from 'lodash-es/uniqBy'
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+export default function getProductGallery (product: Product) {
+ if (product.type_id === 'configurable' && product.hasOwnProperty('configurable_children')) {
+ if (!config.products.gallery.mergeConfigurableChildren && product.is_configured) {
+ return attributeImages(product)
+ }
+ }
+
+ const productGallery = uniqBy(configurableChildrenImages(product).concat(getMediaGallery(product)), 'src')
+ .filter(f => f.src && f.src !== config.images.productPlaceholder)
+
+ return productGallery
+}
diff --git a/core/modules/catalog/helpers/index.ts b/core/modules/catalog/helpers/index.ts
index 57e5c37c7..4510c56e7 100644
--- a/core/modules/catalog/helpers/index.ts
+++ b/core/modules/catalog/helpers/index.ts
@@ -1,31 +1,37 @@
-import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
-import rootStore from '@vue-storefront/core/store'
-import flattenDeep from 'lodash-es/flattenDeep'
-import omit from 'lodash-es/omit'
-import remove from 'lodash-es/remove'
-import toString from 'lodash-es/toString'
-import union from 'lodash-es/union'
+import Vue from 'vue'
// TODO: Remove this dependency
import { optionLabel } from './optionLabel'
-import i18n from '@vue-storefront/i18n'
import { getThumbnailPath } from '@vue-storefront/core/helpers'
-import { Logger } from '@vue-storefront/core/lib/logger'
-import { isServer } from '@vue-storefront/core/helpers'
import config from 'config'
-
-function _filterRootProductByStockitem (context, stockItem, product, errorCallback) {
- if (stockItem) {
- product.stock = stockItem
- if (stockItem.is_in_stock === false) {
- product.errors.variants = i18n.t('No available product variants')
- context.state.current.errors = product.errors
- EventBus.$emit('product-after-removevariant', { product: product })
- if (config.products.listOutOfStockProducts === false) {
- errorCallback(new Error('Product query returned an empty result'))
- }
- }
- }
-}
+import registerProductsMapping from './registerProductsMapping'
+import getProductGallery from './getProductGallery'
+import { findConfigurableVariant, isOptionAvailable } from './variant'
+import { filterOutUnavailableVariants } from './stock'
+import { doPlatformPricesSync } from './price'
+import { setProductConfigurableOptions } from './productOptions'
+
+/** Below helpers are not used from 1.12 and can be removed to reduce bundle */
+import { populateProductConfigurationAsync, configureProductAsync } from './deprecatedHelpers'
+export {
+ populateProductConfigurationAsync,
+ configureProductAsync
+}
+/***/
+
+export {
+ registerProductsMapping,
+ getProductGallery,
+ optionLabel,
+ findConfigurableVariant as findConfigurableChildAsync,
+ isOptionAvailable as isOptionAvailableAsync,
+ filterOutUnavailableVariants,
+ doPlatformPricesSync,
+ setProductConfigurableOptions as setConfigurableProductOptionsAsync
+}
+
+export const hasConfigurableChildren = (product) => product && product.configurable_children && product.configurable_children.length
+export const isGroupedProduct = (product) => product.type_id === 'grouped'
+export const isBundleProduct = (product) => product.type_id === 'bundle'
/**
* check if object have an image
@@ -36,293 +42,6 @@ export const hasImage = (product) => product && product.image && product.image !
*/
export const childHasImage = (children = []) => children.some(hasImage)
-export function findConfigurableChildAsync ({ product, configuration = null, selectDefaultChildren = false, availabilityCheck = true }) {
- let regularProductPrice = product.original_price_incl_tax ? product.original_price_incl_tax : product.price_incl_tax
- let selectedVariant = product.configurable_children.find((configurableChild) => {
- if (availabilityCheck) {
- if (configurableChild.stock && !config.products.listOutOfStockProducts) {
- if (!configurableChild.stock.is_in_stock) {
- return false
- }
- }
- }
- if (configurableChild.status >= 2/** disabled product */) {
- return false
- }
- if (selectDefaultChildren) {
- return true // return first
- }
- if (configuration.sku) {
- return configurableChild.sku === configuration.sku // by sku or first one
- } else {
- if (!configuration || (configuration && Object.keys(configuration).length === 0)) { // no configuration - return the first child cheaper than the original price - if found
- if (configurableChild.price_incl_tax <= regularProductPrice) {
- return true
- }
- } else {
- return Object.keys(omit(configuration, ['price'])).every((configProperty) => {
- let configurationPropertyFilters = configuration[configProperty] || []
- if (!Array.isArray(configurationPropertyFilters)) configurationPropertyFilters = [configurationPropertyFilters]
- const configurationIds = configurationPropertyFilters.map(filter => toString(filter.id)).filter(filterId => !!filterId)
- if (!configurationIds.length) return true // skip empty
- return configurationIds.includes(toString(configurableChild[configProperty]))
- })
- }
- }
- })
- return selectedVariant
-}
-
-export function isOptionAvailableAsync (context, { product, configuration }) {
- const variant = findConfigurableChildAsync({ product: product, configuration: configuration, availabilityCheck: true })
- return typeof variant !== 'undefined' && variant !== null
-}
-
-function _filterChildrenByStockitem (context, stockItems, product, diffLog) {
- if (config.products.filterUnavailableVariants) {
- if (product.type_id === 'configurable' && product.configurable_children) {
- for (const stockItem of stockItems) {
- const confChild = product.configurable_children.find(p => { return p.id === stockItem.product_id })
- if (stockItem.is_in_stock === false || (confChild && confChild.status >= 2/* conf child is disabled */)) {
- product.configurable_children = product.configurable_children.filter((p) => { return p.id !== stockItem.product_id })
- diffLog.push(stockItem.product_id)
- } else {
- if (confChild) {
- confChild.stock = stockItem
- }
- }
- }
- let totalOptions = 0
- let removedOptions = 0
- for (const optionKey in context.state.current_options) {
- let optionsAvailable = context.state.current_options[optionKey] // TODO: it should take the attribute combinations into consideration
- if (optionsAvailable && optionsAvailable.length > 0) {
- optionsAvailable = optionsAvailable.filter((opt) => {
- const config = {}
- config[optionKey] = opt
- const variant = isOptionAvailableAsync(context, { product: product, configuration: config })
- if (!variant) {
- Logger.log('No variant for' + opt, 'helper')()
- EventBus.$emit('product-after-removevariant', { product: product })
- removedOptions++
- return false
- } else {
- totalOptions++
- return true
- }
- })
- Logger.debug('Options still available' + optionsAvailable + removedOptions, 'helper')()
- context.state.current_options[optionKey] = optionsAvailable
- }
- }
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- configureProductAsync(context, { product, configuration: context.state.current_configuration, selectDefaultVariant: true, fallbackToDefaultWhenNoAvailable: true })
- if (totalOptions === 0) {
- product.errors.variants = i18n.t('No available product variants')
- context.state.current.errors = product.errors
- EventBus.$emit('product-after-removevariant', { product: product })
- }
- }
- }
-}
-
-export function filterOutUnavailableVariants (context, product) {
- return new Promise((resolve, reject) => {
- if (config.products.filterUnavailableVariants) {
- const _filterConfigurableHelper = () => {
- if (product.type_id === 'configurable' && product.configurable_children) {
- const stockItems = []
- let confChildSkus = product.configurable_children.map((c) => { return c.sku })
- for (const confChild of product.configurable_children) {
- const stockCached = context.rootState.stock.cache[confChild.id]
- if (stockCached) {
- stockItems.push(stockCached)
- confChildSkus = remove(confChildSkus, (skuToCheck) => skuToCheck === confChild.sku)
- }
- }
- Logger.debug('Cached stock items and delta' + stockItems + confChildSkus)()
- if (confChildSkus.length > 0) {
- context.dispatch('stock/list', { skus: confChildSkus }, { root: true }).then((task) => {
- if (task && task.resultCode === 200) {
- const diffLog = []
- _filterChildrenByStockitem(context, union(task.result, stockItems), product, diffLog)
- Logger.debug('Filtered configurable_children with the network call' + diffLog, 'helper')()
- resolve()
- } else {
- Logger.error('Cannot sync the availability of the product options. Please update the vue-storefront-api or switch on the Internet', 'helper')()
- }
- }).catch(err => {
- Logger.error(err, 'helper')()
- })
- } else {
- const diffLog = []
- _filterChildrenByStockitem(context, stockItems, product, diffLog)
- Logger.debug('Filtered configurable_children without the network call' + diffLog, 'helper')()
- resolve()
- }
- } else {
- resolve()
- }
- }
- const rootStockCached = context.rootState.stock.cache[product.id]
- if (!rootStockCached) {
- context.dispatch('stock/list', { skus: [product.sku] }, { root: true }).then((task) => {
- _filterRootProductByStockitem(context, task && task.result && task.result.length ? task.result[0] : null, product, reject)
- Logger.debug('Filtered root product stock with the network call')()
- _filterConfigurableHelper()
- })
- } else {
- _filterRootProductByStockitem(context, rootStockCached, product, reject)
- Logger.debug('Filtered root product stock without the network call')()
- _filterConfigurableHelper()
- }
- } else {
- resolve()
- }
- })
-}
-
-export function syncProductPrice (product, backProduct) { // TODO: we probably need to update the Net prices here as well
- product.sgn = backProduct.sgn // copy the signature for the modified price
- product.price_incl_tax = backProduct.price_info.final_price
- product.original_price_incl_tax = backProduct.price_info.regular_price
- product.special_price_incl_tax = backProduct.price_info.special_price
-
- product.special_price = backProduct.price_info.extension_attributes.tax_adjustments.special_price
- product.price = backProduct.price_info.extension_attributes.tax_adjustments.final_price
- product.original_price = backProduct.price_info.extension_attributes.tax_adjustments.regular_price
-
- product.price_tax = product.price_incl_tax - product.price
- product.special_price_tax = product.special_price_incl_tax - product.special_price
- product.original_price_tax = product.original_price_incl_tax - product.original_trice
-
- if (product.price_incl_tax >= product.original_price_incl_tax) {
- product.special_price_incl_tax = 0
- product.special_price = 0
- }
-
- /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
- product.priceInclTax = product.price_incl_tax
- product.priceTax = product.price_tax
- product.originalPrice = product.original_price
- product.originalPriceInclTax = product.original_price_incl_tax
- product.originalPriceTax = product.original_price_tax
- product.specialPriceInclTax = product.special_price_incl_tax
- product.specialPriceTax = product.special_price_tax
- /** END */
- EventBus.$emit('product-after-priceupdate', product)
- // Logger.log(product.sku, product, backProduct)()
- return product
-}
-/**
- * Synchronize / override prices got from ElasticSearch with current one's from Magento2 or other platform backend
- * @param {Array} products
- */
-export function doPlatformPricesSync (products) {
- return new Promise((resolve, reject) => {
- if (config.products.alwaysSyncPlatformPricesOver) {
- if (config.products.clearPricesBeforePlatformSync) {
- for (let product of products) { // clear out the prices as we need to sync them with Magento
- product.price_incl_tax = null
- product.original_price_incl_tax = null
- product.special_price_incl_tax = null
-
- product.special_price = null
- product.price = null
- product.original_price = null
-
- product.price_tax = null
- product.special_price_tax = null
- product.original_price_tax = null
-
- /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
- product.priceInclTax = product.price_incl_tax
- product.priceTax = product.price_tax
- product.originalPrice = product.original_price
- product.originalPriceInclTax = product.original_price_incl_tax
- product.originalPriceTax = product.original_price_tax
- product.specialPriceInclTax = product.special_price_incl_tax
- product.specialPriceTax = product.special_price_tax
- /** END */
-
- if (product.configurable_children) {
- for (let sc of product.configurable_children) {
- sc.price_incl_tax = null
- sc.original_price_incl_tax = null
- sc.special_price_incl_tax = null
-
- sc.special_price = null
- sc.price = null
- sc.original_price = null
-
- sc.price_tax = null
- sc.special_price_tax = null
- sc.original_price_tax = null
-
- /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
- sc.priceInclTax = sc.price_incl_tax
- sc.priceTax = sc.price_tax
- sc.originalPrice = sc.original_price
- sc.originalPriceInclTax = sc.original_price_incl_tax
- sc.originalPriceTax = sc.original_price_tax
- sc.specialPriceInclTax = sc.special_price_incl_tax
- sc.specialPriceTax = sc.special_price_tax
- /** END */
- }
- }
- }
- }
-
- let skus = products.map((p) => { return p.sku })
-
- if (products.length === 1) { // single product - download child data
- const childSkus = flattenDeep(products.map((p) => { return (p.configurable_children) ? p.configurable_children.map((cc) => { return cc.sku }) : null }))
- skus = union(skus, childSkus)
- }
- if (skus && skus.length > 0) {
- Logger.log('Starting platform prices sync for', skus) // TODO: add option for syncro and non syncro return()
- rootStore.dispatch('product/syncPlatformPricesOver', { skus: skus }, { root: true }).then((syncResult) => {
- if (syncResult) {
- syncResult = syncResult.items
-
- for (let product of products) {
- const backProduct = syncResult.find((itm) => { return itm.id === product.id })
- if (backProduct) {
- product.price_is_current = true // in case we're syncing up the prices we should mark if we do have current or not
- product.price_refreshed_at = new Date()
- product = syncProductPrice(product, backProduct)
-
- if (product.configurable_children) {
- for (let configurableChild of product.configurable_children) {
- const backProductChild = syncResult.find((itm) => { return itm.id === configurableChild.id })
- if (backProductChild) {
- configurableChild = syncProductPrice(configurableChild, backProductChild)
- }
- }
- }
- // TODO: shall we update local storage here for the main product?
- }
- }
- }
- resolve(products)
- })
- } else { // empty list of products
- resolve(products)
- }
- if (!config.products.waitForPlatformSync && !isServer) {
- Logger.log('Returning products, the prices yet to come from backend!')()
- for (let product of products) {
- product.price_is_current = false // in case we're syncing up the prices we should mark if we do have current or not
- product.price_refreshed_at = null
- }
- resolve(products)
- }
- } else {
- resolve(products)
- }
- })
-}
-
function _prepareProductOption (product) {
let product_option = {
extension_attributes: {
@@ -336,45 +55,6 @@ function _prepareProductOption (product) {
} */
return product_option
}
-export function setConfigurableProductOptionsAsync (context, { product, configuration }) {
- if (product.configurable_options) {
- const product_option = _prepareProductOption(product)
- /* eslint camelcase: "off" */
- const configurable_item_options = product_option.extension_attributes.configurable_item_options
- for (const configKey of Object.keys(configuration)) {
- const configOption = configuration[configKey]
- if (configOption.attribute_code && configOption.attribute_code !== 'price') {
- const option = product.configurable_options.find(co => {
- return (co.attribute_code === configOption.attribute_code)
- })
-
- if (!option) {
- Logger.error('Wrong option id for setProductOptions', configOption.attribute_code)()
- return null
- }
- let existingOption = configurable_item_options.find(cop => {
- return cop.option_id === option.attribute_id
- })
- if (!existingOption) {
- existingOption = {
- option_id: option.attribute_id,
- option_value: configOption.id,
- label: option.label || i18n.t(configOption.attribute_code),
- value: configOption.label
- }
- configurable_item_options.push(existingOption)
- }
- existingOption.option_value = configOption.id
- existingOption.label = option.label || i18n.t(configOption.attribute_code)
- existingOption.value = configOption.label
- }
- }
- // Logger.debug('Server product options object', product_option)()
- return product_option
- } else {
- return null
- }
-}
export function setCustomProductOptionsAsync (context, { product, customOptions }) {
const productOption = _prepareProductOption(product)
@@ -388,155 +68,6 @@ export function setBundleProductOptionsAsync (context, { product, bundleOptions
return productOption
}
-function _internalMapOptions (productOption) {
- const optionsMapped = []
- for (let option of productOption.extension_attributes.configurable_item_options) {
- optionsMapped.push({
- label: option.label,
- value: option.value
- })
- }
- productOption.extension_attributes.configurable_item_options = productOption.extension_attributes.configurable_item_options.map((op) => {
- return omit(op, ['label', 'value'])
- })
- return optionsMapped
-}
-
-export function populateProductConfigurationAsync (context, { product, selectedVariant }) {
- if (product.configurable_options) {
- for (let option of product.configurable_options) {
- let attribute_code
- let attribute_label
- if (option.attribute_code) {
- attribute_code = option.attribute_code
- attribute_label = option.label ? option.label : (option.frontend_label ? option.frontend_label : option.default_frontend_label)
- } else {
- if (option.attribute_id) {
- let attr = context.rootState.attribute.list_by_id[option.attribute_id]
- if (!attr) {
- Logger.error('Wrong attribute given in configurable_options - can not find by attribute_id', option)()
- continue
- } else {
- attribute_code = attr.attribute_code
- attribute_label = attr.frontend_label ? attr.frontend_label : attr.default_frontend_label
- }
- } else {
- Logger.error('Wrong attribute given in configurable_options - no attribute_code / attribute_id', option)()
- }
- }
- let selectedOption = null
- if (selectedVariant.custom_attributes) {
- selectedOption = selectedVariant.custom_attributes.find((a) => { // this is without the "label"
- return (a.attribute_code === attribute_code)
- })
- } else {
- selectedOption = {
- attribute_code: attribute_code,
- value: selectedVariant[attribute_code]
- }
- }
- if (option.values && option.values.length) {
- const selectedOptionMeta = option.values.find(ov => { return ov.value_index === selectedOption.value })
- if (selectedOptionMeta) {
- selectedOption.label = selectedOptionMeta.label ? selectedOptionMeta.label : selectedOptionMeta.default_label
- selectedOption.value_data = selectedOptionMeta.value_data
- }
- }
-
- const confVal = {
- attribute_code: attribute_code,
- id: selectedOption.value,
- label: selectedOption.label ? selectedOption.label : /* if not set - find by attribute */optionLabel(context.rootState.attribute, { attributeKey: selectedOption.attribute_code, searchBy: 'code', optionId: selectedOption.value })
- }
- context.state.current_configuration[attribute_code] = confVal
- }
- if (config.cart.setConfigurableProductOptions) {
- const productOption = setConfigurableProductOptionsAsync(context, { product: product, configuration: context.state.current_configuration }) // set the custom options
- if (productOption) {
- product.options = _internalMapOptions(productOption)
- product.product_option = productOption
- }
- }
- }
- return selectedVariant
-}
-
-export function configureProductAsync (context, { product, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = true, setProductErorrs = false }) {
- // use current product if product wasn't passed
- if (product === null) product = context.getters.getCurrentProduct
- const hasConfigurableChildren = (product.configurable_children && product.configurable_children.length > 0)
-
- if (hasConfigurableChildren) {
- // handle custom_attributes for easier comparing in the future
- product.configurable_children.forEach((child) => {
- let customAttributesAsObject = {}
- if (child.custom_attributes) {
- child.custom_attributes.forEach((attr) => {
- customAttributesAsObject[attr.attribute_code] = attr.value
- })
- // add values from custom_attributes in a different form
- Object.assign(child, customAttributesAsObject)
- }
- })
- // find selected variant
- let desiredProductFound = false
- let selectedVariant = findConfigurableChildAsync({ product, configuration, availabilityCheck: true })
- if (!selectedVariant) {
- if (fallbackToDefaultWhenNoAvailable) {
- selectedVariant = findConfigurableChildAsync({ product, selectDefaultChildren: true, availabilityCheck: true }) // return first available child
- desiredProductFound = false
- } else {
- desiredProductFound = false
- }
- } else {
- desiredProductFound = true
- }
-
- if (selectedVariant) {
- if (!desiredProductFound && selectDefaultVariant /** don't change the state when no selectDefaultVariant is set */) { // update the configuration
- populateProductConfigurationAsync(context, { product: product, selectedVariant: selectedVariant })
- configuration = context.state.current_configuration
- }
- if (setProductErorrs) {
- product.errors = {} // clear the product errors
- }
- product.is_configured = true
-
- if (config.cart.setConfigurableProductOptions && !selectDefaultVariant && !(Object.keys(configuration).length === 1 && configuration.sku)) {
- // the condition above: if selectDefaultVariant - then "setCurrent" is seeting the configurable options; if configuration = { sku: '' } -> this is a special case when not configuring the product but just searching by sku
- const productOption = setConfigurableProductOptionsAsync(context, { product: product, configuration: configuration }) // set the custom options
- if (productOption) {
- selectedVariant.product_option = productOption
- selectedVariant.options = _internalMapOptions(productOption)
- }
- }/* else {
- Logger.debug('Skipping configurable options setup', configuration)()
- } */
- const fieldsToOmit = ['name']
- if (!hasImage(selectedVariant)) fieldsToOmit.push('image')
- selectedVariant = omit(selectedVariant, fieldsToOmit) // We need to send the parent SKU to the Magento cart sync but use the child SKU internally in this case
- // use chosen variant for the current product
- if (selectDefaultVariant) {
- context.dispatch('setCurrent', selectedVariant)
- }
- EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })
- }
- if (!selectedVariant && setProductErorrs) { // can not find variant anyway, even the default one
- product.errors.variants = i18n.t('No available product variants')
- if (selectDefaultVariant) {
- context.dispatch('setCurrent', product) // without the configuration
- }
- }
- return selectedVariant
- } else {
- if (fallbackToDefaultWhenNoAvailable) {
- return product
- } else {
- return null
- }
- }
-}
-
/**
* Get media Gallery images from product
*/
@@ -607,3 +138,11 @@ export function configurableChildrenImages (product) {
}
return configurableChildrenImages
}
+
+export const setRequestCacheTags = ({ products = [] }) => {
+ if (Vue.prototype.$cacheTags) {
+ products.forEach((product) => {
+ Vue.prototype.$cacheTags.add(`P${product.id}`);
+ })
+ }
+}
diff --git a/core/modules/catalog/helpers/optionLabel.ts b/core/modules/catalog/helpers/optionLabel.ts
index 12549b658..d16985515 100644
--- a/core/modules/catalog/helpers/optionLabel.ts
+++ b/core/modules/catalog/helpers/optionLabel.ts
@@ -5,24 +5,22 @@
* @param {String} optionId - value to get label for
*/
import toString from 'lodash-es/toString'
+import get from 'lodash-es/get'
export function optionLabel (state, { attributeKey, searchBy = 'code', optionId }) {
- let attrCache = state.labels[attributeKey]
+ if (!state.labels) {
+ state.labels = {}
+ }
+ // check cached attribute
+ const attrCache = get(state, `labels.${attributeKey}.${optionId}`, null)
if (attrCache) {
- let label = attrCache[optionId]
-
- if (label) {
- return label
- }
+ return attrCache
}
+
let attr = state['list_by_' + searchBy][attributeKey]
if (attr) {
- let opt = attr.options.find((op) => { // TODO: cache it in memory
- if (toString(op.value) === toString(optionId)) {
- return op
- }
- }) // TODO: i18n support with multi-website attribute names
+ let opt = attr.options.find((op) => toString(op.value) === toString(optionId))
if (opt) {
if (!state.labels[attributeKey]) {
diff --git a/core/modules/catalog/helpers/prepare/index.ts b/core/modules/catalog/helpers/prepare/index.ts
new file mode 100644
index 000000000..62417fd11
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/index.ts
@@ -0,0 +1,35 @@
+import setDefaultQty from './setDefaultQty';
+import setDefaultObjects from './setDefaultObjects';
+import setParentSku from './setParentSku';
+import setParentId from './setParentId';
+import setCustomAttributesForChild from './setCustomAttributesForChild';
+import setDefaultProductOptions from './setDefaultProductOptions';
+
+/**
+ * Apply base modification to product, after those modification we can store product in cache.
+ */
+function preConfigureProduct (product) {
+ // base product modifications
+ setDefaultQty(product)
+ setDefaultObjects(product)
+ setParentSku(product)
+ setParentId(product)
+ setCustomAttributesForChild(product)
+ setDefaultProductOptions(product)
+
+ return product;
+}
+
+/**
+ * Apply base modification to product list, after those modification we can store product in cache.
+ */
+function prepareProducts (products) {
+ const preparedProducts = products.map(preConfigureProduct)
+
+ return preparedProducts
+}
+
+export {
+ prepareProducts,
+ preConfigureProduct
+}
diff --git a/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts b/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts
new file mode 100644
index 000000000..73ba3a7be
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts
@@ -0,0 +1,19 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+import { hasConfigurableChildren } from '../';
+/**
+ * Fill custom attributes for every configurable child
+ */
+export default function setCustomAttributesForChild (product: Product) {
+ if (!hasConfigurableChildren(product)) return
+ // handle custom_attributes for easier comparing in the future
+ product.configurable_children.forEach((child) => {
+ let customAttributesAsObject = {}
+ if (child.custom_attributes) {
+ child.custom_attributes.forEach((attr) => {
+ customAttributesAsObject[attr.attribute_code] = attr.value
+ })
+ // add values from custom_attributes in a different form
+ Object.assign(child, customAttributesAsObject)
+ }
+ })
+}
diff --git a/core/modules/catalog/helpers/prepare/setDefaultObjects.ts b/core/modules/catalog/helpers/prepare/setDefaultObjects.ts
new file mode 100644
index 000000000..00fc289ca
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setDefaultObjects.ts
@@ -0,0 +1,8 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+/**
+ * Default object that are used in vsf
+ */
+export default function setDefaultObjects (product: Product) {
+ product.errors = {}; // this is an object to store validation result for custom options and others
+ product.info = {};
+}
diff --git a/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts b/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts
new file mode 100644
index 000000000..22ef35717
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts
@@ -0,0 +1,15 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+/**
+ * Init product_option, needed to next configuration step
+ */
+export default function setDefaultProductOptions (product: Product) {
+ if (product.product_option) return
+ product.product_option = {
+ extension_attributes: {
+ custom_options: [],
+ configurable_item_options: [],
+ bundle_options: []
+ }
+ }
+}
diff --git a/core/modules/catalog/helpers/prepare/setDefaultQty.ts b/core/modules/catalog/helpers/prepare/setDefaultQty.ts
new file mode 100644
index 000000000..bd3d27321
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setDefaultQty.ts
@@ -0,0 +1,10 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+/**
+ * set product quantity to 1
+ */
+export default function setDefaultQty (product: Product) {
+ if (!product.qty) {
+ product.qty = 1
+ }
+}
diff --git a/core/modules/catalog/helpers/prepare/setParentId.ts b/core/modules/catalog/helpers/prepare/setParentId.ts
new file mode 100644
index 000000000..169fea584
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setParentId.ts
@@ -0,0 +1,10 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+/**
+ * set parent id, this is needed, because in configuration process we will override id by configurable_children.id
+ */
+export default function setParentId (product: Product) {
+ if (!product.parentId) {
+ product.parentId = product.id
+ }
+}
diff --git a/core/modules/catalog/helpers/prepare/setParentSku.ts b/core/modules/catalog/helpers/prepare/setParentSku.ts
new file mode 100644
index 000000000..d0c0038c0
--- /dev/null
+++ b/core/modules/catalog/helpers/prepare/setParentSku.ts
@@ -0,0 +1,10 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+/**
+ * set parent sku, this is needed, because in configuration process we will override sku by configurable_children.sku
+ */
+export default function setParentSku (product: Product) {
+ if (!product.parentSku) {
+ product.parentSku = product.sku
+ }
+}
diff --git a/core/modules/catalog/helpers/price/doPlatformPricesSync.ts b/core/modules/catalog/helpers/price/doPlatformPricesSync.ts
new file mode 100644
index 000000000..2c9556ad4
--- /dev/null
+++ b/core/modules/catalog/helpers/price/doPlatformPricesSync.ts
@@ -0,0 +1,119 @@
+import { isServer } from '@vue-storefront/core/helpers'
+import config from 'config'
+import flattenDeep from 'lodash-es/flattenDeep'
+import union from 'lodash-es/union'
+import { Logger } from '@vue-storefront/core/lib/logger'
+import rootStore from '@vue-storefront/core/store'
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import syncProductPrice from './syncProductPrice'
+
+/**
+ * Synchronize / override prices got from ElasticSearch with current one's from Magento2 or other platform backend
+ * @param {Array} products
+ */
+export default function doPlatformPricesSync (products) {
+ return new Promise(async (resolve, reject) => {
+ if (config.products.alwaysSyncPlatformPricesOver) {
+ if (config.products.clearPricesBeforePlatformSync) {
+ for (let product of products) { // clear out the prices as we need to sync them with Magento
+ product.price_incl_tax = null
+ product.original_price_incl_tax = null
+ product.special_price_incl_tax = null
+
+ product.special_price = null
+ product.price = null
+ product.original_price = null
+
+ product.price_tax = null
+ product.special_price_tax = null
+ product.original_price_tax = null
+
+ /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
+ product.priceInclTax = product.price_incl_tax
+ product.priceTax = product.price_tax
+ product.originalPrice = product.original_price
+ product.originalPriceInclTax = product.original_price_incl_tax
+ product.originalPriceTax = product.original_price_tax
+ product.specialPriceInclTax = product.special_price_incl_tax
+ product.specialPriceTax = product.special_price_tax
+ /** END */
+
+ if (product.configurable_children) {
+ for (let sc of product.configurable_children) {
+ sc.price_incl_tax = null
+ sc.original_price_incl_tax = null
+ sc.special_price_incl_tax = null
+
+ sc.special_price = null
+ sc.price = null
+ sc.original_price = null
+
+ sc.price_tax = null
+ sc.special_price_tax = null
+ sc.original_price_tax = null
+
+ /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
+ sc.priceInclTax = sc.price_incl_tax
+ sc.priceTax = sc.price_tax
+ sc.originalPrice = sc.original_price
+ sc.originalPriceInclTax = sc.original_price_incl_tax
+ sc.originalPriceTax = sc.original_price_tax
+ sc.specialPriceInclTax = sc.special_price_incl_tax
+ sc.specialPriceTax = sc.special_price_tax
+ /** END */
+ }
+ }
+ }
+ }
+
+ let skus = products.map((p) => { return p.sku })
+
+ if (products.length === 1) { // single product - download child data
+ const childSkus = flattenDeep(products.map((p) => { return (p.configurable_children) ? p.configurable_children.map((cc) => { return cc.sku }) : null }))
+ skus = union(skus, childSkus)
+ }
+ if (skus && skus.length > 0) {
+ Logger.log('Starting platform prices sync for', skus) // TODO: add option for syncro and non syncro return()
+ const { items } = await ProductService.getProductRenderList({
+ skus,
+ isUserGroupedTaxActive: rootStore.getters['tax/getIsUserGroupedTaxActive'],
+ userGroupId: rootStore.getters['tax/getUserTaxGroupId'],
+ token: rootStore.getters['user/getToken']
+ })
+ if (items) {
+ for (let product of products) {
+ const backProduct = items.find((itm) => itm.id === product.id)
+ if (backProduct) {
+ product.price_is_current = true // in case we're syncing up the prices we should mark if we do have current or not
+ product.price_refreshed_at = new Date()
+ product = syncProductPrice(product, backProduct)
+
+ if (product.configurable_children) {
+ for (let configurableChild of product.configurable_children) {
+ const backProductChild = items.find((itm) => itm.id === configurableChild.id)
+ if (backProductChild) {
+ configurableChild = syncProductPrice(configurableChild, backProductChild)
+ }
+ }
+ }
+ // TODO: shall we update local storage here for the main product?
+ }
+ }
+ }
+ resolve(products)
+ } else { // empty list of products
+ resolve(products)
+ }
+ if (!config.products.waitForPlatformSync && !isServer) {
+ Logger.log('Returning products, the prices yet to come from backend!')()
+ for (let product of products) {
+ product.price_is_current = false // in case we're syncing up the prices we should mark if we do have current or not
+ product.price_refreshed_at = null
+ }
+ resolve(products)
+ }
+ } else {
+ resolve(products)
+ }
+ })
+}
diff --git a/core/modules/catalog/helpers/price/index.ts b/core/modules/catalog/helpers/price/index.ts
new file mode 100644
index 000000000..93dc819cc
--- /dev/null
+++ b/core/modules/catalog/helpers/price/index.ts
@@ -0,0 +1,5 @@
+import doPlatformPricesSync from './doPlatformPricesSync'
+
+export {
+ doPlatformPricesSync
+}
diff --git a/core/modules/catalog/helpers/price/syncProductPrice.ts b/core/modules/catalog/helpers/price/syncProductPrice.ts
new file mode 100644
index 000000000..946f660a3
--- /dev/null
+++ b/core/modules/catalog/helpers/price/syncProductPrice.ts
@@ -0,0 +1,34 @@
+import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
+
+export default function syncProductPrice (product, backProduct) { // TODO: we probably need to update the Net prices here as well
+ product.sgn = backProduct.sgn // copy the signature for the modified price
+ product.price_incl_tax = backProduct.price_info.final_price
+ product.original_price_incl_tax = backProduct.price_info.regular_price
+ product.special_price_incl_tax = backProduct.price_info.special_price
+
+ product.special_price = backProduct.price_info.extension_attributes.tax_adjustments.special_price
+ product.price = backProduct.price_info.extension_attributes.tax_adjustments.final_price
+ product.original_price = backProduct.price_info.extension_attributes.tax_adjustments.regular_price
+
+ product.price_tax = product.price_incl_tax - product.price
+ product.special_price_tax = product.special_price_incl_tax - product.special_price
+ product.original_price_tax = product.original_price_incl_tax - product.original_trice
+
+ if (product.price_incl_tax >= product.original_price_incl_tax) {
+ product.special_price_incl_tax = 0
+ product.special_price = 0
+ }
+
+ /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */
+ product.priceInclTax = product.price_incl_tax
+ product.priceTax = product.price_tax
+ product.originalPrice = product.original_price
+ product.originalPriceInclTax = product.original_price_incl_tax
+ product.originalPriceTax = product.original_price_tax
+ product.specialPriceInclTax = product.special_price_incl_tax
+ product.specialPriceTax = product.special_price_tax
+ /** END */
+ EventBus.$emit('product-after-priceupdate', product)
+ // Logger.log(product.sku, product, backProduct)()
+ return product
+}
diff --git a/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts b/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts
new file mode 100644
index 000000000..2f50e8e7f
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts
@@ -0,0 +1,35 @@
+import { ProductOptions } from '@vue-storefront/core/modules/catalog/types/Product';
+
+/**
+ * It returns all available options for configurable product
+ */
+export default function getAllProductConfigurations ({ configurableOptions, configuration }): ProductOptions {
+ const product_option: ProductOptions = {
+ extension_attributes: {
+ custom_options: [],
+ configurable_item_options: [],
+ bundle_options: []
+ }
+ }
+ /* eslint camelcase: "off" */
+ product_option.extension_attributes.configurable_item_options = Object.keys(configuration)
+ .map((key) => configuration[key])
+ .filter((configOption) =>
+ configOption &&
+ configOption.attribute_code &&
+ configOption.attribute_code !== 'price'
+ )
+ .map((configOption) => ({
+ configOption,
+ productOption: configurableOptions.find((productConfigOption) => productConfigOption.attribute_code === configOption.attribute_code)
+ }))
+ .filter(({ productOption }) => productOption)
+ .map(({ configOption, productOption }) => ({
+ option_id: String(productOption.attribute_id),
+ option_value: String(configOption.id),
+ label: productOption.label || configOption.attribute_code,
+ value: configOption.label
+ }))
+
+ return product_option
+}
diff --git a/core/modules/catalog/helpers/productOptions/getAttributeCode.ts b/core/modules/catalog/helpers/productOptions/getAttributeCode.ts
new file mode 100644
index 000000000..136263095
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getAttributeCode.ts
@@ -0,0 +1,9 @@
+/**
+ * Returns attribute_code for product option
+ */
+export default function getAttributeCode (option, attribute): string {
+ const attribute_code = option.attribute_code
+ ? option.attribute_code
+ : option.attribute_id && (attribute.list_by_id[option.attribute_id] || {}).attribute_code
+ return attribute_code || option.label.toLowerCase()
+}
diff --git a/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts b/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts
new file mode 100644
index 000000000..407e0d0a5
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts
@@ -0,0 +1,7 @@
+/**
+ * Returns internal format for product options
+ */
+export default function getInternalOptionsFormat (productOption) {
+ return productOption.extension_attributes.configurable_item_options
+ .map(({ label, value }) => ({ label, value }))
+}
diff --git a/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts b/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts
new file mode 100644
index 000000000..55c8e6ea9
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts
@@ -0,0 +1,28 @@
+import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers';
+import getAttributeCode from './getAttributeCode'
+import getSelectedOption from './getSelectedOption'
+
+/**
+ * Returns configuration based on selected variant. Only applies to configurable product
+ */
+export default function getProductConfiguration ({ product, selectedVariant, attribute }) {
+ const currentProductOption = {}
+ const configurableOptions = product.configurable_options || []
+ for (const option of configurableOptions) {
+ const attributeCode = getAttributeCode(option, attribute)
+ const selectedOption = getSelectedOption(selectedVariant, attributeCode, option)
+ const label = selectedOption.label
+ ? selectedOption.label
+ : optionLabel(attribute, {
+ attributeKey: selectedOption.attribute_code,
+ searchBy: 'code',
+ optionId: selectedOption.value
+ })
+ currentProductOption[attributeCode] = {
+ attribute_code: attributeCode,
+ id: String(selectedOption.value),
+ label: label
+ }
+ }
+ return currentProductOption
+}
diff --git a/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts b/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts
new file mode 100644
index 000000000..93a0adf45
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts
@@ -0,0 +1,27 @@
+import getAttributeCode from './getAttributeCode'
+import trim from 'lodash-es/trim'
+import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers';
+
+export default function getProductConfigurationOptions ({ product, attribute }) {
+ const productOptions = {}
+ const configurableOptions = product.configurable_options || []
+ for (let option of configurableOptions) {
+ const attributeCode = getAttributeCode(option, attribute)
+ const productOptionValues = option.values
+ .map((optionValue) => ({
+ label: optionValue.label
+ ? optionValue.label
+ : optionLabel(attribute, {
+ attributeKey: option.attribute_id,
+ searchBy: 'id',
+ optionId: optionValue.value_index
+ }),
+ id: String(optionValue.value_index),
+ attribute_code: option.attribute_code
+ }))
+ .filter((optionValue) => trim(optionValue.label) !== '')
+
+ productOptions[attributeCode] = productOptionValues
+ }
+ return productOptions
+}
diff --git a/core/modules/catalog/helpers/productOptions/getSelectedOption.ts b/core/modules/catalog/helpers/productOptions/getSelectedOption.ts
new file mode 100644
index 000000000..a3491f0b6
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/getSelectedOption.ts
@@ -0,0 +1,19 @@
+/**
+ * Returns single option for configurable product based on attribute code
+ */
+export default function getSelectedOption (selectedVariant, attributeCode, option) {
+ let selectedOption = (selectedVariant.custom_attributes || []).find((a) => a.attribute_code === attributeCode)
+ selectedOption = selectedOption || {
+ attribute_code: attributeCode,
+ value: selectedVariant[attributeCode]
+ }
+ if (option.values && option.values.length) {
+ const selectedOptionMeta = option.values.find(ov => ov.value_index === selectedOption.value)
+ if (selectedOptionMeta) {
+ selectedOption.label = selectedOptionMeta.label
+ ? selectedOptionMeta.label
+ : selectedOptionMeta.default_label
+ }
+ }
+ return selectedOption
+}
diff --git a/core/modules/catalog/helpers/productOptions/index.ts b/core/modules/catalog/helpers/productOptions/index.ts
new file mode 100644
index 000000000..6370d77c6
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/index.ts
@@ -0,0 +1,9 @@
+import setProductConfigurableOptions from './setProductConfigurableOptions'
+import getProductConfiguration from './getProductConfiguration'
+import getProductConfigurationOptions from './getProductConfigurationOptions'
+
+export {
+ setProductConfigurableOptions,
+ getProductConfiguration,
+ getProductConfigurationOptions
+}
diff --git a/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts b/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts
new file mode 100644
index 000000000..1df256ba1
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts
@@ -0,0 +1,9 @@
+import omit from 'lodash/omit'
+
+/**
+ * Omit props that is not needed for product_option
+ */
+export default function omitInternalOptionsFormat (productOption) {
+ productOption.extension_attributes.configurable_item_options = productOption.extension_attributes.configurable_item_options
+ .map((option) => omit(option, ['label', 'value']))
+}
diff --git a/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts b/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts
new file mode 100644
index 000000000..b71c226ab
--- /dev/null
+++ b/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts
@@ -0,0 +1,23 @@
+import getAllProductConfigurations from './getAllProductConfigurations'
+import getInternalOptionsFormat from './getInternalOptionsFormat'
+import omitInternalOptionsFormat from './omitInternalOptionsFormat'
+
+/**
+ * set 'product_option' and 'options' based on selected configuration
+ */
+export default function setProductConfigurableOptions ({ product, configuration, setConfigurableProductOptions }) {
+ // return if there is no 'setConfigurableProductOptions' option
+ if (!setConfigurableProductOptions) return
+
+ const configurableOptions = product.configurable_options
+
+ if (!configurableOptions) return
+
+ const productOptions = getAllProductConfigurations({ configurableOptions, configuration })
+
+ product.options = getInternalOptionsFormat(productOptions)
+
+ omitInternalOptionsFormat(productOptions)
+
+ product.product_option = productOptions
+}
diff --git a/core/modules/catalog/helpers/registerProductsMapping.ts b/core/modules/catalog/helpers/registerProductsMapping.ts
new file mode 100644
index 000000000..13ebf84d2
--- /dev/null
+++ b/core/modules/catalog/helpers/registerProductsMapping.ts
@@ -0,0 +1,16 @@
+import { transformProductUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl';
+import { localizedDispatcherRoute } from '@vue-storefront/core/lib/multistore';
+import { ActionContext } from 'vuex'
+import RootState from '@vue-storefront/core/types/RootState'
+
+export default async function registerProductsMapping ({ dispatch }: ActionContext, products = []): Promise {
+ await Promise.all(products.map(product => {
+ if (product.url_path) {
+ const { url_path, sku, slug, type_id, parentSku } = product
+ return dispatch('url/registerMapping', {
+ url: localizedDispatcherRoute(url_path),
+ routeData: transformProductUrl({ sku, parentSku, slug, type_id })
+ }, { root: true })
+ }
+ }))
+}
diff --git a/core/modules/catalog/helpers/slugifyCategories.ts b/core/modules/catalog/helpers/slugifyCategories.ts
index 0436de07c..930cf1874 100644
--- a/core/modules/catalog/helpers/slugifyCategories.ts
+++ b/core/modules/catalog/helpers/slugifyCategories.ts
@@ -12,11 +12,11 @@ const createSlug = (category: ChildrenData): string => {
const slugifyCategories = (category: Category | ChildrenData): Category | ChildrenData => {
if (category.children_data) {
- for (let subcat of category.children_data) {
- if (subcat.name && !subcat.slug) {
- slugifyCategories({ ...subcat, slug: createSlug(subcat) } as any as ChildrenData)
+ category.children_data.forEach((subCat: ChildrenData): void => {
+ if (subCat.name && !subCat.slug) {
+ slugifyCategories({ ...subCat, slug: createSlug(subCat) } as any as ChildrenData)
}
- }
+ })
}
return category
}
diff --git a/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts b/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts
new file mode 100644
index 000000000..c41fca73a
--- /dev/null
+++ b/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts
@@ -0,0 +1,15 @@
+/**
+ * Add 'stock' object to 'configurable_children' and filter configurable child that is not available
+ */
+export default function filterChildrenByStockitem (product, stockItems = []) {
+ for (const stockItem of stockItems) {
+ const confChild = product.configurable_children.find((child) => child.id === stockItem.product_id)
+ if (!stockItem.is_in_stock || (confChild && confChild.status >= 2/* conf child is disabled */)) {
+ product.configurable_children = product.configurable_children.filter((child) => child.id !== stockItem.product_id)
+ } else {
+ if (confChild) {
+ confChild.stock = stockItem
+ }
+ }
+ }
+}
diff --git a/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts b/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts
new file mode 100644
index 000000000..3d289cd7d
--- /dev/null
+++ b/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts
@@ -0,0 +1,18 @@
+import filterChildrenByStockitem from './filterChildrenByStockitem'
+import { hasConfigurableChildren } from './..';
+
+/**
+ * Add 'stock' object to product. If product is out of stock then add error to product.
+ * Also check all children for configurable products and remove if any children is out of stock.
+ */
+export default function filterOutUnavailableVariants (product, stockItems = []) {
+ const productStockItem = stockItems.find(p => p.product_id === product.id)
+ product.stock = productStockItem
+ if (productStockItem && !productStockItem.is_in_stock) {
+ product.errors.variants = 'No available product variants'
+ }
+
+ if (product.type_id === 'configurable' && hasConfigurableChildren(product)) {
+ filterChildrenByStockitem(stockItems, product)
+ }
+}
diff --git a/core/modules/catalog/helpers/stock/getStockItems.ts b/core/modules/catalog/helpers/stock/getStockItems.ts
new file mode 100644
index 000000000..76e5b98b9
--- /dev/null
+++ b/core/modules/catalog/helpers/stock/getStockItems.ts
@@ -0,0 +1,24 @@
+import { StockService } from '@vue-storefront/core/data-resolver';
+import config from 'config'
+
+/**
+ * Get products skus and products children skus. Based on that search for stock objects and return them.
+ */
+export default async function getStockItems (products) {
+ const skuArray = products.map(({ sku, configurable_children = [] }) => {
+ const childSkus = configurable_children.map((c) => c.sku)
+ return [sku, ...childSkus]
+ }).reduce((acc, curr) => acc.concat(curr), [])
+ if (!config.stock.synchronize) return
+ try {
+ const task = await StockService.list(skuArray)
+
+ if (task.resultCode === 200) {
+ return task.result
+ }
+ return []
+ } catch (err) {
+ console.error(err)
+ return []
+ }
+}
diff --git a/core/modules/catalog/helpers/stock/index.ts b/core/modules/catalog/helpers/stock/index.ts
index c25836333..fa4c68299 100644
--- a/core/modules/catalog/helpers/stock/index.ts
+++ b/core/modules/catalog/helpers/stock/index.ts
@@ -1,7 +1,11 @@
import getStatus from './getStatus'
import getProductInfos from './getProductInfos'
+import getStockItems from './getStockItems'
+import filterOutUnavailableVariants from './filterOutUnavailableVariants'
export {
getStatus,
- getProductInfos
+ getProductInfos,
+ getStockItems,
+ filterOutUnavailableVariants
}
diff --git a/core/modules/catalog/helpers/transformMetadataToAttributes.ts b/core/modules/catalog/helpers/transformMetadataToAttributes.ts
new file mode 100644
index 000000000..e857f1008
--- /dev/null
+++ b/core/modules/catalog/helpers/transformMetadataToAttributes.ts
@@ -0,0 +1,34 @@
+import uniqBy from 'lodash-es/uniqBy'
+
+const transformMetadataToAttributes = (attributeMetadata) => attributeMetadata
+ .reduce((prev, curr) => ([ ...prev, ...curr ]), [])
+ .reduce((prev, curr) => {
+ const attribute = prev.find(a => a.attribute_id === curr.attribute_id && a.options)
+
+ if (attribute) {
+ return prev.map(attr => {
+ if (attr.attribute_id === curr.attribute_id) {
+ return {
+ ...attr,
+ options: uniqBy([...(attr.options || []), ...(curr.options || [])], (obj) => `${obj.label}_${obj.value}`)
+ }
+ }
+
+ return attr
+ })
+ }
+
+ return [...prev, curr]
+ }, [])
+ .reduce((prev, curr) => ({
+ attrHashByCode: {
+ ...(prev.attrHashByCode || {}),
+ [curr.attribute_code]: curr
+ },
+ attrHashById: {
+ ...(prev.attrHashById || {}),
+ [curr.attribute_id]: curr
+ }
+ }), { attrHashByCode: {}, attrHashById: {} })
+
+export default transformMetadataToAttributes
diff --git a/core/modules/catalog/helpers/variant/findConfigurableVariant.ts b/core/modules/catalog/helpers/variant/findConfigurableVariant.ts
new file mode 100644
index 000000000..3587d57f5
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/findConfigurableVariant.ts
@@ -0,0 +1,43 @@
+import getConfigurationMatchLevel from './getConfigurationMatchLevel'
+import getVariantWithLowestPrice from './getVariantWithLowestPrice'
+import config from 'config'
+
+/**
+ * This function responsiblity is to find best matching variant for configurable product based on configuration object or stock availability.
+ */
+export default function findConfigurableVariant ({ product, configuration = null, selectDefaultChildren = false, availabilityCheck = true }) {
+ const selectedVariant = product.configurable_children.reduce((prevVariant, nextVariant) => {
+ if (availabilityCheck) {
+ if (nextVariant.stock && !config.products.listOutOfStockProducts) {
+ if (!nextVariant.stock.is_in_stock) {
+ return prevVariant
+ }
+ }
+ }
+ if (nextVariant.status >= 2/** disabled product */) {
+ return prevVariant
+ }
+ if (selectDefaultChildren) {
+ return prevVariant || nextVariant // return first
+ }
+ if (
+ (configuration && configuration.sku) &&
+ (nextVariant.sku === configuration.sku)
+ ) { // by sku or first one
+ return nextVariant
+ } else {
+ // get match level for each variant
+ const prevVariantMatch = getConfigurationMatchLevel(configuration, prevVariant)
+ const nextVariantMatch = getConfigurationMatchLevel(configuration, nextVariant)
+
+ // if we have draw between prev variant and current variant then return one that has lowest price
+ if (prevVariantMatch === nextVariantMatch) {
+ return getVariantWithLowestPrice(prevVariant, nextVariant)
+ }
+
+ // return variant with best matching level
+ return nextVariantMatch > prevVariantMatch ? nextVariant : prevVariant
+ }
+ }, undefined)
+ return selectedVariant
+}
diff --git a/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts b/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts
new file mode 100644
index 000000000..5ea5999b1
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts
@@ -0,0 +1,23 @@
+import toString from 'lodash/toString'
+import omit from 'lodash/omit'
+
+/**
+ * Counts how much coniguration match for specific variant
+ */
+export default function getConfigurationMatchLevel (configuration, variant): number {
+ if (!variant || !configuration) return 0
+ const configProperties = Object.keys(omit(configuration, ['price']))
+ return configProperties
+ .map(configProperty => {
+ const variantPropertyId = variant[configProperty]
+ if (configuration[configProperty] === null) {
+ return false
+ }
+
+ return [].concat(configuration[configProperty])
+ .map(f => typeof f === 'object' ? toString(f.id) : f)
+ .includes(toString(variantPropertyId))
+ })
+ .filter(Boolean)
+ .length
+}
diff --git a/core/modules/catalog/helpers/variant/getSelectedVariant.ts b/core/modules/catalog/helpers/variant/getSelectedVariant.ts
new file mode 100644
index 000000000..359705bfa
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/getSelectedVariant.ts
@@ -0,0 +1,15 @@
+import findConfigurableVariant from './findConfigurableVariant'
+
+/**
+ * Returns product based on configuration or if there is no match then return first variant as default.
+ */
+export default function getSelectedVariant (product, configuration, { fallbackToDefaultWhenNoAvailable }) {
+ let selectedVariant = findConfigurableVariant({ product, configuration, availabilityCheck: true })
+ if (!selectedVariant) {
+ if (fallbackToDefaultWhenNoAvailable) {
+ selectedVariant = findConfigurableVariant({ product, selectDefaultChildren: true, availabilityCheck: true }) // return first available child
+ }
+ }
+
+ return selectedVariant
+}
diff --git a/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts b/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts
new file mode 100644
index 000000000..1dbfa6879
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts
@@ -0,0 +1,12 @@
+/**
+ * Makes product variants comparission and returns variant with lowest price
+ */
+export default function getVariantWithLowestPrice (prevVariant, nextVariant) {
+ if (!prevVariant || !prevVariant.original_price_incl_tax) {
+ return nextVariant
+ }
+
+ const prevPrice = prevVariant.price_incl_tax || prevVariant.original_price_incl_tax
+ const nextPrice = nextVariant.price_incl_tax || nextVariant.original_price_incl_tax
+ return nextPrice < prevPrice ? nextVariant : prevVariant
+}
diff --git a/core/modules/catalog/helpers/variant/index.ts b/core/modules/catalog/helpers/variant/index.ts
new file mode 100644
index 000000000..6a9bdc17a
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/index.ts
@@ -0,0 +1,11 @@
+import omitSelectedVariantFields from './omitSelectedVariantFields'
+import getSelectedVariant from './getSelectedVariant'
+import isOptionAvailable from './isOptionAvailable'
+import findConfigurableVariant from './findConfigurableVariant'
+
+export {
+ omitSelectedVariantFields,
+ getSelectedVariant,
+ isOptionAvailable,
+ findConfigurableVariant
+}
diff --git a/core/modules/catalog/helpers/variant/isOptionAvailable.ts b/core/modules/catalog/helpers/variant/isOptionAvailable.ts
new file mode 100644
index 000000000..1c5ee5542
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/isOptionAvailable.ts
@@ -0,0 +1,9 @@
+import findConfigurableVariant from './findConfigurableVariant'
+
+/**
+ * Checks if variant with specific configuration exist
+ */
+export default function isOptionAvailable (context, { product, configuration }): boolean {
+ const variant = findConfigurableVariant({ product: product, configuration: configuration, availabilityCheck: true })
+ return typeof variant !== 'undefined' && variant !== null
+}
diff --git a/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts b/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts
new file mode 100644
index 000000000..5b9905271
--- /dev/null
+++ b/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts
@@ -0,0 +1,11 @@
+import omit from 'lodash/omit'
+
+/**
+ * Omit some variant fields to prevent overriding same base product fields
+ */
+export default function omitSelectedVariantFields (selectedVariant): void {
+ const hasImage = selectedVariant && selectedVariant.image && selectedVariant.image !== 'no_selection'
+ const fieldsToOmit = ['name', 'visibility']
+ if (!hasImage) fieldsToOmit.push('image')
+ return omit(selectedVariant, fieldsToOmit)
+}
diff --git a/core/modules/catalog/index.ts b/core/modules/catalog/index.ts
index 33ff1e321..abbb1a45f 100644
--- a/core/modules/catalog/index.ts
+++ b/core/modules/catalog/index.ts
@@ -23,9 +23,11 @@ export const CatalogModule: StorefrontModule = async function ({ store, router,
store.registerModule('tax', taxModule)
store.registerModule('category', categoryModule)
- await store.dispatch('attribute/list', { // loading attributes for application use
- filterValues: uniq([...config.products.defaultFilters, ...config.entities.productListWithChildren.includeFields])
- })
+ if (!config.entities.attribute.loadByAttributeMetadata) {
+ await store.dispatch('attribute/list', { // loading attributes for application use
+ filterValues: uniq([...config.products.defaultFilters, ...config.entities.productListWithChildren.includeFields])
+ })
+ }
if (!isServer) {
// Things moved from Product.js
diff --git a/core/modules/catalog/queries/common.js b/core/modules/catalog/queries/common.js
index a77db5fac..adcc18346 100644
--- a/core/modules/catalog/queries/common.js
+++ b/core/modules/catalog/queries/common.js
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import config from 'config'
export function prepareQuery ({ queryText = '', filters = [], queryConfig = '' }) {
diff --git a/core/modules/catalog/queries/related.js b/core/modules/catalog/queries/related.js
index 8bda79a1a..12a0d46da 100644
--- a/core/modules/catalog/queries/related.js
+++ b/core/modules/catalog/queries/related.js
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import config from 'config'
export function prepareRelatedQuery (key, sku) {
diff --git a/core/modules/catalog/queries/searchPanel.js b/core/modules/catalog/queries/searchPanel.js
index f8fad4b75..3543093f0 100644
--- a/core/modules/catalog/queries/searchPanel.js
+++ b/core/modules/catalog/queries/searchPanel.js
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import config from 'config'
export function prepareQuickSearchQuery (queryText) {
diff --git a/core/modules/catalog/store/attribute/actions.ts b/core/modules/catalog/store/attribute/actions.ts
index 38a9aed07..d89fa3246 100644
--- a/core/modules/catalog/store/attribute/actions.ts
+++ b/core/modules/catalog/store/attribute/actions.ts
@@ -1,16 +1,17 @@
import * as types from './mutation-types'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
-import AttributeState from '../../types/AttributeState'
+import AttributeState from '@vue-storefront/core/modules/catalog/types/AttributeState'
import RootState from '@vue-storefront/core/types/RootState'
import { ActionTree } from 'vuex'
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 createAttributesListQuery from './../../helpers/createAttributesListQuery'
-import reduceAttributesLists from './../../helpers/reduceAttributesLists'
-import filterAttributes from '../../helpers/filterAttributes'
+import { prefetchCachedAttributes } from '@vue-storefront/core/modules/catalog/helpers/prefetchCachedAttributes'
+import createAttributesListQuery from '@vue-storefront/core/modules/catalog/helpers/createAttributesListQuery'
+import reduceAttributesLists from '@vue-storefront/core/modules/catalog/helpers/reduceAttributesLists'
+import transformMetadataToAttributes from '@vue-storefront/core/modules/catalog/helpers/transformMetadataToAttributes'
+import filterAttributes from '@vue-storefront/core/modules/catalog/helpers/filterAttributes'
const actions: ActionTree = {
async updateAttributes ({ commit, getters }, { attributes }) {
@@ -82,6 +83,26 @@ const actions: ActionTree = {
await dispatch('updateAttributes', { attributes })
return resp
+ },
+ async loadProductAttributes (context, { products, merge = false }) {
+ const attributeMetadata = products
+ .filter(product => product.attributes_metadata)
+ .map(product => product.attributes_metadata)
+
+ const attributes = transformMetadataToAttributes(attributeMetadata)
+
+ if (merge) {
+ attributes.attrHashByCode = { ...attributes.attrHashByCode, ...context.state.list_by_code }
+ attributes.attrHashById = { ...attributes.attrHashById, ...context.state.list_by_id }
+ }
+
+ context.commit(types.ATTRIBUTE_UPD_ATTRIBUTES, attributes)
+ },
+ async loadCategoryAttributes (context, { attributeMetadata }) {
+ if (!attributeMetadata) return
+ const attributes = transformMetadataToAttributes([attributeMetadata])
+
+ context.commit(types.ATTRIBUTE_UPD_ATTRIBUTES, attributes)
}
}
diff --git a/core/modules/catalog/store/category/actions.ts b/core/modules/catalog/store/category/actions.ts
index ba590a658..e309307a4 100644
--- a/core/modules/catalog/store/category/actions.ts
+++ b/core/modules/catalog/store/category/actions.ts
@@ -11,7 +11,6 @@ import toString from 'lodash-es/toString'
import { optionLabel } from '../../helpers/optionLabel'
import RootState from '@vue-storefront/core/types/RootState'
import CategoryState from '../../types/CategoryState'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'
import { Logger } from '@vue-storefront/core/lib/logger'
import { isServer } from '@vue-storefront/core/helpers'
@@ -20,6 +19,7 @@ import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
import createCategoryListQuery from '@vue-storefront/core/modules/catalog/helpers/createCategoryListQuery'
import { formatCategoryLink } from 'core/modules/url/helpers'
+import { transformCategoryUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl';
const actions: ActionTree = {
/**
@@ -68,12 +68,7 @@ const actions: ActionTree = {
if (category.url_path) {
await dispatch('url/registerMapping', {
url: localizedDispatcherRoute(category.url_path, storeCode),
- routeData: {
- params: {
- 'slug': category.slug
- },
- 'name': localizedDispatcherRouteName('category', storeCode, appendStoreCode)
- }
+ routeData: transformCategoryUrl(category)
}, { root: true })
}
}
diff --git a/core/modules/catalog/store/product/actions.ts b/core/modules/catalog/store/product/actions.ts
index 0f0c7e9fe..82c1471c7 100644
--- a/core/modules/catalog/store/product/actions.ts
+++ b/core/modules/catalog/store/product/actions.ts
@@ -1,282 +1,44 @@
-import Vue from 'vue'
import { ActionTree } from 'vuex'
import * as types from './mutation-types'
-import { formatBreadCrumbRoutes, isServer } from '@vue-storefront/core/helpers'
-import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'
-import { configureProductAsync,
- doPlatformPricesSync,
- filterOutUnavailableVariants,
- populateProductConfigurationAsync,
- setCustomProductOptionsAsync,
- setBundleProductOptionsAsync,
- getMediaGallery,
- configurableChildrenImages,
- attributeImages } from '../../helpers'
-import { preConfigureProduct, getOptimizedFields, configureChildren, storeProductToCache, canCache, isGroupedOrBundle } from '@vue-storefront/core/modules/catalog/helpers/search'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
-import { entityKeyName } from '@vue-storefront/core/lib/store/entities'
-import { optionLabel } from '../../helpers/optionLabel'
-import { isOnline } from '@vue-storefront/core/lib/search'
-import omit from 'lodash-es/omit'
-import trim from 'lodash-es/trim'
+import { isServer } from '@vue-storefront/core/helpers'
+import { SearchQuery } from 'storefront-query-builder'
import cloneDeep from 'lodash-es/cloneDeep'
-import uniqBy from 'lodash-es/uniqBy'
import rootStore from '@vue-storefront/core/store'
import RootState from '@vue-storefront/core/types/RootState'
import ProductState from '../../types/ProductState'
import { Logger } from '@vue-storefront/core/lib/logger';
-import { TaskQueue } from '@vue-storefront/core/lib/sync'
-import toString from 'lodash-es/toString'
import config from 'config'
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 { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import {
+ registerProductsMapping,
+ doPlatformPricesSync,
+ setCustomProductOptionsAsync,
+ setBundleProductOptionsAsync,
+ getProductGallery,
+ setRequestCacheTags
+} from '@vue-storefront/core/modules/catalog/helpers'
+import { getProductConfigurationOptions } from '@vue-storefront/core/modules/catalog/helpers/productOptions'
import { checkParentRedirection } from '@vue-storefront/core/modules/catalog/events'
-const PRODUCT_REENTER_TIMEOUT = 20000
-
const actions: ActionTree = {
- /**
- * Reset current configuration and selected variatnts
- */
- reset (context) {
- const originalProduct = Object.assign({}, context.getters.getOriginalProduct)
- context.commit(types.PRODUCT_RESET_CURRENT, originalProduct)
- },
- /**
- * Setup product breadcrumbs path
- */
- async setupBreadcrumbs (context, { product }) {
- console.warn('deprecated, will be removed in 1.13')
- let breadcrumbsName = null
- let setBreadcrumbRoutesFromPath = (path) => {
- if (path.findIndex(itm => {
- return itm.slug === context.rootGetters['category/getCurrentCategory'].slug
- }) < 0) {
- path.push({
- url_path: context.rootGetters['category/getCurrentCategory'].url_path,
- slug: context.rootGetters['category/getCurrentCategory'].slug,
- name: context.rootGetters['category/getCurrentCategory'].name
- }) // current category at the end
- }
- // deprecated, TODO: base on breadcrumbs module
- breadcrumbsName = product.name
- const breadcrumbs = {
- routes: formatBreadCrumbRoutes(path),
- current: breadcrumbsName,
- name: breadcrumbsName
- }
- context.commit(types.CATALOG_SET_BREADCRUMBS, breadcrumbs)
- }
-
- if (product.category && product.category.length > 0) {
- const categoryIds = product.category.reverse().map(cat => cat.category_id)
- await context.dispatch('category/list', { key: 'id', value: categoryIds }, { root: true }).then(async (categories) => {
- const catList = []
-
- for (let catId of categoryIds) {
- let category = categories.items.find((itm) => { return toString(itm['id']) === toString(catId) })
- if (category) {
- catList.push(category)
- }
- }
-
- const rootCat = catList.shift()
- let catForBreadcrumbs = rootCat
-
- for (let cat of catList) {
- const catPath = cat.path
- if (catPath && catPath.includes(rootCat.path) && (catPath.split('/').length > catForBreadcrumbs.path.split('/').length)) {
- catForBreadcrumbs = cat
- }
- }
- if (typeof catForBreadcrumbs !== 'undefined') {
- await context.dispatch('category/single', { key: 'id', value: catForBreadcrumbs.id }, { root: true }).then(() => { // this sets up category path and current category
- setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
- }).catch(err => {
- setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
- Logger.error(err)()
- })
- } else {
- setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
- }
- })
- }
- },
doPlatformPricesSync (context, { products }) {
return doPlatformPricesSync(products)
},
- /**
- * Download Magento2 / other platform prices to put them over ElasticSearch prices
- */
- syncPlatformPricesOver ({ rootGetters }, { skus }) {
- const storeView = currentStoreView()
- let url = `${config.products.endpoint}/render-list?skus=${encodeURIComponent(skus.join(','))}¤cyCode=${encodeURIComponent(storeView.i18n.currencyCode)}&storeId=${encodeURIComponent(storeView.storeId)}`
- if (rootGetters['tax/getIsUserGroupedTaxActive']) {
- url = `${url}&userGroupId=${rootGetters['tax/getUserTaxGroupId']}`
- }
-
- return TaskQueue.execute({ url, // sync the cart
- payload: {
- method: 'GET',
- headers: { 'Content-Type': 'application/json' },
- mode: 'cors'
- },
- callback_event: 'prices-after-sync'
- }).then((task: any) => {
- return task.result
- })
- },
- /**
- * Setup associated products
- */
- setupAssociated (context, { product, skipCache = true }) {
- let subloaders = []
- if (product.type_id === 'grouped') {
- product.price = 0
- product.price_incl_tax = 0
- Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)()
- if (product.product_links && product.product_links.length > 0) {
- for (let pl of product.product_links) {
- if (pl.link_type === 'associated' && pl.linked_product_type === 'simple') { // prefetch links
- Logger.debug('Prefetching grouped product link for ' + pl.sku + ' = ' + pl.linked_product_sku)()
- subloaders.push(context.dispatch('single', {
- options: { sku: pl.linked_product_sku },
- setCurrentProduct: false,
- selectDefaultVariant: false,
- skipCache: skipCache
- }).catch(err => { Logger.error(err) }).then((asocProd) => {
- if (asocProd) {
- pl.product = asocProd
- pl.product.qty = 1
- product.price += pl.product.price
- product.price_incl_tax += pl.product.price_incl_tax
- product.tax += pl.product.tax
- } else {
- Logger.error('Product link not found', pl.linked_product_sku)()
- }
- }))
- }
- }
- } else {
- Logger.error('Product with type grouped has no product_links set!', product)()
- }
- }
- if (product.type_id === 'bundle') {
- product.price = 0
- product.price_incl_tax = 0
- Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)()
- if (product.bundle_options && product.bundle_options.length > 0) {
- for (let bo of product.bundle_options) {
- let defaultOption = bo.product_links.find((p) => { return p.is_default })
- if (!defaultOption) defaultOption = bo.product_links[0]
- for (let pl of bo.product_links) {
- Logger.debug('Prefetching bundle product link for ' + bo.sku + ' = ' + pl.sku)()
- subloaders.push(context.dispatch('single', {
- options: { sku: pl.sku },
- setCurrentProduct: false,
- selectDefaultVariant: false,
- skipCache: skipCache
- }).catch(err => { Logger.error(err) }).then((asocProd) => {
- if (asocProd) {
- pl.product = asocProd
- pl.product.qty = pl.qty
-
- if (pl.id === defaultOption.id) {
- product.price += pl.product.price * pl.product.qty
- product.price_incl_tax += pl.product.price_incl_tax * pl.product.qty
- product.tax += pl.product.tax * pl.product.qty
- }
- } else {
- Logger.error('Product link not found', pl.sku)()
- }
- }))
- }
- }
- }
- }
- return Promise.all(subloaders)
- },
/**
* This is fix for https://github.com/DivanteLtd/vue-storefront/issues/508
* TODO: probably it would be better to have "parent_id" for simple products or to just ensure configurable variants are not visible in categories/search
*/
- checkConfigurableParent (context, { product }) {
+ checkConfigurableParent ({ commit, dispatch, getters }, { product }) {
if (product.type_id === 'simple') {
Logger.log('Checking configurable parent')()
-
- let searchQuery = new SearchQuery()
- searchQuery = searchQuery.applyFilter({ key: 'configurable_children.sku', value: { 'eq': context.getters.getCurrentProduct.sku } })
-
- return context.dispatch('list', { query: searchQuery, start: 0, size: 1, updateState: false }).then((resp) => {
- if (resp.items.length >= 1) {
- const parentProduct = resp.items[0]
- context.commit(types.PRODUCT_SET_PARENT, parentProduct)
- }
- }).catch((err) => {
- Logger.error(err)()
- })
- }
- },
- /**
- * Load required configurable attributes
- * @param context
- * @param product
- */
- loadConfigurableAttributes (context, { product }) {
- let attributeKey = 'attribute_id'
- const configurableAttrKeys = product.configurable_options.map(opt => {
- if (opt.attribute_id) {
- attributeKey = 'attribute_id'
- return opt.attribute_id
- } else {
- attributeKey = 'attribute_code'
- return opt.attribute_code
+ const parent = dispatch('findConfigurableParent', { product: { sku: getters.getCurrentProduct.sku } })
+ if (parent) {
+ commit(types.PRODUCT_SET_PARENT, parent)
}
- })
- return context.dispatch('attribute/list', {
- filterValues: configurableAttrKeys,
- filterField: attributeKey
- }, { root: true })
- },
- /**
- * Setup product current variants
- */
- setupVariants (context, { product }) {
- let subloaders = []
- if (product.type_id === 'configurable' && product.hasOwnProperty('configurable_options')) {
- subloaders.push(context.dispatch('product/loadConfigurableAttributes', { product }, { root: true }).then((attributes) => {
- let productOptions = {}
- for (let option of product.configurable_options) {
- for (let ov of option.values) {
- let lb = ov.label ? ov.label : optionLabel(context.rootState.attribute, { attributeKey: option.attribute_id, searchBy: 'id', optionId: ov.value_index })
- if (trim(lb) !== '') {
- let optionKey = option.attribute_code ? option.attribute_code : option.label.toLowerCase()
- if (!productOptions[optionKey]) {
- productOptions[optionKey] = []
- }
- productOptions[optionKey].push({
- label: lb,
- id: ov.value_index,
- attribute_code: option.attribute_code
- })
- }
- }
- }
- context.commit(types.PRODUCT_SET_CURRENT_OPTIONS, productOptions)
- let selectedVariant = context.getters.getCurrentProduct
- populateProductConfigurationAsync(context, { selectedVariant: selectedVariant, product: product })
- }).catch(err => {
- Logger.error(err)()
- }))
+ return parent
}
- return Promise.all(subloaders)
- },
- filterUnavailableVariants (context, { product }) {
- return filterOutUnavailableVariants(context, product)
},
-
/**
* Search ElasticSearch catalog of products using simple text query
* Use bodybuilder to build the query, aggregations etc: http://bodybuilder.js.org/
@@ -285,78 +47,96 @@ const actions: ActionTree = {
* @param {Int} size page size
* @return {Promise}
*/
- async list ({ dispatch, commit }, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', prefetchGroupProducts = !isServer, updateState = false, meta = {}, excludeFields = null, includeFields = null, configuration = null, append = false, populateRequestCacheTags = true }) {
- const searchResult = await dispatch('findProducts', { query, start, size, entityType, sort, cacheByKey, excludeFields, includeFields, configuration, populateRequestCacheTags })
- await dispatch('preConfigureAssociated', { searchResult, prefetchGroupProducts })
+ async list (context, {
+ query,
+ start = 0,
+ size = 50,
+ sort = '',
+ prefetchGroupProducts = !isServer,
+ excludeFields = null,
+ includeFields = null,
+ configuration = null,
+ populateRequestCacheTags = true,
+ updateState = false,
+ append = false
+ } = {}) {
+ Logger.warn('`product/list` deprecated, will be not used from 1.12, use "findProducts" instead')()
+ const { items } = await context.dispatch('findProducts', {
+ query,
+ start,
+ size,
+ sort,
+ excludeFields,
+ includeFields,
+ configuration,
+ options: {
+ populateRequestCacheTags,
+ prefetchGroupProducts
+ }
+ })
if (updateState) {
- if (append) commit(types.PRODUCT_ADD_PAGED_PRODUCTS, searchResult)
- else commit(types.PRODUCT_SET_PAGED_PRODUCTS, searchResult)
+ Logger.warn('updateState and append are deprecated, will be not used from 1.12')()
+ if (append) context.commit(types.PRODUCT_ADD_PAGED_PRODUCTS, { items })
+ else context.commit(types.PRODUCT_SET_PAGED_PRODUCTS, { items })
}
- EventBus.$emit('product-after-list', { query, start, size, sort, entityType, meta, result: searchResult })
-
- return searchResult
- },
- preConfigureAssociated (context, { searchResult, prefetchGroupProducts }) {
- const { storeCode, appendStoreCode } = currentStoreView()
- for (let product of searchResult.items) {
- if (product.url_path) {
- const { parentSku, slug } = product
-
- context.dispatch('url/registerMapping', {
- url: localizedDispatcherRoute(product.url_path, storeCode),
- routeData: {
- params: { parentSku, slug },
- 'name': localizedDispatcherRouteName(product.type_id + '-product', storeCode, appendStoreCode)
- }
- }, { root: true })
+ EventBus.$emit('product-after-list', { query, start, size, sort, entityType: 'product', result: { items } })
+
+ return { items }
+ },
+ async findProducts (context, {
+ query,
+ start = 0,
+ size = 50,
+ sort = '',
+ excludeFields = null,
+ includeFields = null,
+ configuration = null,
+ populateRequestCacheTags = false,
+ options: {
+ populateRequestCacheTags: populateRequestCacheTagsNew = false,
+ prefetchGroupProducts = !isServer,
+ setProductErrors = false,
+ fallbackToDefaultWhenNoAvailable = true,
+ assignProductConfiguration = false,
+ separateSelectedVariant = false,
+ setConfigurableProductOptions = config.cart.setConfigurableProductOptions,
+ filterUnavailableVariants = config.products.filterUnavailableVariants
+ } = {}
+ } = {}) {
+ const { items, ...restResponseData } = await ProductService.getProducts({
+ query,
+ start,
+ size,
+ sort,
+ excludeFields,
+ includeFields,
+ configuration,
+ options: {
+ prefetchGroupProducts,
+ fallbackToDefaultWhenNoAvailable,
+ setProductErrors,
+ setConfigurableProductOptions,
+ filterUnavailableVariants,
+ assignProductConfiguration,
+ separateSelectedVariant
}
+ })
- if (isGroupedOrBundle(product) && prefetchGroupProducts && !isServer) {
- context.dispatch('setupAssociated', { product })
- }
- }
- },
- preConfigureProduct (context, { product, populateRequestCacheTags, configuration }) {
- console.warn('deprecated, will be removed in 1.13')
- let prod = preConfigureProduct({ product, populateRequestCacheTags })
+ registerProductsMapping(context, items)
- if (configuration) {
- const selectedVariant = configureProductAsync(context, { product: prod, selectDefaultVariant: false, configuration })
- prod = Object.assign({}, prod, omit(selectedVariant, ['visibility']))
+ if (populateRequestCacheTags) {
+ Logger.warn('deprecated from 1.13, use "options.populateRequestCacheTags" instead')()
}
- return prod
- },
- async configureLoadedProducts (context, { products, isCacheable, cacheByKey, populateRequestCacheTags, configuration }) {
- const configuredProducts = await context.dispatch(
- 'category-next/configureProducts',
- {
- products: products.items,
- filters: configuration || {},
- populateRequestCacheTags
- },
- { root: true }
- )
-
- await context.dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true })
-
- for (let product of configuredProducts) { // we store each product separately in cache to have offline access to products/single method
- if (isCacheable) { // store cache only for full loads
- storeProductToCache(product, cacheByKey)
- }
+ if (populateRequestCacheTags || populateRequestCacheTagsNew) {
+ setRequestCacheTags({ products: items })
}
- return products
- },
- async findProducts (context, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', excludeFields = null, includeFields = null, configuration = null, populateRequestCacheTags = true }) {
- const isCacheable = canCache({ includeFields, excludeFields })
- const { excluded, included } = getOptimizedFields({ excludeFields, includeFields })
- const resp = await quickSearchByQuery({ query, start, size, entityType, sort, excludeFields: excluded, includeFields: included })
- const products = await context.dispatch('configureLoadedProducts', { products: resp, isCacheable, cacheByKey, populateRequestCacheTags, configuration })
+ await context.dispatch('tax/calculateTaxes', { products: items }, { root: true })
- return products
+ return { ...restResponseData, items }
},
async findConfigurableParent (context, { product, configuration }) {
const searchQuery = new SearchQuery()
@@ -364,189 +144,34 @@ const actions: ActionTree = {
const products = await context.dispatch('findProducts', { query, configuration })
return products.items && products.items.length > 0 ? products.items[0] : null
},
- /**
- * Update associated products for bundle product
- * @param context
- * @param product
- */
- configureBundleAsync (context, product) {
- return context.dispatch(
- 'setupAssociated', {
- product: product,
- skipCache: true
- })
- .then(() => { context.dispatch('setCurrent', product) })
- .then(() => { EventBus.$emit('product-after-setup-associated') })
- },
-
- /**
- * Update associated products for group product
- * @param context
- * @param product
- */
- configureGroupedAsync (context, product) {
- return context.dispatch(
- 'setupAssociated', {
- product: product,
- skipCache: true
- })
- .then(() => { context.dispatch('setCurrent', product) })
- },
-
/**
* Search products by specific field
* @param {Object} options
*/
- async single (context, { options, setCurrentProduct = true, selectDefaultVariant = true, assignDefaultVariant = false, key = 'sku', skipCache = false }) {
+ async single (context, {
+ options = {},
+ setCurrentProduct = false,
+ key = 'sku',
+ skipCache = false
+ } = {}) {
+ if (setCurrentProduct) {
+ Logger.warn('option `setCurrentProduct` is deprecated, will be not used from 1.13')()
+ }
if (!options[key]) {
- throw Error('Please provide the search key ' + key + ' for product/single action!')
+ throw new Error('Please provide the search key ' + key + ' for product/single action!')
}
- const cacheKey = entityKeyName(key, options[key])
-
- return new Promise((resolve, reject) => {
- const benchmarkTime = new Date()
- const cache = StorageManager.get('elasticCache')
-
- const setupProduct = (prod) => {
- // set product quantity to 1
- if (!prod.qty) {
- prod.qty = 1
- }
- // set original product
- if (setCurrentProduct) {
- context.dispatch('setOriginal', prod)
- }
- // check is prod has configurable children
- const hasConfigurableChildren = prod && prod.configurable_children && prod.configurable_children.length
- if (prod.type_id === 'simple' && hasConfigurableChildren) { // workaround for #983
- prod = omit(prod, ['configurable_children', 'configurable_options'])
- }
-
- // set current product - configurable or not
- if (prod.type_id === 'configurable' && hasConfigurableChildren) {
- // set first available configuration
- // todo: probably a good idea is to change this [0] to specific id
- const selectedVariant = configureProductAsync(context, { product: prod, configuration: { sku: options.childSku }, selectDefaultVariant: selectDefaultVariant, setProductErorrs: true })
- if (selectedVariant && assignDefaultVariant) {
- prod = Object.assign({}, prod, selectedVariant)
- }
- } else if (!skipCache || (prod.type_id === 'simple' || prod.type_id === 'downloadable')) {
- if (setCurrentProduct) context.dispatch('setCurrent', prod)
- }
-
- return prod
- }
-
- const syncProducts = () => {
- let searchQuery = new SearchQuery()
- searchQuery = searchQuery.applyFilter({ key: key, value: { 'eq': options[key] } })
-
- return context.dispatch('list', { // product list syncs the platform price on it's own
- query: searchQuery,
- prefetchGroupProducts: false,
- updateState: false
- }).then((res) => {
- if (res && res.items && res.items.length) {
- let prd = res.items[0]
- const _returnProductNoCacheHelper = (subresults) => {
- EventBus.$emitFilter('product-after-single', { key: key, options: options, product: prd })
- resolve(setupProduct(prd))
- }
- if (setCurrentProduct || selectDefaultVariant) {
- const subConfigPromises = []
- if (prd.type_id === 'bundle') {
- subConfigPromises.push(context.dispatch('configureBundleAsync', prd))
- }
-
- if (prd.type_id === 'grouped') {
- subConfigPromises.push(context.dispatch('configureGroupedAsync', prd))
- }
- subConfigPromises.push(context.dispatch('setupVariants', { product: prd }))
- Promise.all(subConfigPromises).then(_returnProductNoCacheHelper)
- } else {
- _returnProductNoCacheHelper(null)
- }
- } else {
- reject(new Error('Product query returned empty result'))
- }
- })
- }
-
- const getProductFromCache = () => {
- cache.getItem(cacheKey, (err, res) => {
- // report errors
- if (!skipCache && err) {
- Logger.error(err, 'product')()
- }
-
- if (res !== null) {
- Logger.debug('Product:single - result from localForage (for ' + cacheKey + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()), 'product')()
- const _returnProductFromCacheHelper = (subresults) => {
- const cachedProduct = setupProduct(res)
- if (config.products.alwaysSyncPlatformPricesOver) {
- doPlatformPricesSync([cachedProduct]).then((products) => {
- EventBus.$emitFilter('product-after-single', { key: key, options: options, product: products[0] })
- resolve(products[0])
- })
- if (!config.products.waitForPlatformSync) {
- EventBus.$emitFilter('product-after-single', { key: key, options: options, product: cachedProduct })
- resolve(cachedProduct)
- }
- } else {
- EventBus.$emitFilter('product-after-single', { key: key, options: options, product: cachedProduct })
- resolve(cachedProduct)
- }
- }
- if (setCurrentProduct || selectDefaultVariant) {
- const subConfigPromises = []
- subConfigPromises.push(context.dispatch('setupVariants', { product: res }))
- if (res.type_id === 'bundle') {
- subConfigPromises.push(context.dispatch('configureBundleAsync', res))
- }
- if (res.type_id === 'grouped') {
- subConfigPromises.push(context.dispatch('configureGroupedAsync', res))
- }
- Promise.all(subConfigPromises).then(_returnProductFromCacheHelper)
- } else {
- _returnProductFromCacheHelper(null)
- }
- } else {
- syncProducts()
- }
- })
- }
-
- if (!skipCache) {
- getProductFromCache()
- } else {
- if (!isOnline()) {
- skipCache = false;
- }
-
- syncProducts()
- }
+ const product = await ProductService.getProductByKey({
+ options,
+ key,
+ skipCache
})
- },
- /**
- * Configure product with given configuration and set it as current
- * @param {Object} context
- * @param {Object} product
- * @param {Array} configuration
- */
- configure (context, { product = null, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = true }) {
- return configureProductAsync(context, { product: product, configuration: configuration, selectDefaultVariant: selectDefaultVariant, fallbackToDefaultWhenNoAvailable: fallbackToDefaultWhenNoAvailable })
- },
- setCurrentOption (context, productOption) {
- if (productOption && typeof productOption === 'object') { // TODO: this causes some kind of recurrency error
- context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { product_option: productOption }))
- }
- },
+ await context.dispatch('tax/calculateTaxes', { products: [product] }, { root: true })
- setCurrentErrors (context, errors) {
- if (errors && typeof errors === 'object') {
- context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { errors: errors }))
- }
+ if (setCurrentProduct) await context.dispatch('setCurrent', product)
+ EventBus.$emitFilter('product-after-single', { key, options, product })
+
+ return product
},
/**
* Assign the custom options object to the currentl product
@@ -569,74 +194,46 @@ const actions: ActionTree = {
* @param {Object} context
* @param {Object} productVariant
*/
- setCurrent (context, productVariant) {
- if (productVariant && typeof productVariant === 'object') {
- // get original product
- const originalProduct = context.getters.getOriginalProduct
-
- // check if passed variant is the same as original
- const productUpdated = Object.assign({}, originalProduct, productVariant)
- populateProductConfigurationAsync(context, { product: productUpdated, selectedVariant: productVariant })
+ setCurrent (context, product) {
+ if (product && typeof product === 'object') {
+ const { configuration, ...restProduct } = product
+ const productUpdated = Object.assign({}, restProduct)
if (!config.products.gallery.mergeConfigurableChildren) {
- context.commit(types.PRODUCT_SET_GALLERY, attributeImages(productVariant))
+ context.dispatch('setProductGallery', { product: productUpdated })
}
- context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, productUpdated))
+ const productOptions = getProductConfigurationOptions({ product, attribute: context.rootState.attribute })
+ context.commit(types.PRODUCT_SET_CURRENT_OPTIONS, productOptions)
+ context.commit(types.PRODUCT_SET_CURRENT_CONFIGURATION, configuration || {})
+ context.commit(types.PRODUCT_SET_CURRENT, productUpdated)
return productUpdated
} else Logger.debug('Unable to update current product.', 'product')()
},
- /**
- * Set given product as original
- * @param {Object} context
- * @param {Object} originalProduct
- */
- setOriginal (context, originalProduct) {
- if (originalProduct && typeof originalProduct === 'object') context.commit(types.PRODUCT_SET_ORIGINAL, Object.assign({}, originalProduct))
- else Logger.debug('Unable to setup original product.', 'product')()
- },
/**
* Set related products
*/
related (context, { key = 'related-products', items }) {
context.commit(types.PRODUCT_SET_RELATED, { key, items })
},
-
- // Deprecated methods, remove in 2.0
- async fetch () {
- throw new Error('product/fetch has been moved into product/loadProduct')
- },
- async fetchAsync () {
- throw new Error('product/fetchAsync has been moved into product/loadProduct')
- },
-
- /**
- * Load product attributes
- */
- async loadProductAttributes ({ dispatch }, { product }) {
- const productFields = Object.keys(product).filter(fieldName => {
- return !config.entities.product.standardSystemFields.includes(fieldName) // don't load metadata info for standard fields
- })
- const { product: { useDynamicAttributeLoader }, optimize, attribute } = config.entities
- return dispatch('attribute/list', { // load attributes to be shown on the product details - the request is now async
- filterValues: useDynamicAttributeLoader ? productFields : null,
- only_visible: !!useDynamicAttributeLoader,
- only_user_defined: true,
- includeFields: optimize ? attribute.includeFields : null
- }, { root: true })
- },
-
/**
* Load the product data and sets current product
*/
- async loadProduct ({ dispatch }, { parentSku, childSku = null, route = null }) {
+ async loadProduct ({ dispatch, state }, { parentSku, childSku = null, route = null, skipCache = false }) {
Logger.info('Fetching product data asynchronously', 'product', { parentSku, childSku })()
EventBus.$emit('product-before-load', { store: rootStore, route: route })
- await dispatch('reset')
- // pass both id and sku to render a product
- const productSingleOptions = {
- sku: parentSku,
- childSku: childSku
- }
- const product = await dispatch('single', { options: productSingleOptions })
+
+ const product = await dispatch('single', {
+ options: {
+ sku: parentSku,
+ childSku: childSku
+ },
+ key: 'sku',
+ skipCache
+ })
+
+ setRequestCacheTags({ products: [product] })
+
+ await dispatch('setCurrent', product)
+
if (product.status >= 2) {
throw new Error(`Product query returned empty result product status = ${product.status}`)
}
@@ -649,12 +246,15 @@ const actions: ActionTree = {
}
}
- await dispatch('loadProductAttributes', { product })
+ if (config.entities.attribute.loadByAttributeMetadata) {
+ await dispatch('attribute/loadProductAttributes', { products: [product] }, { root: true })
+ } else {
+ await dispatch('loadProductAttributes', { product })
+ }
+
const syncPromises = []
- const variantsFilter = dispatch('filterUnavailableVariants', { product })
const gallerySetup = dispatch('setProductGallery', { product })
if (isServer) {
- syncPromises.push(variantsFilter)
syncPromises.push(gallerySetup)
}
await Promise.all(syncPromises)
@@ -673,17 +273,8 @@ const actions: ActionTree = {
*/
setProductGallery (context, { product }) {
- if (product.type_id === 'configurable' && product.hasOwnProperty('configurable_children')) {
- if (!config.products.gallery.mergeConfigurableChildren && product.is_configured) {
- context.commit(types.PRODUCT_SET_GALLERY, attributeImages(context.getters.getCurrentProduct))
- } else {
- let productGallery = uniqBy(configurableChildrenImages(product).concat(getMediaGallery(product)), 'src').filter(f => { return f.src && f.src !== config.images.productPlaceholder })
- context.commit(types.PRODUCT_SET_GALLERY, productGallery)
- }
- } else {
- let productGallery = uniqBy(configurableChildrenImages(product).concat(getMediaGallery(product)), 'src').filter(f => { return f.src && f.src !== config.images.productPlaceholder })
- context.commit(types.PRODUCT_SET_GALLERY, productGallery)
- }
+ const productGallery = getProductGallery(product)
+ context.commit(types.PRODUCT_SET_GALLERY, productGallery)
},
async loadProductBreadcrumbs ({ dispatch, rootGetters }, { product } = {}) {
if (product && product.category_ids) {
@@ -702,7 +293,29 @@ const actions: ActionTree = {
}
await dispatch('category-next/loadCategoryBreadcrumbs', { category: breadcrumbCategory, currentRouteName: product.name }, { root: true })
}
- }
+ },
+ async getProductVariant (context, { product, configuration } = {}) {
+ let searchQuery = new SearchQuery()
+ searchQuery = searchQuery.applyFilter({ key: 'sku', value: { 'eq': product.parentSku } })
+ if (!product.parentSku) {
+ throw new Error('Product doesn\'t have parentSku, please check if this is configurable product')
+ }
+ const { items: [newProductVariant] } = await context.dispatch('findProducts', {
+ query: searchQuery,
+ size: 1,
+ configuration,
+ options: {
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: true,
+ separateSelectedVariant: true
+ }
+ })
+ const { selectedVariant = {}, options, product_option } = newProductVariant
+
+ return { ...selectedVariant, options, product_option }
+ },
+ /** Below actions are not used from 1.12 and can be removed to reduce bundle */
+ ...require('./deprecatedActions').default
}
export default actions
diff --git a/core/modules/catalog/store/product/deprecatedActions.ts b/core/modules/catalog/store/product/deprecatedActions.ts
new file mode 100644
index 000000000..ef8940608
--- /dev/null
+++ b/core/modules/catalog/store/product/deprecatedActions.ts
@@ -0,0 +1,355 @@
+import { optionLabel } from '../../helpers/optionLabel'
+import trim from 'lodash-es/trim'
+import { formatBreadCrumbRoutes, isServer } from '@vue-storefront/core/helpers'
+import { preConfigureProduct, storeProductToCache, isGroupedOrBundle } from '@vue-storefront/core/modules/catalog/helpers/search'
+import toString from 'lodash-es/toString'
+import {
+ registerProductsMapping,
+ filterOutUnavailableVariants
+} from '@vue-storefront/core/modules/catalog/helpers'
+import { Logger } from '@vue-storefront/core/lib/logger';
+import * as types from './mutation-types'
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import config from 'config'
+import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
+const { populateProductConfigurationAsync } = require('@vue-storefront/core/modules/catalog/helpers')
+
+const actions = {
+ /**
+ * Reset current configuration and selected variatnts
+ */
+ reset (context) {
+ Logger.warn('`product/reset` deprecated, will be not used from 1.12')()
+ const originalProduct = Object.assign({}, context.getters.getOriginalProduct)
+ context.commit(types.PRODUCT_RESET_CURRENT, originalProduct)
+ },
+ /**
+ * Setup product breadcrumbs path
+ */
+ async setupBreadcrumbs (context, { product }) {
+ Logger.warn('`product/setupBreadcrumbs` deprecated, will be not used from 1.12')()
+ let breadcrumbsName = null
+ let setBreadcrumbRoutesFromPath = (path) => {
+ if (path.findIndex(itm => {
+ return itm.slug === context.rootGetters['category/getCurrentCategory'].slug
+ }) < 0) {
+ path.push({
+ url_path: context.rootGetters['category/getCurrentCategory'].url_path,
+ slug: context.rootGetters['category/getCurrentCategory'].slug,
+ name: context.rootGetters['category/getCurrentCategory'].name
+ }) // current category at the end
+ }
+ // deprecated, TODO: base on breadcrumbs module
+ breadcrumbsName = product.name
+ const breadcrumbs = {
+ routes: formatBreadCrumbRoutes(path),
+ current: breadcrumbsName,
+ name: breadcrumbsName
+ }
+ context.commit(types.CATALOG_SET_BREADCRUMBS, breadcrumbs)
+ }
+
+ if (product.category && product.category.length > 0) {
+ const categoryIds = product.category.reverse().map(cat => cat.category_id)
+ await context.dispatch('category/list', { key: 'id', value: categoryIds }, { root: true }).then(async (categories) => {
+ const catList = []
+
+ for (let catId of categoryIds) {
+ let category = categories.items.find((itm) => { return toString(itm['id']) === toString(catId) })
+ if (category) {
+ catList.push(category)
+ }
+ }
+
+ const rootCat = catList.shift()
+ let catForBreadcrumbs = rootCat
+
+ for (let cat of catList) {
+ const catPath = cat.path
+ if (catPath && catPath.includes(rootCat.path) && (catPath.split('/').length > catForBreadcrumbs.path.split('/').length)) {
+ catForBreadcrumbs = cat
+ }
+ }
+ if (typeof catForBreadcrumbs !== 'undefined') {
+ await context.dispatch('category/single', { key: 'id', value: catForBreadcrumbs.id }, { root: true }).then(() => { // this sets up category path and current category
+ setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
+ }).catch(err => {
+ setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
+ Logger.error(err)()
+ })
+ } else {
+ setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath'])
+ }
+ })
+ }
+ },
+ /**
+ * Download Magento2 / other platform prices to put them over ElasticSearch prices
+ */
+ async syncPlatformPricesOver ({ rootGetters }, { skus }) {
+ Logger.warn('`product/syncPlatformPricesOver`deprecated, will be not used from 1.12')()
+ const result = await ProductService.getProductRenderList({
+ skus,
+ isUserGroupedTaxActive: rootGetters['tax/getIsUserGroupedTaxActive'],
+ userGroupId: rootGetters['tax/getUserTaxGroupId'],
+ token: rootGetters['user/getToken']
+ })
+ return result
+ },
+ /**
+ * Setup associated products
+ */
+ setupAssociated (context, { product, skipCache = true }) {
+ Logger.warn('`product/setupAssociated` deprecated, will be not used from 1.12')()
+ let subloaders = []
+ if (product.type_id === 'grouped') {
+ product.price = 0
+ product.price_incl_tax = 0
+ Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)()
+ if (product.product_links && product.product_links.length > 0) {
+ for (let pl of product.product_links) {
+ if (pl.link_type === 'associated' && pl.linked_product_type === 'simple') { // prefetch links
+ Logger.debug('Prefetching grouped product link for ' + pl.sku + ' = ' + pl.linked_product_sku)()
+ subloaders.push(context.dispatch('single', {
+ options: { sku: pl.linked_product_sku },
+ setCurrentProduct: false,
+ selectDefaultVariant: false,
+ skipCache: skipCache
+ }).catch(err => { Logger.error(err) }).then((asocProd) => {
+ if (asocProd) {
+ pl.product = asocProd
+ pl.product.qty = 1
+ product.price += pl.product.price
+ product.price_incl_tax += pl.product.price_incl_tax
+ product.tax += pl.product.tax
+ } else {
+ Logger.error('Product link not found', pl.linked_product_sku)()
+ }
+ }))
+ }
+ }
+ } else {
+ Logger.error('Product with type grouped has no product_links set!', product)()
+ }
+ }
+ if (product.type_id === 'bundle') {
+ product.price = 0
+ product.price_incl_tax = 0
+ Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)()
+ if (product.bundle_options && product.bundle_options.length > 0) {
+ for (let bo of product.bundle_options) {
+ let defaultOption = bo.product_links.find((p) => { return p.is_default })
+ if (!defaultOption) defaultOption = bo.product_links[0]
+ for (let pl of bo.product_links) {
+ Logger.debug('Prefetching bundle product link for ' + bo.sku + ' = ' + pl.sku)()
+ subloaders.push(context.dispatch('single', {
+ options: { sku: pl.sku },
+ setCurrentProduct: false,
+ selectDefaultVariant: false,
+ skipCache: skipCache
+ }).catch(err => { Logger.error(err) }).then((asocProd) => {
+ if (asocProd) {
+ pl.product = asocProd
+ pl.product.qty = pl.qty
+
+ if (pl.id === defaultOption.id) {
+ product.price += pl.product.price * pl.product.qty
+ product.price_incl_tax += pl.product.price_incl_tax * pl.product.qty
+ product.tax += pl.product.tax * pl.product.qty
+ }
+ } else {
+ Logger.error('Product link not found', pl.sku)()
+ }
+ }))
+ }
+ }
+ }
+ }
+ return Promise.all(subloaders)
+ },
+ /**
+ * Load required configurable attributes
+ * @param context
+ * @param product
+ */
+ loadConfigurableAttributes (context, { product }) {
+ Logger.warn('`product/loadConfigurableAttributes` deprecated, will be not used from 1.12')()
+ let attributeKey = 'attribute_id'
+ const configurableAttrKeys = product.configurable_options.map(opt => {
+ if (opt.attribute_id) {
+ attributeKey = 'attribute_id'
+ return opt.attribute_id
+ } else {
+ attributeKey = 'attribute_code'
+ return opt.attribute_code
+ }
+ })
+ return context.dispatch('attribute/list', {
+ filterValues: configurableAttrKeys,
+ filterField: attributeKey
+ }, { root: true })
+ },
+ /**
+ * Setup product current variants
+ */
+ async setupVariants (context, { product }) {
+ Logger.warn('`product/setupVariants` deprecated, will be not used from 1.12')()
+ if (product.type_id !== 'configurable' || !product.hasOwnProperty('configurable_options')) {
+ return
+ }
+ if (config.entities.attribute.loadByAttributeMetadata) {
+ await context.dispatch('attribute/loadProductAttributes', { products: [product] }, { root: true })
+ }
+ let productOptions = {}
+ for (let option of product.configurable_options) {
+ for (let ov of option.values) {
+ let lb = ov.label ? ov.label : optionLabel(context.rootState.attribute, { attributeKey: option.attribute_id, searchBy: 'id', optionId: ov.value_index })
+ if (trim(lb) !== '') {
+ let optionKey = option.attribute_code ? option.attribute_code : option.label.toLowerCase()
+ if (!productOptions[optionKey]) {
+ productOptions[optionKey] = []
+ }
+
+ productOptions[optionKey].push({
+ label: lb,
+ id: ov.value_index,
+ attribute_code: option.attribute_code
+ })
+ }
+ }
+ }
+ context.commit(types.PRODUCT_SET_CURRENT_OPTIONS, productOptions)
+ let selectedVariant = context.getters.getCurrentProduct
+ populateProductConfigurationAsync(context, { selectedVariant: selectedVariant, product: product })
+ },
+ filterUnavailableVariants (context, { product }) {
+ Logger.warn('`product/filterUnavailableVariants` deprecated, will be not used from 1.12')()
+ return filterOutUnavailableVariants(context, product)
+ },
+ preConfigureAssociated (context, { searchResult, prefetchGroupProducts }) {
+ Logger.warn('`product/preConfigureAssociated` deprecated, will be not used from 1.12')()
+ registerProductsMapping(context, searchResult.items)
+ for (let product of searchResult.items) {
+ if (isGroupedOrBundle(product) && prefetchGroupProducts && !isServer) {
+ context.dispatch('setupAssociated', { product })
+ }
+ }
+ },
+ async preConfigureProduct (context, { product, populateRequestCacheTags, configuration }) {
+ Logger.warn('`product/preConfigureProduct` deprecated, will be not used from 1.12')()
+ let _product = preConfigureProduct({ product, populateRequestCacheTags })
+
+ if (configuration) {
+ const selectedVariant = await context.dispatch('getProductVariant', { product: _product, configuration })
+ _product = Object.assign({}, _product, selectedVariant)
+ }
+
+ return _product
+ },
+ async configureLoadedProducts (context, { products, isCacheable, cacheByKey, populateRequestCacheTags, configuration }) {
+ Logger.warn('`product/configureLoadedProducts` deprecated, will be not used from 1.12')()
+ const configuredProducts = await context.dispatch(
+ 'category-next/configureProducts',
+ {
+ products: products.items,
+ filters: configuration || {},
+ populateRequestCacheTags
+ },
+ { root: true }
+ )
+
+ await context.dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true })
+
+ for (let product of configuredProducts) { // we store each product separately in cache to have offline access to products/single method
+ if (isCacheable) { // store cache only for full loads
+ storeProductToCache(product, cacheByKey)
+ }
+ }
+
+ return products
+ },
+ /**
+ * Update associated products for bundle product
+ * @param context
+ * @param product
+ */
+ configureBundleAsync (context, product) {
+ Logger.warn('`product/configureBundleAsync` deprecated, will be not used from 1.12')()
+ return context.dispatch(
+ 'setupAssociated', {
+ product: product,
+ skipCache: true
+ })
+ .then(() => { context.dispatch('setCurrent', product) })
+ .then(() => { EventBus.$emit('product-after-setup-associated') })
+ },
+
+ /**
+ * Update associated products for group product
+ * @param context
+ * @param product
+ */
+ configureGroupedAsync (context, product) {
+ Logger.warn('`product/configureGroupedAsync` deprecated, will be not used from 1.12')()
+ return context.dispatch(
+ 'setupAssociated', {
+ product: product,
+ skipCache: true
+ })
+ .then(() => { context.dispatch('setCurrent', product) })
+ },
+ /**
+ * Configure product with given configuration and set it as current
+ * @param {Object} context
+ * @param {Object} product
+ * @param {Array} configuration
+ */
+ async configure (context, { product = null, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = false }) {
+ Logger.warn('`product/configure` deprecated, will be not used from 1.12, use "product/getProductVariant"')()
+ const result = await context.dispatch('getProductVariant', { product, configuration })
+ return result
+ },
+
+ setCurrentOption (context, productOption) {
+ Logger.warn('`product/setCurrentOption` deprecated, will be not used from 1.12')()
+ if (productOption && typeof productOption === 'object') { // TODO: this causes some kind of recurrency error
+ context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { product_option: productOption }))
+ }
+ },
+
+ setCurrentErrors (context, errors) {
+ Logger.warn('`product/setCurrentErrors` deprecated, will be not used from 1.12')()
+ if (errors && typeof errors === 'object') {
+ context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { errors: errors }))
+ }
+ },
+ /**
+ * Set given product as original
+ * @param {Object} context
+ * @param {Object} originalProduct
+ */
+ setOriginal (context, originalProduct) {
+ Logger.warn('`product/setOriginal` deprecated, will be not used from 1.12')()
+ if (originalProduct && typeof originalProduct === 'object') context.commit(types.PRODUCT_SET_ORIGINAL, Object.assign({}, originalProduct))
+ else Logger.debug('Unable to setup original product.', 'product')()
+ },
+
+ /**
+ * Load product attributes
+ */
+ async loadProductAttributes ({ dispatch }, { product }) {
+ Logger.warn('`product/loadProductAttributes` deprecated, will be not used from 1.12')()
+ const productFields = Object.keys(product).filter(fieldName => {
+ return !config.entities.product.standardSystemFields.includes(fieldName) // don't load metadata info for standard fields
+ })
+ const { product: { useDynamicAttributeLoader }, optimize, attribute } = config.entities
+ return dispatch('attribute/list', { // load attributes to be shown on the product details - the request is now async
+ filterValues: useDynamicAttributeLoader ? productFields : null,
+ only_visible: !!useDynamicAttributeLoader,
+ only_user_defined: true,
+ includeFields: optimize ? attribute.includeFields : null
+ }, { root: true })
+ }
+}
+
+export default actions
diff --git a/core/modules/catalog/store/product/getters.ts b/core/modules/catalog/store/product/getters.ts
index e8d9d4948..700c69420 100644
--- a/core/modules/catalog/store/product/getters.ts
+++ b/core/modules/catalog/store/product/getters.ts
@@ -1,12 +1,19 @@
import { GetterTree } from 'vuex'
import RootState from '@vue-storefront/core/types/RootState'
import ProductState from '../../types/ProductState'
+import { Logger } from '@vue-storefront/core/lib/logger';
const getters: GetterTree = {
getCurrentProduct: state => state.current,
getCurrentProductConfiguration: state => state.current_configuration,
getCurrentProductOptions: state => state.current_options,
- getOriginalProduct: state => state.original,
+ getOriginalProduct: (state, getters) => {
+ if (!getters.getCurrentProduct) return null
+ return state.original || {
+ ...getters.getCurrentProduct,
+ id: getters.getCurrentProduct.parentId || getters.getCurrentProduct.id
+ }
+ },
getParentProduct: state => state.parent,
getProductsSearchResult: state => state.list,
getProducts: (state, getters) => getters.getProductsSearchResult.items,
diff --git a/core/modules/catalog/store/product/mutations.ts b/core/modules/catalog/store/product/mutations.ts
index 2c81ada34..bd415ba0d 100644
--- a/core/modules/catalog/store/product/mutations.ts
+++ b/core/modules/catalog/store/product/mutations.ts
@@ -1,6 +1,8 @@
import { MutationTree } from 'vuex'
+import { Logger } from '@vue-storefront/core/lib/logger'
import * as types from './mutation-types'
import ProductState, { PagedProductList } from '../../types/ProductState'
+import Vue from 'vue'
const mutations: MutationTree = {
[types.PRODUCT_SET_PAGED_PRODUCTS] (state, searchResult) {
@@ -47,7 +49,7 @@ const mutations: MutationTree = {
state.current_options = configuration
},
[types.PRODUCT_SET_CURRENT_CONFIGURATION] (state, configuration = {}) {
- state.current_configuration = configuration
+ Vue.set(state, 'current_configuration', configuration || {})
},
[types.PRODUCT_SET_ORIGINAL] (state, product) {
state.original = product
@@ -91,28 +93,28 @@ const mutations: MutationTree = {
state.breadcrumbs = payload
},
[types.CATALOG_ADD_CUSTOM_OPTION_VALIDATOR] (state, { validationRule, validatorFunction }) {
- console.error('Deprecated mutation CATALOG_ADD_CUSTOM_OPTION_VALIDATOR - use PRODUCT_SET_CUSTOM_OPTION_VALIDATOR instead')
+ Logger.error('Deprecated mutation CATALOG_ADD_CUSTOM_OPTION_VALIDATOR - use PRODUCT_SET_CUSTOM_OPTION_VALIDATOR instead')()
},
[types.CATALOG_UPD_RELATED] (state, { key, items }) {
- console.error('Deprecated mutation CATALOG_UPD_RELATED - use PRODUCT_SET_RELATED instead')
+ Logger.error('Deprecated mutation CATALOG_UPD_RELATED - use PRODUCT_SET_RELATED instead')()
},
[types.CATALOG_UPD_BUNDLE_OPTION] (state, { optionId, optionQty, optionSelections }) {
- console.error('Deprecated mutation CATALOG_UPD_BUNDLE_OPTION - use PRODUCT_SET_BUNDLE_OPTION instead')
+ Logger.error('Deprecated mutation CATALOG_UPD_BUNDLE_OPTION - use PRODUCT_SET_BUNDLE_OPTION instead')()
},
[types.CATALOG_UPD_PRODUCTS] (state, { products, append }) {
- console.error('Deprecated mutation CATALOG_UPD_PRODUCTS - use PRODUCT_SET_PAGED_PRODUCTS or PRODUCT_ADD_PAGED_PRODUCTS instead')
+ Logger.error('Deprecated mutation CATALOG_UPD_PRODUCTS - use PRODUCT_SET_PAGED_PRODUCTS or PRODUCT_ADD_PAGED_PRODUCTS instead')()
},
[types.CATALOG_SET_PRODUCT_CURRENT] (state, product) {
- console.error('Deprecated mutation CATALOG_SET_PRODUCT_CURRENT - use PRODUCT_SET_CURRENT instead')
+ Logger.error('Deprecated mutation CATALOG_SET_PRODUCT_CURRENT - use PRODUCT_SET_CURRENT instead')()
},
[types.CATALOG_SET_PRODUCT_ORIGINAL] (state, product) {
- console.error('Deprecated mutation CATALOG_SET_PRODUCT_ORIGINAL - use PRODUCT_SET_ORIGINAL instead')
+ Logger.error('Deprecated mutation CATALOG_SET_PRODUCT_ORIGINAL - use PRODUCT_SET_ORIGINAL instead')()
},
[types.CATALOG_RESET_PRODUCT] (state, productOriginal) {
- console.error('Deprecated mutation CATALOG_RESET_PRODUCT - use PRODUCT_RESET_CURRENT instead')
+ Logger.error('Deprecated mutation CATALOG_RESET_PRODUCT - use PRODUCT_RESET_CURRENT instead')()
},
[types.CATALOG_UPD_GALLERY] (state, productGallery) {
- console.error('Deprecated mutation CATALOG_UPD_GALLERY - use PRODUCT_SET_GALLERY instead')
+ Logger.error('Deprecated mutation CATALOG_UPD_GALLERY - use PRODUCT_SET_GALLERY instead')()
}
}
diff --git a/core/modules/catalog/store/tax/actions.ts b/core/modules/catalog/store/tax/actions.ts
index 72faad3ea..420c37613 100644
--- a/core/modules/catalog/store/tax/actions.ts
+++ b/core/modules/catalog/store/tax/actions.ts
@@ -1,7 +1,7 @@
import { ActionTree } from 'vuex'
import * as types from './mutation-types'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import RootState from '@vue-storefront/core/types/RootState'
import TaxState from '../../types/TaxState'
import { Logger } from '@vue-storefront/core/lib/logger'
diff --git a/core/modules/catalog/test/helpers/createProduct.ts b/core/modules/catalog/test/helpers/createProduct.ts
new file mode 100644
index 000000000..2e3c68918
--- /dev/null
+++ b/core/modules/catalog/test/helpers/createProduct.ts
@@ -0,0 +1,96 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+export const createSimpleProduct = (): Product => ({
+ 'id': 21,
+ 'sku': '24-WG084',
+ 'name': 'Sprite Foam Yoga Brick',
+ 'price': 5,
+ 'status': 1,
+ 'visibility': 4,
+ 'type_id': 'simple',
+ 'product_links': [],
+ 'custom_attributes': null,
+ 'final_price': 5,
+ 'max_price': 5,
+ 'max_regular_price': 5,
+ 'minimal_regular_price': 5,
+ 'special_price': null,
+ 'minimal_price': 5,
+ 'regular_price': 5,
+ 'description': '
Our top-selling yoga prop, the 4-inch, high-quality Sprite Foam Yoga Brick is popular among yoga novices and studio professionals alike. An essential yoga accessory, the yoga brick is a critical tool for finding balance and alignment in many common yoga poses. Choose from 5 color options.
A well-rounded yoga workout takes more than a mat. The Sprite Yoga Companion Kit helps stock your studio with the basics you need for a full-range workout. The kit is composed of four best-selling Luma Sprite accessories in one easy bundle: statis ball, foam block, yoga strap, and foam roller. Choose sizes and colors and leave the rest to us. The kit includes:
Sprite Statis Ball
Sprite Foam Yoga Brick
Sprite Yoga Strap
Sprite Foam Roller
',
+ 'image': '/l/u/luma-yoga-kit-2.jpg',
+ 'small_image': '/l/u/luma-yoga-kit-2.jpg',
+ 'thumbnail': '/l/u/luma-yoga-kit-2.jpg',
+ 'category_ids': [3, 5],
+ 'url_key': 'sprite-yoga-companion-kit',
+ 'tax_class_id': '2',
+ 'slug': 'sprite-yoga-companion-kit-45',
+ 'stock': { 'item_id': 45, 'product_id': 45, 'stock_id': 1, 'qty': 0, 'is_in_stock': true, 'is_qty_decimal': false, 'show_default_notification_message': false, 'use_config_min_qty': true, 'min_qty': 0, 'use_config_min_sale_qty': 1, 'min_sale_qty': 1, 'use_config_max_sale_qty': true, 'max_sale_qty': 10000, 'use_config_backorders': true, 'backorders': 0, 'use_config_notify_stock_qty': true, 'notify_stock_qty': 1, 'use_config_qty_increments': true, 'qty_increments': 0, 'use_config_enable_qty_inc': true, 'enable_qty_increments': false, 'use_config_manage_stock': true, 'manage_stock': true, 'low_stock_date': null, 'is_decimal_divided': false, 'stock_status_changed_auto': 0 },
+ 'media_gallery': [{ 'image': '/l/u/luma-yoga-kit-2.jpg', 'pos': 1, 'typ': 'image', 'lab': 'Image', 'vid': null }],
+ 'bundle_options': [{ 'option_id': 1, 'title': 'Sprite Stasis Ball', 'required': true, 'type': 'radio', 'position': 1, 'sku': '24-WG080', 'product_links': [{ 'id': '1', 'sku': '24-WG081-blue', 'option_id': 1, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '2', 'sku': '24-WG082-blue', 'option_id': 1, 'qty': 1, 'position': 2, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '3', 'sku': '24-WG083-blue', 'option_id': 1, 'qty': 1, 'position': 3, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 2, 'title': 'Sprite Foam Yoga Brick', 'required': true, 'type': 'radio', 'position': 2, 'sku': '24-WG080', 'product_links': [{ 'id': '4', 'sku': '24-WG084', 'option_id': 2, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 3, 'title': 'Sprite Yoga Strap', 'required': true, 'type': 'radio', 'position': 3, 'sku': '24-WG080', 'product_links': [{ 'id': '5', 'sku': '24-WG085', 'option_id': 3, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '6', 'sku': '24-WG086', 'option_id': 3, 'qty': 1, 'position': 2, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '7', 'sku': '24-WG087', 'option_id': 3, 'qty': 1, 'position': 3, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 4, 'title': 'Sprite Foam Roller', 'required': true, 'type': 'radio', 'position': 4, 'sku': '24-WG080', 'product_links': [{ 'id': '8', 'sku': '24-WG088', 'option_id': 4, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }],
+ 'category': [{ 'category_id': 3, 'name': 'Gear', 'slug': 'gear-3', 'path': 'gear/gear-3' }, { 'category_id': 5, 'name': 'Fitness Equipment', 'slug': 'fitness-equipment-5', 'path': 'gear/fitness-equipment/fitness-equipment-5' }],
+ 'url_path': 'gear/gear-3/sprite-yoga-companion-kit-45.html'
+})
diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts
new file mode 100644
index 000000000..edb960af1
--- /dev/null
+++ b/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts
@@ -0,0 +1,54 @@
+import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct';
+import setBundleProducts from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setBundleProducts';
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ error: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/store', () => ({}));
+jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({
+ ProductService: {
+ getProducts: jest.fn(),
+ getProductRenderList: jest.fn(),
+ getProductByKey: jest.fn()
+ }
+}));
+
+describe('setBundleProducts helper', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items: [] }));
+ })
+ it('should not fire ProductService.getProducts if it is not bundle product', async () => {
+ const groupedProduct = createGroupProduct()
+
+ setBundleProducts(groupedProduct)
+
+ expect(ProductService.getProducts).toHaveBeenCalledTimes(0)
+ })
+ it('should fire ProductService.getProducts with simple configuration', async () => {
+ const bundleProduct = createBundleProduct()
+
+ setBundleProducts(bundleProduct)
+
+ expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, {
+ query: expect.anything(),
+ excludeFields: null,
+ includeFields: null,
+ options: {
+ prefetchGroupProducts: false,
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: false,
+ setConfigurableProductOptions: false,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ })
+ })
+})
diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts
new file mode 100644
index 000000000..752de2cc1
--- /dev/null
+++ b/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts
@@ -0,0 +1,56 @@
+import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct';
+import setGroupedProduct from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setGroupedProduct';
+import setProductLink from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink';
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ error: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/store', () => ({}));
+jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({
+ ProductService: {
+ getProducts: jest.fn(),
+ getProductRenderList: jest.fn(),
+ getProductByKey: jest.fn()
+ }
+}));
+jest.mock('@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink', () => jest.fn());
+
+describe('setGroupedProduct helper', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items: [] }));
+ })
+ it('should not fire ProductService.getProducts if it is not grouped product', async () => {
+ const bundleProduct = createBundleProduct()
+
+ setGroupedProduct(bundleProduct)
+
+ expect(ProductService.getProducts).toHaveBeenCalledTimes(0)
+ })
+ it('should fire ProductService.getProducts with simple configuration', async () => {
+ const groupedProduct = createGroupProduct()
+
+ setGroupedProduct(groupedProduct)
+
+ expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, {
+ query: expect.anything(),
+ excludeFields: null,
+ includeFields: null,
+ options: {
+ prefetchGroupProducts: false,
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: false,
+ setConfigurableProductOptions: false,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false
+ }
+ })
+ })
+})
diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts
new file mode 100644
index 000000000..0dd2cff21
--- /dev/null
+++ b/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts
@@ -0,0 +1,44 @@
+import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct';
+import setProductLink from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink';
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ error: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/store', () => ({}));
+
+describe('setProductLink helper', () => {
+ it('should add product if associatedProduct exist for bundle link', async () => {
+ const bundleProduct = createBundleProduct()
+ const productLink = bundleProduct.bundle_options[0].product_links[0]
+ const simpleProduct = createSimpleProduct()
+
+ setProductLink(productLink, simpleProduct)
+
+ expect(productLink.product).toStrictEqual(simpleProduct)
+ expect(productLink.product.qty).toBe(1)
+ })
+ it('should add product if associatedProduct exist for group link', async () => {
+ const groupProduct = createGroupProduct()
+ const productLink = groupProduct.product_links[0]
+ const simpleProduct = createSimpleProduct()
+
+ setProductLink(productLink, simpleProduct)
+
+ expect(productLink.product).toStrictEqual(simpleProduct)
+ expect(productLink.product.qty).toBe(1)
+ })
+ it('should not add product if associatedProduct doesn\'t exist', async () => {
+ const groupProduct = createGroupProduct()
+ const productLink = groupProduct.product_links[0]
+
+ setProductLink(productLink, null)
+
+ expect(productLink.product).toBe(undefined)
+ })
+})
diff --git a/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts b/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts
new file mode 100644
index 000000000..f9aad29f2
--- /dev/null
+++ b/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts
@@ -0,0 +1,187 @@
+import { DataResolver } from '@vue-storefront/core/data-resolver/types/DataResolver';
+import productActions from '@vue-storefront/core/modules/catalog/store/product/actions';
+import config from 'config';
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import { registerProductsMapping, setRequestCacheTags } from '@vue-storefront/core/modules/catalog/helpers'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
+ registerProductsMapping: jest.fn(),
+ setRequestCacheTags: jest.fn()
+}))
+
+jest.mock('@vue-storefront/core/store', () => ({
+ dispatch: jest.fn(),
+ state: {}
+}));
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ log: jest.fn(() => () => {}),
+ debug: jest.fn(() => () => {}),
+ warn: jest.fn(() => () => {}),
+ error: jest.fn(() => () => {}),
+ info: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({
+ $emit: jest.fn()
+}));
+jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({
+ ProductService: {
+ getProducts: jest.fn(),
+ getProductRenderList: jest.fn(),
+ getProductByKey: jest.fn()
+ }
+}));
+jest.mock('config', () => ({}));
+jest.mock('@vue-storefront/core/modules/catalog/events', () => ({
+ checkParentRedirection: (str) => jest.fn()
+}))
+
+describe('product/findProducts action', () => {
+ let contextMock
+ let items
+ beforeEach(() => {
+ jest.clearAllMocks()
+ contextMock = {
+ dispatch: jest.fn(() => ({}))
+ }
+ items = [{ url_path: 'dsada', sku: 'dsad' }]
+ config.cart = {
+ setConfigurableProductOptions: true
+ }
+ config.products = {
+ filterUnavailableVariants: false
+ }
+ ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items }));
+ })
+ it('should trigger ProductService.getProducts with default values', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock)
+
+ await wrapper(productActions)
+
+ expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, {
+ query: undefined,
+ start: 0,
+ size: 50,
+ sort: '',
+ excludeFields: null,
+ includeFields: null,
+ configuration: null,
+ options: {
+ prefetchGroupProducts: true,
+ setProductErrors: false,
+ fallbackToDefaultWhenNoAvailable: true,
+ assignProductConfiguration: false,
+ separateSelectedVariant: false,
+ setConfigurableProductOptions: true,
+ filterUnavailableVariants: false
+ }
+ })
+ })
+
+ it('should trigger ProductService.getProducts with provided values', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock, {
+ query: { test: 'test' },
+ start: 123,
+ size: 1221,
+ sort: 'test',
+ excludeFields: ['test'],
+ includeFields: ['test'],
+ configuration: { test: 'test' },
+ populateRequestCacheTags: true,
+ options: {
+ populateRequestCacheTags: true,
+ prefetchGroupProducts: false,
+ setProductErrors: true,
+ fallbackToDefaultWhenNoAvailable: false,
+ assignProductConfiguration: true,
+ separateSelectedVariant: true,
+ setConfigurableProductOptions: false,
+ filterUnavailableVariants: false
+ }
+ })
+
+ await wrapper(productActions)
+
+ expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, {
+ query: { test: 'test' },
+ start: 123,
+ size: 1221,
+ sort: 'test',
+ excludeFields: ['test'],
+ includeFields: ['test'],
+ configuration: { test: 'test' },
+ options: {
+ prefetchGroupProducts: false,
+ setProductErrors: true,
+ fallbackToDefaultWhenNoAvailable: false,
+ assignProductConfiguration: true,
+ separateSelectedVariant: true,
+ setConfigurableProductOptions: false,
+ filterUnavailableVariants: false
+ }
+ })
+ })
+ it('should register mapping for products returned from ProductService.getProducts', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock)
+
+ await wrapper(productActions)
+
+ expect(registerProductsMapping).toHaveBeenNthCalledWith(1, contextMock, items)
+ })
+ it('should not set cache tags if populateRequestCacheTags or options.populateRequestCacheTags is false', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock)
+
+ await wrapper(productActions)
+
+ expect(setRequestCacheTags).toHaveBeenCalledTimes(0)
+ })
+ it('should set cache tags if populateRequestCacheTags is true', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock, { populateRequestCacheTags: true })
+
+ await wrapper(productActions)
+
+ expect(setRequestCacheTags).toHaveBeenNthCalledWith(1, { products: items })
+ })
+ it('should set cache tags if options.populateRequestCacheTags is true', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock, { options: { populateRequestCacheTags: true } })
+
+ await wrapper(productActions)
+
+ expect(setRequestCacheTags).toHaveBeenNthCalledWith(1, { products: items })
+ })
+ it('should mutatate prices by triggering tax/calculateTaxes', async () => {
+ const wrapper = (actions: any) => actions.findProducts(contextMock)
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'tax/calculateTaxes', { products: items }, { root: true })
+ })
+ it('should return items and rest response data as one object', async () => {
+ ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({
+ items,
+ perPage: 1,
+ start: 2,
+ total: 3,
+ aggregations: [],
+ attributeMetadata: []
+ }));
+
+ const wrapper = (actions: any) => actions.findProducts(contextMock)
+
+ const result: DataResolver.ProductsListResponse = await wrapper(productActions)
+
+ expect(result).toStrictEqual({
+ items,
+ perPage: 1,
+ start: 2,
+ total: 3,
+ aggregations: [],
+ attributeMetadata: []
+ })
+ })
+})
diff --git a/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts b/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts
new file mode 100644
index 000000000..7d9b9e8ab
--- /dev/null
+++ b/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts
@@ -0,0 +1,70 @@
+import productActions from '@vue-storefront/core/modules/catalog/store/product/actions';
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/core/store', () => ({
+ dispatch: jest.fn()
+}));
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ log: jest.fn(() => () => {}),
+ debug: jest.fn(() => () => {}),
+ warn: jest.fn(() => () => {}),
+ error: jest.fn(() => () => {}),
+ info: jest.fn(() => () => {})
+ }
+}));
+jest.mock('config', () => ({}));
+jest.mock('@vue-storefront/core/modules/catalog/events', () => ({
+ checkParentRedirection: (str) => jest.fn()
+}))
+
+describe('product/getProductVariant action', () => {
+ let contextMock
+ beforeEach(() => {
+ jest.clearAllMocks()
+ contextMock = {
+ dispatch: jest.fn(() => ({ items: ['test'] })),
+ commit: jest.fn(() => ({}))
+ }
+ })
+ it('should throw error if no arguments is provided', async () => {
+ const wrapper = (actions: any) => actions.getProductVariant(contextMock)
+ await expect(wrapper(productActions)).rejects.toThrow(expect.anything())
+ })
+ it('should throw error if product doesn\'t have parentSku', async () => {
+ const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { sku: 'sku' } })
+ await expect(wrapper(productActions)).rejects.toThrow(expect.anything())
+ })
+
+ it('should dispatch findProducts', async () => {
+ const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { parentSku: 'sku' } })
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', {
+ query: expect.anything(),
+ size: 1,
+ configuration: undefined,
+ options: {
+ fallbackToDefaultWhenNoAvailable: false,
+ setProductErrors: true,
+ separateSelectedVariant: true
+ }
+ })
+ })
+
+ it('should return options and product_option beside variant data', async () => {
+ contextMock = {
+ dispatch: jest.fn(() => ({ items: [{ sku: 'sku', options: ['test'], product_option: ['test2'] }] }))
+ }
+ const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { parentSku: 'sku' } })
+
+ const result = await wrapper(productActions)
+
+ expect(result.options).toStrictEqual(['test'])
+ expect(result.product_option).toStrictEqual(['test2'])
+ })
+})
diff --git a/core/modules/catalog/test/unit/store/product/actions/list.spec.ts b/core/modules/catalog/test/unit/store/product/actions/list.spec.ts
new file mode 100644
index 000000000..f9c9dd1d2
--- /dev/null
+++ b/core/modules/catalog/test/unit/store/product/actions/list.spec.ts
@@ -0,0 +1,137 @@
+import productActions from '@vue-storefront/core/modules/catalog/store/product/actions';
+import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
+import * as mutationTypes from '@vue-storefront/core/modules/catalog/store/product/mutation-types'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+
+jest.mock('@vue-storefront/core/store', () => ({
+ dispatch: jest.fn(),
+ state: {}
+}));
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ log: jest.fn(() => () => {}),
+ debug: jest.fn(() => () => {}),
+ warn: jest.fn(() => () => {}),
+ error: jest.fn(() => () => {}),
+ info: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({
+ $emit: jest.fn()
+}));
+jest.mock('config', () => ({}));
+jest.mock('@vue-storefront/core/modules/catalog/events', () => ({
+ checkParentRedirection: (str) => jest.fn()
+}))
+
+describe('product/list action', () => {
+ it('should dispatch findProducts with default values for list', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock)
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', {
+ query: undefined,
+ start: 0,
+ size: 50,
+ sort: '',
+ excludeFields: null,
+ includeFields: null,
+ configuration: null,
+ options: {
+ populateRequestCacheTags: true,
+ prefetchGroupProducts: true
+ }
+ })
+ })
+
+ it('should dispatch findProducts with provided values for list', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock, {
+ query: { test: 'test' },
+ start: 1,
+ size: 10,
+ sort: 'final_price',
+ excludeFields: [],
+ includeFields: [],
+ configuration: { test: 'test' },
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ })
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', {
+ query: { test: 'test' },
+ start: 1,
+ size: 10,
+ sort: 'final_price',
+ excludeFields: [],
+ includeFields: [],
+ configuration: { test: 'test' },
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
+ })
+ })
+
+ it('should emit "product-after-list" event', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock)
+
+ await wrapper(productActions)
+
+ expect(EventBus.$emit).toHaveBeenCalledWith('product-after-list', expect.anything())
+ })
+
+ it('should not update state by deafult', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock)
+
+ await wrapper(productActions)
+
+ expect(contextMock.commit).toHaveBeenCalledTimes(0)
+ })
+
+ it('should not append state by deafult if update store', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock, { updateState: true })
+
+ await wrapper(productActions)
+
+ expect(contextMock.commit).toHaveBeenCalledWith(mutationTypes.PRODUCT_SET_PAGED_PRODUCTS, { items: ['test'] })
+ })
+
+ it('should append state', async () => {
+ const contextMock = {
+ commit: jest.fn(),
+ dispatch: jest.fn(() => ({ items: ['test'] }))
+ }
+ const wrapper = (actions: any) => actions.list(contextMock, { updateState: true, append: true })
+
+ await wrapper(productActions)
+
+ expect(contextMock.commit).toHaveBeenCalledWith(mutationTypes.PRODUCT_ADD_PAGED_PRODUCTS, { items: ['test'] })
+ })
+})
diff --git a/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts b/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts
new file mode 100644
index 000000000..774840a4d
--- /dev/null
+++ b/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts
@@ -0,0 +1,82 @@
+import productActions from '@vue-storefront/core/modules/catalog/store/product/actions';
+import config from 'config';
+import * as mutationTypes from '@vue-storefront/core/modules/catalog/store/product/mutation-types'
+import { getProductConfigurationOptions } from '@vue-storefront/core/modules/catalog/helpers/productOptions'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/core/store', () => ({
+ dispatch: jest.fn(),
+ commit: jest.fn()
+}));
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ log: jest.fn(() => () => {}),
+ debug: jest.fn(() => () => {}),
+ warn: jest.fn(() => () => {}),
+ error: jest.fn(() => () => {}),
+ info: jest.fn(() => () => {})
+ }
+}));
+jest.mock('config', () => ({}));
+jest.mock('@vue-storefront/core/modules/catalog/helpers/productOptions', () => ({
+ getProductConfigurationOptions: jest.fn()
+}));
+jest.mock('@vue-storefront/core/modules/catalog/events', () => ({
+ checkParentRedirection: (str) => jest.fn()
+}))
+
+describe('product/setCurrent action', () => {
+ let contextMock
+ let product
+ beforeEach(() => {
+ jest.clearAllMocks()
+ contextMock = {
+ dispatch: jest.fn(() => ({})),
+ commit: jest.fn(() => ({})),
+ rootState: {
+ attribute: {}
+ }
+ }
+ config.products = {
+ gallery: {
+ mergeConfigurableChildren: false
+ }
+ }
+ product = {
+ sku: 'sku',
+ configuration: { color: 42 }
+ }
+ })
+ it('should return if no product provided', async () => {
+ const wrapper = (actions: any) => actions.setCurrent(contextMock)
+ await wrapper(productActions)
+ expect(contextMock.dispatch).toBeCalledTimes(0)
+ expect(contextMock.commit).toBeCalledTimes(0)
+ })
+ it('should commit product data and configuration', async () => {
+ ;(getProductConfigurationOptions as jest.Mock).mockImplementation(() => ({ color: [{ attribute_code: 'color', id: '42', label: 'Green' }] }));
+ const wrapper = (actions: any) => actions.setCurrent(contextMock, product)
+ await wrapper(productActions)
+ expect(contextMock.commit).toHaveBeenNthCalledWith(1, mutationTypes.PRODUCT_SET_CURRENT_OPTIONS, { color: [{ attribute_code: 'color', id: '42', label: 'Green' }] })
+ expect(contextMock.commit).toHaveBeenNthCalledWith(2, mutationTypes.PRODUCT_SET_CURRENT_CONFIGURATION, { color: 42 })
+ expect(contextMock.commit).toHaveBeenNthCalledWith(3, mutationTypes.PRODUCT_SET_CURRENT, { sku: 'sku' })
+ })
+ it('should call setProductGallery if mergeConfigurableChildren is set false', async () => {
+ const wrapper = (actions: any) => actions.setCurrent(contextMock, product)
+ await wrapper(productActions)
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'setProductGallery', { product: { sku: 'sku' } })
+ })
+ it('should not call setProductGallery if mergeConfigurableChildren is set true', async () => {
+ config.products = {
+ gallery: {
+ mergeConfigurableChildren: true
+ }
+ }
+ const wrapper = (actions: any) => actions.setCurrent(contextMock, product)
+ await wrapper(productActions)
+ expect(contextMock.dispatch).toHaveBeenCalledTimes(0)
+ })
+})
diff --git a/core/modules/catalog/test/unit/store/product/actions/single.spec.ts b/core/modules/catalog/test/unit/store/product/actions/single.spec.ts
new file mode 100644
index 000000000..468ad22ea
--- /dev/null
+++ b/core/modules/catalog/test/unit/store/product/actions/single.spec.ts
@@ -0,0 +1,98 @@
+import productActions from '@vue-storefront/core/modules/catalog/store/product/actions';
+import config from 'config';
+import { ProductService } from '@vue-storefront/core/data-resolver/ProductService'
+import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
+
+jest.mock('@vue-storefront/core/helpers', () => ({
+ once: (str) => jest.fn()
+}))
+jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({
+ registerProductsMapping: jest.fn(),
+ setRequestCacheTags: jest.fn()
+}))
+
+jest.mock('@vue-storefront/core/store', () => ({
+ dispatch: jest.fn(),
+ state: {}
+}));
+jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
+jest.mock('@vue-storefront/core/lib/logger', () => ({
+ Logger: {
+ log: jest.fn(() => () => {}),
+ debug: jest.fn(() => () => {}),
+ warn: jest.fn(() => () => {}),
+ error: jest.fn(() => () => {}),
+ info: jest.fn(() => () => {})
+ }
+}));
+jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({
+ $emit: jest.fn(),
+ $emitFilter: jest.fn()
+}));
+jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({
+ ProductService: {
+ getProducts: jest.fn(),
+ getProductRenderList: jest.fn(),
+ getProductByKey: jest.fn()
+ }
+}));
+jest.mock('config', () => ({}));
+jest.mock('@vue-storefront/core/modules/catalog/events', () => ({
+ checkParentRedirection: (str) => jest.fn()
+}))
+
+describe('product/single action', () => {
+ let contextMock
+ let product
+ let options
+ beforeEach(() => {
+ jest.clearAllMocks()
+ contextMock = {
+ dispatch: jest.fn(() => ({}))
+ }
+ options = { sku: 'sku' }
+ product = [{ url_path: 'dsada', sku: 'dsad' }]
+ config.cart = {
+ setConfigurableProductOptions: true
+ }
+ config.products = {
+ filterUnavailableVariants: false
+ }
+ ;(ProductService.getProductByKey as jest.Mock).mockImplementation(async () => product);
+ })
+ it('should throw error if there is no option value based on key', async () => {
+ const wrapper = (actions: any) => actions.single(contextMock)
+
+ await expect(wrapper(productActions)).rejects.toThrow('Please provide the search key sku for product/single action!')
+ })
+ it('should trigger ProductService.getProductByKey with default values', async () => {
+ const wrapper = (actions: any) => actions.single(contextMock, { options })
+ await wrapper(productActions)
+ expect(ProductService.getProductByKey).toHaveBeenCalledWith({
+ options: { sku: 'sku' },
+ key: 'sku',
+ skipCache: false
+ })
+ })
+ it('should mutatate prices by triggering tax/calculateTaxes', async () => {
+ const wrapper = (actions: any) => actions.single(contextMock, { options })
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'tax/calculateTaxes', { products: [product] }, { root: true })
+ })
+ it('should set current product', async () => {
+ const wrapper = (actions: any) => actions.single(contextMock, { options, setCurrentProduct: true })
+
+ await wrapper(productActions)
+
+ expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'setCurrent', product)
+ })
+ it('should emit "product-after-single" event', async () => {
+ const wrapper = (actions: any) => actions.single(contextMock, { options })
+
+ await wrapper(productActions)
+
+ expect(EventBus.$emitFilter).toHaveBeenCalledWith('product-after-single', expect.anything())
+ })
+})
diff --git a/core/modules/catalog/types/Attribute.ts b/core/modules/catalog/types/Attribute.ts
index 0a4851ecc..85baf9711 100644
--- a/core/modules/catalog/types/Attribute.ts
+++ b/core/modules/catalog/types/Attribute.ts
@@ -2,3 +2,22 @@ export default interface Attribute {
attribute_code?: string,
attribute_id?: number | string
}
+
+export interface AttributesMetadata {
+ is_visible_on_front: string,
+ is_visible: boolean,
+ default_frontend_label: string,
+ attribute_id: number,
+ entity_type_id: string,
+ id: number,
+ frontend_input: string,
+ is_user_defined: boolean,
+ is_comparable: string,
+ attribute_code: string,
+ options: AttributesMetadataOptions[]
+}
+
+export interface AttributesMetadataOptions {
+ label: string,
+ value: string
+}
diff --git a/core/modules/catalog/types/BundleOption.ts b/core/modules/catalog/types/BundleOption.ts
new file mode 100644
index 000000000..b2d9b3809
--- /dev/null
+++ b/core/modules/catalog/types/BundleOption.ts
@@ -0,0 +1,30 @@
+import Product from '@vue-storefront/core/modules/catalog/types/Product';
+
+export interface BundleOption {
+ option_id: number,
+ title: string,
+ required: boolean,
+ type: string,
+ position: number,
+ sku: string,
+ product_links: BundleOptionsProductLink[]
+}
+
+export interface BundleOptionsProductLink {
+ id: string | number,
+ sku: string,
+ option_id: number,
+ qty: number,
+ position: number,
+ is_default: boolean,
+ price?: number,
+ price_type?: number,
+ can_change_quantity: number,
+ product?: Product
+}
+
+export interface SelectedBundleOption {
+ option_id: number,
+ option_qty: number,
+ option_selections: number[]
+}
diff --git a/core/modules/catalog/types/ConfigurableOption.ts b/core/modules/catalog/types/ConfigurableOption.ts
new file mode 100644
index 000000000..854464d43
--- /dev/null
+++ b/core/modules/catalog/types/ConfigurableOption.ts
@@ -0,0 +1,6 @@
+export interface ConfigurableItemOption {
+ label: string,
+ option_id: string,
+ option_value: string,
+ value: string
+}
diff --git a/core/modules/catalog/types/Product.ts b/core/modules/catalog/types/Product.ts
index 7bb815d3c..5f53bf254 100644
--- a/core/modules/catalog/types/Product.ts
+++ b/core/modules/catalog/types/Product.ts
@@ -1,18 +1,25 @@
+import { ProductOption } from './ProductConfiguration';
+import { ConfigurableItemOption } from './ConfigurableOption';
+import { BundleOption, SelectedBundleOption } from './BundleOption';
+import { AttributesMetadata } from './Attribute';
import { CustomOption } from './CustomOption';
export default interface Product {
+ attributes_metadata?: AttributesMetadata[],
+ bundle_options?: BundleOption[],
category: Record[],
- category_ids: string[],
- color: string,
+ category_ids: string[] | number[],
+ color?: string,
color_options?: number[] | string[],
- configurable_children: Record[],
- configurable_options: Record[],
+ configurable_children?: Record[],
+ configurable_options?: ProductOption[],
custom_attributes?: any,
+ custom_options?: CustomOption[],
description: string,
errors?: Record,
- final_price: number,
- finalPrice: number,
- gift_message_available: string,
+ final_price?: number,
+ finalPrice?: number,
+ gift_message_available?: string,
has_options?: string,
id?: number | string,
image: string,
@@ -34,13 +41,13 @@ export default interface Product {
priceInclTax?: number,
price_tax?: number,
priceTax?: number,
- product_links?: Record[],
- product_option?: Record,
+ product_links?: ProductLink[],
+ product_option?: ProductOptions,
regular_price: number,
required_options?: string,
sale?: string,
sgn?: string,
- size: string,
+ size?: string,
size_options?: number[] | string[],
sku: string,
slug?: string,
@@ -59,8 +66,31 @@ export default interface Product {
tsk?: number,
type_id: string,
url_key: string,
+ url_path?: string,
visibility: number,
_score?: number,
qty?: number,
- custom_options?: CustomOption
+ tier_prices?: any[],
+ links?: any,
+ parentId?: number | string
+}
+
+export interface ProductLink {
+ sku: string,
+ link_type: string,
+ linked_product_sku: string,
+ linked_product_type: string,
+ position: number,
+ extension_attributes: {
+ qty: number
+ },
+ product?: Product
+}
+
+export interface ProductOptions {
+ extension_attributes: {
+ custom_options: any[],
+ configurable_item_options: ConfigurableItemOption[],
+ bundle_options: SelectedBundleOption[]
+ }
}
diff --git a/core/modules/catalog/types/ProductConfiguration.ts b/core/modules/catalog/types/ProductConfiguration.ts
index b5965b54b..e820335f2 100644
--- a/core/modules/catalog/types/ProductConfiguration.ts
+++ b/core/modules/catalog/types/ProductConfiguration.ts
@@ -1,7 +1,8 @@
export interface ProductOption {
attribute_code?: string,
id: number | string,
- label: string
+ label: string,
+ values?: any[]
}
export interface ProductConfiguration {
diff --git a/core/modules/checkout/components/Shipping.ts b/core/modules/checkout/components/Shipping.ts
index d34af6a37..a8a93097e 100644
--- a/core/modules/checkout/components/Shipping.ts
+++ b/core/modules/checkout/components/Shipping.ts
@@ -121,7 +121,7 @@ export const Shipping = {
},
useMyAddress () {
if (this.shipToMyAddress) {
- this.shipping = {
+ this.$set(this, 'shipping', {
firstName: this.myAddressDetails.firstname,
lastName: this.myAddressDetails.lastname,
country: this.myAddressDetails.country_id,
@@ -133,9 +133,9 @@ export const Shipping = {
phoneNumber: this.myAddressDetails.telephone,
shippingMethod: this.checkoutShippingDetails.shippingMethod,
shippingCarrier: this.checkoutShippingDetails.shippingCarrier
- }
+ })
} else {
- this.shipping = this.checkoutShippingDetails
+ this.$set(this, 'shipping', this.checkoutShippingDetails)
}
this.changeCountry()
},
diff --git a/core/modules/checkout/index.ts b/core/modules/checkout/index.ts
index 18047616b..899e547fa 100644
--- a/core/modules/checkout/index.ts
+++ b/core/modules/checkout/index.ts
@@ -2,6 +2,7 @@ import { StorefrontModule } from '@vue-storefront/core/lib/modules'
import { checkoutModule } from './store/checkout'
import { paymentModule } from './store/payment'
import { shippingModule } from './store/shipping'
+import { Logger } from '@vue-storefront/core/lib/logger'
import * as types from './store/checkout/mutation-types'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
@@ -19,7 +20,7 @@ export const CheckoutModule: StorefrontModule = function ({ store }) {
type.endsWith(types.CHECKOUT_SAVE_PERSONAL_DETAILS)
) {
StorageManager.get('checkout').setItem('personal-details', state.checkout.personalDetails).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
@@ -27,7 +28,7 @@ export const CheckoutModule: StorefrontModule = function ({ store }) {
type.endsWith(types.CHECKOUT_SAVE_SHIPPING_DETAILS) || type.endsWith(types.CHECKOUT_UPDATE_PROP_VALUE)
) {
StorageManager.get('checkout').setItem('shipping-details', state.checkout.shippingDetails).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
@@ -35,7 +36,7 @@ export const CheckoutModule: StorefrontModule = function ({ store }) {
type.endsWith(types.CHECKOUT_SAVE_PAYMENT_DETAILS)
) {
StorageManager.get('checkout').setItem('payment-details', state.checkout.paymentDetails).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
})
diff --git a/core/modules/cms/helpers/createHierarchyLoadQuery.ts b/core/modules/cms/helpers/createHierarchyLoadQuery.ts
index af47fa051..671072069 100644
--- a/core/modules/cms/helpers/createHierarchyLoadQuery.ts
+++ b/core/modules/cms/helpers/createHierarchyLoadQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createHierarchyLoadQuery = ({ id }): SearchQuery => {
let query = new SearchQuery()
diff --git a/core/modules/cms/helpers/createLoadingBlockQuery.ts b/core/modules/cms/helpers/createLoadingBlockQuery.ts
index 02ea841a9..8cbb30530 100644
--- a/core/modules/cms/helpers/createLoadingBlockQuery.ts
+++ b/core/modules/cms/helpers/createLoadingBlockQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createLoadingBlockQuery = ({ filterField, filterValues }): SearchQuery => {
let query = new SearchQuery()
diff --git a/core/modules/cms/helpers/createPageLoadingQuery.ts b/core/modules/cms/helpers/createPageLoadingQuery.ts
index 3c1a03f81..b93f514fe 100644
--- a/core/modules/cms/helpers/createPageLoadingQuery.ts
+++ b/core/modules/cms/helpers/createPageLoadingQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createPageLoadingQuery = ({ filterField, filterValues }): SearchQuery => {
let query = new SearchQuery()
diff --git a/core/modules/cms/helpers/createSingleBlockQuery.ts b/core/modules/cms/helpers/createSingleBlockQuery.ts
index 3f975105a..1277a5d76 100644
--- a/core/modules/cms/helpers/createSingleBlockQuery.ts
+++ b/core/modules/cms/helpers/createSingleBlockQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createSingleBlockQuery = ({ key, value }): SearchQuery => {
let query = new SearchQuery()
diff --git a/core/modules/cms/helpers/createSinglePageLoadQuery.ts b/core/modules/cms/helpers/createSinglePageLoadQuery.ts
index 79463bc57..ba7db2e08 100644
--- a/core/modules/cms/helpers/createSinglePageLoadQuery.ts
+++ b/core/modules/cms/helpers/createSinglePageLoadQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createSinglePageLoadQuery = ({ key, value }): SearchQuery => {
let query = new SearchQuery()
diff --git a/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts b/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts
index 09b7495cc..b21c9494a 100644
--- a/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts
+++ b/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts
@@ -8,7 +8,7 @@ describe('createHierarchyLoadQuery', () => {
expect(loadQuery).toHaveProperty('_availableFilters')
expect(loadQuery).toHaveProperty('_searchText')
- let [ appliedFilter ] = loadQuery._appliedFilters
+ let [ appliedFilter ] = loadQuery.getAppliedFilters()
expect(appliedFilter).toHaveProperty('value', { eq: filter.id })
expect(appliedFilter).toHaveProperty('attribute', 'identifier')
@@ -20,6 +20,6 @@ describe('createHierarchyLoadQuery', () => {
const filter = { id: null }
let hierarchyLoadQuery = createHierarchyLoadQuery(filter)
- expect(hierarchyLoadQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
+ expect(hierarchyLoadQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
diff --git a/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts b/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts
index ceb4edb14..4673de015 100644
--- a/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts
+++ b/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts
@@ -5,7 +5,7 @@ describe('createLoadingBlockQuery', () => {
const filter = { filterField: 'test', filterValues: ['test1', 'test2'] }
let loadingBlockQuery = createLoadingBlockQuery(filter)
- let [ appliedFilter ] = loadingBlockQuery._appliedFilters
+ let [ appliedFilter ] = loadingBlockQuery.getAppliedFilters()
expect(appliedFilter).toHaveProperty('attribute', filter.filterField)
expect(appliedFilter).toHaveProperty('value', { like: filter.filterValues })
@@ -15,6 +15,6 @@ describe('createLoadingBlockQuery', () => {
const filter = { filterField: 'test', filterValues: undefined }
let loadingBlockQuery = createLoadingBlockQuery(filter)
- expect(loadingBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
+ expect(loadingBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
diff --git a/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts b/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts
index 290e9fb96..471fe1ac5 100644
--- a/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts
+++ b/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts
@@ -5,7 +5,7 @@ describe('createPageLoadingQuery', () => {
const filter = { filterField: 'test', filterValues: ['test1', 'test2'] }
let pageLoadingQuery = createPageLoadingQuery(filter)
- let [ appliedFilter ] = pageLoadingQuery._appliedFilters
+ let [ appliedFilter ] = pageLoadingQuery.getAppliedFilters()
expect(appliedFilter).toHaveProperty('attribute', filter.filterField)
expect(appliedFilter).toHaveProperty('value', { like: filter.filterValues })
@@ -15,6 +15,6 @@ describe('createPageLoadingQuery', () => {
const filter = { filterField: 'test', filterValues: undefined }
let pageLoadingQuery = createPageLoadingQuery(filter)
- expect(pageLoadingQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
+ expect(pageLoadingQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
diff --git a/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts b/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts
index 8409621e2..feca385fa 100644
--- a/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts
+++ b/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts
@@ -5,7 +5,7 @@ describe('createSingleBlockLoadQuery should', () => {
const argsMock = { key: 'test', value: ['test1', 'test2'] }
let mockSingleBlockQuery = createSingleBlockQuery(argsMock)
- let [ appliedFilter ] = mockSingleBlockQuery._appliedFilters
+ let [ appliedFilter ] = mockSingleBlockQuery.getAppliedFilters()
expect(appliedFilter).toHaveProperty('attribute', argsMock.key)
expect(appliedFilter).toHaveProperty('value', { like: argsMock.value })
@@ -15,6 +15,6 @@ describe('createSingleBlockLoadQuery should', () => {
const argsMock = { key: 'test', value: undefined }
let mockSingleBlockQuery = createSingleBlockQuery(argsMock)
- expect(mockSingleBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
+ expect(mockSingleBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
diff --git a/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts b/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts
index ab518b7d9..343b3d4b4 100644
--- a/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts
+++ b/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts
@@ -5,7 +5,7 @@ describe('createSinglePageLoadQuery should', () => {
const filter = { key: 'test', value: ['test1', 'test2'] }
let singlePageMockQuery = createSinglePageLoadQuery(filter)
- let [ appliedFilter ] = singlePageMockQuery._appliedFilters
+ let [ appliedFilter ] = singlePageMockQuery.getAppliedFilters()
expect(appliedFilter).toHaveProperty('attribute', filter.key)
expect(appliedFilter).toHaveProperty('value', { like: filter.value })
@@ -15,6 +15,6 @@ describe('createSinglePageLoadQuery should', () => {
const filter = { key: 'test', value: undefined }
let singlePageMockQuery = createSinglePageLoadQuery(filter)
- expect(singlePageMockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _searchText: '' })
+ expect(singlePageMockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' })
})
})
diff --git a/core/modules/compare/components/Compare.ts b/core/modules/compare/components/Compare.ts
index c2782fcc8..1a231eda8 100644
--- a/core/modules/compare/components/Compare.ts
+++ b/core/modules/compare/components/Compare.ts
@@ -1,6 +1,7 @@
import { mapGetters } from 'vuex'
import Product from '@vue-storefront/core/modules/catalog/types/Product'
import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin'
+import config from 'config'
export const Compare = {
name: 'Compare',
@@ -12,10 +13,12 @@ export const Compare = {
})
},
created () {
- this.$store.dispatch('attribute/list', {
- filterValues: [],
- filterField: 'is_user_defined'
- })
+ if (!config.entities.attribute.loadByAttributeMetadata) {
+ this.$store.dispatch('attribute/list', {
+ filterValues: [],
+ filterField: 'is_user_defined'
+ })
+ }
},
methods: {
removeFromCompare (product: Product) {
diff --git a/core/modules/compare/store/actions.ts b/core/modules/compare/store/actions.ts
index 37f2718b8..d930385f6 100644
--- a/core/modules/compare/store/actions.ts
+++ b/core/modules/compare/store/actions.ts
@@ -4,16 +4,26 @@ import RootState from '@vue-storefront/core/types/RootState'
import CompareState from '../types/CompareState'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
import { Logger } from '@vue-storefront/core/lib/logger'
+import config from 'config'
const actions: ActionTree = {
async load ({ commit, getters, dispatch }, force: boolean = false) {
- if (!force && getters.isCompareLoaded) return
- commit(types.SET_COMPARE_LOADED)
- const storedItems = await dispatch('fetchCurrentCompare')
+ if (force || !getters.isCompareLoaded) {
+ commit(types.SET_COMPARE_LOADED)
+ const storedItems = await dispatch('fetchCurrentCompare')
- if (storedItems) {
- commit(types.COMPARE_LOAD_COMPARE, storedItems)
- Logger.info('Compare state loaded from browser cache: ', 'cache', storedItems)()
+ if (storedItems) {
+ commit(types.COMPARE_LOAD_COMPARE, storedItems)
+ Logger.info('Compare state loaded from browser cache: ', 'cache', storedItems)()
+ }
+
+ if (config.entities.attribute.loadByAttributeMetadata) {
+ dispatch(
+ 'attribute/loadProductAttributes',
+ { products: getters.getCompareItems, merge: true },
+ { root: true }
+ )
+ }
}
},
async fetchCurrentCompare () {
diff --git a/core/modules/compare/test/unit/components/Compare.spec.ts b/core/modules/compare/test/unit/components/Compare.spec.ts
index e31b56e62..6053af315 100644
--- a/core/modules/compare/test/unit/components/Compare.spec.ts
+++ b/core/modules/compare/test/unit/components/Compare.spec.ts
@@ -1,10 +1,12 @@
import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils';
import { Compare } from '../../../components/Compare'
+import config from 'config'
jest.mock('@vue-storefront/core/helpers', () => ({
once: jest.fn()
}));
jest.mock('@vue-storefront/core/modules/compare/mixins/compareMountedMixin', () => ({}))
+jest.mock('config', () => ({}));
describe('Compare', () => {
it('Compare dispatches attribute list action on created', () => {
@@ -19,6 +21,12 @@ describe('Compare', () => {
}
};
+ config.entities = {
+ attribute: {
+ loadByAttributeMetadata: false
+ }
+ }
+
mountMixinWithStore(Compare, storeMock);
expect(storeMock.modules.attribute.actions.list).toBeCalledWith(expect.anything(), {
diff --git a/core/modules/initial-resources/clientResourcesLoader.ts b/core/modules/initial-resources/clientResourcesLoader.ts
new file mode 100644
index 000000000..99a301266
--- /dev/null
+++ b/core/modules/initial-resources/clientResourcesLoader.ts
@@ -0,0 +1,47 @@
+import { addRegexpListToConfig, createRegexpMatcher, flatToRegexpList } from './helpers';
+import config from 'config'
+
+const initialResources = addRegexpListToConfig(config)
+
+const prefetchRegexps = flatToRegexpList(
+ initialResources.filter(filterConfig => filterConfig.rel !== 'preload' && filterConfig.onload)
+)
+const preloadRegexps = flatToRegexpList(
+ initialResources.filter(filterConfig => filterConfig.rel === 'preload' && filterConfig.onload)
+)
+
+/**
+ * Build links that need to be load and add them on the end of head.
+ */
+const addLinksFromManifest = (manifestFilesUrls: string[], regexps: RegExp[], publicPath: string) => {
+ manifestFilesUrls
+ .filter((file) => createRegexpMatcher(file)(regexps))
+ .forEach((file) => {
+ const link = document.createElement('link')
+ link.href = publicPath + file
+ link.rel = 'prefetch'
+
+ document.head.appendChild(link)
+ })
+}
+
+const getManifest = async () => {
+ let ssrManifest = null
+ try {
+ ssrManifest = (await (await fetch('/dist/vue-ssr-client-manifest.json')).json()) || null
+ } catch (_) {
+ ssrManifest = null
+ }
+ return ssrManifest
+}
+
+/**
+ * Add links from manifest to head element.
+ */
+export default async () => {
+ const ssrManifest = await getManifest()
+ if (!ssrManifest) return
+
+ addLinksFromManifest(ssrManifest.async, prefetchRegexps, ssrManifest.publicPath)
+ addLinksFromManifest(ssrManifest.initial, preloadRegexps, ssrManifest.publicPath)
+}
diff --git a/core/modules/initial-resources/helpers.ts b/core/modules/initial-resources/helpers.ts
new file mode 100644
index 000000000..38ab5be57
--- /dev/null
+++ b/core/modules/initial-resources/helpers.ts
@@ -0,0 +1,52 @@
+import { InitialResources } from './types';
+
+/**
+ * Creates RegExp based on type and provided filter/filname in config.
+ * There is option to create custom regexp by not providing type.
+ * @param type - type of resources
+ * @param filter - part of regex that will filter files from prefetch or preload
+ */
+const createRegexp = (type, filter = ''): RegExp => {
+ switch (type) {
+ case 'script': {
+ return new RegExp(`^${filter}(\\..+\\.|\\.)js`, 'gi')
+ }
+ case 'style': {
+ return new RegExp(`^${filter}(\\..+\\.|\\.)css`, 'gi')
+ }
+ default: {
+ return new RegExp(`${filter}`, 'gi')
+ }
+ }
+}
+
+/**
+ * Create RegExp list based on initialResources config
+ */
+const createRegexpList = ({ type, filters = [] }: InitialResources): RegExp[] => filters.map(createRegexp.bind(null, type))
+
+/**
+ * Returns function that require RegExp list. Then after second call we will get boolean that determines if file match any regexp.
+ * @param file - this is filename that will be checked
+ */
+export const createRegexpMatcher = (file: string) => (regexps: RegExp[]): boolean => regexps.some(regexp => file.match(regexp))
+
+/**
+ * Extended initialResurces config by adding to it list of RegExp.
+ */
+export const addRegexpListToConfig = (config): InitialResources[] => {
+ const initialResourcesConfig: InitialResources[] = config.initialResources || []
+
+ return initialResourcesConfig
+ .map(resourceConfig => ({
+ ...resourceConfig,
+ regexps: createRegexpList(resourceConfig)
+ }))
+}
+
+/**
+ * Returns RegExp list from extended initialResources config.
+ */
+export const flatToRegexpList = (configs: InitialResources[]) => configs
+ .map(pConfig => pConfig.regexps)
+ .reduce((acc, val) => acc.concat(val), [])
diff --git a/core/modules/initial-resources/index.ts b/core/modules/initial-resources/index.ts
new file mode 100644
index 000000000..630019f0a
--- /dev/null
+++ b/core/modules/initial-resources/index.ts
@@ -0,0 +1,8 @@
+import { isServer } from '@vue-storefront/core/helpers'
+import { StorefrontModule } from '@vue-storefront/core/lib/modules'
+import clientResourcesLoader from './clientResourcesLoader'
+
+export const InitialResourcesModule: StorefrontModule = function () {
+ if (isServer) return
+ window.addEventListener('load', clientResourcesLoader)
+}
diff --git a/core/modules/initial-resources/serverResourcesFilter.ts b/core/modules/initial-resources/serverResourcesFilter.ts
new file mode 100644
index 000000000..e0167c5aa
--- /dev/null
+++ b/core/modules/initial-resources/serverResourcesFilter.ts
@@ -0,0 +1,36 @@
+import { addRegexpListToConfig, createRegexpMatcher, flatToRegexpList } from './helpers';
+
+const config = require('config')
+const initialResources = addRegexpListToConfig(config)
+
+const prefetchRegexps = flatToRegexpList(
+ initialResources.filter(filterConfig => filterConfig.rel !== 'preload')
+)
+/**
+ * vue-ssr method that filters prefetch files based on initialResources config
+ */
+export const shouldPrefetch = (file: string) => {
+ if (prefetchRegexps.length) {
+ const checkRegexpList = createRegexpMatcher(file)
+ const matchFilter = checkRegexpList(prefetchRegexps)
+
+ return !matchFilter
+ }
+ return true
+}
+
+const preloadRegexps = flatToRegexpList(
+ initialResources.filter(filterConfig => filterConfig.rel === 'preload')
+)
+/**
+ * vue-ssr method that filters preload files based on initialResources config
+ */
+export const shouldPreload = (file: string) => {
+ if (preloadRegexps.length) {
+ const checkRegexpList = createRegexpMatcher(file)
+ const matchFilter = checkRegexpList(preloadRegexps)
+
+ return !matchFilter
+ }
+ return true
+}
diff --git a/core/modules/initial-resources/types.ts b/core/modules/initial-resources/types.ts
new file mode 100644
index 000000000..e31f3319e
--- /dev/null
+++ b/core/modules/initial-resources/types.ts
@@ -0,0 +1,7 @@
+export interface InitialResources {
+ filters: string[],
+ regexps?: RegExp[],
+ type?: 'script' | 'style',
+ onload?: boolean,
+ rel?: 'prefetch' | 'preload'
+}
diff --git a/core/modules/mailer/store/index.ts b/core/modules/mailer/store/index.ts
index aad78a607..6f505af09 100644
--- a/core/modules/mailer/store/index.ts
+++ b/core/modules/mailer/store/index.ts
@@ -3,13 +3,14 @@ import MailItem from '../types/MailItem'
import { Module } from 'vuex'
import config from 'config'
import { processURLAddress } from '@vue-storefront/core/helpers'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
export const mailerStore: Module = {
namespaced: true,
actions: {
async sendEmail (context, letter: MailItem) {
try {
- const res = await fetch(processURLAddress(config.mailer.endpoint.token))
+ const res = await fetch(processURLAddress(getApiEndpointUrl(config.mailer.endpoint, 'token')))
const resData = await res.json()
if (resData.code === 200) {
try {
diff --git a/core/modules/order/components/UserOrders.ts b/core/modules/order/components/UserOrders.ts
index 56dfcb1f9..fdf4f38b2 100644
--- a/core/modules/order/components/UserOrders.ts
+++ b/core/modules/order/components/UserOrders.ts
@@ -19,7 +19,7 @@ export const UserOrders = {
this.$bus.$emit('notification-progress-start', this.$t('Please wait ...'))
const productsToAdd = []
for (const item of products) {
- const product = await this.$store.dispatch('product/single', { options: { sku: item.sku }, setCurrentProduct: false, selectDefaultVariant: false })
+ const product = await this.$store.dispatch('product/single', { options: { sku: item.sku } })
product.qty = item.qty_ordered
productsToAdd.push(product)
}
diff --git a/core/modules/order/components/UserSingleOrder.ts b/core/modules/order/components/UserSingleOrder.ts
index 273c36cec..07b245979 100644
--- a/core/modules/order/components/UserSingleOrder.ts
+++ b/core/modules/order/components/UserSingleOrder.ts
@@ -36,7 +36,7 @@ export const UserSingleOrder = {
this.$bus.$emit('notification-progress-start', this.$t('Please wait ...'))
const productsToAdd = []
for (const item of products) {
- const product = await this.$store.dispatch('product/single', { options: { sku: item.sku }, setCurrentProduct: false, selectDefaultVariant: false })
+ const product = await this.$store.dispatch('product/single', { options: { sku: item.sku } })
product.qty = item.qty_ordered
productsToAdd.push(product)
}
diff --git a/core/modules/order/store/actions.ts b/core/modules/order/store/actions.ts
index 4c141a717..21f304c43 100644
--- a/core/modules/order/store/actions.ts
+++ b/core/modules/order/store/actions.ts
@@ -44,7 +44,7 @@ const actions: ActionTree = {
EventBus.$emit('notification-progress-start', i18n.t('Processing order...'))
try {
- return dispatch('processOrder', { newOrder: order, currentOrderHash })
+ return await dispatch('processOrder', { newOrder: order, currentOrderHash })
} catch (error) {
dispatch('handlePlacingOrderFailed', { newOrder: order, currentOrderHash })
throw error
diff --git a/core/modules/recently-viewed/store/plugin.ts b/core/modules/recently-viewed/store/plugin.ts
index aa7cd8a95..fc6a91df7 100644
--- a/core/modules/recently-viewed/store/plugin.ts
+++ b/core/modules/recently-viewed/store/plugin.ts
@@ -7,7 +7,7 @@ export function plugin (mutation, state) {
if (type.startsWith(types.SN_RECENTLY_VIEWED)) { // check if this mutation is recently-viewed related
cacheStorage.setItem('recently-viewed', state['recently-viewed'].items).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
})
}
}
diff --git a/core/modules/review/helpers/createLoadReviewsQuery.ts b/core/modules/review/helpers/createLoadReviewsQuery.ts
index 8e127dfeb..8a1de303d 100644
--- a/core/modules/review/helpers/createLoadReviewsQuery.ts
+++ b/core/modules/review/helpers/createLoadReviewsQuery.ts
@@ -1,4 +1,4 @@
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
const createLoadReviewsQuery = ({ productId, approved }) => {
let query = new SearchQuery()
diff --git a/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts b/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts
index 73c2d2652..cce730194 100644
--- a/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts
+++ b/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts
@@ -4,7 +4,11 @@ const SearchQuery = {
applyFilter: jest.fn(() => SearchQuery)
}
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => () => SearchQuery)
+jest.mock('storefront-query-builder', () => ({
+ SearchQuery: function () {
+ return SearchQuery
+ }
+}));
describe('createLoadReviewsQuery', () => {
beforeEach(() => {
diff --git a/core/modules/review/test/unit/store/actions.spec.ts b/core/modules/review/test/unit/store/actions.spec.ts
index 9d67aa46c..af5c438da 100644
--- a/core/modules/review/test/unit/store/actions.spec.ts
+++ b/core/modules/review/test/unit/store/actions.spec.ts
@@ -3,7 +3,7 @@ import reviewActions from '../../../store/actions';
import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
import { createLoadReviewsQuery } from '@vue-storefront/core/modules/review/helpers'
import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import { ReviewsService } from '@vue-storefront/core/data-resolver'
jest.mock('@vue-storefront/core/helpers', () => ({
@@ -27,9 +27,6 @@ jest.mock('@vue-storefront/core/lib/search', () => ({
jest.mock('@vue-storefront/core/modules/review/helpers', () => ({
createLoadReviewsQuery: jest.fn()
}));
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => ({
- SearchQuery: jest.fn()
-}));
jest.mock('@vue-storefront/core/lib/sync', () => ({
TaskQueue: {
execute: jest.fn(() => Promise.resolve({ code: 200 }))
diff --git a/core/modules/url/helpers/index.ts b/core/modules/url/helpers/index.ts
index c00bc3fad..3b0562c35 100644
--- a/core/modules/url/helpers/index.ts
+++ b/core/modules/url/helpers/index.ts
@@ -102,7 +102,7 @@ export function formatProductLink (
return localizedDispatcherRoute(routeData, storeCode)
} else {
const routeData: LocalizedRoute = {
- name: product.type_id + '-product',
+ name: product.type_id + '-product', // we should use here localizedDispatcherRouteName?
params: {
parentSku: product.parentSku ? product.parentSku : product.sku,
slug: product.slug,
diff --git a/core/modules/url/helpers/transformUrl.ts b/core/modules/url/helpers/transformUrl.ts
new file mode 100644
index 000000000..8f30c7990
--- /dev/null
+++ b/core/modules/url/helpers/transformUrl.ts
@@ -0,0 +1,32 @@
+import { localizedDispatcherRouteName, currentStoreView } from '@vue-storefront/core/lib/multistore';
+
+export const transformProductUrl = (product, urlParams = {}) => {
+ const { storeCode, appendStoreCode } = currentStoreView()
+ return {
+ name: localizedDispatcherRouteName(product.type_id + '-product', storeCode, appendStoreCode),
+ params: {
+ slug: product.slug,
+ parentSku: product.parentSku || product.sku,
+ childSku: urlParams['childSku'] ? urlParams['childSku'] : product.sku
+ }
+ }
+}
+
+export const transformCategoryUrl = (category) => {
+ const { storeCode, appendStoreCode } = currentStoreView()
+ return {
+ name: localizedDispatcherRouteName('category', storeCode, appendStoreCode),
+ params: {
+ slug: category.slug
+ }
+ }
+}
+
+export const transformCmsPageUrl = (cmsPage) => {
+ return {
+ name: 'cms-page',
+ params: {
+ slug: cmsPage.identifier
+ }
+ }
+}
diff --git a/core/modules/url/store/actions.ts b/core/modules/url/store/actions.ts
index 276c70597..efbd456a1 100644
--- a/core/modules/url/store/actions.ts
+++ b/core/modules/url/store/actions.ts
@@ -1,23 +1,33 @@
+import { transformProductUrl, transformCategoryUrl, transformCmsPageUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl';
import { isServer } from '@vue-storefront/core/helpers';
import { UrlState } from '../types/UrlState'
import { ActionTree } from 'vuex';
-import * as types from './mutation-types'
// you can use this storage if you want to enable offline capabilities
import { cacheStorage } from '../'
import queryString from 'query-string'
import config from 'config'
-import SearchQuery from '@vue-storefront/core/lib/search/searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import { preProcessDynamicRoutes, normalizeUrlPath, parametrizeRouteData, getFallbackRouteData } from '../helpers'
-import { removeStoreCodeFromRoute, currentStoreView, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'
+import { removeStoreCodeFromRoute, currentStoreView, localizedDispatcherRouteName, adjustMultistoreApiUrl } from '@vue-storefront/core/lib/multistore'
import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute'
+import fetch from 'isomorphic-fetch'
+import { Logger } from '@vue-storefront/core/lib/logger'
+import { processURLAddress } from '@vue-storefront/core/helpers';
+import * as categoryMutationTypes from '@vue-storefront/core/modules/catalog-next/store/category/mutation-types'
+import * as cmsPageMutationTypes from '@vue-storefront/core/modules/cms/store/page/mutation-types'
import isEqual from 'lodash-es/isEqual'
+import * as types from './mutation-types'
import omit from 'lodash-es/omit'
+import { storeProductToCache } from '@vue-storefront/core/modules/catalog/helpers/search';
+import { prepareProducts } from '@vue-storefront/core/modules/catalog/helpers/prepare';
// it's a good practice for all actions to return Promises with effect of their execution
export const actions: ActionTree = {
// if you want to use cache in your module you can load cached data like this
- async registerMapping ({ commit }, { url, routeData }: { url: string, routeData: any}) {
- commit(types.REGISTER_MAPPING, { url, routeData })
+ async registerMapping ({ state }, { url, routeData }: { url: string, routeData: any}) {
+ if (!state.dispatcherMap[url]) {
+ state.dispatcherMap[url] = routeData
+ }
try {
await cacheStorage.setItem(normalizeUrlPath(url), routeData, null, config.seo.disableUrlRoutesPersistentCache)
} catch (err) {
@@ -55,7 +65,8 @@ export const actions: ActionTree = {
if (routeData !== null) {
return resolve(parametrizeRouteData(routeData, query, storeCodeInPath))
} else {
- dispatch('mappingFallback', { url, params: parsedQuery }).then(mappedFallback => {
+ const mappingActionName = config.urlModule.enableMapFallbackUrl ? 'mapFallbackUrl' : 'mappingFallback'
+ dispatch(mappingActionName, { url, params: parsedQuery }).then(mappedFallback => {
const routeData = getFallbackRouteData({ mappedFallback, url })
dispatch('registerMapping', { url, routeData }) // register mapping for further usage
resolve(parametrizeRouteData(routeData, query, storeCodeInPath))
@@ -64,43 +75,155 @@ export const actions: ActionTree = {
}).catch(reject)
})
},
-
/**
+ * @deprecated from 1.12
* Router mapping fallback - get the proper URL from API
* This method could be overriden in custom module to provide custom URL mapping logic
*/
async mappingFallback ({ dispatch }, { url, params }: { url: string, params: any}) {
- const { storeCode, appendStoreCode } = currentStoreView()
+ Logger.warn(`
+ Deprecated action mappingFallback - use mapFallbackUrl instead.
+ You can enable mapFallbackUrl by changing 'config.urlModule.enableMapFallbackUrl' to true
+ `)()
const productQuery = new SearchQuery()
url = (removeStoreCodeFromRoute(url.startsWith('/') ? url.slice(1) : url) as string)
productQuery.applyFilter({ key: 'url_path', value: { 'eq': url } }) // Tees category
const products = await dispatch('product/list', { query: productQuery }, { root: true })
if (products && products.items && products.items.length) {
const product = products.items[0]
- return {
- name: localizedDispatcherRouteName(product.type_id + '-product', storeCode, appendStoreCode),
- params: {
- slug: product.slug,
- parentSku: product.sku,
- childSku: params['childSku'] ? params['childSku'] : product.sku
- }
- }
+ return transformProductUrl(product, params)
} else {
const category = await dispatch('category/single', { key: 'url_path', value: url }, { root: true })
if (category !== null) {
+ return transformCategoryUrl(category)
+ }
+ }
+ },
+ /**
+ * Router mapping fallback - get the proper URL from API
+ * This method could be overriden in custom module to provide custom URL mapping logic
+ */
+ async mapFallbackUrl ({ dispatch }, { url, params }: { url: string, params: any}) {
+ url = (removeStoreCodeFromRoute(url.startsWith('/') ? url.slice(1) : url) as string)
+
+ // search for record in ES based on `url`
+ const fallbackData = await dispatch('getFallbackByUrl', { url, params })
+
+ // if there is record in ES then map data
+ if (fallbackData) {
+ const [result] = await Promise.all([
+ dispatch('transformFallback', { ...fallbackData, params }),
+ dispatch('saveFallbackData', fallbackData)
+ ])
+ return result
+ }
+
+ return {
+ name: 'page-not-found',
+ params: {
+ slug: 'page-not-found'
+ }
+ }
+ },
+ /**
+ * Search for record in ES which contains url value (check which fields it searches in vsf-api config.urlModule.map.searchedFields)
+ */
+ async getFallbackByUrl (context, { url, params }) {
+ const groupId = (config.usePriceTiers && context.rootState.user.groupId) || null
+ const groupToken = context.rootState.user.groupToken || null
+ try {
+ const requestUrl = `${adjustMultistoreApiUrl(processURLAddress(config.urlModule.map_endpoint))}`
+ let response: any = await fetch(
+ requestUrl,
+ {
+ method: 'POST',
+ mode: 'cors',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ url,
+ includeFields: null, // send `includeFields: null || undefined` to fetch all fields
+ excludeFields: [],
+ options: {
+ prefetchGroupProducts: true,
+ assignProductConfiguration: true,
+ populateRequestCacheTags: false,
+ setProductErrors: false,
+ fallbackToDefaultWhenNoAvailable: true,
+ separateSelectedVariant: false,
+ setConfigurableProductOptions: config.cart.setConfigurableProductOptions,
+ filterUnavailableVariants: config.products.filterUnavailableVariants
+ },
+ filters: { sku: params.childSku },
+ groupId,
+ groupToken
+ })
+ }
+ )
+ if (!response.ok) {
+ return null
+ }
+ response = await response.json()
+ return response
+ } catch (err) {
+ Logger.error('FetchError in request to ES: ', 'search', err)()
+ return null
+ }
+ },
+ /**
+ * Transforms data to vue-router route format
+ */
+ async transformFallback (context, { _type, _source, params }) {
+ switch (_type) {
+ case 'product': {
+ return transformProductUrl(_source, params)
+ }
+ case 'category': {
+ return transformCategoryUrl(_source)
+ }
+ case 'cms_page': {
+ return transformCmsPageUrl(_source)
+ }
+ default: {
return {
- name: localizedDispatcherRouteName('category', storeCode, appendStoreCode),
+ name: 'page-not-found',
params: {
- slug: category.slug
+ slug: 'page-not-found'
}
}
}
}
},
+ /**
+ * Here we can save data based on _type, so there will be no need to create another request for it.
+ */
+ async saveFallbackData ({ commit }, { _type, _source }) {
+ switch (_type) {
+ case 'product': {
+ const [product] = prepareProducts([_source])
+ storeProductToCache(product, 'sku')
+ break
+ }
+ case 'category': {
+ commit('category-next/' + categoryMutationTypes.CATEGORY_ADD_CATEGORY, _source, { root: true })
+ break
+ }
+ case 'cms_page': {
+ commit('cmsPage/' + cmsPageMutationTypes.CMS_PAGE_ADD_CMS_PAGE, _source, { root: true })
+ commit('cmsPage/' + cmsPageMutationTypes.CMS_PAGE_SET_CURRENT, _source, { root: true })
+ break
+ }
+ default: {
+ break
+ }
+ }
+ },
setCurrentRoute ({ commit, state, rootGetters }, { to, from } = {}) {
commit(types.SET_CURRENT_ROUTE, {
...to,
- scrollPosition: {...state.prevRoute.scrollPosition},
+ scrollPosition: { ...state.prevRoute.scrollPosition },
categoryPageSize: state.prevRoute.categoryPageSize
})
diff --git a/core/modules/url/store/index.ts b/core/modules/url/store/index.ts
index 315e3fc21..12101dfff 100644
--- a/core/modules/url/store/index.ts
+++ b/core/modules/url/store/index.ts
@@ -1,14 +1,14 @@
import { Module } from 'vuex'
import { UrlState } from '../types/UrlState'
-import { mutations } from './mutations'
import { actions } from './actions'
import { state } from './state'
import { getters } from './getters'
+import { mutations } from './mutations'
export const urlStore: Module = {
namespaced: true,
- mutations,
actions,
state,
- getters
+ getters,
+ mutations
}
diff --git a/core/modules/url/store/mutation-types.ts b/core/modules/url/store/mutation-types.ts
index 7d47ee8ca..556addc9b 100644
--- a/core/modules/url/store/mutation-types.ts
+++ b/core/modules/url/store/mutation-types.ts
@@ -1,4 +1,3 @@
-export const REGISTER_MAPPING = 'URL/REGISTER_MAPPING'
export const SET_CURRENT_ROUTE = 'URL/SET_CURRENT_ROUTE'
export const SET_PREV_ROUTE = 'URL/SET_PREV_ROUTE'
export const IS_BACK_ROUTE = 'URL/IS_BACK_ROUTE'
diff --git a/core/modules/url/store/mutations.ts b/core/modules/url/store/mutations.ts
index abbabf26d..dc0bbeab4 100644
--- a/core/modules/url/store/mutations.ts
+++ b/core/modules/url/store/mutations.ts
@@ -3,14 +3,11 @@ import * as types from './mutation-types'
import omit from 'lodash-es/omit'
export const mutations: MutationTree = {
- [types.REGISTER_MAPPING] (state, payload) {
- state.dispatcherMap = Object.assign({}, state.dispatcherMap, { [payload.url]: payload.routeData })
- },
[types.SET_CURRENT_ROUTE] (state, payload = {}) {
- state.currentRoute = omit({...payload}, ['matched'])
+ state.currentRoute = omit({ ...payload }, ['matched'])
},
[types.SET_PREV_ROUTE] (state, payload = {}) {
- state.prevRoute = omit({...payload}, ['matched'])
+ state.prevRoute = omit({ ...payload }, ['matched'])
},
[types.IS_BACK_ROUTE] (state, payload) {
state.isBackRoute = payload
diff --git a/core/modules/url/test/unit/store/actions.spec.ts b/core/modules/url/test/unit/store/actions.spec.ts
index bdbd29b02..74115fd8f 100644
--- a/core/modules/url/test/unit/store/actions.spec.ts
+++ b/core/modules/url/test/unit/store/actions.spec.ts
@@ -1,17 +1,10 @@
-import * as types from '@vue-storefront/core/modules/url/store/mutation-types';
import { cacheStorage } from '@vue-storefront/core/modules/recently-viewed/index';
import { actions as urlActions } from '../../../store/actions';
-import { currentStoreView, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore';
+import { currentStoreView, removeStoreCodeFromRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore';
import { normalizeUrlPath, parametrizeRouteData } from '../../../helpers';
-
-const SearchQuery = {
- applyFilter: jest.fn()
-};
+import { transformProductUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl';
jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() }))
-jest.mock('@vue-storefront/core/lib/search/searchQuery', () => () =>
- SearchQuery
-);
jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) }));
jest.mock('@vue-storefront/core/modules/recently-viewed/index', () => ({
cacheStorage: {
@@ -61,6 +54,11 @@ jest.mock('@vue-storefront/core/app', () => ({
addRoutes: jest.fn()
}
}));
+jest.mock('@vue-storefront/core/modules/url/helpers/transformUrl', () => ({
+ transformProductUrl: jest.fn(),
+ transformCategoryUrl: jest.fn(),
+ transformCmsPageUrl: jest.fn()
+}));
let url: string;
let routeData: any;
@@ -76,17 +74,15 @@ describe('Url actions', () => {
describe('registerMapping action', () => {
it('should call register mapping mutation', async () => {
const contextMock = {
- commit: jest.fn()
+ state: {
+ dispatcherMap: {}
+ }
};
const result = await (urlActions as any).registerMapping(contextMock, {
url,
routeData
});
- expect(contextMock.commit).toHaveBeenCalledWith(types.REGISTER_MAPPING, {
- url,
- routeData
- });
expect(result).toEqual(routeData);
});
});
@@ -183,7 +179,7 @@ describe('Url actions', () => {
});
});
- describe('mappingFallBack action', () => {
+ describe('mapFallbackUrl action', () => {
beforeEach(() => {
(currentStoreView as jest.Mock).mockImplementation(() => ({
storeCode: '',
@@ -191,54 +187,70 @@ describe('Url actions', () => {
}));
});
- it('should return the proper URL from API for products', async () => {
- url = '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html';
- (localizedDispatcherRouteName as jest.Mock).mockImplementation(() => url);
+ it('should trigger fetch from url module', async () => {
+ url = 'men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html';
+ (removeStoreCodeFromRoute as jest.Mock).mockImplementation(() => url);
const contextMock = {
dispatch: jest.fn()
};
- const params = {
- slug: 'slug',
- sku: 'parentsku2',
- childSku: 'childSku'
+
+ const wrapper = (actions: any) => actions.mapFallbackUrl(contextMock, { url });
+
+ await wrapper(urlActions);
+
+ expect(contextMock.dispatch).toBeCalledWith('getFallbackByUrl', { url })
+ });
+
+ it('should return page-not-found if missing record from ES', async () => {
+ url = 'men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html';
+ (removeStoreCodeFromRoute as jest.Mock).mockImplementation(() => url);
+
+ const contextMock = {
+ dispatch: jest.fn()
};
- contextMock.dispatch.mockImplementation(() => Promise.resolve({ items: [ { name: 'name1', qty: 2, slug: 'slug1', sku: 'parentsku2' } ] }))
+ const wrapper = (actions: any) => actions.mapFallbackUrl(contextMock, { url });
- const result = await (urlActions as any).mappingFallback(contextMock, { url, params });
+ const result = await wrapper(urlActions);
expect(result).toEqual({
- name: '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html',
+ name: 'page-not-found',
params: {
- slug: 'slug1',
- parentSku: 'parentsku2',
- childSku: 'childSku'
+ slug: 'page-not-found'
}
- });
+ })
});
+ });
- it('should return return the proper URL from API for category', async () => {
- url = '/men/bottoms-men/shorts-men/shorts-19';
- (localizedDispatcherRouteName as jest.Mock).mockImplementation(() => url);
-
+ describe('transformFallback action', () => {
+ it('should call transformation function based on _type', async () => {
const contextMock = {
dispatch: jest.fn()
};
- const params = {
- slug: 'shorts-19'
+
+ const wrapper = (actions: any) => actions.transformFallback(contextMock, { _type: 'product' });
+
+ await wrapper(urlActions);
+
+ expect(transformProductUrl).toBeCalled()
+ });
+
+ it('should return by default page-not-found', async () => {
+ const contextMock = {
+ dispatch: jest.fn()
};
- contextMock.dispatch.mockImplementation(() => Promise.resolve({ slug: 'shorts-19' }))
+ const wrapper = (actions: any) => actions.transformFallback(contextMock, { _type: 'xyz' });
- const result = await (urlActions as any).mappingFallback(contextMock, { url, params });
+ const result = await wrapper(urlActions);
expect(result).toEqual({
- name: '/men/bottoms-men/shorts-men/shorts-19',
+ name: 'page-not-found',
params: {
- slug: 'shorts-19'
+ slug: 'page-not-found'
}
- });
- })
+ })
+ });
});
});
diff --git a/core/modules/url/test/unit/store/mutations.spec.ts b/core/modules/url/test/unit/store/mutations.spec.ts
deleted file mode 100644
index e5f0ee1a3..000000000
--- a/core/modules/url/test/unit/store/mutations.spec.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import * as types from '../../../store/mutation-types'
-import { mutations as urlMutations } from '../../../store/mutations'
-
-describe('url mutations', () => {
- beforeEach(() => {
- jest.clearAllMocks()
- })
-
- describe('REGISTER_MAPPING', () => {
- it('should register mapping', () => {
- const stateMock = {
- dispatcherMap: {}
- }
- const payloadData = {
- url: 'https://www.example.com',
- routeData: { name: 'example' }
- }
- const expectedState = {
- dispatcherMap: {
- 'https://www.example.com': { name: 'example' }
- }
- }
- const wrapper = (mutations: any) => mutations[types.REGISTER_MAPPING](stateMock, payloadData)
-
- wrapper(urlMutations)
-
- expect(stateMock).toEqual(expectedState)
- })
- })
-})
diff --git a/core/modules/user/index.ts b/core/modules/user/index.ts
index 7d864b473..7f88614cc 100644
--- a/core/modules/user/index.ts
+++ b/core/modules/user/index.ts
@@ -2,6 +2,7 @@ import { userStore } from './store'
import { StorefrontModule } from '@vue-storefront/core/lib/modules'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
import { isServer } from '@vue-storefront/core/helpers'
+import { Logger } from '@vue-storefront/core/lib/logger'
import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
import * as types from './store/mutation-types'
@@ -36,7 +37,7 @@ export const UserModule: StorefrontModule = async function ({ store }) {
type.endsWith(types.USER_INFO_LOADED)
) {
StorageManager.get('user').setItem('current-user', state.user.current).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
@@ -44,7 +45,7 @@ export const UserModule: StorefrontModule = async function ({ store }) {
type.endsWith(types.USER_ORDERS_HISTORY_LOADED)
) {
StorageManager.get('user').setItem('orders-history', state.user.orders_history).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
@@ -52,11 +53,11 @@ export const UserModule: StorefrontModule = async function ({ store }) {
type.endsWith(types.USER_TOKEN_CHANGED)
) {
StorageManager.get('user').setItem('current-token', state.user.token).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
if (state.user.refreshToken) {
StorageManager.get('user').setItem('current-refresh-token', state.user.refreshToken).catch((reason) => {
- console.error(reason) // it doesn't work on SSR
+ Logger.error(reason)() // it doesn't work on SSR
}) // populate cache
}
}
diff --git a/core/modules/user/store/actions.ts b/core/modules/user/store/actions.ts
index 16561ec8a..6ddad8891 100644
--- a/core/modules/user/store/actions.ts
+++ b/core/modules/user/store/actions.ts
@@ -46,6 +46,13 @@ const actions: ActionTree = {
resetPassword (context, { email }) {
return UserService.resetPassword(email)
},
+ /**
+ * Create new password for provided email with resetToken
+ * We could receive resetToken by running user.resetPassword action
+ */
+ createPassword (context, { email, newPassword, resetToken }) {
+ return UserService.createPassword(email, newPassword, resetToken)
+ },
/**
* Login user and return user profile and current token
*/
diff --git a/core/package.json b/core/package.json
index 5f5cf8f6d..35ec249b4 100644
--- a/core/package.json
+++ b/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/core",
- "version": "1.11.4",
+ "version": "1.12.0",
"description": "Vue Storefront Core",
"license": "MIT",
"main": "app.js",
diff --git a/core/pages/Category.js b/core/pages/Category.js
index 882886461..b89817ca4 100644
--- a/core/pages/Category.js
+++ b/core/pages/Category.js
@@ -1,7 +1,6 @@
import Vue from 'vue'
import toString from 'lodash-es/toString'
import config from 'config'
-
import i18n from '@vue-storefront/i18n'
import store from '@vue-storefront/core/store'
import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus'
@@ -84,10 +83,6 @@ export default {
const defaultFilters = config.products.defaultFilters
store.dispatch('category/resetFilters')
EventBus.$emit('filter-reset')
- await store.dispatch('attribute/list', { // load filter attributes for this specific category
- filterValues: defaultFilters, // TODO: assign specific filters/ attribute codes dynamicaly to specific categories
- includeFields: config.entities.optimize && isServer ? config.entities.attribute.includeFields : null
- })
const parentCategory = await store.dispatch('category/single', { key: config.products.useMagentoUrlKeys ? 'url_key' : 'slug', value: route.params.slug })
let query = store.getters['category/getCurrentCategoryProductQuery']
if (!query.searchProductQuery) {
@@ -277,7 +272,7 @@ export default {
metaInfo () {
const storeView = currentStoreView()
return {
- link: [
+ /* link: [
{ rel: 'amphtml',
href: this.$router.resolve(localizedRoute({
name: 'category-amp',
@@ -286,7 +281,7 @@ export default {
}
}, storeView.storeCode)).href
}
- ],
+ ], */
title: htmlDecode(this.category.meta_title || this.categoryName),
meta: this.category.meta_description ? [{ vmid: 'description', name: 'description', content: htmlDecode(this.category.meta_description) }] : []
}
diff --git a/core/pages/Checkout.js b/core/pages/Checkout.js
index e11133914..0dc2c893f 100644
--- a/core/pages/Checkout.js
+++ b/core/pages/Checkout.js
@@ -5,7 +5,7 @@ import VueOfflineMixin from 'vue-offline/mixin'
import { mapGetters } from 'vuex'
import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
import Composite from '@vue-storefront/core/mixins/composite'
-import { currentStoreView } from '@vue-storefront/core/lib/multistore'
+import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore'
import { isServer } from '@vue-storefront/core/helpers'
import { Logger } from '@vue-storefront/core/lib/logger'
@@ -343,7 +343,7 @@ export default {
asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data
return new Promise((resolve, reject) => {
if (context) context.output.cacheTags.add(`checkout`)
- if (context) context.server.response.redirect('/')
+ if (context) context.server.response.redirect(localizedRoute('/'))
resolve()
})
}
diff --git a/core/pages/CmsPage.js b/core/pages/CmsPage.js
index e505cefcf..058794da0 100644
--- a/core/pages/CmsPage.js
+++ b/core/pages/CmsPage.js
@@ -7,7 +7,13 @@ export default {
mixins: [Composite],
computed: {
pageTitle () {
- return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.title : ''
+ return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_title || this.$store.state.cmsPage.current.title : ''
+ },
+ pageDescription () {
+ return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_description : ''
+ },
+ pageKeywords () {
+ return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_keywords : ''
}
},
watch: {
@@ -39,7 +45,10 @@ export default {
metaInfo () {
return {
title: htmlDecode(this.pageTitle || this.$route.meta.title),
- meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: htmlDecode(this.$route.meta.description) }] : []
+ meta: [
+ { vmid: 'description', name: 'description', content: htmlDecode(this.pageDescription || this.$route.meta.description) },
+ { vmid: 'keywords', name: 'keywords', content: htmlDecode(this.pageKeywords) }
+ ]
}
}
}
diff --git a/core/pages/PageNotFound.js b/core/pages/PageNotFound.js
index 808df37a5..a55fda111 100644
--- a/core/pages/PageNotFound.js
+++ b/core/pages/PageNotFound.js
@@ -14,13 +14,17 @@ export default {
context.server.response.statusCode = 404
}
let ourBestsellersQuery = prepareQuery({ queryConfig: 'bestSellers' })
- const response = await store.dispatch('product/list', {
+ const { items } = await store.dispatch('product/findProducts', {
query: ourBestsellersQuery,
size: 8,
- sort: 'created_at:desc'
+ sort: 'created_at:desc',
+ options: {
+ populateRequestCacheTags: false,
+ prefetchGroupProducts: false
+ }
})
- if (response) {
- store.state.homepage.bestsellers = response.items
+ if (items.length) {
+ store.state.homepage.bestsellers = items
}
},
metaInfo () {
diff --git a/core/pages/Product.js b/core/pages/Product.js
index 439cd9d88..8f885521e 100644
--- a/core/pages/Product.js
+++ b/core/pages/Product.js
@@ -227,7 +227,7 @@ export default {
metaInfo () {
const storeView = currentStoreView()
return {
- link: [
+ /* link: [
{ rel: 'amphtml',
href: this.$router.resolve(localizedRoute({
name: this.product.type_id + '-product-amp',
@@ -238,7 +238,7 @@ export default {
}
}, storeView.storeCode)).href
}
- ],
+ ], */
title: htmlDecode(this.product.meta_title || this.productName),
meta: this.product.meta_description ? [{ vmid: 'description', name: 'description', content: htmlDecode(this.product.meta_description) }] : []
}
diff --git a/core/scripts/generate-files.ts b/core/scripts/generate-files.ts
new file mode 100644
index 000000000..284201964
--- /dev/null
+++ b/core/scripts/generate-files.ts
@@ -0,0 +1,28 @@
+import fs from 'fs';
+import path from 'path';
+import config from 'config';
+
+fs.writeFileSync(
+ path.resolve(__dirname, '../build/config.json'),
+ JSON.stringify(config)
+)
+
+const csvDirectories = [
+ path.resolve(__dirname, '../../node_modules/@vue-storefront/i18n/resource/i18n/')
+]
+
+const moduleRoot = path.resolve(__dirname, '../../src/modules')
+fs.readdirSync(moduleRoot).forEach(directory => {
+ const dirName = moduleRoot + '/' + directory + '/resource/i18n'
+
+ if (fs.existsSync(dirName)) {
+ csvDirectories.push(dirName);
+ }
+});
+
+const themeRoot = require('../build/theme-path');
+const themeResources = themeRoot + '/resource'
+csvDirectories.push(path.resolve(__dirname, themeResources + '/i18n/'));
+
+const translationPreprocessor = require('@vue-storefront/i18n/scripts/translation.preprocessor.js')
+translationPreprocessor(csvDirectories, config)
diff --git a/core/scripts/installer.js b/core/scripts/installer.js
index 21e6d35ad..e80aeeeb9 100644
--- a/core/scripts/installer.js
+++ b/core/scripts/installer.js
@@ -428,9 +428,11 @@ class Storefront extends Abstract {
config.elasticsearch.host = `${backendPath}/api/catalog`
config.orders.endpoint = `${backendPath}/api/order`
config.products.endpoint = `${backendPath}/api/product`
+ config.users.loginAfterCreatePassword = true
config.users.endpoint = `${backendPath}/api/user`
config.users.history_endpoint = `${backendPath}/api/user/order-history?token={{token}}&pageSize={{pageSize}}¤tPage={{currentPage}}`
config.users.resetPassword_endpoint = `${backendPath}/api/user/reset-password`
+ config.users.createPassword_endpoint = `${backendPath}/api/user/create-password`
config.users.changePassword_endpoint = `${backendPath}/api/user/change-password?token={{token}}`
config.users.login_endpoint = `${backendPath}/api/user/login`
config.users.create_endpoint = `${backendPath}/api/user/create`
@@ -457,6 +459,55 @@ class Storefront extends Abstract {
config.cms.endpoint = `${backendPath}/api/ext/cms-data/cms{{type}}/{{cmsId}}`
config.cms.endpointIdentifier = `${backendPath}/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}`
+ if (this.answers.ssr_endpoints) {
+ if (Abstract.wasLocalBackendInstalled) {
+ graphQlHost = 'localhost'
+ backendPath = 'http://localhost:8080'
+ } else {
+ backendPath = STOREFRONT_REMOTE_BACKEND_URL
+ graphQlHost = backendPath.replace('https://', '').replace('http://', '')
+ }
+
+ // Do we really need protocol_ssr in a different place than GraphQL?
+ config.server.protocol_ssr = 'http'
+ config.api.url_ssr = backendPath
+ config.graphql.host_ssr = graphQlHost
+ config.graphql.port_ssr = graphQlPort
+ config.elasticsearch.host_ssr = `${backendPath}/api/catalog`
+ config.orders.endpoint_ssr = `${backendPath}/api/order`
+ config.products.endpoint_ssr = `${backendPath}/api/product`
+ config.users.endpoint_ssr = `${backendPath}/api/user`
+ config.users.history_endpoint_ssr = `${backendPath}/api/user/order-history?token={{token}}`
+ config.users.resetPassword_endpoint_ssr = `${backendPath}/api/user/reset-password`
+ config.users.changePassword_endpoint_ssr = `${backendPath}/api/user/change-password?token={{token}}`
+ config.users.login_endpoint_ssr = `${backendPath}/api/user/login`
+ config.users.create_endpoint_ssr = `${backendPath}/api/user/create`
+ config.users.me_endpoint_ssr = `${backendPath}/api/user/me?token={{token}}`
+ config.users.refresh_endpoint_ssr = `${backendPath}/api/user/refresh`
+ config.stock.endpoint_ssr = `${backendPath}/api/stock`
+ config.cart.create_endpoint_ssr = `${backendPath}/api/cart/create?token={{token}}`
+ config.cart.updateitem_endpoint_ssr = `${backendPath}/api/cart/update?token={{token}}&cartId={{cartId}}`
+ config.cart.deleteitem_endpoint_ssr = `${backendPath}/api/cart/delete?token={{token}}&cartId={{cartId}}`
+ config.cart.pull_endpoint_ssr = `${backendPath}/api/cart/pull?token={{token}}&cartId={{cartId}}`
+ config.cart.totals_endpoint_ssr = `${backendPath}/api/cart/totals?token={{token}}&cartId={{cartId}}`
+ config.cart.paymentmethods_endpoint_ssr = `${backendPath}/api/cart/payment-methods?token={{token}}&cartId={{cartId}}`
+ config.cart.shippingmethods_endpoint_ssr = `${backendPath}/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}`
+ config.cart.shippinginfo_endpoint_ssr = `${backendPath}/api/cart/shipping-information?token={{token}}&cartId={{cartId}}`
+ config.cart.collecttotals_endpoint_ssr = `${backendPath}/api/cart/collect-totals?token={{token}}&cartId={{cartId}}`
+ config.cart.deletecoupon_endpoint_ssr = `${backendPath}/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}`
+ config.cart.applycoupon_endpoint_ssr = `${backendPath}/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}`
+ config.reviews.create_endpoint_ssr = `${backendPath}/api/review/create?token={{token}}`
+
+ // Probably pointless (only CS)
+ // config.newsletter.endpoint_ssr = `${backendPath}/api/ext/mailchimp-subscribe/subscribe`
+ config.mailer.endpoint.send_ssr = `${backendPath}/api/ext/mail-service/send-email`
+ config.mailer.endpoint.token_ssr = `${backendPath}/api/ext/mail-service/get-token`
+ // Probably pointless (only CS)
+ // config.images.baseUrl_ssr = this.answers.images_endpoint
+ config.cms.endpoint_ssr = `${backendPath}/api/ext/cms-data/cms{{type}}/{{cmsId}}`
+ config.cms.endpointIdentifier_ssr = `${backendPath}/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}`
+ }
+
config.install = {
is_local_backend: Abstract.wasLocalBackendInstalled,
backend_dir: this.answers.backend_dir || false
@@ -806,6 +857,12 @@ let questions = [
when: function (answers) {
return answers.m2_api_oauth2 === true
}
+ },
+ {
+ type: 'confirm',
+ name: 'ssr_endpoints',
+ message: `Would You like to create fields for SSR endpoints?`,
+ default: false
}
]
diff --git a/core/scripts/resolvers/resolveGraphQL.js b/core/scripts/resolvers/resolveGraphQL.js
index a1b651c60..539e0609d 100644
--- a/core/scripts/resolvers/resolveGraphQL.js
+++ b/core/scripts/resolvers/resolveGraphQL.js
@@ -2,6 +2,7 @@ import { server, graphql } from 'config'
import Vue from 'vue'
import { Logger } from '@vue-storefront/core/lib/logger'
import { once } from '@vue-storefront/core/helpers'
+import { isServer } from '@vue-storefront/core/helpers';
export const getApolloProvider = async () => {
if (server.api === 'graphql') {
@@ -15,8 +16,22 @@ export const getApolloProvider = async () => {
const HttpLinkModule = await import(/* webpackChunkName: "vsf-graphql" */ 'apollo-link-http')
const HttpLink = HttpLinkModule.HttpLink
+ let uri
+ if (isServer && (graphql.host_ssr || graphql.port_ssr)) {
+ const host = graphql.host_ssr || graphql.host
+ const port = graphql.port_ssr || graphql.port
+
+ uri = host.indexOf('://') >= 0
+ ? host
+ : (server.protocol + '://' + host + ':' + port + '/graphql')
+ } else {
+ uri = graphql.host.indexOf('://') >= 0
+ ? graphql.host
+ : (server.protocol + '://' + graphql.host + ':' + graphql.port + '/graphql')
+ }
+
const httpLink = new HttpLink({
- uri: graphql.host.indexOf('://') >= 0 ? graphql.host : (server.protocol + '://' + graphql.host + ':' + graphql.port + '/graphql')
+ uri
})
const ApolloClientModule = await import(/* webpackChunkName: "vsf-graphql" */ 'apollo-client')
@@ -51,7 +66,7 @@ export const getApolloProvider = async () => {
})
return apolloProvider
- }
+ } else return null
}
export default {
diff --git a/core/scripts/server.ts b/core/scripts/server.ts
index c8dcf54d4..ddf44f667 100755
--- a/core/scripts/server.ts
+++ b/core/scripts/server.ts
@@ -1,7 +1,9 @@
import { serverHooksExecutors } from '@vue-storefront/core/server/hooks'
+
let config = require('config')
const path = require('path')
const glob = require('glob')
+const fs = require('fs')
const rootPath = require('app-root-path').path
const resolve = file => path.resolve(rootPath, file)
const serverExtensions = glob.sync('src/modules/*/server.{ts,js}')
@@ -91,13 +93,13 @@ function invalidateCache (req, res) {
}
})
- serverHooksExecutors.afterCacheInvalidated()
-
Promise.all(subPromises).then(r => {
apiStatus(res, `Tags invalidated successfully [${req.query.tag}]`, 200)
}).catch(error => {
apiStatus(res, error, 500)
console.error(error)
+ }).finally(() => {
+ serverHooksExecutors.afterCacheInvalidated({ tags, req })
})
if (config.server.invalidateCacheForwarding) { // forward invalidate request to the next server in the chain
@@ -146,6 +148,12 @@ app.use('/service-worker.js', serve('dist/service-worker.js', false, {
app.post('/invalidate', invalidateCache)
app.get('/invalidate', invalidateCache)
+function cacheVersion (req, res) {
+ res.send(fs.readFileSync(resolve('core/build/cache-version.json')))
+}
+
+app.get('/cache-version.json', cacheVersion)
+
app.get('*', (req, res, next) => {
if (NOT_ALLOWED_SSR_EXTENSIONS_REGEX.test(req.url)) {
apiStatus(res, 'Vue Storefront: Resource is not found', 404)
@@ -165,10 +173,14 @@ app.get('*', (req, res, next) => {
} else {
console.error(`Error during render : ${req.url}`)
console.error(err)
+ serverHooksExecutors.ssrException({ err, req, isProd })
return res.redirect('/error')
}
}
+ const site = req.headers['x-vs-store-code'] || 'main'
+ const cacheKey = `page:${site}:${req.url}`
+
const dynamicRequestHandler = renderer => {
if (!renderer) {
res.setHeader('Content-Type', 'text/html')
@@ -205,7 +217,7 @@ app.get('*', (req, res, next) => {
output = ssr.applyAdvancedOutputProcessing(context, output, templatesCache, isProd);
if (config.server.useOutputCache && cache) {
cache.set(
- 'page:' + req.url,
+ cacheKey,
{ headers: res.getHeaders(), body: output, httpCode: res.statusCode },
tagsArray
).catch(errorHandler)
@@ -238,7 +250,7 @@ app.get('*', (req, res, next) => {
const dynamicCacheHandler = () => {
if (config.server.useOutputCache && cache) {
cache.get(
- 'page:' + req.url
+ cacheKey
).then(output => {
if (output !== null) {
if (output.headers) {
@@ -309,20 +321,21 @@ app.get('*', (req, res, next) => {
let port = process.env.PORT || config.server.port
const host = process.env.HOST || config.server.host
const start = () => {
- app.listen(port, host)
- .on('listening', () => {
- console.log(`\n\n----------------------------------------------------------`)
- console.log('| |')
- console.log(`| Vue Storefront Server started at http://${host}:${port} |`)
- console.log('| |')
- console.log(`----------------------------------------------------------\n\n`)
- })
- .on('error', (e) => {
- if (e.code === 'EADDRINUSE') {
- port = parseInt(port) + 1
- console.log(`The port is already in use, trying ${port}`)
- start()
- }
- })
+ const server = app.listen(port, host)
+ server.on('listening', () => {
+ console.log(`\n\n----------------------------------------------------------`)
+ console.log('| |')
+ console.log(`| Vue Storefront Server started at http://${host}:${port} |`)
+ console.log('| |')
+ console.log(`----------------------------------------------------------\n\n`)
+
+ serverHooksExecutors.httpServerIsReady({ server, config: config.server, isProd })
+ }).on('error', (e) => {
+ if (e.code === 'EADDRINUSE') {
+ port = parseInt(port) + 1
+ console.log(`The port is already in use, trying ${port}`)
+ start()
+ }
+ })
}
start()
diff --git a/core/scripts/utils/catalog-client.ts b/core/scripts/utils/catalog-client.ts
index 66a92bc62..8d2a1f802 100644
--- a/core/scripts/utils/catalog-client.ts
+++ b/core/scripts/utils/catalog-client.ts
@@ -1,14 +1,15 @@
import queryString from 'query-string'
import fetch from 'isomorphic-fetch'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
export const processURLAddress = (url: string = '', config: any) => {
- if (url.startsWith('/')) return `${config.api.url}${url}`
+ if (url.startsWith('/')) return `${getApiEndpointUrl(config.api, 'url')}${url}`
return url
}
export async function search (request, storeView, config) {
const elasticsearchQueryBody = request.searchQuery
if (!request.index) request.index = storeView.elasticsearch.index
- let url = processURLAddress(storeView.elasticsearch.host, config)
+ let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host'), config)
const httpQuery: {
size: number,
diff --git a/core/scripts/utils/ssr-renderer.js b/core/scripts/utils/ssr-renderer.ts
similarity index 88%
rename from core/scripts/utils/ssr-renderer.js
rename to core/scripts/utils/ssr-renderer.ts
index 8694fe9d7..c64f177a9 100644
--- a/core/scripts/utils/ssr-renderer.js
+++ b/core/scripts/utils/ssr-renderer.ts
@@ -1,3 +1,4 @@
+import { Context } from './types';
const fs = require('fs')
const path = require('path')
const compile = require('lodash.template')
@@ -9,7 +10,18 @@ const get = require('lodash/get')
const config = require('config')
const minify = require('html-minifier').minify
-function createRenderer (bundle, clientManifest, template) {
+function createRenderer (bundle, clientManifest, template?) {
+ let shouldPreload = () => {}
+ let shouldPrefetch = () => {}
+ try {
+ const scripts = require('../../modules/initial-resources/serverResourcesFilter')
+ shouldPreload = scripts.shouldPreload
+ shouldPrefetch = scripts.shouldPrefetch
+ } catch (err) {
+ if (config.initialResources) {
+ console.error(err)
+ }
+ }
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, {
@@ -18,7 +30,9 @@ function createRenderer (bundle, clientManifest, template) {
cache: new LRU({
max: 1000,
maxAge: 1000 * 60 * 15
- })
+ }),
+ shouldPreload,
+ shouldPrefetch
})
}
@@ -92,7 +106,7 @@ function initTemplatesCache (config, compileOptions) {
return templatesCache
}
-function initSSRRequestContext (app, req, res, config) {
+function initSSRRequestContext (app, req, res, config): Context {
return {
url: decodeURI(req.url),
output: {
@@ -122,11 +136,11 @@ function clearContext (context) {
delete context['meta']
}
-module.exports = {
+export {
createRenderer,
initTemplatesCache,
initSSRRequestContext,
applyAdvancedOutputProcessing,
- compileTemplate: compile,
+ compile as compileTemplate,
clearContext
}
diff --git a/core/scripts/utils/types/index.ts b/core/scripts/utils/types/index.ts
new file mode 100644
index 000000000..373620cac
--- /dev/null
+++ b/core/scripts/utils/types/index.ts
@@ -0,0 +1,23 @@
+import { Express } from 'express'
+
+export interface Context {
+ url: string,
+ output: {
+ prepend: (context: any) => string,
+ append: (context: any) => string,
+ filter: (output: T, context: any) => T,
+ appendHead: (context: any) => string,
+ template: string,
+ cacheTags: Set
+ },
+ server: {
+ app: Express,
+ response: Express.Response,
+ request: Express.Request
+ },
+ meta: any|null,
+ vs: {
+ config: Record,
+ storeCode: string
+ }
+}
diff --git a/core/server/hooks.ts b/core/server/hooks.ts
index 630871343..0fa31db24 100644
--- a/core/server/hooks.ts
+++ b/core/server/hooks.ts
@@ -1,5 +1,6 @@
import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks'
-import { Express, Request } from 'express';
+import { Express, Request } from 'express'
+import http from 'http'
// To add like tracing which needs to be done as early as possible
@@ -13,6 +14,11 @@ interface BeforeCacheInvalidatedParamter {
req: Request
}
+interface AfterCacheInvalidatedParamter {
+ tags: string[],
+ req: Request
+}
+
const {
hook: beforeCacheInvalidatedHook,
executor: beforeCacheInvalidatedExecutor
@@ -21,7 +27,7 @@ const {
const {
hook: afterCacheInvalidatedHook,
executor: afterCacheInvalidatedExecutor
-} = createListenerHook()
+} = createListenerHook()
// beforeStartApp
interface Extend {
@@ -29,11 +35,34 @@ interface Extend {
config: any,
isProd: boolean
}
+
const {
hook: afterApplicationInitializedHook,
executor: afterApplicationInitializedExecutor
} = createListenerHook()
+interface Server {
+ server: http.Server,
+ config: any,
+ isProd: boolean
+}
+
+const {
+ hook: httpServerIsReadyHook,
+ executor: httpServerIsReadyExecutor
+} = createListenerHook()
+
+interface Exception {
+ err: Exception,
+ req: Request,
+ isProd: boolean
+}
+
+const {
+ hook: ssrExceptionHook,
+ executor: ssrExceptionExecutor
+} = createListenerHook()
+
const {
hook: beforeOutputRenderedResponseHook,
executor: beforeOutputRenderedResponseExecutor
@@ -48,6 +77,8 @@ const {
const serverHooksExecutors = {
afterProcessStarted: afterProcessStartedExecutor,
afterApplicationInitialized: afterApplicationInitializedExecutor,
+ httpServerIsReady: httpServerIsReadyExecutor,
+ ssrException: ssrExceptionExecutor,
beforeOutputRenderedResponse: beforeOutputRenderedResponseExecutor,
afterOutputRenderedResponse: afterOutputRenderedResponseExecutor,
beforeCacheInvalidated: beforeCacheInvalidatedExecutor,
@@ -63,6 +94,8 @@ const serverHooks = {
*
*/
afterApplicationInitialized: afterApplicationInitializedHook,
+ httpServerIsReady: httpServerIsReadyHook,
+ ssrException: ssrExceptionHook,
beforeOutputRenderedResponse: beforeOutputRenderedResponseHook,
afterOutputRenderedResponse: afterOutputRenderedResponseHook,
beforeCacheInvalidated: beforeCacheInvalidatedHook,
diff --git a/core/test/unit/helpers/buildFilterProductsQuery.spec.ts b/core/test/unit/helpers/buildFilterProductsQuery.spec.ts
index 66938381f..77d35cbe9 100644
--- a/core/test/unit/helpers/buildFilterProductsQuery.spec.ts
+++ b/core/test/unit/helpers/buildFilterProductsQuery.spec.ts
@@ -62,7 +62,7 @@ describe('buildFilterProductsQuery method', () => {
it('should build default query', () => {
const result = buildFilterProductsQuery(currentCategory)
- const categoryFilter = result._appliedFilters.find(filter => filter.attribute === 'category_ids')
+ const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'category_ids')
expect(categoryFilter).toBeDefined()
expect(categoryFilter.value.in).toEqual([20, 21, 23, 24, 25, 26, 22, 27, 28])
});
@@ -85,7 +85,7 @@ describe('buildFilterProductsQuery method', () => {
]
}
const result = buildFilterProductsQuery(currentCategory, filters)
- const categoryFilter = result._appliedFilters.find(filter => filter.attribute === 'color')
+ const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'color')
expect(categoryFilter).toBeDefined()
expect(categoryFilter.value.in).toEqual(['49', '50'])
});
@@ -102,7 +102,7 @@ describe('buildFilterProductsQuery method', () => {
}
}
const result = buildFilterProductsQuery(currentCategory, filters)
- const categoryFilter = result._appliedFilters.find(filter => filter.attribute === 'price')
+ const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price')
expect(categoryFilter).toBeDefined()
expect(categoryFilter.value.lte).toEqual(50)
});
@@ -119,7 +119,7 @@ describe('buildFilterProductsQuery method', () => {
}
}
const result = buildFilterProductsQuery(currentCategory, filters)
- const categoryFilter = result._appliedFilters.find(filter => filter.attribute === 'price')
+ const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price')
expect(categoryFilter).toBeDefined()
expect(categoryFilter.value.gte).toEqual(50)
expect(categoryFilter.value.lte).toEqual(100)
@@ -137,7 +137,7 @@ describe('buildFilterProductsQuery method', () => {
}]
}
const result = buildFilterProductsQuery(currentCategory, filters)
- const categoryFilter = result._appliedFilters.find(filter => filter.attribute === 'price')
+ const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price')
expect(categoryFilter).toBeDefined()
expect(categoryFilter.value.lte).toEqual(50)
});
@@ -168,10 +168,10 @@ describe('buildFilterProductsQuery method', () => {
]
}
const result = buildFilterProductsQuery(currentCategory, filters)
- const colorFilter = result._appliedFilters.find(filter => filter.attribute === 'color')
+ const colorFilter = result.getAppliedFilters().find(filter => filter.attribute === 'color')
expect(colorFilter).toBeDefined()
expect(colorFilter.value.in).toEqual(['49', '50'])
- const erinFilter = result._appliedFilters.find(filter => filter.attribute === 'erin_recommends')
+ const erinFilter = result.getAppliedFilters().find(filter => filter.attribute === 'erin_recommends')
expect(erinFilter).toBeDefined()
expect(erinFilter.value.in).toEqual(['1'])
});
diff --git a/core/types/asyncData.d.ts b/core/types/asyncData.d.ts
new file mode 100644
index 000000000..d4dc961ed
--- /dev/null
+++ b/core/types/asyncData.d.ts
@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import { Route } from 'vue-router';
+import { Context } from '@vue-storefront/core/scripts/utils/types'
+
+interface AsyncDataParameter {
+ store: any,
+ route: Route,
+ context?: Context
+}
+
+declare module 'vue/types/options' {
+ interface ComponentOptions {
+ asyncData?: ({ store, route, context }: AsyncDataParameter) => Promise
+ }
+}
diff --git a/core/types/search/HttpQuery.ts b/core/types/search/HttpQuery.ts
index dd04705e1..bcbab4bd4 100644
--- a/core/types/search/HttpQuery.ts
+++ b/core/types/search/HttpQuery.ts
@@ -4,6 +4,8 @@ export default interface HttpQuery {
from: number,
sort: string,
request?: string,
+ request_format?: string,
+ response_format?: string,
_source_exclude?: string,
_source_include?: string
}
diff --git a/core/types/search/SearchResponse.ts b/core/types/search/SearchResponse.ts
index 4622d4580..49880d1c4 100644
--- a/core/types/search/SearchResponse.ts
+++ b/core/types/search/SearchResponse.ts
@@ -7,5 +7,6 @@ export interface SearchResponse {
offline?: boolean,
cache?: boolean,
noresults?: boolean,
- suggestions: any
+ suggestions: any,
+ attributeMetadata?: any
}
diff --git a/cypress.json b/cypress.json
index 80eb57500..51f68f789 100644
--- a/cypress.json
+++ b/cypress.json
@@ -4,5 +4,7 @@
"fixturesFolder": "test/e2e/fixtures",
"integrationFolder": "test/e2e/integration",
"pluginsFile": "test/e2e/plugins/index.js",
- "supportFile": "test/e2e/support/index.js"
+ "supportFile": "test/e2e/support/index.js",
+ "viewportWidth": 1280,
+ "viewportHeight": 1024
}
diff --git a/docker/vue-storefront/vue-storefront.sh b/docker/vue-storefront/vue-storefront.sh
index a6db7c867..920b5c52b 100755
--- a/docker/vue-storefront/vue-storefront.sh
+++ b/docker/vue-storefront/vue-storefront.sh
@@ -3,7 +3,7 @@ set -e
yarn install || exit $?
-yarn build:client && yarn build:server && yarn build:sw || exit $?
+yarn generate-files && yarn build:client && yarn build:server && yarn build:sw || exit $?
if [ "$VS_ENV" = 'dev' ]; then
yarn dev
diff --git a/docs/guide/basics/configuration.md b/docs/guide/basics/configuration.md
index df9d852e4..1a471f358 100644
--- a/docs/guide/basics/configuration.md
+++ b/docs/guide/basics/configuration.md
@@ -467,6 +467,12 @@ This is related to `alwaysSyncPlatformPricesOver` and when it's set to true, the
This is related to `alwaysSyncPlatformPricesOver`. When true, Vue Storefront will wait for dynamic prices before rendering the page. Otherwise, the product and category pages will be rendered using the default (Elasticsearch-based) prices and then asynchronously override them with current ones.
+```json
+ "alwaysSyncPricesClientSide": false,
+```
+
+This is related to `alwaysSyncPlatformPricesOver`. When true, Vue Storefront will force a refresh of the prices on the client side, including the token from the current logged in user, so customer specific pricing can be applied.
+
```json
"endpoint": "http://localhost:8080/api/product",
diff --git a/docs/guide/cookbook/module.md b/docs/guide/cookbook/module.md
index 3ec015afe..45f50decd 100644
--- a/docs/guide/cookbook/module.md
+++ b/docs/guide/cookbook/module.md
@@ -678,10 +678,98 @@ It's hands down no-brainer to bootstrap a module _manually_ because the skeleton
### 2. Recipe
### 3. Peep into the kitchen (what happens internally)
### 4. Chef's secret (protip)
-
-
-## 6. Anti-patterns & Common pitfalls
+## 6. Extend Elasticsearch request body using `storefront-query-builder`
+
+If you're using the new [`storefront-query-builder`](https://github.com/DivanteLtd/storefront-query-builder) and the `api-search-query` search-adapter ([introduced with v1.1.12](/guide/upgrade-notes/#_1-11-1-12)) it is now possible to extend it by new filters, or even overwrite a existing filter, to customize your Elasticsearch request-bodies.
+
+So, this way you can add custom Elasticsearch queries to the query-chain and still use the notation of `SearchQuery` in the Vue Storefront.
+
+> **Note:** This will only work from `storefront-query-builder` version `1.0.0` and `vue-storefront` version `1.12.2`.
+
+### Usecases
+
+One usecases where this feature would come in handy is for example if you like to add complex queries on multiple points in your source code. Using the following technique you can just add a custom filter to your `SearchQuery` in a single line inside your VSF source-code using the `query.applyFilter(...)` method and then add the complex logic into your custom-filter inside the API.
+
+### Registering a new filter
+
+The `vue-storefront-api` will only try to load filters that are registered in the configs. The extension/module, that contains the filter, must be enabled and the new filter module-classes needs to be registered in its extension config inside the `catalogFilter` array. The filter files must be located inside `filter/catalog/` of your module folder.
+
+For example: If you have a module called `extend-catalog` with a filter called `StockFilter`, the file path to filter would be `src/api/extensions/extend-catalog/filter/catalog/StockFilter.ts` and the config would look like:
+```
+{
+ "registeredExtensions": [ "extend-catalog" ],
+ "extensions": {
+ "extend-catalog": {
+ "catalogFilter": [ "StockFilter" ]
+ }
+ }
+}
+```
+
+### Filter module-class properties
+
+The filter can contain four different properties. Followed a short explaination, what they are doing.
+
+* `check` – This method checks the condition that be must matched to execute the filter. The first valid filter is executed – all afterwards are ignored.
+* `priority` – This is the priority in which the filters are going to be called. The sort is lower to higher.
+* `mutator` – The mutator method is in charge of prehandling the filter value, to e.g. set defaults or check and change the type.
+* `filter` – This method contains the query logic we wan't to add and mutates the `bodybuilder` query-chain.
+
+### Example
+
+Lets assume we like to add a possibility to add a default set of product-attribute filters we can apply to each `SearchQuery` without repeating ourselfs in source-code. So, for example, it should filter for two `color`'s and a specific `cut` to supply a filter for spring-coloured short's we implement at several places in our VSF.
+
+#### Changes in `vue-storefront` repository
+
+The query in the VSF code would look like this (that's it on the VSF side):
+```js
+import { SearchQuery } from 'storefront-query-builder'
+import { quickSearchByQuery } from '@vue-storefront/core/lib/search'
+
+//...
+
+const query = new SearchQuery()
+query.applyFilter({ key: 'spring-shorts', value: 'male', scope: 'default' })
+const products = await dispatch('product/list', { query, size: 5 })
+```
+
+#### Changes in `vue-storefront-api` repository
+
+In the `vue-storefront-api` we are going to add the real filter/query magic.
+There is already an example module called `example-custom-filter` which we are going to use for our filter.
+
+As you look inside its module folder `src/api/extensions/example-custom-filter/`, you will find a child folder `filter/catalog/` with all existing custom filters for this module. Inside this folder we are going to duplicate the existing `SampleFilter.ts` into another one called `SpringShorts.ts` – this is our new custom filter module-class.
+
+This file needs to be registered in the config JSON to let the API know that there is a new custom filter inside our extension.
+Therefore you open your `default.json` or specific config JSON file and add our new filename `SpringShorts` to the config node `extensions.example-custom-filter.catalogFilter` array.
+
+Our `SpringShorts.ts` contains an object that contains [four properties](#filter-module-class-properties): `priority`, `check`, `filter`, `mutator`. We don't need a `mutator` nor `priority`, so we can remove these lines. `check` and `filter` needs to be changed to fulfill our needs. So, this is how our filter finally looks like:
+
+```js
+import { FilterInterface } from 'storefront-query-builder'
+
+const filter: FilterInterface = {
+ check: ({ attribute }) => attribute === 'spring-shorts',
+ filter ({ value, attribute, operator, queryChain }) {
+ return queryChain
+ .filter('terms', 'pants', [ 'shorts' ])
+ .filter('terms', 'cut', [ 1, 2 ])
+ .filter('terms', 'color', [ 3, 4 ])
+ .filter('terms', 'gender', [ value ])
+ }
+}
+
+export default filter
+```
+
+Inside `check` we tell the filter to just be applied if the attribute is named exactly `spring-shorts`.
+
+Inside `filter` we extend the Elasticsearch query-chain by our desired filters, using the `bodybuilder` library syntax.
+
+That's it, now we are able to filter by a complex query in only one line inside VSF.
+
+## 7. Anti-patterns & Common pitfalls
### 1. Preparation
### 2. Recipe
@@ -699,7 +787,7 @@ _[INSERT VIDEO HERE]_
-## 7. Building a module from A to Z in an iteration
+## 8. Building a module from A to Z in an iteration
### 1. Preparation
@@ -709,7 +797,7 @@ _[INSERT VIDEO HERE]_
-## 8. Deprecated legacy of Modules
+## 9. Deprecated legacy of Modules
In this recipe, we will take a review of how to deal with modules in an old fashioned way , just in case you really need it.
### 1. Preparation
@@ -720,7 +808,7 @@ In this recipe, we will take a review of how to deal with modules in an old fash
-## 9. Converting old modules to new modules
+## 10. Converting old modules to new modules
There are useful modules out there already developed in the old way.
### 1. Preparation
diff --git a/docs/guide/cookbook/setup.md b/docs/guide/cookbook/setup.md
index 12b0eba88..b00c07244 100644
--- a/docs/guide/cookbook/setup.md
+++ b/docs/guide/cookbook/setup.md
@@ -5,7 +5,7 @@
}
-In this chapter, we will cover :
+In this chapter, we will cover :
[[toc]]
@@ -14,23 +14,23 @@ In this chapter, we will cover :
Now you are definitely interested in **Vue Storefront**. That's why you are here. You've come across the line. You made a choice. You will have something in return, which is great. Be it developers, entrepreneurs or even marketing managers that they may want to try something new for better products in hopes of enhancing their clients or customers' experience. You chose the right path. We will explore anything you need to get you started at all with [**Vue Storefront** infrastructure](https://github.com/DivanteLtd).
## 1. Install with Docker
-Docker has been arguably the most sought-after, brought to the market which took the community by storm ever since its introduction. Although it's yet controversial whether it's the best choice among its peers, I have never seen such an unanimous enthusiasm over one tech product throughout the whole developers community.
+Docker has been arguably the most sought-after, brought to the market which took the community by storm ever since its introduction. Although it's yet controversial whether it's the best choice among its peers, I have never seen such an unanimous enthusiasm over one tech product throughout the whole developers community.
-Then, why so? In modern computer engineering, products are so complex with an endless list of dependencies intertwined with each other. Building such dependencies in place for every occasion where it's required is one hell of a job, not to mention glitches from all the version variation. That's where Docker steps in to make you achieve **infrastructure automation**. This concept was conceived to help you focus on your business logic rather than having you stuck with hassles of lower level tinkering.
+Then, why so? In modern computer engineering, products are so complex with an endless list of dependencies intertwined with each other. Building such dependencies in place for every occasion where it's required is one hell of a job, not to mention glitches from all the version variation. That's where Docker steps in to make you achieve **infrastructure automation**. This concept was conceived to help you focus on your business logic rather than having you stuck with hassles of lower level tinkering.
Luckily, we already have been through all this for you, got our hands dirty. All you need is run a set of docker commands to get you up and running from scratch. Without further ado, let's get started!
### 1. Preparation
-- You need [`docker`](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) and [`docker-compose`](https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04) installed.
+- You need [`docker`](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) and [`docker-compose`](https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04) installed.
- You need [`git`](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-18-04) installed.
:::tip NOTE
We will walk you with docker on *Linux*. (Specifically *Ubuntu 18.04* if needed)
-There is only one bias for Docker before using it; *Run it on Linux*. Docker is native Linux, was created using a Linux technology; LXC (linux container) in the first place. Even though there were many attempts made to make it available to other platforms as it does on Linux, and it has definitely been on a progress, however, using Docker on Linux is the solidest way to deal with the technology.
+There is only one bias for Docker before using it; *Run it on Linux*. Docker is native Linux, was created using a Linux technology; LXC (linux container) in the first place. Even though there were many attempts made to make it available to other platforms as it does on Linux, and it has definitely been on a progress, however, using Docker on Linux is the solidest way to deal with the technology.
-That being sad, there are tips for using other platforms for docker at [Chef's Secrets](#_4-chef-s-secret-protip) as well.
+That being sad, there are tips for using other platforms for docker at [Chef's Secrets](#_4-chef-s-secret-protip) as well.
:::
### 2. Recipe
@@ -44,19 +44,19 @@ cd vue-storefront-api
```bash
cp config/default.json config/local.json
```
-Then edit `local.json` to your need.
+Then edit `local.json` to your need.
We will look into this in greater detail at [Chef's secret](#_4-chef-s-secret-protip)
:::tip TIP
This step can be skipped if you are OK with values of `default.json` since it follows the [files load order](https://github.com/lorenwest/node-config/wiki/Configuration-Files#file-load-order) of [node-config](https://github.com/lorenwest/node-config)
:::
-3. Run the following Docker command :
+3. Run the following Docker command :
```bash
docker-compose -f docker-compose.yml -f docker-compose.nodejs.yml up -d
```
-Then the result would look something like this :
+Then the result would look something like this :
```bash
Building app
Step 1/8 : FROM node:10-alpine
@@ -107,10 +107,10 @@ warning eslint > file-entry-cache > flat-cache > circular-json@0.3.3: CircularJS
:::
3. In order to verify, run `docker ps` to show which containers are up
```bash
-docker ps
+docker ps
```
-Then,
+Then,
```bash
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
53a47d5a6440 vuestorefrontapi_kibana "/bin/bash /usr/loca…" 31 seconds ago Up 29 seconds 0.0.0.0:5601->5601/tcp vuestorefrontapi_kibana_1
@@ -118,7 +118,7 @@ CONTAINER ID IMAGE COMMAND CREATED
165ae945dbe5 vuestorefrontapi_es1 "/bin/bash bin/es-do…" 8 days ago Up 30 seconds 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch
8dd144746cef redis:4-alpine "docker-entrypoint.s…" 11 days ago Up 31 seconds 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1
```
-The ports number will be used later in the frontend configuration. In fact, they are already set in as default values.
+The ports number will be used later in the frontend configuration. In fact, they are already set in as default values.
You will see 4 containers are running, which is :
| Container | Port |
@@ -141,13 +141,13 @@ cd vue-storefront
cp config/default.json config/local.json
```
Then fix the value as you need it in the `local.json` file.
-In `local.json`, you may change values for information of backend family. But if you followed this recipe verbatim, you don't have to, because it's already there with the default value. Should you study the contents, please see to [Chef's secret](#secret-1-study-in-local-json)
+In `local.json`, you may change values for information of backend family. But if you followed this recipe verbatim, you don't have to, because it's already there with the default value. Should you study the contents, please see to [Chef's secret](#secret-1-study-in-local-json)
-6. Finally run the following Docker command :
+6. Finally run the following Docker command :
```bash
-docker-compose up -d
+docker-compose up -d
```
-The result should be something like this :
+The result should be something like this :
```bash
Building app
Step 1/8 : FROM node:10-alpine
@@ -200,22 +200,22 @@ de560221fdaf vuestorefrontapi_kibana "/bin/bash /usr/loca…" 8 hours
d46c1e0a22af redis:4-alpine "docker-entrypoint.s…" 8 hours ago Up 24 minutes 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1
```
-8. Open your browser and visit [http://localhost:3000/](http://localhost:3000/)
+8. Open your browser and visit [http://localhost:3000/](http://localhost:3000/)
After compiling, *Voila!*
![vs_home_intro_borderline](../images/home-vuestorefront.png)
-### 3. Peep into the kitchen (what happens internally)
+### 3. Peep into the kitchen (what happens internally)
We used `docker-compose` for setting up the entire environment of Vue Storefront. It was more than enough to launch the machines behind for running the shop.
-It was possible because `docker` encapsulated the whole bunch of infrastructure into a linear set of declarative definition for the desired state.
+It was possible because `docker` encapsulated the whole bunch of infrastructure into a linear set of declarative definition for the desired state.
We had 2 steps of `docker-compose` one of which is for backend **Vue Storefront API**, the other for frontend **Vue Storefront**.
The first `docker-compose` had two `yml` files for input. The first input file `docker-compose.yml` describe its base requirement all but **Vue Storefront API** itself; that is, **Elasticsearch** as data store, **Redis** for cache and **Kibana** for helping you grab your data visually (a pair of Elasticsearch).
```yaml
-# docker-compose.yml
+# docker-compose.yml
version: '3.0'
services:
es1:
@@ -247,10 +247,10 @@ services:
volumes:
esdat1:
```
-:::tip NOTE
+:::tip NOTE
Once a term explained, it will be ignored thereafter for consecutive occurrence.
:::
-`version` denotes which version of `docker-compose` this file uses.
+`version` denotes which version of `docker-compose` this file uses.
`services` describe containers. It codifies how they should run. In other words, it codifies option flags used with `docker run ...`
@@ -258,21 +258,21 @@ Once a term explained, it will be ignored thereafter for consecutive occurrence.
- `build` denotes build path of container.
- `volumes` contains the mount path of volumes shared between host and container as *host:container*
- `ports` connect ports between host and container as in *host:container*
-- `environment` allows you to add environment variables. `Xmx512m` means JVM will take up to maximum 512MB memory. `Xms512m` means minimum memory. Combining them, there will be no memory resize, it will just stick to 512MB from start to end throughout its life cycle.
+- `environment` allows you to add environment variables. `Xmx512m` means JVM will take up to maximum 512MB memory. `Xms512m` means minimum memory. Combining them, there will be no memory resize, it will just stick to 512MB from start to end throughout its life cycle.
`kibana` contains information of *Kibana* application container.
-- `depends_on` creates dependency for a container of other containers. So, this container is dependent on `es1` that's just described above.
-- `volumes` mean volumes shared, `:ro` creates the volume in `read-only` mode for the container.
+- `depends_on` creates dependency for a container of other containers. So, this container is dependent on `es1` that's just described above.
+- `volumes` mean volumes shared, `:ro` creates the volume in `read-only` mode for the container.
-`redis` contains information of *Redis* cache application container.
+`redis` contains information of *Redis* cache application container.
-- `image` node contains the name of image this container is based on.
+- `image` node contains the name of image this container is based on.
-`volumes` in top level can be used as a reference to be used across multiple services(containers).
+`volumes` in top level can be used as a reference to be used across multiple services(containers).
-The second input file `docker-compose.nodejs.yml` deals with **Vue Storefront API** node application.
+The second input file `docker-compose.nodejs.yml` deals with **Vue Storefront API** node application.
```yaml
version: '3.0'
services:
@@ -301,17 +301,17 @@ services:
ports:
- '8080:8080'
```
-`app` contains information of *Vue Storefront API* application.
+`app` contains information of *Vue Storefront API* application.
- `build` is path for build information. If the value is string, it's a plain path. When it's object, you may have a few options to add. `context` is relative path or git repo url where `Dockerfile` is located. `dockerfile` node may change the path/name of `Dockerfile`. [more info](https://docs.docker.com/compose/compose-file/#build)
-- `depends_on` tells us this container is based on `es1` and `redis` containers we created above.
-- `env_file` helps you add environment values from files. It's relative path from the `docker-compose` file that is in the process, in this case, it's `docker-compose.nodejs.yml`
-- `environment` is to set `VS_ENV` as `dev` so that environment will be setup for developer mode.
+- `depends_on` tells us this container is based on `es1` and `redis` containers we created above.
+- `env_file` helps you add environment values from files. It's relative path from the `docker-compose` file that is in the process, in this case, it's `docker-compose.nodejs.yml`
+- `environment` is to set `VS_ENV` as `dev` so that environment will be setup for developer mode.
- `tmpfs` denotes temporary volumes that are only available to host memory. Unlike `volumes`, this `tmpfs` will be gone once the container stops. This option is only available to *Linux*.
-The second `docker-compose` step handles **Vue Storefront** frontend.
+The second `docker-compose` step handles **Vue Storefront** frontend.
``` yaml
version: '2.0'
services:
@@ -343,32 +343,32 @@ services:
ports:
- '3000:3000'
```
-This looks like by and large the same with *Vue Storefront API* with a few changes.
+This looks like by and large the same with *Vue Storefront API* with a few changes.
`app` service describes options for *Vue Storefront* frontend application.
- `network_mode` allows you to modify values for `--network` option of docker client. `host` option allows your designated container to open to host network. For example, if you bind your container in host's `80` port, then the container will be accessible at host's `:80` from the internet. In other words, the container is not isolated. [more info](https://docs.docker.com/network/host/)
-If you take a closer look inside `Dockerfile`s, you will notice they install all the dependencies of the project from `package.json` not to mention required OS features including `git`, `wget` and certificates. You don't have to worry what to do because we made it do for you.
+If you take a closer look inside `Dockerfile`s, you will notice they install all the dependencies of the project from `package.json` not to mention required OS features including `git`, `wget` and certificates. You don't have to worry what to do because we made it do for you.
-Next, you might want to import your goods data. Please jump to [Data imports](./data-import.md) if you don't want to stop.
+Next, you might want to import your goods data. Please jump to [Data imports](./data-import.md) if you don't want to stop.
### 4. Chef's secret (protip)
#### Secret 1. Study in `local.json` for *Vue Storefront API*
-Starting point of customization is `default.json` or its copy `local.json` where the platform seeks configuration values.
+Starting point of customization is `default.json` or its copy `local.json` where the platform seeks configuration values.
:::tip NOTE
If you want to modify `default.json`, don't edit it directly but copy the whole file into `local.json` and start editing it in that file. Why it should be done that way is explained later at [Secret 3. Why use node-config?](#secret-3-why-use-node-config)
:::
-We have 2 `local.json` files, one of which is for backend here, and we will look at [Secret 2](#secret-2-study-in-local-json-for-vue-storefront), the other for frontend .
+We have 2 `local.json` files, one of which is for backend here, and we will look at [Secret 2](#secret-2-study-in-local-json-for-vue-storefront), the other for frontend .
-At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-storefront-api/blob/master/config/default.json) for **backend** :
+At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-storefront-api/blob/master/config/default.json) for **backend** :
```json
"server": {
"host": "localhost",
"port": 8080,
"searchEngine": "elasticsearch"
- },
+ },
```
-- This is where your API backend is defined. The server will listen `server.host`:`server.port` unless it's defined otherwise in environment variables.
+- This is where your API backend is defined. The server will listen `server.host`:`server.port` unless it's defined otherwise in environment variables.
- `server.searchEngine` is used in the integration with `graphql` so please don't change it. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/graphql/resolvers.js#L6)
```json
@@ -381,7 +381,7 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
```
- `orders.useServerQueue` allows you to use queue process when `order` API is used to create an order. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/order.js#L65)
-- `catalog.excludeDisabledProducts` allows you to skip disabled products when importing products using `mage2vs`.
+- `catalog.excludeDisabledProducts` allows you to skip disabled products when importing products using `mage2vs`.
[jump to code](https://github.com/DivanteLtd/mage2vuestorefront/blob/master/src/adapters/magento/product.js#L166)
```json
@@ -408,21 +408,21 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
"apiVersion": "5.6"
},
```
-- `elasticsearch` element is used widely across the whole platform. Considering `elasticsearch` works as a data store (database), it's natural.
+- `elasticsearch` element is used widely across the whole platform. Considering `elasticsearch` works as a data store (database), it's natural.
- - `host`, `port`, `protocol` defines `elasticsearch` connect information.
+ - `host`, `port`, `protocol` defines `elasticsearch` connect information.
- `user`, `password` is default credentials of `elasticsearch`. If you changed the credentials of `elasticsearch`, please change this accordingly. [more info](https://www.elastic.co/guide/en/x-pack/current/security-getting-started.html)
- `min_score` sets a `min_score` when building a query for `elasticsearch`. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/graphql/elasticsearch/queryBuilder.js#L172)
:::tip TIP
- `min_score` helps you exclude documents with `_score` less than `min_score` value.
+ `min_score` helps you exclude documents with `_score` less than `min_score` value.
:::
- - `indices` may contain one or multiple indexes. Each index acts as a data store for a storefront. You may add entries to the array with arbitrary names or remove entries from it.
+ - `indices` may contain one or multiple indexes. Each index acts as a data store for a storefront. You may add entries to the array with arbitrary names or remove entries from it.
:::warning CAUTION !
- However, the index name should match the one you will use for [data pump](data-import.md#_2-2-recipe-b-using-on-premise).
+ However, the index name should match the one you will use for [data pump](data-import.md#_2-2-recipe-b-using-on-premise).
:::
- The default values for `indices` assume you have 2 additional stores(`de`, `it`) plus the default store.
+ The default values for `indices` assume you have 2 additional stores(`de`, `it`) plus the default store.
- `indexTypes` contains values for mapping. You can consider it as `table` if you take `indices` as database.
- - `apiVersion` defines the `elasticsearch` version it uses.
+ - `apiVersion` defines the `elasticsearch` version it uses.
```json
"redis": {
@@ -441,7 +441,7 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
"it"
],
```
-- `availableStores` contains additional stores code name. If this value is an empty array, it means you only have one default store.
+- `availableStores` contains additional stores code name. If this value is an empty array, it means you only have one default store.
```json
"storeViews": {
@@ -506,38 +506,38 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
```
- `storeViews` element contains the whole information of ***additional*** stores. The default store information doesn't exist here, it exists on top level.
-- `multistore` is supposed to tell the platform if it has multiple stores to consider. For example, it is used to configure `tax` values of additional store. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L14)
+- `multistore` is supposed to tell the platform if it has multiple stores to consider. For example, it is used to configure `tax` values of additional store. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L14)
- `mapStoreUrlsFor` is used for building url routes in frontend. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/lib/multistore.ts#L85)
-- `de` element contains detailed information of `de` store. You need to have this kind of element for all the additional stores you added to `availableStores` with `storeCode` as the key. `de` and `it` in the `default.json` exhibits an example you can copy & paste for other stores you need to add.
- - `storeCode` denotes store code for the store.
+- `de` element contains detailed information of `de` store. You need to have this kind of element for all the additional stores you added to `availableStores` with `storeCode` as the key. `de` and `it` in the `default.json` exhibits an example you can copy & paste for other stores you need to add.
+ - `storeCode` denotes store code for the store.
- `storeId` denotes store ID of the store.
- `name` denotes the store name.
- `url` denotes URL for the store.
- - `elasticsearch` contains information for the store. This information may override the default one defined above.
+ - `elasticsearch` contains information for the store. This information may override the default one defined above.
- `host` is where your *Elasticsearch* listens on.
- `index` is the name of the index for the store.
- `tax` contains tax information of the store.
- - `defaultCountry` is the code name of the country on which tax is calculated for the store.
+ - `defaultCountry` is the code name of the country on which tax is calculated for the store.
- `defaultRegion` is default region.
- `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/product.js#L48)
- `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12)
- - `i18n` connotes *internationalization*. [more info](https://en.wikipedia.org/wiki/Internationalization_and_localization)
+ - `i18n` connotes *internationalization*. [more info](https://en.wikipedia.org/wiki/Internationalization_and_localization)
- `fullCountryName` is the full name of the country this `i18n` is applied to.
- `fullLanguageName` is the full name of the language this `i18n` is applied to.
- `defaultCountry` is the abbreviated name of the country this `i18n` is applied to by default.
- `defaultLanguage` is the abbreviated name of the language this `i18n` is applied to by default.
- - `defaultLocale` is the default locale this `i18n` uses.
- - `currencyCode` is the currency code this store uses.
- - `currencySign` is the currency sign this store uses.
- - `dateFormat` is the date format this store uses.
-
-
+ - `defaultLocale` is the default locale this `i18n` uses.
+ - `currencyCode` is the currency code this store uses.
+ - `currencySign` is the currency sign this store uses.
+ - `dateFormat` is the date format this store uses.
+
+
```json
"authHashSecret": "__SECRET_CHANGE_ME__",
"objHashSecret": "__SECRET_CHANGE_ME__",
```
-- `authHashSecret` is used to encode & decode JWT for API use.
-- `objHashSecret` is 1) fallback secret hash for `authHashSecret`, 2) used for hashing in tax calculation.
+- `authHashSecret` is used to encode & decode JWT for API use.
+- `objHashSecret` is 1) fallback secret hash for `authHashSecret`, 2) used for hashing in tax calculation.
```json
"cart": {
@@ -553,20 +553,20 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
"sourcePriceIncludesTax": false
},
```
-- `cart`
+- `cart`
- `setConfigurableProductOptions` flag determines to show either the parent item or the child item (aka selected option item) in the cart context. `true` shows parent item instead of the option item selected. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/platform/magento2/o2m.js#L94)
- `tax`
- `alwaysSyncPlatformPricesOver` [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/order.js#L49)
- - `usePlatformTotals`
- These two options are used to determine whether to fetch prices from data source on the fly or not. If you set `alwaysSyncPlatformPricesOver` true, then it skips checking the checksum for cart items based on price.
-
+ - `usePlatformTotals`
+ These two options are used to determine whether to fetch prices from data source on the fly or not. If you set `alwaysSyncPlatformPricesOver` true, then it skips checking the checksum for cart items based on price.
+
```json
"bodyLimit": "100kb",
"corsHeaders": [
"Link"
],
```
-- `bodyLimit` limits how big a request can be for your application.
+- `bodyLimit` limits how big a request can be for your application.
- `corsHeaders` allows you to add entries to `Access-Control-Expose-Headers`
```json
@@ -606,26 +606,26 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
- `mailchimp` provides `POST`, `DELETE` APIs for *Mailchimp* `subscribe` method.
- `listId` is the ID of list you are publishing.
- `apiKey` is API key you are assigned.
- - `apiUrl` is API base url for *Mailchimp* service.
+ - `apiUrl` is API base url for *Mailchimp* service.
- `mailService` is used to send emails from Vue Storefront via *Gmail*.
- `transport` contains basic information for *Gmail* service.
- `host` is where your mail is sent en route.
- `port` is the port number used for the service.
- - `secure` determines to use SSL connection.
+ - `secure` determines to use SSL connection.
- `user` is `username` for the service.
- `pass` is `password` for the service.
- - `targetAddressWhitelist` checks if an user confirmed his/her email address *and* source email is white-listed.
- - `secretString` is used for hashing.
+ - `targetAddressWhitelist` checks if an user confirmed his/her email address *and* source email is white-listed.
+ - `secretString` is used for hashing.
```json
"magento2": {
- "url": "http://demo-magento2.vuestorefront.io/",
- "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product",
- "assetPath": "/../var/magento2-sample-data/pub/media",
- "magentoUserName": "",
- "magentoUserPassword": "",
- "httpUserName": "",
- "httpUserPassword": "",
+ "url": "http://demo-magento2.vuestorefront.io/",
+ "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product",
+ "assetPath": "/../var/magento2-sample-data/pub/media",
+ "magentoUserName": "",
+ "magentoUserPassword": "",
+ "httpUserName": "",
+ "httpUserPassword": "",
"api": {
"url": "http://demo-magento2.vuestorefront.io/rest",
"consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq",
@@ -650,26 +650,26 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
}
},
```
-- `magento2` is used to integrate with Magento 2 as a data source.
-
+- `magento2` is used to integrate with Magento 2 as a data source.
+
- `imgUrl` is base image url. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/api/img.js#L38)
-
+
- `assetPath` is used for the `media` path. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/index.js#L22)
-
+
- `api` contains API credentials for integration.
-
+
- `url` is base url for Magento 2 instance.
- `consumerKey` See **TIP**
- `consumerSecret`
- `accessToken`
- `accessTokenSecret`
-
-
-
+
+
+
:::tip TIP
-
+
These 4 nodes above is the required credentials for integration with Magento 2. [how to get them](data-import.html#_2-2-recipe-b-using-on-premise)
-
+
:::
`magento1` has just the same structure with `magento2`.
@@ -678,7 +678,7 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
```json
"imageable": {
- "namespace": "",
+ "namespace": "",
"maxListeners": 512,
"imageSizeLimit": 1024,
"whitelist": {
@@ -698,44 +698,44 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
"process": 4
},
"simd": true,
- "keepDownloads": true
+ "keepDownloads": true
},
```
-- `imageable` deals with everything you need to configure when it comes to your storefront images, especially product images.
-
+- `imageable` deals with everything you need to configure when it comes to your storefront images, especially product images.
+
- `maxListeners` limits maximum listeners to request's socket. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/img.js#L21)
- `imageSizeLimit` limits maximum image size. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/img.js#L56)
- `whitelist` contains a white-list of image source domains
-
+
- `allowedHosts` contains the array of white-list
-
+
:::warning DON'T FORGET
-
+
You should include your source domain in `allowedHosts` or your request for product images will fail. [more info](data-import.html#secret-1-product-image-is-not-synced)
-
+
:::
-
+
:::tip NOTE
-
- From `cache` to `simd` they are used to configure [Sharp](https://github.com/lovell/sharp) library. *Sharp* is a popular library for image processing in *Node.js*. [jump to option docs](https://sharp.dimens.io/en/stable/api-utility/#cache)
-
+
+ From `cache` to `simd` they are used to configure [Sharp](https://github.com/lovell/sharp) library. *Sharp* is a popular library for image processing in *Node.js*. [jump to option docs](https://sharp.dimens.io/en/stable/api-utility/#cache)
+
:::
-
- - `cache` limits `libvips` operation cache from *Sharp*. Values hereunder are default values. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/lib/image.js#L5)
-
- - `memory` is the maximum memory in MB to use for the cache.
- - `files` is the maximum number of files to hold open.
- - `items` is the maximum number of operations to cache.
-
- - `concurrency` is the number of threads for processing each image.
-
- - `counters` provides access to internal task counters.
-
- - `queue` is the number of tasks in queue for *libuv* to provide a worker thread.
- - `process` limits the number of resize tasks concurrently processed.
-
- - `simd` to use SIMD vector unit of the CPU in order to enhance the performance.
-
+
+ - `cache` limits `libvips` operation cache from *Sharp*. Values hereunder are default values. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/lib/image.js#L5)
+
+ - `memory` is the maximum memory in MB to use for the cache.
+ - `files` is the maximum number of files to hold open.
+ - `items` is the maximum number of operations to cache.
+
+ - `concurrency` is the number of threads for processing each image.
+
+ - `counters` provides access to internal task counters.
+
+ - `queue` is the number of tasks in queue for *libuv* to provide a worker thread.
+ - `process` limits the number of resize tasks concurrently processed.
+
+ - `simd` to use SIMD vector unit of the CPU in order to enhance the performance.
+
```json
@@ -764,13 +764,13 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
}
},
```
-- `entities` is used to integrate with *GraphQL* in **Vue Storefront API**.
+- `entities` is used to integrate with *GraphQL* in **Vue Storefront API**.
- `category`
- `includeFields` contains an array of fields to be added as `sourceInclude` [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/graphql/elasticsearch/category/resolver.js#L10)
- `product`
- `filterFieldMapping` adds a field mapping to apply a filter in a query [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/graphql/elasticsearch/mapping.js#L19)
- - `category.name`
-
+ - `category.name`
+
```json
"usePriceTiers": false,
"boost": {
@@ -783,17 +783,17 @@ At [`vue-storefront-api/config/default.json`](https://github.com/DivanteLtd/vue-
}
```
- `usePriceTiers` determines whether to use price tiers for customers in groups
-- `boost` is used to give weighted values to fields for a query to *Elasticsearch*, the bigger, the heavier.
+- `boost` is used to give weighted values to fields for a query to *Elasticsearch*, the bigger, the heavier.
- `name` field has the value *3* so that matching query with the `name` has the highest priority.
- - `category.name` ,`short_description`, `description`, `sku`, `configurable_children.sku ` the rest of fields have the default value; 1.
-
+ - `category.name` ,`short_description`, `description`, `sku`, `configurable_children.sku ` the rest of fields have the default value; 1.
+
#### Secret 2. Study in `local.json` for *Vue Storefront*
-At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-storefront/blob/master/config/default.json) for **frontend** :
+At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-storefront/blob/master/config/default.json) for **frontend** :
```json
"server": {
@@ -816,43 +816,43 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
```
- `server` contains information of various features related to *frontend* server.
-
- - `host` is the host address in which your *Vue Storefront* instance starts at.
-
- - `port` is the port number in which your *Vue Storefront* instance listens to.
-
+
+ - `host` is the host address in which your *Vue Storefront* instance starts at.
+
+ - `port` is the port number in which your *Vue Storefront* instance listens to.
+
- `protocol` is used for *GraphQL* integration. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/lib/search/adapter/graphql/searchAdapter.ts#L48)
-
+
- `api` determines API mode between `api` and `graphql`. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/resolvers/resolveGraphQL.js#L7)
-
+
:::tip TIP
-
- You may take a look at [*GraphQL Action Plan*](/guide/basics/graphql.html) guide to help yourself make a decision which mode you should take.
+
+ You may take a look at [*GraphQL Action Plan*](/guide/basics/graphql.html) guide to help yourself make a decision which mode you should take.
:::
-
+
- `devServiceWorker` enables *service worker* in `develop` mode. The *service worker* is normally enabled by default for `production` mode, but not for `develop` mode. Setting this flag *true* forces to use *service worker* in `develop` mode too. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/service-worker/registration.js#L5)
:::tip TIP
-
- You may take a look at [Working with Service Workers](/guide/core-themes/service-workers.html) for better understanding.
- :::
-
+
+ You may take a look at [Working with Service Workers](/guide/core-themes/service-workers.html) for better understanding.
+ :::
+
- `useOutputCacheTagging` determines to allow *Output Cache Tags*. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/server.js#L168)
-
+
- `useOutputCache` determines to allow *Output Cache*. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/server.js#L64)
-
+
- `outputCacheDefaultTtl` defines the default timeout for *Redis Tag Cache*. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/utils/cache-instance.js#L16)
-
+
- `availableCacheTags` contains a list of available cache tags. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/cache.js#L7)
-
+
- `invalidateCacheKey` is the key used for checking validity of invalidation. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/server.js#L66)
:::tip TIP
-
+
You may take a look at [SSR Cache](/guide/basics/ssr-cache.html) in order to grab the idea of *Output Cache* in *Vue Storefront*
:::
-
+
- `dynamicConfigReload` enables to reload `config.json` on the fly with each server request. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/server.js#L232)
- `dynamicConfigContinueOnError` allows to skip errors during configuration merge on the fly. [jump to code](https://github.com/DivanteLtd/vue-storefront/blob/master/core/scripts/server.js#L240)
- - `dynamicConfigExclude`
+ - `dynamicConfigExclude`
- `dynamicConfigInclude`
- `elasticCacheQuota`
@@ -860,7 +860,7 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
```json
"seo": {
"useUrlDispatcher": true
-},
+},
"console": {
"showErrorOnProduction" : true,
"verbosityLevel": "display-everything"
@@ -905,9 +905,9 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
"cutoff_frequency": 0.01,
"max_expansions": 3,
"minimum_should_match": "75%",
- "prefix_length": 2,
+ "prefix_length": 2,
"boost_mode": "multiply",
- "score_mode": "multiply",
+ "score_mode": "multiply",
"max_boost": 100,
"function_min_score": 1
},
@@ -939,7 +939,7 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
"useInitialStateFilter": true
},
```
-- `ssr`
+- `ssr`
- `templates`
- `default`
@@ -1229,7 +1229,7 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
},
```
- `tax`: ...
- - `defaultCountry` is the code name of the country on which tax is calculated for the store.
+ - `defaultCountry` is the code name of the country on which tax is calculated for the store.
- `defaultRegion` is default region.
- `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12)
- `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/DivanteLtd/vue-storefront-api/blob/master/src/api/product.js#L48)
@@ -1258,6 +1258,9 @@ At [`vue-storefront/config/default.json`](https://github.com/DivanteLtd/vue-stor
"defaultLocale": "en-US",
"currencyCode": "USD",
"currencySign": "$",
+ "currencyDecimal": null,
+ "currencyGroup": null,
+ "fractionDigits": 2,
"priceFormat": "{sign}{amount}",
"dateFormat": "HH:mm D/M/YYYY",
"fullCountryName": "United States",
@@ -1356,58 +1359,58 @@ Sometimes we need to know the inside of the perfect machine so that we can prepa
-## 4. Storefront CLI at your service
-Upon the release of 1.10, we also present a new way of setup and all its sorts from `CLI` which is the all-time most favorite tool of developers worldwide if I must say. There are lots of benefits when `CLI` methods are available such as automation in scripts in cooperation with other automation tools out there.
+## 4. Storefront CLI at your service
+Upon the release of 1.10, we also present a new way of setup and all its sorts from `CLI` which is the all-time most favorite tool of developers worldwide if I must say. There are lots of benefits when `CLI` methods are available such as automation in scripts in cooperation with other automation tools out there.
-We will continuously add new features to [`CLI`](https://www.npmjs.com/package/@vue-storefront/cli) as the version goes up.
+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 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 :
+1. Install _Vue Storefront CLI_ package on your machine with `-g` flag as follows :
```bash
npm install -g @vue-storefront/cli@0.0.15
```
:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/ZK0BVF7cQ8OaHHRcsaZgcOCfN)
-2. Now go to any random folder you want to install a _Vue Storefront_ app under, and run the following :
+2. Now go to any random folder you want to install a _Vue Storefront_ app under, and run the following :
```bash
vsf init
```
-3. You will encounter a series of questions to install the app, first of which is as follows :
+3. You will encounter a series of questions to install the app, first of which is as follows :
```bash
✔ Check avalilable versions
-? Which version of Vue Storefront you'd like to install?
-❯ Stable versions (recommended for production)
- Release Candidates
+? Which version of Vue Storefront you'd like to install?
+❯ Stable versions (recommended for production)
+ Release Candidates
In development branches (could be unstable!)
```
Select an option based on which you are to install.
-4. Next question is about specific version to be installed as follows :
+4. Next question is about specific version to be installed as follows :
```bash
-? Select specific version
- v1.8.0
-❯ v1.10.0
- v1.9.2
- v1.9.1
- v1.9.0
- v1.8.5
- v1.8.4
+? Select specific version
+ v1.8.0
+❯ v1.10.0
+ v1.9.2
+ v1.9.1
+ v1.9.0
+ v1.8.5
+ v1.8.4
```
Choose a version of your target.
-5. Next question is about how you install it between `installer`/`manual` like below :
+5. Next question is about how you install it between `installer`/`manual` like below :
```bash
-? Would you like to use friendly installer or install Vue Storefront manually?
-❯ Installer (MacOS/Linux only)
+? Would you like to use friendly installer or install Vue Storefront manually?
+❯ Installer (MacOS/Linux only)
Manual installation
```
-Let's pick the `Installer` option for now.
+Let's pick the `Installer` option for now.
-Then you will see the machine start working on installation :
+Then you will see the machine start working on installation :
```bash
? Would you like to use friendly installer or install Vue Storefront manually? Installer (MacOS/Linux only)
✔ Copying Vue Storefront files
@@ -1415,7 +1418,7 @@ Then you will see the machine start working on installation :
Running installer
```
-6. Once the preparation is finished then another series of questions pops up as `installer` is associated with as follows :
+6. Once the preparation is finished then another series of questions pops up as `installer` is associated with as follows :
```bash
yarn run v1.17.3
$ node ./core/scripts/installer
@@ -1427,20 +1430,20 @@ $ node ./core/scripts/installer
? Would you like to use https://demo.vuestorefront.io as the backend? (Y/n)
```
-From this on, the questions would be the same as installation through `installer`.
+From this on, the questions would be the same as installation through `installer`.
You can follow it further at [Install using installer](#_2-using-installer)
7. Once the questions have been answered then the remaining process is taken to action. You will see a screen as follows when they finished :
```bash
? Please provide path for images endpoint https://demo.vuestorefront.io/img/
- Trying to create log files...
+ Trying to create log files...
- Creating storefront config 'config/local.json'...
+ Creating storefront config 'config/local.json'...
- Build storefront npm...
+ Build storefront npm...
- Starting storefront server...
+ Starting storefront server...
┌────────────────────────────────────────────────────┐
│ Congratulations! │
@@ -1463,10 +1466,10 @@ You can follow it further at [Install using installer](#_2-using-installer)
![home_borderline](../images/home-vuestorefront.png)
-Congratulation!
+Congratulation!
### 3. Peep into the kitchen (what happens internally)
-_Vue Storefront_ people prepared the `CLI` way of installing the whole infrastructure for your _Vue Storefront_ app provided as an `npm` package. It's now as easy as to install an `npm` package on any machine. Installed then run a command with a few options would be more than enough for the app to be up and running. Believe me your next _Vue Storefront_ app will be with you instantly with a breeze as long as `CLI` is accessible.
+_Vue Storefront_ people prepared the `CLI` way of installing the whole infrastructure for your _Vue Storefront_ app provided as an `npm` package. It's now as easy as to install an `npm` package on any machine. Installed then run a command with a few options would be more than enough for the app to be up and running. Believe me your next _Vue Storefront_ app will be with you instantly with a breeze as long as `CLI` is accessible.
### 4. Chef's secret (protip)
#### Secret 1. Install with _manual_ path
@@ -1480,7 +1483,7 @@ _Vue Storefront_ people prepared the `CLI` way of installing the whole infrastru
## 5. How to debug *Anything*
-When it comes to developing a software, there is one thing you really need to know. Knowing the blocker. Fixing a problem means you already know what's going on before what went wrong, and most of the time your pains are from yourself failing at that, which is, you don't know the problem and that is the problem.
+When it comes to developing a software, there is one thing you really need to know. Knowing the blocker. Fixing a problem means you already know what's going on before what went wrong, and most of the time your pains are from yourself failing at that, which is, you don't know the problem and that is the problem.
### 1. Preparation
### 2. Recipe
### 3. Peep into the kitchen (what happens internally)
diff --git a/docs/guide/data/entity-types.md b/docs/guide/data/entity-types.md
index 975371d74..71db8f852 100644
--- a/docs/guide/data/entity-types.md
+++ b/docs/guide/data/entity-types.md
@@ -38,7 +38,7 @@ searchAdapter.registerEntityTypeByQuery('testentity', {
throw new Error(JSON.stringify(resp.error));
} else {
throw new Error(
- "Unknown error with graphQl result in resultPorcessor for entity type 'category'",
+ "Unknown error with graphQl result in resultProcessor for entity type 'category'",
);
}
}
diff --git a/docs/guide/general/introduction.md b/docs/guide/general/introduction.md
index ffde3c29b..31f9e4a5f 100644
--- a/docs/guide/general/introduction.md
+++ b/docs/guide/general/introduction.md
@@ -86,7 +86,11 @@ The mechanism of injecting core business logic into themes is ridiculously simpl
So assume we have a core Microcart component with business logic as above (left side), we can easily inject it into any of our theme components (right side) just by importing it and adding as a mixin `mixins: [Microcart]`. This is all you need to make use of core business logic inside your theme. With this approach, we can easily ship updates to all core components without breaking your shop.
-The easiest way to create your own theme is to create a copy of the default one, change its name in its `package.json` file, change the active theme in `config/local.json` and run `yarn` to make [Lerna](https://github.com/lerna/lerna) linking (which we use for monorepos).
+The easiest way to create your own theme is to create a copy from one of our official themes, change its name in its `package.json` file, change the active theme in `config/local.json` and run `yarn` to make [Lerna](https://github.com/lerna/lerna) linking (which we use for monorepos).
+
+Our official themes:
+- Capybara - https://github.com/DivanteLtd/vsf-capybara
+- Default - https://github.com/DivanteLtd/vsf-default
## Offline mode and cache
Vue Storefront still works even while the user is offline.
diff --git a/docs/guide/installation/windows.md b/docs/guide/installation/windows.md
index 152f862d2..87bf20f10 100644
--- a/docs/guide/installation/windows.md
+++ b/docs/guide/installation/windows.md
@@ -77,24 +77,6 @@ yarn install
```
5. Copy `config/default.json` to `config/local.json`
-6. Images: because `vue-storefront-api` uses `imagemagick` and some nodejs command line bindings, it can be difficult to run the image proxy on a localhost/Windows machine. Please point out the `vue-storefront` to image proxy provided by changing `config/local.json` `images.baseUrl`:
-
-```js
-export default {
- elasticsearch: {
- httpAuth: '',
- host: 'localhost:8080/api/catalog',
- index: 'vue_storefront_catalog',
- },
- // we have vue-storefront-api (https://github.com/DivanteLtd/vue-storefront-api) endpoints below:
- orders: {
- endpoint: 'localhost:8080/api/order/create',
- },
- images: {
- baseUrl: 'https://demo.vuestorefront.io/img/',
- },
-};
-```
:::tip NOTE
We're using the powerful node.js library for config files. Check the docs to learn more about it: [https://github.com/lorenwest/node-config](https://github.com/lorenwest/node-config)
diff --git a/docs/guide/upgrade-notes/README.md b/docs/guide/upgrade-notes/README.md
index 5034414a4..6d2643af9 100644
--- a/docs/guide/upgrade-notes/README.md
+++ b/docs/guide/upgrade-notes/README.md
@@ -2,6 +2,73 @@
We're trying to keep the upgrade process as easy as possible. Unfortunately, sometimes manual code changes are required. Before pulling out the latest version, please take a look at the upgrade notes below:
+## 1.11 -> 1.12
+
+Most of the changes added to 1.12 are backward compatible. To enable the new features (mostly the optimization features) please follow the steps described below.
+
+**Remove bodybuilder and compact API responses**
+
+The new search adapter `api-search-query` has been added. When you switch to it, by setting the `config.server.api = "api-search-query"` the ElasticSearch query is being built in the [`vue-storefront-api`](https://github.com/DivanteLtd/vue-storefront-api/pull/390) which saves around 400kB in the bundle size as `bodybuilder` is no longer needed in the frontend.
+
+This new `api-search-query` adapter supports the `response_format` query parameter which now is sent to the `/api/catalog` endpoint. Currently there is just one additional format supported: `response_format=compact`. When used, the response format got optimized by: a) remapping the results, removing the `_source` from the `hits.hits`; b) compressing the JSON fields names according to the `config.products.fieldsToCompact`; c) removing the JSON fields from the `product.configurable_children` when their values === parent product values; overall response size reduced over -70%.
+
+**Re-enable amp-renderer**
+
+The `amp-renderer` module has been disabled by default to save the bundle size; If you'd like to enable it uncomment the module from the `src/modules` and uncomment the `product-amp` and `category-amp` links that are added to the `` section in the `src/themes/default/Product.vue` and `src/themes/default/Category.vue`
+
+**Check entity optimization settings**
+
+Cart optimization was earlier disabled automatically if entity optimization was disabled. Now they can be used independently from each other. If you don't want to use cart optimization, make sure that the `entities.optimizeShoppingCart` configuration entry is disabled explicitly.
+
+**deprecated actions and helpers**
+Product module has been refactored, here is list of actions that are not used anymore and you can remove them to reduce bundle.
+deprecated actions:
+product/reset
+product/setupBreadcrumbs
+product/syncPlatformPricesOver
+product/setupAssociated
+product/loadConfigurableAttributes
+product/setupVariants
+product/filterUnavailableVariants
+product/list
+product/preConfigureAssociated
+product/preConfigureProduct
+product/configureLoadedProducts
+product/configureBundleAsync
+product/configureGroupedAsync
+product/configure
+product/setCurrentOption
+product/setCurrentErrors
+product/setOriginal
+product/loadProductAttributes
+category/list (new action is category-next/fetchMenuCategories)
+
+deprecated helpers:
+configureProductAsync
+populateProductConfigurationAsync
+setConfigurableProductOptionsAsync
+
+Here is list of actions that are used from 1.12 in product module:
+product/doPlatformPricesSync
+product/single
+product/checkConfigurableParent
+product/findProducts
+product/findConfigurableParent
+product/setCustomOptions
+product/setBundleOptions
+product/setCurrent
+product/loadProduct
+product/addCustomOptionValidator
+product/setProductGallery
+product/loadProductBreadcrumbs
+product/getProductVariant
+
+All of those actions and helpers that are deprecated, can be removed so you will have smaller bundle.
+Comment those lines:
+- core/modules/catalog/store/product/actions.ts:318
+- core/modules/catalog/helpers/index.ts:14-18
+- core/modules/catalog-next/store/category/actions.ts:265
+
## 1.10 -> 1.11
This is the last major release of Vue Storefront 1.x before 2.0 therefore more manual updates are required to keep external packages compatible with 1.x as long as possible.
diff --git a/docs/package.json b/docs/package.json
index f1a99307a..6f2c15ddb 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,7 +1,7 @@
{
"name": "@vue-storefront/docs",
"private": true,
- "version": "1.11.4",
+ "version": "1.12.0",
"scripts": {
"docs:dev": "vuepress dev",
"docs:build": "vuepress build",
diff --git a/package.json b/package.json
old mode 100755
new mode 100644
index 5d319408d..21a120e94
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vue-storefront",
- "version": "1.11.4",
+ "version": "1.12.0",
"description": "A Vue.js, PWA eCommerce frontend",
"private": true,
"engines": {
@@ -28,19 +28,20 @@
"scripts": {
"static-server": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/static-server.ts",
"generate": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/generate.ts",
+ "generate-files": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/generate-files.ts",
"start": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" pm2 start ecosystem.json $PM2_ARGS",
"start:inspect": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server",
"installer": "node ./core/scripts/installer",
"installer:ci": "yarn installer --default-config",
"all": "cross-env NODE_ENV=development node ./core/scripts/all",
"cache": "node ./core/scripts/cache",
- "dev": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/server.ts",
- "dev:sw": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" yarn build:sw && yarn dev",
- "dev:inspect": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server",
+ "dev": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/server.ts",
+ "dev:sw": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" yarn build:sw && yarn dev",
+ "dev:inspect": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server",
"build:sw": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.sw.config.ts --mode production --progress --hide-modules",
"build:client": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.client.config.ts --mode production --progress --hide-modules",
"build:server": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.server.config.ts --mode production --progress --hide-modules",
- "build": "rimraf dist && yarn build:client && yarn build:server && yarn build:sw",
+ "build": "rimraf dist && yarn generate-files && npm-run-all -p build:*",
"test:unit": "jest -c test/unit/jest.conf.js",
"test:unit:watch": "jest -c test/unit/jest.conf.js --watch",
"test:e2e": "cypress open",
@@ -78,6 +79,7 @@
"redis-tag-cache": "^1.2.1",
"reflect-metadata": "^0.1.12",
"register-service-worker": "^1.5.2",
+ "storefront-query-builder": "https://github.com/DivanteLtd/storefront-query-builder.git",
"ts-node": "^8.6.2",
"vue": "^2.6.11",
"vue-analytics": "^5.16.1",
@@ -98,10 +100,10 @@
"vuex-router-sync": "^5.0.0"
},
"devDependencies": {
- "@babel/core": "^7.8.6",
+ "@babel/core": "^7.9.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/polyfill": "^7.8.3",
- "@babel/preset-env": "^7.8.6",
+ "@babel/preset-env": "^7.9.0",
"@types/jest": "^25.1.3",
"@types/node": "^13.7.7",
"@typescript-eslint/eslint-plugin": "^1.7.1-alpha.17",
@@ -146,6 +148,7 @@
"lint-staged": "^8.2.1",
"mkdirp": "^0.5.1",
"node-sass": "^4.12.0",
+ "npm-run-all": "^4.1.5",
"phantomjs-prebuilt": "^2.1.10",
"postcss-flexbugs-fixes": "^4.1.0",
"postcss-loader": "^3.0.0",
diff --git a/packages/cli/boilerplates/module/package.json b/packages/cli/boilerplates/module/package.json
index 7f1745975..bcaa84869 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.1",
+ "@vue-storefront/core": "^1.12.0",
"ts-loader": "^6.0.4",
"typescript": "^3.5.2",
"webpack": "^4.35.2",
"webpack-cli": "^3.3.11"
},
"peerDependencies": {
- "@vue-storefront/core": "^1.11.1"
+ "@vue-storefront/core": "^1.12.0"
}
}
diff --git a/packages/cli/index.js b/packages/cli/index.js
index cece1378d..e2dc45e53 100755
--- a/packages/cli/index.js
+++ b/packages/cli/index.js
@@ -9,11 +9,13 @@ switch (command) {
case 'init:module':
require('./scripts/generateModule.js')(process.argv[3])
break;
+ case '-h':
case '--help':
require('./scripts/manual.js')()
break;
+ case '-v':
case '--version':
- console.log('v' + require('../package.json').version)
+ console.log('v' + require('./package.json').version)
break;
default:
console.log('Unknown command. try one of those:\n')
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 3fe495996..433d0b6ec 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -17,7 +17,8 @@
"fs-extra": "^8.1.0",
"inquirer": "^6.3.1",
"listr": "^0.14.3",
+ "lodash": "^4.17.15",
"replace-in-file": "^4.1.1",
- "semver-sort": "^0.0.4"
+ "semver": "^7.1.3"
}
}
diff --git a/packages/cli/scripts/install.js b/packages/cli/scripts/install.js
index f1e1040ee..6d3192184 100644
--- a/packages/cli/scripts/install.js
+++ b/packages/cli/scripts/install.js
@@ -5,7 +5,11 @@ const Listr = require('listr')
const execa = require('execa')
const spawn = require('child_process')
const fs = require('fs')
-const semverSort = require('semver-sort')
+const semverSortDesc = require('semver/functions/rsort')
+const semverSatisfies = require('semver/functions/satisfies')
+const semverCoerce = require('semver/functions/coerce')
+const semverInc = require('semver/functions/inc')
+const merge = require('lodash/merge')
module.exports = function (installationDir) {
installationDir = installationDir || 'vue-storefront'
@@ -17,9 +21,9 @@ module.exports = function (installationDir) {
const options = {
version: {
- stable: 'Stable versions (recommended for production)',
- rc: 'Release Candidates',
- nightly: 'In development branches (could be unstable!)'
+ stable: 'Stable version (recommended for production)',
+ rc: 'Release Candidate',
+ nightly: 'In development branch (could be unstable!)'
},
installation: {
installer: 'Installer (MacOS/Linux only)',
@@ -27,6 +31,24 @@ module.exports = function (installationDir) {
}
}
+ const themes = {
+ capybara: {
+ label: 'Capybara - based on Storefront UI',
+ branches: {
+ master: options.version.stable,
+ develop: options.version.nightly
+ },
+ minVsfVersion: '^1.11.0'
+ },
+ default: {
+ label: 'Default',
+ branches: {
+ master: options.version.stable
+ },
+ minVsfVersion: '*'
+ }
+ }
+
const tasks = {
installDeps: {
title: 'Installing dependencies',
@@ -38,23 +60,79 @@ module.exports = function (installationDir) {
return execa.shell(`git clone --quiet --single-branch --branch ${answers.specificVersion} https://github.com/DivanteLtd/vue-storefront.git ${installationDir} && cd ${installationDir}/core/scripts && git remote rm origin`)
}
},
+ cloneTheme: {
+ title: 'Copying Vue Storefront theme',
+ task: answers => execa.shell([
+ `git clone --quiet --single-branch --branch ${answers.themeBranch} https://github.com/DivanteLtd/vsf-${answers.themeName}.git ${installationDir}/src/themes/${answers.themeName}`,
+ `cd ${installationDir}/src/themes/${answers.themeName}`,
+ `git remote rm origin`
+ ].join(' && ')),
+ skip: answers => {
+ if (fs.existsSync(`${installationDir}/src/themes/${answers.themeName}`)) {
+ return `Chosen theme already exists in Vue Storefront installation directory ./${installationDir}/src/themes/`
+ }
+ }
+ },
+ configureTheme: {
+ title: 'Configuring Vue Storefront theme',
+ task: answers => {
+ const configurationFiles = ['local.config.js', 'local.json']
+ const [themeLocalConfigJsPath, themeLocalJsonPath] = configurationFiles.map(
+ file => `${installationDir}/src/themes/${answers.themeName}/${file}`
+ )
+ const vsfLocalJsonPath = `${installationDir}/config/local.json`
+ const vsfPackageJsonPath = `${installationDir}/package.json`
+
+ try {
+ const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion)
+ const vsfVersionFromPackageJson = JSON.parse(fs.readFileSync(vsfPackageJsonPath)).version
+ const vsfVersion = isVsfVersionAsBranch
+ ? semverInc(vsfVersionFromPackageJson, 'minor')
+ : vsfVersionFromPackageJson
+
+ const vsfLocalJson = fs.existsSync(vsfLocalJsonPath)
+ ? JSON.parse(fs.readFileSync(vsfLocalJsonPath))
+ : {}
+
+ const themeLocalJson = fs.existsSync(themeLocalConfigJsPath)
+ ? require(fs.realpathSync(themeLocalConfigJsPath))(vsfVersion)
+ : fs.existsSync(themeLocalJsonPath)
+ ? JSON.parse(fs.readFileSync(themeLocalJsonPath))
+ : null
+
+ if (themeLocalJson) {
+ fs.writeFileSync(vsfLocalJsonPath, JSON.stringify(merge(vsfLocalJson, themeLocalJson), null, 2))
+ }
+ } catch (e) {
+ console.error(`Problem with parsing or merging configurations (${configurationFiles})\n`, e)
+ }
+ },
+ skip: answers => {
+ const configurationFiles = ['local.config.js', 'local.json']
+ const themePath = `${installationDir}/src/themes/${answers.themeName}`
+
+ if (configurationFiles.every(file => !fs.existsSync(`${themePath}/${file}`))) {
+ return `Missing configuration file in theme folder (${configurationFiles}) - nothing to configure`
+ }
+ }
+ },
runInstaller: {
title: 'Running installer',
- task: () => spawn.execFileSync('yarn', ['installer'], {stdio: 'inherit', cwd: installationDir})
+ task: () => spawn.execFileSync('yarn', ['installer'], { stdio: 'inherit', cwd: installationDir })
},
getStorefrontVersions: {
- title: 'Check avalilable versions',
+ title: 'Check available versions',
task: () => execa.stdout('git', ['ls-remote', '--tags', 'https://github.com/DivanteLtd/vue-storefront.git']).then(result => {
- allTags = result.match(/refs\/tags\/v1.([0-9.]+)(-rc.[0-9])?/gm).map(tag => tag.replace('refs/tags/', ''))
- allTags = semverSort.desc(allTags)
- execa.stdout('git', ['ls-remote', '--heads', 'https://github.com/DivanteLtd/vue-storefront.git']).then(branches => {
- let rcBranches = branches.match(/refs\/heads\/release\/v1.([0-9.]+)/gm).map(tag => tag.replace('refs/heads/', ''))
- availableBranches = [...rcBranches, ...availableBranches]
- })
+ allTags = result.match(/refs\/tags\/v1.([0-9.]+)(-rc.[0-9])?/gm).map(tag => tag.replace('refs/tags/', ''))
+ allTags = semverSortDesc(allTags)
+ execa.stdout('git', ['ls-remote', '--heads', 'https://github.com/DivanteLtd/vue-storefront.git']).then(branches => {
+ let rcBranches = branches.match(/refs\/heads\/release\/v1.([0-9.x]+)/gm).map(tag => tag.replace('refs/heads/', ''))
+ availableBranches = [...rcBranches, ...availableBranches]
+ })
}).catch(e => {
- console.error('Problem with checking versions', e)
+ console.error('Problem with checking versions\n', e)
})
- },
+ }
}
if (fs.existsSync(installationDir)) {
@@ -81,10 +159,36 @@ module.exports = function (installationDir) {
message: 'Select specific version',
choices: function (answers) {
if (answers.version === options.version.stable) return allTags.filter(tag => !tag.includes('rc')).slice(0, 10)
- if (answers.version === options.version.rc) return allTags.filter(tag => tag.includes('rc')).slice(0,5)
+ if (answers.version === options.version.rc) return allTags.filter(tag => tag.includes('rc')).slice(0, 5)
return availableBranches
}
},
+ {
+ type: 'list',
+ name: 'themeName',
+ message: 'Select theme for Vue Storefront',
+ choices: answers => {
+ const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion)
+ const selectedVsfVersion = semverCoerce(answers.specificVersion)
+
+ return Object.entries(themes)
+ .filter(([, themeConfig]) => isVsfVersionAsBranch || semverSatisfies(selectedVsfVersion, themeConfig.minVsfVersion, { includePrerelease: true }))
+ .map(([themeName, themeConfig]) => ({
+ name: themeConfig.label,
+ value: themeName
+ }))
+ }
+ },
+ {
+ type: 'list',
+ name: 'themeBranch',
+ message: 'Select theme version',
+ choices: answers => Object.entries(themes[answers.themeName].branches)
+ .map(([branchName, branchLabel]) => ({
+ name: branchLabel,
+ value: branchName
+ }))
+ },
{
type: 'list',
name: 'installation',
@@ -98,10 +202,12 @@ module.exports = function (installationDir) {
.then(answers => {
const taskQueue = []
taskQueue.push(tasks.cloneVersion)
+ taskQueue.push(tasks.cloneTheme)
if (answers.installation === options.installation.installer) {
taskQueue.push(tasks.installDeps)
taskQueue.push(tasks.runInstaller)
}
+ taskQueue.push(tasks.configureTheme)
new Listr(taskQueue).run(answers)
})
})
diff --git a/packages/cli/scripts/manual.js b/packages/cli/scripts/manual.js
index 55b1b8c91..a08d94738 100644
--- a/packages/cli/scripts/manual.js
+++ b/packages/cli/scripts/manual.js
@@ -1,8 +1,8 @@
module.exports = function () {
console.log('Usage: vsf [command] [options]\n')
console.log('Options:')
- console.log(' --help available commands')
- console.log(' --version CLI version\n')
+ console.log(' --help | -h available commands')
+ console.log(' --version | -v CLI version\n')
console.log('Commands:')
console.log(' init [dir] setup new VS project')
console.log(' init:module [name] generate vs module boilerplate')
diff --git a/packages/cli/vue-storefront b/packages/cli/vue-storefront
deleted file mode 160000
index 3af5a1a15..000000000
--- a/packages/cli/vue-storefront
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3af5a1a152489f7335aff49c9f1d29d92a45da2f
diff --git a/src/modules/amp-renderer/router.ts b/src/modules/amp-renderer/router.ts
index 0eea5d67e..ceda4742e 100644
--- a/src/modules/amp-renderer/router.ts
+++ b/src/modules/amp-renderer/router.ts
@@ -1,2 +1,12 @@
-import AmpThemeRouting from 'src/themes/default-amp/router'
+import config from 'config'
+
+let AmpThemeRouting
+
+try {
+ const themeName = config.theme.replace('@vue-storefront/theme-', '')
+ AmpThemeRouting = require(`src/themes/${themeName}-amp/router`)
+} catch (err) {
+ AmpThemeRouting = null
+}
+
export default AmpThemeRouting
diff --git a/src/modules/client.ts b/src/modules/client.ts
index 192debde8..2e62808b5 100644
--- a/src/modules/client.ts
+++ b/src/modules/client.ts
@@ -10,12 +10,14 @@ import { UrlModule } from '@vue-storefront/core/modules/url'
import { BreadcrumbsModule } from '@vue-storefront/core/modules/breadcrumbs'
import { UserModule } from '@vue-storefront/core/modules/user'
import { CmsModule } from '@vue-storefront/core/modules/cms'
-import { GoogleTagManagerModule } from './google-tag-manager';
-import { AmpRendererModule } from './amp-renderer';
+// import { GoogleTagManagerModule } from './google-tag-manager';
+// import { AmpRendererModule } from './amp-renderer';
import { PaymentBackendMethodsModule } from './payment-backend-methods'
import { PaymentCashOnDeliveryModule } from './payment-cash-on-delivery'
import { NewsletterModule } from '@vue-storefront/core/modules/newsletter'
+import { InitialResourcesModule } from '@vue-storefront/core/modules/initial-resources'
+// import { DeviceModule } from './device/index';
import { registerModule } from '@vue-storefront/core/lib/modules'
// TODO:distributed across proper pages BEFORE 1.11
@@ -32,10 +34,12 @@ export function registerClientModules () {
registerModule(CatalogNextModule)
registerModule(CompareModule)
registerModule(BreadcrumbsModule)
- registerModule(GoogleTagManagerModule)
- registerModule(AmpRendererModule)
+ // registerModule(GoogleTagManagerModule)
+ // registerModule(AmpRendererModule)
registerModule(CmsModule)
registerModule(NewsletterModule)
+ registerModule(InitialResourcesModule)
+ // registerModule(DeviceModule)
}
// Deprecated API, will be removed in 2.0
diff --git a/src/modules/device/README.md b/src/modules/device/README.md
new file mode 100644
index 000000000..efb1b5d01
--- /dev/null
+++ b/src/modules/device/README.md
@@ -0,0 +1,32 @@
+### Device
+
+Based on: https://github.com/nuxt-community/device-module
+
+It provides as some logic helpers based on UserAgent. List of tests:
+```
+isMobile,
+isMobileOrTablet,
+isTablet,
+isDesktop,
+isDesktopOrTablet,
+isIos,
+isWindows,
+isMacOS
+```
+
+They are accessible by, e.g::
+```
+this.$device.isMobile
+```
+Or in asyncData by (you need to import Vue):
+```
+Vue.prototype.$device.isMobile
+```
+
+We could totally disable this feature by setting `config.device.appendToInstance` to false and the small library will not by imported.
+
+In addition when we are using installer script. I've added multiselect so we could pick which tests we want to have.
+
+I've tested it with `curl -A "some user agent" http://localhost:3000 and it worked.
+
+That's obvious we should use media queries whenever we can. However, sometimes we have more advanced structure and we are ending with hidden useless vue instance.
diff --git a/src/modules/device/index.ts b/src/modules/device/index.ts
new file mode 100644
index 000000000..0ca15e1b4
--- /dev/null
+++ b/src/modules/device/index.ts
@@ -0,0 +1,34 @@
+import { isServer } from '@vue-storefront/core/helpers/index';
+import { StorefrontModule } from '@vue-storefront/core/lib/modules';
+import Vue from 'vue'
+
+const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36'
+
+export const DeviceModule: StorefrontModule = async function ({ app, appConfig }) {
+ let headersOrUserAgent
+ if (isServer) {
+ headersOrUserAgent = Vue.prototype.$ssrRequestContext.userAgent || DEFAULT_USER_AGENT
+ } else {
+ headersOrUserAgent = window.navigator.userAgent || DEFAULT_USER_AGENT
+ }
+
+ if (appConfig.device && appConfig.device.appendToInstance && appConfig.device.tests && appConfig.device.tests.length) {
+ const deviceLibrary: any = await import(/* webpackChunkName: "device" */ './logic')
+ let userAgent = typeof headersOrUserAgent === 'string'
+ ? headersOrUserAgent
+ : headersOrUserAgent['user-agent']
+
+ Vue.prototype.$device = deviceLibrary.default(userAgent, appConfig.device.tests)
+ if (userAgent === 'Amazon CloudFront') {
+ if (headersOrUserAgent['cloudfront-is-mobile-viewer'] === 'true') {
+ Vue.prototype.$device.isMobile = true
+ Vue.prototype.$device.isMobileOrTablet = true
+ }
+ if (headersOrUserAgent['cloudfront-is-tablet-viewer'] === 'true') {
+ Vue.prototype.$device.isMobile = false
+ Vue.prototype.$device.isMobileOrTablet = true
+ }
+ }
+ (app as any).device = Vue.prototype.$device
+ }
+}
diff --git a/src/modules/device/logic/index.ts b/src/modules/device/logic/index.ts
new file mode 100644
index 000000000..14c4281cb
--- /dev/null
+++ b/src/modules/device/logic/index.ts
@@ -0,0 +1,70 @@
+// these regular expressions are borrowed from below page.
+// https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
+
+// eslint-disable-next-line
+const REGEX_MOBILE1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i
+
+// eslint-disable-next-line
+const REGEX_MOBILE2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
+
+// eslint-disable-next-line
+const REGEX_MOBILE_OR_TABLET1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i
+// eslint-disable-next-line
+const REGEX_MOBILE_OR_TABLET2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
+
+const testers = {
+ isMobile (a) {
+ return REGEX_MOBILE1.test(a) || REGEX_MOBILE2.test(a.substr(0, 4))
+ },
+
+ isMobileOrTablet (a) {
+ return REGEX_MOBILE_OR_TABLET1.test(a) || REGEX_MOBILE_OR_TABLET2.test(a.substr(0, 4))
+ },
+
+ isIos (a) {
+ return /iPad|iPhone|iPod/.test(a)
+ },
+
+ isWindows (a) {
+ return /Windows/.test(a)
+ },
+
+ isMacOS (a) {
+ return /Mac OS X/.test(a)
+ }
+}
+
+interface DeviceTests {
+ isMobile?: boolean,
+ isMobileOrTablet?: boolean,
+ isTablet?: boolean,
+ isDesktop?: boolean,
+ isDesktopOrTablet?: boolean,
+ isIos?: boolean,
+ isWindows?: boolean,
+ isMacOS?: boolean
+}
+
+export default (userAgent: string, tests: string[]): DeviceTests => {
+ const deviceTests: DeviceTests = {}
+
+ for (let test of tests) {
+ if (testers[test]) {
+ deviceTests[test] = testers[test](userAgent)
+ } else {
+ switch (test) {
+ case 'isTablet':
+ deviceTests[test] = !testers['isMobile'](userAgent) && !testers['isMobileOrTablet'](userAgent);
+ break;
+ case 'isDesktop':
+ deviceTests[test] = !testers['isMobileOrTablet'](userAgent)
+ break;
+ case 'isDesktopOrTablet':
+ deviceTests[test] = !testers['isMobile'](userAgent)
+ break;
+ }
+ }
+ }
+
+ return deviceTests
+}
diff --git a/src/modules/fastly/README.md b/src/modules/fastly/README.md
new file mode 100644
index 000000000..50293a731
--- /dev/null
+++ b/src/modules/fastly/README.md
@@ -0,0 +1,24 @@
+# VSF Cache Fastly
+This module extends default caching docs/guide/basics/ssr-cache.md to allow using fastly as cache provider.
+
+## How to install
+Add to config:
+```json
+"fastly": {
+ "enabled": true,
+ "serviceId": "xyz", // (https://docs.fastly.com/en/guides/finding-and-managing-your-account-info#finding-your-service-id)
+ "token": "xyz" // fastly api token (https://docs.fastly.com/api/auth#tokens)
+}
+```
+
+Change those values in `server` section:
+```json
+"useOutputCacheTagging": true,
+"useOutputCache": true
+```
+
+## How to purge cache?
+Open:
+```
+http://localhost:3000/invalidate?key=aeSu7aip&tag=home
+```
diff --git a/src/modules/fastly/server.ts b/src/modules/fastly/server.ts
new file mode 100644
index 000000000..474c5525a
--- /dev/null
+++ b/src/modules/fastly/server.ts
@@ -0,0 +1,39 @@
+import { serverHooks } from '@vue-storefront/core/server/hooks'
+import fetch from 'isomorphic-fetch'
+import config from 'config'
+
+const chunk = require('lodash/chunk')
+
+serverHooks.beforeOutputRenderedResponse(({ output, res, context }) => {
+ if (!config.get('fastly.enabled')) {
+ return output
+ }
+
+ const tagsArray = Array.from(context.output.cacheTags)
+ const cacheTags = tagsArray.join(' ')
+ res.setHeader('Surrogate-Key', cacheTags)
+
+ return output
+})
+
+serverHooks.beforeCacheInvalidated(async ({ tags }) => {
+ if (!config.get('fastly.enabled') || !config.get('server.useOutputCache') || !config.get('server.useOutputCacheTagging')) {
+ return
+ }
+
+ console.log('Invalidating Fastly Surrogate-Key')
+ const tagsChunks = chunk(tags.filter((tag) =>
+ config.server.availableCacheTags.indexOf(tag) >= 0 ||
+ config.server.availableCacheTags.find(t => tag.indexOf(t) === 0)
+ ), 256) // we can send maximum 256 keys per request, more info https://docs.fastly.com/api/purge#purge_db35b293f8a724717fcf25628d713583
+
+ for (const tagsChunk of tagsChunks) {
+ const response = await fetch(`https://api.fastly.com/service/${config.get('fastly.serviceId')}/purge`, {
+ method: 'POST',
+ headers: { 'Fastly-Key': config.get('fastly.token') },
+ body: JSON.stringify({ surrogate_keys: tagsChunk })
+ })
+ const text = await response.text()
+ console.log(text)
+ }
+})
diff --git a/src/modules/instant-checkout/components/InstantCheckout.vue b/src/modules/instant-checkout/components/InstantCheckout.vue
index 461ebb499..084316296 100644
--- a/src/modules/instant-checkout/components/InstantCheckout.vue
+++ b/src/modules/instant-checkout/components/InstantCheckout.vue
@@ -14,8 +14,7 @@ import rootStore from '@vue-storefront/core/store'
import { currentStoreView } from '@vue-storefront/core/lib/multistore'
import { registerModule } from '@vue-storefront/core/lib/modules'
import { OrderModule } from '@vue-storefront/core/modules/order'
-
-const storeView = currentStoreView()
+import { Logger } from '@vue-storefront/core/lib/logger'
export default {
name: 'InstantCheckoutButton',
@@ -25,7 +24,7 @@ export default {
data () {
return {
supported: false,
- country: rootStore.state.checkout.shippingDetails.country ? rootStore.state.checkout.shippingDetails.country : storeView.tax.defaultCountry,
+ country: rootStore.state.checkout.shippingDetails.country ? rootStore.state.checkout.shippingDetails.country : currentStoreView().tax.defaultCountry,
paymentMethods: [
{
supportedMethods: ['basic-card'],
@@ -56,7 +55,7 @@ export default {
bucket.push({
label: product.name,
amount: {
- currency: storeView.i18n.currencyCode,
+ currency: currentStoreView().i18n.currencyCode,
value: this.getProductPrice(product)
}
})
@@ -67,7 +66,7 @@ export default {
if (this.selectedShippingOption.length > 0) {
bucket.push({
label: i18n.t('Shipping'),
- amount: { currency: storeView.i18n.currencyCode, value: this.selectedShippingOption[0].amount.value }
+ amount: { currency: currentStoreView().i18n.currencyCode, value: this.selectedShippingOption[0].amount.value }
})
}
@@ -75,13 +74,11 @@ export default {
}
// If synchronization is eanbled get shipping and discount values from Magento
- const shipping = this.platformTotal.filter(segment => {
- return segment.code === 'shipping'
- })
+ const shipping = this.platformTotal.filter(segment => segment.code === 'shipping' && segment.value)
if (shipping.length > 0) {
bucket.push({
label: shipping[0].title,
- amount: { currency: storeView.i18n.currencyCode, value: shipping[0].value }
+ amount: { currency: currentStoreView().i18n.currencyCode, value: shipping[0].value }
})
}
@@ -107,7 +104,7 @@ export default {
return {
label: i18n.t('Grand total'),
- amount: { currency: storeView.i18n.currencyCode, value: subtotal }
+ amount: { currency: currentStoreView().i18n.currencyCode, value: subtotal }
}
}
@@ -118,7 +115,7 @@ export default {
if (total.length > 0) {
return {
label: total[0].title,
- amount: { currency: storeView.i18n.currencyCode, value: total[0].value }
+ amount: { currency: currentStoreView().i18n.currencyCode, value: total[0].value }
}
}
@@ -155,7 +152,7 @@ export default {
})
})
.catch(e => {
- console.log(e)
+ Logger.log(e)()
})
},
shippingOptionChange (event) {
@@ -181,7 +178,7 @@ export default {
total: this.total
})
}).catch(e => {
- console.error(e)
+ Logger.error(e)()
reject(e)
})
})
@@ -211,7 +208,7 @@ export default {
total: this.total
})
}).catch(e => {
- console.error(e)
+ Logger.error(e)()
reject(e)
})
})
@@ -231,14 +228,14 @@ export default {
label: method.method_title,
selected: setDefault ? this.$store.getters['checkout/getShippingMethods'][0].method_code === method.method_code : false,
amount: {
- currency: storeView.i18n.currencyCode,
+ currency: currentStoreView().i18n.currencyCode,
value: method.price_incl_tax
}
})
})
resolve()
}).catch(e => {
- console.error(e)
+ Logger.error(e)()
reject(e)
})
})
diff --git a/src/modules/vsf-cache-nginx b/src/modules/vsf-cache-nginx
new file mode 160000
index 000000000..c2c07879c
--- /dev/null
+++ b/src/modules/vsf-cache-nginx
@@ -0,0 +1 @@
+Subproject commit c2c07879ca261b88c7cc76c45eaa49519bfcfc87
diff --git a/src/modules/vsf-cache-varnish b/src/modules/vsf-cache-varnish
new file mode 160000
index 000000000..09cc48f65
--- /dev/null
+++ b/src/modules/vsf-cache-varnish
@@ -0,0 +1 @@
+Subproject commit 09cc48f65ee174dc39eec809cb5f816fd3f039fa
diff --git a/src/search/adapter/.gitkeep b/src/search/adapter/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/core/lib/search/adapter/graphql/gqlQuery.js b/src/search/adapter/graphql/gqlQuery.js
similarity index 100%
rename from core/lib/search/adapter/graphql/gqlQuery.js
rename to src/search/adapter/graphql/gqlQuery.js
diff --git a/core/lib/search/adapter/graphql/processor/processType.ts b/src/search/adapter/graphql/processor/processType.ts
similarity index 100%
rename from core/lib/search/adapter/graphql/processor/processType.ts
rename to src/search/adapter/graphql/processor/processType.ts
diff --git a/core/lib/search/adapter/graphql/queries/categories.gql b/src/search/adapter/graphql/queries/categories.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/categories.gql
rename to src/search/adapter/graphql/queries/categories.gql
diff --git a/core/lib/search/adapter/graphql/queries/cmsBlock.gql b/src/search/adapter/graphql/queries/cmsBlock.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/cmsBlock.gql
rename to src/search/adapter/graphql/queries/cmsBlock.gql
diff --git a/core/lib/search/adapter/graphql/queries/cmsHierarchy.gql b/src/search/adapter/graphql/queries/cmsHierarchy.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/cmsHierarchy.gql
rename to src/search/adapter/graphql/queries/cmsHierarchy.gql
diff --git a/core/lib/search/adapter/graphql/queries/cmsPage.gql b/src/search/adapter/graphql/queries/cmsPage.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/cmsPage.gql
rename to src/search/adapter/graphql/queries/cmsPage.gql
diff --git a/core/lib/search/adapter/graphql/queries/customAttributeMetadata.gql b/src/search/adapter/graphql/queries/customAttributeMetadata.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/customAttributeMetadata.gql
rename to src/search/adapter/graphql/queries/customAttributeMetadata.gql
diff --git a/core/lib/search/adapter/graphql/queries/products.gql b/src/search/adapter/graphql/queries/products.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/products.gql
rename to src/search/adapter/graphql/queries/products.gql
diff --git a/core/lib/search/adapter/graphql/queries/reviews.gql b/src/search/adapter/graphql/queries/reviews.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/reviews.gql
rename to src/search/adapter/graphql/queries/reviews.gql
diff --git a/core/lib/search/adapter/graphql/queries/taxrule.gql b/src/search/adapter/graphql/queries/taxrule.gql
similarity index 100%
rename from core/lib/search/adapter/graphql/queries/taxrule.gql
rename to src/search/adapter/graphql/queries/taxrule.gql
diff --git a/core/lib/search/adapter/graphql/searchAdapter.ts b/src/search/adapter/graphql/searchAdapter.ts
similarity index 88%
rename from core/lib/search/adapter/graphql/searchAdapter.ts
rename to src/search/adapter/graphql/searchAdapter.ts
index 56dfb495a..86640a1bb 100644
--- a/core/lib/search/adapter/graphql/searchAdapter.ts
+++ b/src/search/adapter/graphql/searchAdapter.ts
@@ -1,9 +1,11 @@
import { prepareQueryVars } from './gqlQuery'
-import { currentStoreView, prepareStoreView } from '../../../multistore'
+import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore'
import fetch from 'isomorphic-fetch'
import { processESResponseType, processProductsType, processCmsType } from './processor/processType'
-import SearchQuery from '../../searchQuery'
+import { SearchQuery } from 'storefront-query-builder'
import config from 'config'
+import { isServer } from '@vue-storefront/core/helpers'
+import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl';
export class SearchAdapter {
public entities: any
@@ -41,10 +43,13 @@ export class SearchAdapter {
// define GraphQL url from searchAdapter entity or use default graphQl host with storeCode param
let urlGql = ''
- if (this.entities[Request.type].url) {
- urlGql = this.entities[Request.type].url
+ if (getApiEndpointUrl(this.entities[Request.type], 'url')) {
+ urlGql = getApiEndpointUrl(this.entities[Request.type], 'url')
} else {
- urlGql = config.server.protocol + '://' + config.graphql.host + ':' + config.graphql.port + '/graphql'
+ const serverProtocol = isServer ? getApiEndpointUrl(config.server, 'protocol') : config.server.protocol
+ const host = isServer ? getApiEndpointUrl(config.graphql, 'host') : config.graphql.host
+ const port = isServer ? getApiEndpointUrl(config.graphql, 'port') : config.graphql.port
+ urlGql = serverProtocol + '://' + host + ':' + port + '/graphql'
const urlStoreCode = (storeView.storeCode !== '') ? encodeURIComponent(storeView.storeCode) + '/' : ''
urlGql = urlGql + '/' + urlStoreCode
}
@@ -73,7 +78,7 @@ export class SearchAdapter {
* @param {function} resultProcessor process results of response
* @return {Object}
*/
- public registerEntityType (entityType, { url = '', gql, queryProcessor, resultProcessor }) {
+ public registerEntityType (entityType, { url = '', url_ssr = '', gql, queryProcessor, resultProcessor }) {
this.entities[entityType] = {
query: require(`${gql}`),
queryProcessor: queryProcessor,
@@ -82,6 +87,9 @@ export class SearchAdapter {
if (url !== '') {
this.entities[entityType]['url'] = url
}
+ if (url_ssr !== '') {
+ this.entities[entityType]['url_ssr'] = url_ssr
+ }
return this
}
@@ -93,7 +101,7 @@ export class SearchAdapter {
* @param {function} resultProcessor process results of response
* @return {Object}
*/
- public registerEntityTypeByQuery (entityType, { url = '', query, queryProcessor, resultProcessor }) {
+ public registerEntityTypeByQuery (entityType, { url = '', url_ssr = '', query, queryProcessor, resultProcessor }) {
this.entities[entityType] = {
query: query,
queryProcessor: queryProcessor,
@@ -102,6 +110,9 @@ export class SearchAdapter {
if (url !== '') {
this.entities[entityType]['url'] = url
}
+ if (url_ssr !== '') {
+ this.entities[entityType]['url_ssr'] = url_ssr
+ }
return this
}
diff --git a/src/themes/default-amp/components/core/Header.vue b/src/themes/default-amp/components/core/Header.vue
deleted file mode 100755
index 62202c98e..000000000
--- a/src/themes/default-amp/components/core/Header.vue
+++ /dev/null
@@ -1,173 +0,0 @@
-
-
{{ $t('Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!') }}
-
-
-
-
-
-
-
diff --git a/src/themes/default-amp/router/index.ts b/src/themes/default-amp/router/index.ts
deleted file mode 100755
index ce291ac22..000000000
--- a/src/themes/default-amp/router/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// import router from '@vue-storefront/core/router'
-// uncomment if you want to modify the router e.g. add before/after hooks
-import Category from '../pages/Category.vue'
-import Product from '../pages/Product.vue'
-import { RouteConfig } from 'vue-router'
-let routes: RouteConfig[] = [
-]
-routes = routes.concat([{ name: 'virtual-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'bundle-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'simple-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'downloadable-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'grouped-product-amp', path: '/amp/p/:parentSku/:slug', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'configurable-product-amp', path: '/amp/p/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'product-amp', path: '/amp/p/:parentSku/:slug/:childSku', component: Product, meta: { layout: 'minimal' } }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'category-amp', path: '/amp/c/:slug', component: Category }])
-export default routes
diff --git a/src/themes/default-amp/webpack.config.js b/src/themes/default-amp/webpack.config.js
deleted file mode 100755
index 4aea92873..000000000
--- a/src/themes/default-amp/webpack.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// You can extend default webpack build here. Read more on docs: https://github.com/DivanteLtd/vue-storefront/blob/master/docs/guide/core-themes/webpack.md
-module.exports = function (config, { isClient, isDev }) {
- return config
-}
diff --git a/src/themes/default/App.vue b/src/themes/default/App.vue
deleted file mode 100755
index cb8bcb73a..000000000
--- a/src/themes/default/App.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/themes/default/assets/android-icon-144x144.png b/src/themes/default/assets/android-icon-144x144.png
deleted file mode 100644
index c9e52ac05..000000000
Binary files a/src/themes/default/assets/android-icon-144x144.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-168x168.png b/src/themes/default/assets/android-icon-168x168.png
deleted file mode 100644
index aa88da2a1..000000000
Binary files a/src/themes/default/assets/android-icon-168x168.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-192x192.png b/src/themes/default/assets/android-icon-192x192.png
deleted file mode 100644
index 684955c9d..000000000
Binary files a/src/themes/default/assets/android-icon-192x192.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-48x48.png b/src/themes/default/assets/android-icon-48x48.png
deleted file mode 100644
index aadd50e93..000000000
Binary files a/src/themes/default/assets/android-icon-48x48.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-512x512.png b/src/themes/default/assets/android-icon-512x512.png
deleted file mode 100644
index b04137be4..000000000
Binary files a/src/themes/default/assets/android-icon-512x512.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-72x72.png b/src/themes/default/assets/android-icon-72x72.png
deleted file mode 100644
index a52539ce3..000000000
Binary files a/src/themes/default/assets/android-icon-72x72.png and /dev/null differ
diff --git a/src/themes/default/assets/android-icon-96x96.png b/src/themes/default/assets/android-icon-96x96.png
deleted file mode 100644
index cad8888d5..000000000
Binary files a/src/themes/default/assets/android-icon-96x96.png and /dev/null differ
diff --git a/src/themes/default/assets/apple-touch-icon.png b/src/themes/default/assets/apple-touch-icon.png
deleted file mode 100644
index d7d9883c2..000000000
Binary files a/src/themes/default/assets/apple-touch-icon.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_1125.png b/src/themes/default/assets/apple_splash_1125.png
deleted file mode 100644
index a652e7ba4..000000000
Binary files a/src/themes/default/assets/apple_splash_1125.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_1242.png b/src/themes/default/assets/apple_splash_1242.png
deleted file mode 100644
index fe83551d4..000000000
Binary files a/src/themes/default/assets/apple_splash_1242.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_1536.png b/src/themes/default/assets/apple_splash_1536.png
deleted file mode 100644
index 45e1ad20a..000000000
Binary files a/src/themes/default/assets/apple_splash_1536.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_1668.png b/src/themes/default/assets/apple_splash_1668.png
deleted file mode 100644
index e1a3f1a9d..000000000
Binary files a/src/themes/default/assets/apple_splash_1668.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_2048.png b/src/themes/default/assets/apple_splash_2048.png
deleted file mode 100644
index 34d4ef2c0..000000000
Binary files a/src/themes/default/assets/apple_splash_2048.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_640.png b/src/themes/default/assets/apple_splash_640.png
deleted file mode 100644
index 92d222ee9..000000000
Binary files a/src/themes/default/assets/apple_splash_640.png and /dev/null differ
diff --git a/src/themes/default/assets/apple_splash_750.png b/src/themes/default/assets/apple_splash_750.png
deleted file mode 100644
index 445e473ee..000000000
Binary files a/src/themes/default/assets/apple_splash_750.png and /dev/null differ
diff --git a/src/themes/default/assets/ban1.jpg b/src/themes/default/assets/ban1.jpg
deleted file mode 100644
index fcc3da778..000000000
Binary files a/src/themes/default/assets/ban1.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ban2.jpg b/src/themes/default/assets/ban2.jpg
deleted file mode 100644
index 0b51c66e0..000000000
Binary files a/src/themes/default/assets/ban2.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ban3.jpg b/src/themes/default/assets/ban3.jpg
deleted file mode 100644
index 13607df55..000000000
Binary files a/src/themes/default/assets/ban3.jpg and /dev/null differ
diff --git a/src/themes/default/assets/collection.jpg b/src/themes/default/assets/collection.jpg
deleted file mode 100644
index e7920940a..000000000
Binary files a/src/themes/default/assets/collection.jpg and /dev/null differ
diff --git a/src/themes/default/assets/favicon-16x16.png b/src/themes/default/assets/favicon-16x16.png
deleted file mode 100644
index 9071dc96e..000000000
Binary files a/src/themes/default/assets/favicon-16x16.png and /dev/null differ
diff --git a/src/themes/default/assets/favicon-32x32.png b/src/themes/default/assets/favicon-32x32.png
deleted file mode 100644
index 911ffd3e5..000000000
Binary files a/src/themes/default/assets/favicon-32x32.png and /dev/null differ
diff --git a/src/themes/default/assets/fonts/MaterialIcons-Regular.woff b/src/themes/default/assets/fonts/MaterialIcons-Regular.woff
deleted file mode 100644
index b648a3eea..000000000
Binary files a/src/themes/default/assets/fonts/MaterialIcons-Regular.woff and /dev/null differ
diff --git a/src/themes/default/assets/fonts/MaterialIcons-Regular.woff2 b/src/themes/default/assets/fonts/MaterialIcons-Regular.woff2
deleted file mode 100644
index 9fa211252..000000000
Binary files a/src/themes/default/assets/fonts/MaterialIcons-Regular.woff2 and /dev/null differ
diff --git a/src/themes/default/assets/full_width_banner.jpg b/src/themes/default/assets/full_width_banner.jpg
deleted file mode 100644
index 040750687..000000000
Binary files a/src/themes/default/assets/full_width_banner.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig01.jpg b/src/themes/default/assets/ig/ig01.jpg
deleted file mode 100644
index ee0045b19..000000000
Binary files a/src/themes/default/assets/ig/ig01.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig02.jpg b/src/themes/default/assets/ig/ig02.jpg
deleted file mode 100644
index 168ada8f0..000000000
Binary files a/src/themes/default/assets/ig/ig02.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig03.jpg b/src/themes/default/assets/ig/ig03.jpg
deleted file mode 100644
index 9cae58b54..000000000
Binary files a/src/themes/default/assets/ig/ig03.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig04.jpg b/src/themes/default/assets/ig/ig04.jpg
deleted file mode 100644
index e3be2b3a7..000000000
Binary files a/src/themes/default/assets/ig/ig04.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig05.jpg b/src/themes/default/assets/ig/ig05.jpg
deleted file mode 100644
index bb5261a69..000000000
Binary files a/src/themes/default/assets/ig/ig05.jpg and /dev/null differ
diff --git a/src/themes/default/assets/ig/ig06.jpg b/src/themes/default/assets/ig/ig06.jpg
deleted file mode 100644
index f37bbaacf..000000000
Binary files a/src/themes/default/assets/ig/ig06.jpg and /dev/null differ
diff --git a/src/themes/default/assets/logo.png b/src/themes/default/assets/logo.png
deleted file mode 100644
index 63222d49e..000000000
Binary files a/src/themes/default/assets/logo.png and /dev/null differ
diff --git a/src/themes/default/assets/logo.svg b/src/themes/default/assets/logo.svg
deleted file mode 100644
index 3f81016a6..000000000
--- a/src/themes/default/assets/logo.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/themes/default/assets/manifest.json b/src/themes/default/assets/manifest.json
deleted file mode 100644
index 3069a0bdc..000000000
--- a/src/themes/default/assets/manifest.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "short_name": "VSF Demo",
- "name": "Vue Storefront",
- "background_color": "#ffffff",
- "display": "standalone",
- "theme_color": "#ffffff",
- "start_url": "/pwa.html",
- "icons": [
- {
- "src": "/assets/android-icon-48x48.png",
- "type": "image/png",
- "sizes": "48x48"
- },
- {
- "src": "/assets/android-icon-72x72.png",
- "type": "image/png",
- "sizes": "72x72"
- },
- {
- "src": "/assets/android-icon-96x96.png",
- "type": "image/png",
- "sizes": "96x96"
- },
- {
- "src": "/assets/android-icon-144x144.png",
- "type": "image/png",
- "sizes": "144x144"
- },
- {
- "src": "/assets/android-icon-168x168.png",
- "type": "image/png",
- "sizes": "168x168"
- },
- {
- "src": "/assets/android-icon-192x192.png",
- "type": "image/png",
- "sizes": "192x192"
- },
- {
- "src": "/assets/android-icon-512x512.png",
- "type": "image/png",
- "sizes": "512x512"
- }
- ]
- }
diff --git a/src/themes/default/assets/placeholder.jpg b/src/themes/default/assets/placeholder.jpg
deleted file mode 100644
index 2f3d6a901..000000000
Binary files a/src/themes/default/assets/placeholder.jpg and /dev/null differ
diff --git a/src/themes/default/assets/placeholder.svg b/src/themes/default/assets/placeholder.svg
deleted file mode 100644
index ba821924f..000000000
--- a/src/themes/default/assets/placeholder.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/themes/default/assets/slide_01.jpg b/src/themes/default/assets/slide_01.jpg
deleted file mode 100644
index 2dd2364a0..000000000
Binary files a/src/themes/default/assets/slide_01.jpg and /dev/null differ
diff --git a/src/themes/default/assets/slide_02.jpg b/src/themes/default/assets/slide_02.jpg
deleted file mode 100644
index fb1ad962e..000000000
Binary files a/src/themes/default/assets/slide_02.jpg and /dev/null differ
diff --git a/src/themes/default/assets/slide_03.jpg b/src/themes/default/assets/slide_03.jpg
deleted file mode 100644
index 447ff9620..000000000
Binary files a/src/themes/default/assets/slide_03.jpg and /dev/null differ
diff --git a/src/themes/default/components/core/AddToCart.vue b/src/themes/default/components/core/AddToCart.vue
deleted file mode 100644
index 24ff8c7a7..000000000
--- a/src/themes/default/components/core/AddToCart.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
- {{ $t('Add to cart') }}
-
-
-
-
diff --git a/src/themes/default/components/core/BackToTop.vue b/src/themes/default/components/core/BackToTop.vue
deleted file mode 100644
index 26346bc80..000000000
--- a/src/themes/default/components/core/BackToTop.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
- This website ("website") is operated by Luma Inc., which includes Luma stores, and Luma Private Sales. This privacy policy only covers information collected at this website, and does not cover any information collected offline by Luma. All Luma websites are covered by this privacy policy.
-
-
- Luma Security
-
-
- Personal information provided on the website and online credit card transactions are transmitted through a secure server. We are committed to handling your personal information with high standards of information security. We take appropriate physical, electronic, and administrative steps to maintain the security and accuracy of personally identifiable information we collect, including limiting the number of people who have physical access to our database servers, as well as employing electronic security systems and password protections that guard against unauthorized access.
-
-
- Luma Privacy Policy
-
-
- To help us achieve our goal of providing the highest quality products and services, we use information from our interactions with you and other customers, as well as from other parties. Because we respect your privacy, we have implemented procedures to ensure that your personal information is handled in a safe, secure, and responsible manner. We have posted this privacy policy in order to explain our information collection practices and the choices you have about the way information is collected and used.
-
-
- As we continue to develop the Luma website and take advantage of advances in technology to improve the services we offer, this privacy policy likely will change. We therefore encourage you to refer to this policy on an ongoing basis so that you understand our current privacy policy.
-
- {{ $t('To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items') }}
-
-
- {{ $t("You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm.") }}
-
-
- {{ $t(`Or if you will stay on "Order confirmation" page, the order will be placed automatically without confirmation, once the internet connection will be back.`) }}
-
-
- {{ $t('You will receive Push notification after coming back online. You can confirm the order by clicking on it') }}
-
-
-
- {{ $t('Allow notification about the order') }}
-
-
-
-
- {{ $t('Return to shopping') }}
-
-
-
-
-
-
- {{ $t('What we can improve?') }}
-
-
- {{ $t('Your feedback is important for us. Let us know what we could improve.') }}
-
- Vue Storefront is a PWA storefront for eCommerce.
- It is and always will be in the open source.
- Anyone can use and support the project,
- we want it to be a tool for the improvement of the shopping experience.
-
-
- This demo is synchronized with Magento 2.2.0.
-
-
-
- If you want to use the solution or join our passionate PWA community - feel free to contact us via
- e-mail
- or
- Slack.
-
-
- This website ("website") is operated by Luma Inc., which includes Luma stores, and Luma Private Sales. This privacy policy only covers information collected at this website, and does not cover any information collected offline by Luma. All Luma websites are covered by this privacy policy.
-
-
- Luma Security
-
-
- Personal information provided on the website and online credit card transactions are transmitted through a secure server. We are committed to handling your personal information with high standards of information security. We take appropriate physical, electronic, and administrative steps to maintain the security and accuracy of personally identifiable information we collect, including limiting the number of people who have physical access to our database servers, as well as employing electronic security systems and password protections that guard against unauthorized access.
-
-
- Luma Privacy Policy
-
-
- To help us achieve our goal of providing the highest quality products and services, we use information from our interactions with you and other customers, as well as from other parties. Because we respect your privacy, we have implemented procedures to ensure that your personal information is handled in a safe, secure, and responsible manner. We have posted this privacy policy in order to explain our information collection practices and the choices you have about the way information is collected and used.
-
-
- As we continue to develop the Luma website and take advantage of advances in technology to improve the services we offer, this privacy policy likely will change. We therefore encourage you to refer to this policy on an ongoing basis so that you understand our current privacy policy.
-
- This website ("website") is operated by Luma Inc., which includes Luma stores, and Luma Private Sales. This privacy policy only covers information collected at this website, and does not cover any information collected offline by Luma. All Luma websites are covered by this privacy policy.
-
-
- Luma Privacy Policy
-
-
- To help us achieve our goal of providing the highest quality products and services, we use information from our interactions with you and other customers, as well as from other parties. Because we respect your privacy, we have implemented procedures to ensure that your personal information is handled in a safe, secure, and responsible manner. We have posted this privacy policy in order to explain our information collection practices and the choices you have about the way information is collected and used.
-
-
- As we continue to develop the Luma website and take advantage of advances in technology to improve the services we offer, this privacy policy likely will change. We therefore encourage you to refer to this policy on an ongoing basis so that you understand our current privacy policy.
-
{{ $t('Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!') }}
- {{ $t("Unfortunately we can't find the page you are looking for.") }}
-
-
- {{ $t('If you need an assistance you can drop us a line on') }}
-
- {{ $t('a chat') }}
-
- {{ $t('or write to us through') }}
-
- {{ $t('a contact page') }}
- .
-
-
- {{ $t('You can also use') }}
-
- {{ $t('search') }}
-
- {{ $t('to find product you were looking for.') }}
-
-
-
-
-
-
-
-
diff --git a/src/themes/default/resource/banners/de_main-image.json b/src/themes/default/resource/banners/de_main-image.json
deleted file mode 100644
index 16919a935..000000000
--- a/src/themes/default/resource/banners/de_main-image.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "image": {
- "title": "Neue Wege beschreiten.",
- "subtitle": "Eine Mode kann sich zu einem neuen Stil durchsetzen und offenbart die neusten Kreationen von Designern, Technologen, Ingenieuren und Designmanagern.",
- "image": "/assets/full_width_banner.jpg",
- "link": "/women/frauen-20"
- }
-}
diff --git a/src/themes/default/resource/banners/de_promoted_offers.json b/src/themes/default/resource/banners/de_promoted_offers.json
deleted file mode 100644
index 7b8f09124..000000000
--- a/src/themes/default/resource/banners/de_promoted_offers.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "mainBanners": [
- {
- "title": "Lässige Büro",
- "subtitle": "Kollektion",
- "image": "/assets/ban1.jpg",
- "link": "/women/frauen-20"
- }
- ],
- "smallBanners": [
- {
- "title": "Glänzen Sie mit",
- "subtitle": "Accessoires",
- "image": "/assets/ban2.jpg",
- "link": "/men/herren-11"
- },
- {
- "title": "Der Frühling kommt",
- "subtitle": "Hüte",
- "image": "/assets/ban3.jpg",
- "link": "/gear/gerat-3"
- }
- ],
- "productBanners": [
- {
- "title": "Der Frühling kommt",
- "subtitle": "Hüte",
- "image": "/assets/ban3.jpg",
- "link": "/gear/gerat-3"
- }
- ]
-}
diff --git a/src/themes/default/resource/banners/it_main-image.json b/src/themes/default/resource/banners/it_main-image.json
deleted file mode 100644
index 9d46401de..000000000
--- a/src/themes/default/resource/banners/it_main-image.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "image": {
- "title": "Cammina la passeggiata.",
- "subtitle": "Una moda può diventare lo stile prevalente nel comportamento o manifestare le ultime creazioni di designer, tecnologi, ingegneri e responsabili del design.",
- "image": "/assets/full_width_banner.jpg",
- "link": "/women/la-donne-20"
- }
-}
diff --git a/src/themes/default/resource/banners/it_promoted_offers.json b/src/themes/default/resource/banners/it_promoted_offers.json
deleted file mode 100644
index 3e168dc66..000000000
--- a/src/themes/default/resource/banners/it_promoted_offers.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "mainBanners": [
- {
- "title": "Ufficio casual",
- "subtitle": "Collezione",
- "image": "/assets/ban1.jpg",
- "link": "/women/la-donne-20"
- }
- ],
- "smallBanners": [
- {
- "title": "Brilla",
- "subtitle": "Accessori",
- "image": "/assets/ban2.jpg",
- "link": "/men/signori-11"
- },
- {
- "title": "La primavera sta arrivando",
- "subtitle": "Cappelli",
- "image": "/assets/ban3.jpg",
- "link": "/gear/equipaggiamento-3"
- }
- ],
- "productBanners": [
- {
- "title": "La primavera sta arrivando",
- "subtitle": "Cappelli",
- "image": "/assets/ban3.jpg",
- "link": "/gear/equipaggiamento-3"
- }
- ]
-}
diff --git a/src/themes/default/resource/i18n/ar-SY.csv b/src/themes/default/resource/i18n/ar-SY.csv
deleted file mode 100644
index 6f1937e43..000000000
--- a/src/themes/default/resource/i18n/ar-SY.csv
+++ /dev/null
@@ -1,266 +0,0 @@
-"About us","من نحن"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","أدخل رمز الخصم"
-"Add discount code","إرسال"
-"Add to cart","أضف إلى السلّة"
-"Add to compare","أضف إلى المقارنة"
-"Add to favorite","أضف إلى المفضلّة"
-"Allow notification about the order","السماح بالتنبيهات عن الطبية"
-"Author","المؤلف"
-"Authorization in progress ...","جاري عملية الحصول على تفويض ... "
-"Back to login","العودة لتسجيل الدخول"
-"Back","عودة"
-"Billing address","عنوان الدّفع"
-"Cancel","إلغاء"
-"Cash on delivery","الدفع عند الاستلام"
-"Change my password","تغيير كلمة المرور"
-"Choose your country","اختر بلدك"
-"City *","المدينة *"
-"City","المدينة"
-"Close","إغلاق"
-"Cms Page Sync","مزامنة صفحة المحتوى"
-"Company name *","اسم الشركة *"
-"Compare products","مقارنة المنتجات"
-"Confirm your order","تأكيد طلبيتك"
-"Contact us","تواصل معنا"
-"Continue to payment","الاستمرار للدفع"
-"Continue to shipping","الاستمرار بالتسوّق"
-"Copy address data from shipping","نسخ بيانات العنوان من عنوان الشحن"
-"Country *","البلد *"
-"Country","الدولة"
-"Create a new account","إنشاء حساب جديد"
-"Current password *","كلمة المرور الحالية *"
-"Custom Cms Page","صفح محتوى خاصّة"
-"Customer service","خدمة الزبائن"
-"DPD Courier","DPD Courier"
-"Date and time","التاريخ ولاوقت"
-"Delivery","التوصيل"
-"Departments","الآقسام"
-"Discount code","رمز الخصم"
-"Discount","الخصم"
-"Don't hesitate and","لا تتردد و"
-"E-mail address *","البريد الإلكتروني *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","راسلنا على demo@vuestorefront.io بأي سؤال أو إقتراح كيف يمكننا تحسين المنتجات أو تجربة التّسوّق"
-"Edit newsletter preferences","تعديل خيارات النشرة البريدية"
-"Edit payment","تعديل الدفع"
-"Edit personal details","تعديل المعلومات الشخصية"
-"Edit shipping","تعديل الشحن"
-"Edit your profile","تعديل ملفك"
-"Edit your shipping details","تعديل تفاصيل الشحن"
-"Edit","تعديل"
-"Email address *","البريد * الإلكتروني"
-"Email address","البريد الإلكتروني"
-"English","إنكليزي"
-"Enter your email to receive instructions on how to reset your password.","أدخل بريدك الإلكتروني لتصلك تعليمات إعادة ضبط كلمة المرور."
-"Erin recommends","توصيات Erin"
-"Error while sending reset password e-mail","حدث خطأ أثناء إعادة ضبط كلمة المرور"
-"Everything new","جديدنا"
-"Extension developers would like to thank you for placing an order!","يود مبرمجي الملحقات شكرك على اتمام الطلبية!"
-"Field is required","حقل مطلوب"
-"Filter","تصفية"
-"Filters","التصفية"
-"First name *","الاسم الأوّل"
-"First name","الاسم الأول"
-"Forgot the password?","هل نسيت كلمة المرور؟"
-"General agreement","اتّفاق عام"
-"German","ألماني"
-"Germany","ألمانيا"
-"Get inspired","احصل على الإلهام"
-"Give a feedback","أخبرنا برأيك"
-"Go review the order","الذهاب لمعاينة الطلبية"
-"Go to Facebook","اذهب إلى فيس بوك"
-"Go to Instagram","اذهب إلى انستغرام"
-"Go to Pinterest","اذهب إلى بنتريست"
-"Go to Youtube","اذهب إلي يوتيوب"
-"Go to checkout","الدفع وإنهاء التسوّق"
-"Help","المساعدة"
-"Home","الرئيسة"
-"House/Apartment number *","رقم المنزل/الشقة *"
-"House/Apartment number","رقم الشقّة/المنزل"
-"I accept ","أنا موافق على "
-"I accept terms and conditions","أنا موافق على الشروط والأحكام"
-"I agree to","موافق على"
-"I have a company and want to receive an invoice for every order","أنا لدي شركة وأريد أن استلم فاتورة عن كل طلبية"
-"I want to create an account","أريد إنشاء حساب جديد"
-"I want to create an account'","أريد إنشاء حساب"
-"I want to generate an invoice for the company","أريد إنشاء فاتورة للشركة"
-"I want to receive a newsletter, and agree to its terms","أنا أريد استلام النشرة البريدية وموافق على شروطها"
-"If you need an assistance you can drop us a line on","راسلنا هنا في حال أردت مساعدة"
-"Internal Server Error 500","خطأ داخلي في المخدّم 500"
-"Italian","إيطالي"
-"Italy","إيطاليا"
-"Items ordered","المنتجات التي تمّ طلبها"
-"Kidswear","ملابس أطفال"
-"Last name *","الكنية ّ*"
-"Last name","الكنية"
-"Latest","الأحدث"
-"Legal notice","إشعار قانوني"
-"Load more","تحميل المزيد"
-"Log in to your account","تسجيل الدخول لحسابك"
-"Log in","تسجيل الدخول"
-"Login to your account","الدخول لحسابك"
-"Logout","تسجيل الخروج"
-"Magazine","المجلة"
-"Men's fashion","أرياء رجّالية"
-"My Account","حسابي"
-"My account","حسابي"
-"My loyalty card","بطاقة الولاء"
-"My newsletter","نشرتي البريدية"
-"My orders","طلبياتي"
-"My product reviews","مراجعاتي"
-"My profile","ملفي الشخصي"
-"My shipping details","تفاصيل الشحن"
-"Name must have at least 3 letters.","يجب أن تحوي 3 حروف على الأقل"
-"New Luma Yoga Collection","تشكيلة يوغا لُما الجديدة"
-"New password *","كلمة المرور الجديدة"
-"Newsletter","النشرة البريدية"
-"No orders yet","لا يوجد طلبيات حتى الآن"
-"No products found!","لم يتم إيجاد أي منتج!"
-"No results were found.","لم يتم العثور على نتائج"
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","لم يتم إضافة مراجعات بعد. الرجاء عدم التردد بمشاركتنا برأيك وكتابة مراجعة!"
-"Open menu","فتح القائمة"
-"Open microcart","فتح سلة التسوّق"
-"Open my account","فتح حسابي"
-"Open search panel","فتح لوحة البحث"
-"Open wishlist","فتح المفضّلة"
-"Order #{id}","رقم الطلب #{id}"
-"Order ID","رمز الطلبية"
-"Order Summary","ملخّص الطلبية"
-"Order confirmation","تأكيد الطلبية"
-"Order informations","معلومات الطلبية"
-"Orders","الطلبيات"
-"Password *","كلمة المرور *"
-"Password must have at least 8 letters.","كلمة المرور يجب أن تكون 8 محارف على الأقل."
-"Passwords must be identical","كلمات المرو يجب أن تكون متطابقة"
-"Passwords must be identical.","كلمات المرو يجب أن تكون متطابقة."
-"Payment method","طريقة الدفع"
-"Payment","الدفع"
-"Personal Details","المعلومات الشخصية"
-"Phone Number","رقم الهاتف"
-"Phone number may be needed by carrier","رقم الهاتف قد يكون مطلوب من الموصّل"
-"Place the order","إتمام الطلب"
-"Please change Your search criteria and try again. If still not finding anything relevant", "يرجى المحاولة مرّة ثانية بعد تغيير معايير البحث إن لم تجد أي شيء ذو صلة"
-"Please check if all data are correct","الرجاء التأكّد من صحّة كافة البيانات"
-"Please confirm order you placed when you was offline","الرجاء التأكيد على إتمام الطلبية التي أنشأتها عندما كنت غير متّصل بالإنترنت"
-"Please provide valid e-mail address.","يرجى إدخال بريد إلكتروني صحيح"
-"Price {variant}","السعر {variant}"
-"Price","السعر"
-"Privacy policy","سياسة الخصوصيّة"
-"Product Name","اسم المنتج"
-"Product details","تفاصيل المنتج"
-"Purchase","شراء"
-"Qty","الكميّة"
-"Quantity","الكميّة"
-"Register an account","إنشاء حساب"
-"Register","الاشتراك"
-"Remake order","إعادة الطلب"
-"Remember me","تذكرني"
-"Remove from compare","إزالة من المقارنة"
-"Remove","حذف"
-"Repeat new password *","إعادة كلمة المرور الجديدة *"
-"Repeat password *","إعادة كلمة المرور *"
-"Reset password","إعادة ضبط كلمة المرور"
-"Resetting the password ... ","جاري إعادة ضبط كلمة المرور ..."
-"Return policy","سياسة الإرجاع"
-"Return to shopping","العودة للتسوّق"
-"Returns","الاسترجاع"
-"Review order","معاينة الطلبيّة"
-"SKU","رمز المنتج"
-"SKU: {sku}","رمز المنتج {sku}"
-"Safety","الحماية"
-"Sale","تخفيض"
-"Search","البحث"
-"See details","عرض التفاصيل"
-"See our bestsellers","مشاهدة الأكثر مبيعاً"
-"Select No","اختر لا"
-"Select Yes","اختر نعم"
-"Select color ","اختر لون "
-"Select size {variant}","اختيار قياس {variant}"
-"Ship to my default address","الشحن لعنواني الافتراضي"
-"Shipping address","عنوان الشّحن"
-"Shipping method","طريقة الشحن"
-"Shipping","الشحن"
-"Shopping cart","سلّة التّسوّق"
-"Shopping summary","ملخص التسوّق"
-"Show subcategories","عرض التصنيفات الفرعيّة"
-"Sign up to our newsletter and receive a coupon for 10% off!","اشترك بالنشرة البريديّة واحصل على خصم 10٪"
-"Similar products","منتجات مشابهة"
-"Size guide","دليل القياس"
-"Something went wrong ...","حدث خطأ ما ..."
-"Sort By","ترتيب حسب"
-"State / Province","الولاية / المحافظة"
-"Status","الحالة"
-"Store locator","فروعنا"
-"Street name *","اسم الشارع *"
-"Street name","اسم الشارع"
-"Subscribe to the newsletter and receive a coupon for 10% off","اشترك بالنشرة البريديّة واحصل على خصم 10٪"
-"Subscribe","اشترك"
-"Subtotal","المجموع الفرعي"
-"Tax ID *","الرمز الضريبي *"
-"Tax ID must have at least 3 letters.","يجب أن يكون الرمز الضريبي 3 حروف على الأقل."
-"Tax identification number *","الرقم الضريبي *"
-"Tax identification number must have at least 3 letters.","يجب أن يحتوي الرقم الضريبي على 3 حروف على الأقل."
-"Tax","الضريبة"
-"Terms and conditions","الشروط والأحكام"
-"The new account will be created with the purchase. You will receive details on e-mail.","سوف يتم إنشاء حساب جديد لمشترياتك. سوف يصلك بريد إلكتروني بالتفاصيل."
-"This product is out of stock.","هذا المنتج غير متوفّر."
-"Toggle password visibility","تبديل رؤية كلمة المرور"
-"Track my order","تتبع طلبيّتي"
-"Type your opinion","اكتب رأيك"
-"Type","النوع"
-"Unfortunately we can't find the page you are looking for.","عذراً لم نجد الصفحة التي تبحث عنها."
-"United States","الولايات المتّحدة"
-"Update my preferences","تحديث "
-"Update my profile","تحديث ملفي"
-"Update my shipping details","تحديث تفاصيل الشحن"
-"Use my billing data","استخدم بياناتي الخاصّة بالدفع"
-"Use my company's address details","استخدم عنوان شركتي"
-"Use my company's address details","استخدم عنوان شركتي""Tax ID must have at least 3 letters.","يجب أن يحتوي الرمز الضريبي على 3 حروف."
-"Value","القيمة"
-"View all","عرض الكل"
-"View order","عرض الطلبية"
-"We can't find the page","لم نستطع إيجاد الصفحة"
-"We found other products you might like","منتجات أخرى قد تعجبك"
-"We use cookies to give you the best shopping experience.","نحن نستخدم ملفت تعريف الارتباط لتزويدك بأفضل تجربة تسوّق."
-"We will send you details regarding the order","سوف نرسل لك التفاصيل المتعلّقة بطلبيتك"
-"We will send you the invoice to given e-mail address","سنرسل الفاتورة لبريدك الإلكتروني المعطى"
-"We've noticed Internal Server Error while rendering this request.","لاحظنا حصول خطأ داخلي في المخدّم أثناء تقديم هذا الطلب."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","لقد أرسلنا تعليمات ضبط كلمة المرور إلى بريدك الإلكتروني. يرجى تفقّد بريدك الإلكتروني والضغط على الرابط المرسل."
-"What we can improve?","ما الذي يمكننا تحسينه ؟"
-"Wishlist","قائمة المفضّلة"
-"Women fashion","أزياء نسائية"
-"You are logged in as {firstname}","أنت مسجل دخولك كـ {firstname}"
-"You are offline. Some features might not be available.","أنت غير متّصل بالإنترنت، بعض وظائف الموقع محدودة"
-"You can also use","يمكنك أيضاً استخدام"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","يمكنك الدخول لحسابك باستخدام البريد الإلكتروني وكلمة المرور اللّذان عرفتهما سابقاً. يمكنك في حسابكتعديل بياناتك، ومراجعة سجلات مشترياتك، وتعديل اشتراكك في النشرة البريديّة"
-"You have been successfully subscribed to our newsletter!","تم اشتراكك في النشرة البريدية بنجاح!"
-"You have no items to compare.","لا يوجد منتجات للمقارنة."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","تم إتمام طلبيتك بنجاح. يمكنك مراجعة حالة الطلبية باستخدام حالة الطلبية.سوف نرسل رسالة تأكيد الطلبية إلى بريدك الإلكتروني مع تفاصيل الطلبية ورابط يمكنك من مراجعة حالة الطلبية."
-"You must accept the terms and conditions.","يجب أن توافق على الشروط والأحكام."
-"You've entered an incorrect coupon code. Please try again.","لقد أدخل "
-"Your Account","حسابك"
-"Your feedback is important for us. Let us know what we could improve.","رأيك يهمّنا،دعنا نعلم كيف نستطيع أن نصبح أفضل."
-"Your purchase","مشترياتك"
-"Your shopping cart is empty.","سلّة تسوّقك فارغة."
-"Your wishlist is empty.","قائمة مفضّلتك فارغة."
-"Zip-code *","الرمز البريدي *"
-"Zip-code must have at least 3 letters.","يجب أن يحتوي الرمز البريدي على ثلاث حروف"
-"Zip-code","الرمز البريدي"
-"a chat","المحادثة"
-"a contact page","صفحة اتصل بنا"
-"browse our catalog","تصفح قائمة منتجاتنا"
-"color","اللون"
-"color_filter","اللّون"
-"erin_recommends_filter","توصيات Erin"
-"login to your account","تسجيل الدخول لحسابك"
-"login","دخول"
-"or write to us through","أو اكتب لنا رسالة عبر"
-"or","أو"
-"price_filter","السعر"
-"register an account","إنشاء حساب"
-"return to log in","العودة لتسجيل الدخول"
-"search","البحث"
-"size","القياس"
-"size_filter","القياس"
-"to find product you were looking for.","لإيجاد المنتج الذي تبحث عنه."
-"to find something beautiful for You!","لإيجاد شيء جميل لك!"
diff --git a/src/themes/default/resource/i18n/cs-CZ.csv b/src/themes/default/resource/i18n/cs-CZ.csv
deleted file mode 100644
index 36fa33973..000000000
--- a/src/themes/default/resource/i18n/cs-CZ.csv
+++ /dev/null
@@ -1,269 +0,0 @@
-"About us","O nás"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Přidejte kód pro slevu"
-"Add discount code","Použít"
-"Add to cart","Přidat do košíku"
-"Add to compare","Přidat ke srovnání"
-"Add to favorite","Přidat do oblíbených"
-"Allow notification about the order","Povolit oznámení o objednávce"
-"Author","Autor"
-"Authorization in progress ...","Probíhá autorizace ..."
-"Back to login","Zpět k přihlášení"
-"Back","Zpět"
-"Billing address","Fakturační adresa"
-"Cancel","Zrušit"
-"Cash on delivery","Dobírka"
-"Change my password","Změnit mé heslo"
-"Choose your country","Vyberte svoji zemi"
-"City *","Město *"
-"City","Město"
-"Close","Zavřít"
-"Cms Page Sync","Synchronizace stránky Cms"
-"Company name *","Jméno firmy *"
-"Compare products","Porovnejte produkty"
-"Confirm your order","Potvrďte svou objednávku"
-"Contact us","Kontaktujte nás"
-"Continue to payment","Pokračujte v platbě"
-"Continue to shipping","Pokračujte na údaje o dopravě"
-"Copy address data from shipping","Zkopírujte data z informací pro doručení"
-"Country *","Země *"
-"Country","Země"
-"Create a new account","Vytvořit nový účet"
-"Current password *","Aktuální heslo *"
-"Custom Cms Page","Vlastní stránka Cms"
-"Customer service","Služby zákazníkům"
-"DPD Courier","DPD"
-"Date and time","Datum a čas"
-"Delivery","Doručení"
-"Departments","Oddělení"
-"Discount code","Slevový kód"
-"Discount","Sleva"
-"Don't hesitate and","Neváhejte a"
-"E-mail address *","E-mailová adresa *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Zašlete nám e-mail na demo@vuestorefront.io s jakýmikoliv otázkami či návrhy, jak bychom mohli zlepšit produkty nebo požitek z nákupu"
-"Edit newsletter preferences","Upravte předvolby pro zasílání novinek"
-"Edit payment","Upravit platbu"
-"Edit personal details","Upravit osobní údaje"
-"Edit shipping","Upravit informace o dopravě"
-"Edit your profile","Upravte svůj profil"
-"Edit your shipping details","Upravte své informace pro doručení"
-"Edit","Upravit"
-"Email address *","Emailová adresa *"
-"Email address","Emailová adresa"
-"English","Angličtina"
-"Enter your email to receive instructions on how to reset your password.","Zadejte svůj e-mail pro zaslání instrukcí pro obnovení hesla."
-"Erin recommends","Erin doporučuje"
-"Error while sending reset password e-mail","Chyba při posílání e-mailu pro obnovu hesla"
-"Everything new","Všechny novinky"
-"Extension developers would like to thank you for placing an order!","Vývojáři rozšíření by vám chtěli poděkovat za zadání objednávky!"
-"Field is required","Políčko je vyžadováno"
-"Filter","Filtr"
-"Filters","Filtry"
-"First name *","Jméno *"
-"First name","Jméno"
-"Forgot the password?","Zapomněli jste heslo?"
-"General agreement","Obecná dohoda"
-"German","Němčina"
-"Germany","Německo"
-"Get inspired","Inspirujte se"
-"Give a feedback","Dejte nám zpětnou vazbu"
-"Go review the order","Přejít na kontrolu objednávky"
-"Go to Facebook","Přejít na Facebook"
-"Go to Instagram","Přejít na Instagram"
-"Go to Pinterest","Přejít na Pinterest"
-"Go to Youtube","Přejít na Youtube"
-"Go to checkout","Přejít k pokladně"
-"Help","Pomoc"
-"Home","Domovská stránka"
-"House/Apartment number *","Číslo domu/bytu *"
-"House/Apartment number","Číslo domu/bytu"
-"I accept ","Přijímám "
-"I accept terms and conditions","Souhlasím s podmínkami"
-"I agree to","Souhlasím"
-"I have a company and want to receive an invoice for every order","Mám společnost a chci obdržet fakturu za každou objednávku"
-"I want to create an account","Chci vytvořit účet"
-"I want to create an account'","Chci vytvořit účet'"
-"I want to generate an invoice for the company","Chci vygenerovat fakturu pro firmu"
-"I want to receive a newsletter, and agree to its terms","Chci dostávat novinky a souhlasím s jejích podmínkami"
-"If you need an assistance you can drop us a line on","Pokud potřebujete pomoc, můžete nás kontaktovat na"
-"If you need an assistance you can drop us a line on","Pokud potřebujete pomoc, můžete nás kontaktovat na"
-"Internal Server Error 500","Interní chyba serveru 500"
-"Italian","Italština"
-"Italy","Itálie"
-"Items ordered","Objednané položky"
-"Kidswear","Dětská móda"
-"Last name *","Příjmení *"
-"Last name","Příjmení"
-"Latest","Nejnovější"
-"Legal notice","Právní upozornění"
-"Load more","Načíst další"
-"Log in to your account","Přihlaste se k účtu"
-"Log in","Přihlášení"
-"Login to your account","Přihlaste se do svého účtu"
-"Logout","Odhlásit se"
-"Magazine","Časopis"
-"Men's fashion","Pánská móda"
-"My Account","Můj Účet"
-"My Recently viewed products","Moje naposledy zhlédnuté produkty"
-"My account","Můj účet"
-"My loyalty card","Moje věrnostní karta"
-"My newsletter","Moje novinky"
-"My orders","Mé objednávky"
-"My product reviews","Moje recenze produktů"
-"My profile","Můj profil"
-"My shipping details","Mé informace pro doručení"
-"Name must have at least 3 letters.","Jméno musí mít alespoň 3 písmena."
-"New Luma Yoga Collection","Nová kolekce Luma Yoga"
-"New password *","Nové heslo *"
-"Newsletter","Odběr novinek"
-"No orders yet","Nemáte zatím žádné objednávky"
-"No products found!","Nebyly nalezeny žádné produkty!"
-"No products yet","Dosud žádné produkty"
-"No results were found.","Nebyly nalezeny žádné výsledky."
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","Zatím nebyly přidány žádné recenze. Neváhejte, sdělte nám svůj názor a napište první recenzi!"
-"Open menu","Otevřít menu"
-"Open microcart","Otevřít náhled košíku"
-"Open my account","Otevřít můj účet"
-"Open search panel","Otevřít panel pro vyhledávání"
-"Open wishlist","Otevřít seznam přání"
-"Order #{id}","Objednávka #{id}"
-"Order ID","Číslo objednávky"
-"Order Summary","Přehled objednávky"
-"Order confirmation","Potvrzení objednávky"
-"Order informations","Informace o objednávce"
-"Orders","Objednávky"
-"Password *","Heslo *"
-"Password must have at least 8 letters.","Heslo musí mít alespoň 8 znaků."
-"Passwords must be identical","Heslo se musí shodovat"
-"Passwords must be identical.","Hesla se musí shodovat."
-"Payment method","Způsob platby"
-"Payment","Platba"
-"Personal Details","Osobní údaje"
-"Phone Number","Telefonní číslo"
-"Phone number may be needed by carrier","Telefonní číslo může být vyžadováno dopravcem"
-"Place the order","Zadat objednávku"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Prosíme změňte svá vyhledávací kritéria a zkuste znovu. Pokud stále nenacházíte, co jste hledali, zkuste nějaké z našich bestsellerů!"
-"Please check if all data are correct","Zkontrolujte, zda jsou všechny údaje správné"
-"Please confirm order you placed when you was offline","Prosíme potvrďte objednávku, kterou jste zadali, když jste byli v režimu offline"
-"Please provide valid e-mail address.","Prosíme uveďte platnou e-mailovou adresu."
-"Price {variant}","Cena {variant}"
-"Price","Cena"
-"Privacy policy","Zásady ochrany osobních údajů"
-"Product Name","Jméno výrobku"
-"Product details","Detaily produktu"
-"Purchase","Nákup"
-"Qty","Množství"
-"Quantity","Množství"
-"Register an account","Zaregistrovat účet"
-"Register","Registrace"
-"Remake order","Předělat objednávku"
-"Remember me","Zapamatovat si mě"
-"Remove from compare","Odstranit ze srovnávání"
-"Remove","Odstranit"
-"Repeat new password *","Zopakujte nové heslo *"
-"Repeat password *","Opakovat heslo *"
-"Reset password","Obnovit heslo"
-"Resetting the password ... ","Heslo se obnovuje ... "
-"Return policy","Vrácení zboží"
-"Return to shopping","Zpět k nákupu"
-"Returns","Návrat zboží"
-"Review order","Zkontrolovat objednávku"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","Bezpečnost"
-"Sale","Sleva"
-"Search","Hledat"
-"See details","Zobrazit podrobnosti"
-"See our bestsellers","Podívejte se na naše bestsellery"
-"Select No","Vyberte možnost Ne"
-"Select Yes","Vyberte možnost Ano"
-"Select color ","Vyberte barvu "
-"Select size {variant}","Vyberte velikost {variant}"
-"Ship to my default address","Doručit na mou výchozí adresu"
-"Shipping address","Doručovací adresa"
-"Shipping method","Způsob dopravy"
-"Shipping","Doprava"
-"Shopping cart","Nákupní košík"
-"Shopping summary","Shrnutí nákupu"
-"Show subcategories","Zobrazit podkategorie"
-"Sign up to our newsletter and receive a coupon for 10% off!","Přihlaste se k odběru našich novinek a získejte kupón na 10% slevu!"
-"Similar products","Podobné produkty"
-"Size guide","Průvodce velikostí"
-"Something went wrong ...","Něco se pokazilo ..."
-"Sort By","Seřazeno podle"
-"State / Province","Stát / provincie"
-"Status","Stav"
-"Store locator","Lokalizátor obchodů"
-"Street name *","Název ulice *"
-"Street name","Název ulice"
-"Subscribe to the newsletter and receive a coupon for 10% off","Přihlaste se k odběru našich novinek a získejte kupón na 10% slevu."
-"Subscribe","Přihlásit se k odběru"
-"Subtotal","Mezisoučet"
-"Tax ID *","DIČ *"
-"Tax ID must have at least 3 letters.","DIČ musí obsahovat alespoň tři znaky."
-"Tax identification number *","Daňové identifikační číslo *"
-"Tax identification number must have at least 3 letters.","Daňové identifikační číslo musí mít alespoň 3 znaky."
-"Tax","Daň"
-"Terms and conditions","Smluvní podmínky"
-"The new account will be created with the purchase. You will receive details on e-mail.","Nový účet bude vytvořen při nákupu. Informace obdržíte na mail."
-"This product is out of stock.","Tento produkt je vyprodán."
-"Toggle password visibility","Přepínání viditelnosti hesla"
-"Track my order","Sledovat objednávku"
-"Type your opinion","Napište svůj názor"
-"Type","Typ"
-"Unfortunately we can't find the page you are looking for.","Bohužel nemůžeme najít stránku, kterou hledáte."
-"United States","Spojené státy"
-"Update my preferences","Aktualizujte své předvolby"
-"Update my profile","Aktualizovat můj profil
-"Update my shipping details","Aktualizovat mé informace pro doručení "
-"Use my billing data","Použijte moje fakturační údaje"
-"Use my company's address details","Použijte podrobnosti o adrese mé firmy"
-"Use my company's address details","Použijte podrobnosti o adrese mé firmy"
-"Value","Hodnota"
-"View all","Zobrazit vše"
-"View order","Zobrazit objednávku"
-"We can't find the page","Stránku nemůžeme najít"
-"We found other products you might like","Vybrali jsme produkty, které by se vám mohli líbit"
-"We use cookies to give you the best shopping experience.","Používáme soubory cookies, abychom vám poskytli ten nejlepší nákupní zážitek."
-"We will send you details regarding the order","Pošleme vám podrobnosti o objednávce"
-"We will send you the invoice to given e-mail address","Fakturu vám zašleme na danou e-mailovou adresu"
-"We've noticed Internal Server Error while rendering this request.","Při vyřizování této žádosti jsme zaznamenali interní chybu serveru."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Na e-mail jsme vám zaslali informace pro obnovení hesla. Zkontrolujte si příchozí poštu a postupujte dle zaslaného odkazu"
-"What we can improve?","Co můžeme zlepšit?"
-"Wishlist","Seznam přání"
-"Women fashion","Dámská móda"
-"You are logged in as {firstname}","Jste přihlášeni jako {firstname}"
-"You are offline. Some features might not be available.","Jste offline, některé funkce jsou tudíž omezeny"
-"You can also use","Můžete také použít"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Můžete se přihlásit ke svému účtu pomocí e-mailu a hesla, které byly dříve nastaveny. Na svém účtu můžete upravovat informace na svém profilu, zkontrolovat historii transakcí, upravit odběr novinek."
-"You have been successfully subscribed to our newsletter!","Byli jste úspěšně přihlášeni k odběru našich novinek!"
-"You have no items to compare.","Nemáte žádné položky k porovnání."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress."," Úspěšně jste zadali objednávku. Stav objednávky můžete zkontrolovat pomocí naší kontroly stavu objednávky. Obdržíte e-mail s potvrzením objednávky s podrobnostmi o objednávce a odkazem ke sledování jejího stavu."
-"You must accept the terms and conditions.","Musíte souhlasit se smluvními podmínkami."
-"You've entered an incorrect coupon code. Please try again.","Zadali jste nesprávný kuponový kód. Prosím zkuste znovu."
-"Your Account","Váš účet"
-"Your feedback is important for us. Let us know what we could improve.","Vaše zpětná vazba je pro nás důležitá. Dejte nám vědět, co bychom mohli zlepšit."
-"Your purchase","Váš nákup"
-"Your shopping cart is empty.","Váš nákupní košík je prázdný."
-"Your wishlist is empty.","Váš seznam přání je prázdný."
-"Zip-code *","PSČ *"
-"Zip-code must have at least 3 letters.","PSČ musí mít alespoň 3 čísla."
-"Zip-code","PSČ"
-"a chat","chatu"
-"a contact page","kontaktní stránku"
-"browse our catalog","prohlídněte si náš katalog"
-"color","barva"
-"color_filter","Barva"
-"erin_recommends_filter","Erin doporučuje"
-"login to your account","přihlaste se do svého účtu"
-"login","přihlášení"
-"or write to us through","nebo nám napište skrze"
-"or","nebo"
-"price_filter","Cena"
-"register an account","zaregistrovat účet"
-"return to log in","návrat k přihlášení"
-"search","vyhledávání"
-"size","velikost"
-"size_filter","Velikost"
-"to find product you were looking for.","pro nalezení produktů, které hledáte."
-"to find something beautiful for You!","najděte pro sebe něco krásného!"
diff --git a/src/themes/default/resource/i18n/de-DE.csv b/src/themes/default/resource/i18n/de-DE.csv
deleted file mode 100644
index 4cd3a4c91..000000000
--- a/src/themes/default/resource/i18n/de-DE.csv
+++ /dev/null
@@ -1,276 +0,0 @@
-"About us (Magento CMS)","Über uns (Magento CMS)"
-"About us","Über uns"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Einen Rabatt-Code hinzufügen"
-"Add discount code","Rabatt-Code hinzufügen"
-"Add to cart","In den Warenkorb"
-"Add to compare","Zum Vergleich hinzufügen"
-"Add to favorite","Zu Favoriten hinzufügen"
-"Allow notification about the order","Erlauben Sie Benachrichtigungen zu Ihrer Bestellung"
-"Author","Autor"
-"Authorization in progress ...","Autorisierung läuft ..."
-"Back to login","Zurück zur Anmeldung"
-"Back","Zurück"
-"Billing address","Rechnungsadresse"
-"Cancel","Abbrechen"
-"Cash on delivery","Bezahlung bei Lieferung"
-"Change my password","Mein Passwort ändern"
-"Choose your country","Wählen Sie Ihr Land aus"
-"City *","Ort *"
-"City","Ort"
-"Clear","Leeren"
-"Close","Schließen"
-"Cms Page Sync","Cms-Seiten synchronisieren"
-"Company name *","Firma *"
-"Compare products","Produkte vergleichen"
-"Confirm your order","Bestätigen Sie Ihre Bestellung"
-"Contact us","Kontaktieren Sie uns"
-"Continue to payment","Weiter zur Zahlung"
-"Continue to shipping","Weiter zum Versand"
-"Copy address data from shipping","Die Rechnungsadresse entspricht der Lieferadresse"
-"Country *","Land *"
-"Country","Land"
-"Create a new account","Erstellen Sie ein neues Konto"
-"Current password *","Aktuelles Passwort *"
-"Custom Cms Page","Angepasste Cms-Seite"
-"Customer service","Kundenservice"
-"DPD Courier","DPD Kurier"
-"Date and time","Datum und Uhrzeit"
-"Delivery","Lieferung"
-"Departments","Kategorien"
-"Discount code","Rabatt-Code"
-"Discount","Rabatt"
-"Don't hesitate and","Zögeren Sie nicht und"
-"E-mail address *","E-Mail-Adresse *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Schreiben Sie uns unter demo@vuestorefront.io an, bezüglich Fragen oder Vorschlägen, wie wir das Einkaufserlebnis und die Produkte verbessern können"
-"Edit newsletter preferences","Newsletter-Einstellungen bearbeiten"
-"Edit payment","Zahlungsart bearbeiten"
-"Edit personal details","Persönliche Daten bearbeiten"
-"Edit shipping","Versand bearbeiten"
-"Edit your profile","Mein Konto bearbeiten"
-"Edit your shipping details","Meine Versanddetails bearbeiten"
-"Edit","Bearbeiten"
-"Email address *","E-Mail-Adresse *"
-"Email address","E-Mail-Adresse"
-"English","Englisch"
-"Enter your email to receive instructions on how to reset your password.","Geben Sie Ihre E-Mail-Adresse ein, um Anweisungen zum Zurücksetzen Ihres Passworts zu erhalten."
-"Erin recommends","Erin empfiehlt"
-"Erin recommends","Erin empfiehlt"
-"Error while sending reset password e-mail","Fehler beim Senden der E-Mail zum Zurücksetzen des Passworts"
-"Everything new","Die neusten Produkte"
-"Extension developers would like to thank you for placing an order!","Die Extension-Entwickler möchten sich bei Ihnen gerne für Ihre Bestellung bedanken!"
-"Field is required","Dies ist ein Pflichtfeld"
-"Filter","Filter"
-"Filters","Filter"
-"First name *","Vorname *"
-"First name","Vorname"
-"Forgot the password?","Passwort vergessen?"
-"General agreement","Allgemeine Zustimmung"
-"German","Deutsch"
-"Germany","Deutschland"
-"Get inspired","Lassen Sie sich inspirieren"
-"Give a feedback","Geben Sie Feedback"
-"Go review the order","Weiter zur Bestellübersicht"
-"Go to Facebook","Zu Facebook gehen"
-"Go to Instagram","Zu Instagram gehen"
-"Go to Pinterest","Zu Pinterest gehen"
-"Go to Youtube","Zu Youtube gehen"
-"Go to checkout","Zur Kasse gehen"
-"Help","Hilfe"
-"Home","Startseite"
-"House/Apartment number *","Hausnummer *"
-"House/Apartment number","Hausnummer"
-"I accept ","Ich akzeptiere "
-"I accept terms and conditions","Ich akzeptiere die Allgemeinen Geschäftsbedingungen"
-"I agree to","Ich akzeptiere die"
-"I have a company and want to receive an invoice for every order","Ich habe eine Firma und möchte zu jeder Bestellung eine Rechnung erhalten."
-"I want to create an account","Ich möchte ein Konto erstellen"
-"I want to create an account'","Ich möchte ein Konto erstellen'"
-"I want to generate an invoice for the company","Ich möchte eine Rechnung für die Firma erstellen"
-"I want to receive a newsletter, and agree to its terms","Ich möchte einen Newsletter erhalten und aktzeptiere die Bedingungen"
-"If you need an assistance you can drop us a line on","Wenn Sie Hilfe benötigen, kontaktieren Sie uns über"
-"If you need an assistance you can drop us a line on","Wenn Sie Hilfe brauchen können Sie uns eine Nachricht schreiben"
-"Internal Server Error 500","500 Interner Server Fehler"
-"Italian","Italienisch"
-"Italy","Italien"
-"Items ordered","Bestellte Artikel"
-"Kidswear","Kinderkleidung"
-"Last name *","Nachname *"
-"Last name","Nachname"
-"Latest","Neueste"
-"Legal notice","Impressum"
-"Load more","Mehr laden"
-"Log in to your account","Mit Ihrem Konto anmelden"
-"Log in","Anmelden"
-"Login to your account","Mit Ihrem Konto anmelden"
-"Logout","Ausloggen"
-"Magazine","Zeitschrift"
-"Men's fashion","Herrenmode"
-"My Account","Mein Konto"
-"My Recently viewed products","Meine zuletzt angesehenen Produkte"
-"My account","Mein Konto"
-"My loyalty card","Meine Kundenkarte"
-"My newsletter","Mein Newsletter"
-"My orders","Meine Bestellungen"
-"My product reviews","Meine Produktrezension"
-"My profile","Mein Konto"
-"My shipping details","Meine Versanddetails"
-"Name must have at least 3 letters.","Ihr Name muss aus mindestens 3 Zeichen bestehen."
-"New Luma Yoga Collection","Die neue Luma Yoga Kollektion"
-"New password *","Neues Passwort *"
-"Newsletter","Newsletter"
-"No orders yet","Noch keine Bestellungen"
-"No products found!","Keine Produkte gefunden!"
-"No products yet","Keine Produkte"
-"No results were found.","Es wurden keine Ergebnisse gefunden."
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","Bisher wurden keine Bewertungen abgegeben. Bitte zögern Sie nicht, Ihre Beurteilung abzugeben und schreiben Sie die erste Bewertung!"
-"Open menu","Menü öffnen"
-"Open microcart","Microcart öffnen"
-"Open my account","Mein Konto öffnen"
-"Open search panel","Suche öffnen"
-"Open wishlist","Wunschliste öffnen"
-"Order #{id}","Bestellung #{id}"
-"Order ID","ID der Bestellung"
-"Order Summary","Bestellübersicht"
-"Order confirmation","Bestellbestätigung"
-"Order informations","Bestellinformationen"
-"Orders","Bestellungen"
-"Password *","Passwort *"
-"Password must have at least 8 letters.","Das Passwort muss mindestens 8 Zeichen enthalten."
-"Passwords must be identical","Die Passwörter müssen identisch sein"
-"Passwords must be identical.","Die Passwörter müssen identisch sein."
-"Payment method","Zahlungsmethode"
-"Payment","Zahlung"
-"Personal Details","Persönliche Daten"
-"Phone Number","Telefonnummer"
-"Phone number may be needed by carrier","Telefonnummer könnte für die Zustellung benötigt werden"
-"Place the order","Bestellung abschicken"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Bitte ändern Sie Ihre Suchkriterien und versuchen Sie es erneut. Wenn Sie weiterhin nichts Relevantes finden können, besuchen Sie unsere Homepage und probieren Sie einige unserer Bestseller!"
-"Please check if all data are correct","Bitte überprüfen Sie, ob alle Daten korrekt sind"
-"Please confirm order you placed when you was offline","Bitte bestätigen Sie Ihre Bestellung, die Sie im Offline-Modus abgegeben haben"
-"Please provide valid e-mail address.","Bitte eine gültige E-Mail-Adresse eingeben."
-"Price {variant}","Preis {variant}"
-"Price","Preis"
-"Price: High to low","Preis: absteigend"
-"Price: Low to high","Preis: aufsteigend"
-"Privacy policy","Datenschutzbestimmung"
-"Product Name","Produktbezeichnung"
-"Product details","Produktdetails"
-"Purchase","Kaufen"
-"Qty","Mg."
-"Quantity","Menge"
-"Register an account","Konto registrieren"
-"Register","Registrieren"
-"Remake order","Nachbestellen"
-"Remember me","Eingeloggt bleiben"
-"Remove from compare","Von Vergleichsliste entfernen"
-"Remove","Entfernen"
-"Repeat new password *","Neues Passwort wiederholen *"
-"Repeat password *","Passwort wiederholen *"
-"Reset password","Passwort zurücksetzen"
-"Resetting the password ... ","Passwort wird zurückgesetzt ... "
-"Return policy"," Rückgaberichtlinien"
-"Return to shopping","Zurück zum Shop"
-"Returns","Retouren"
-"Review order","Bestellung überprüfen"
-"SKU","Artikelnr."
-"SKU: {sku}","Artikelnummer: {sku}"
-"Safety","Sicherheit"
-"Sale","Angebote"
-"Search","Suche"
-"Searched term should consist of at least 3 characters.","Der Suchbegriff sollte mindestens 3 Zeichen haben."
-"See details","Details anzeigen"
-"See our bestsellers","Sehen Sie sich unsere Bestseller an"
-"Select No","Wähle Nein"
-"Select Yes","Wähle Ja"
-"Select color ","Farbe wählen "
-"Select size {variant}","Größe {variant} auswählen"
-"Ship to my default address","An meine Standardadresse senden"
-"Shipping address","Versandadresse"
-"Shipping method","Versandart"
-"Shipping","Versand"
-"Shopping cart","Warenkorb"
-"Shopping summary","Zusammenfassung des Einkaufs"
-"Show subcategories","Unterkategorien anzeigen"
-"Sign up to our newsletter and receive a coupon for 10% off!","Melden Sie sich für unseren Newsletter an und erhalten Sie einen Rabattgutschein in Höhe von 10%!"
-"Similar products","Ähnliche Produkte"
-"Size guide","Größentabelle"
-"Something went wrong ...","Etwas ist schief gegangen..."
-"Sort By","Sortieren nach"
-"State / Province","Bundesland"
-"Status","Status"
-"Store locator","Händlersuche"
-"Street name *","Straße *"
-"Street name","Straße"
-"Subscribe to the newsletter and receive a coupon for 10% off","Abonnieren Sie unseren Newsletter und erhalten Sie einen Rabattgutschein in Höhe von 10%"
-"Subscribe","Abonnieren"
-"Subtotal","Zwischensumme"
-"Tax ID *","Steuer ID *"
-"Tax ID must have at least 3 letters.","Die Steuer ID muss aus mindestend 3 Zeichen bestehen."
-"Tax identification number *","Steuer-Identifikationsnummer *"
-"Tax identification number must have at least 3 letters.","Die Steuer-Identifikationsnummer muss aus mindestens 3 Zeichen bestehen."
-"Tax","Steuer"
-"Terms and conditions","Allgemeine Geschäftsbedingungen"
-"The new account will be created with the purchase. You will receive details on e-mail.","Das neue Konto wird mit dem Abschluss der Bestellung erstellt. Weitere Details werden Ihnen per E-Mail zugesendet."
-"The server order id has been set to ","Die Server-Bestellnummer wurde gesetzt auf "
-"This product is out of stock.","Das Produkt ist nicht mehr auf Lager."
-"Toggle password visibility","Sichtbarkeit des Passwortes umschalten"
-"Track my order","Meine Bestellung verfolgen"
-"Type your opinion","Schreiben Sie Ihre Meinung"
-"Type","Typ"
-"Unfortunately we can't find the page you are looking for.","Leider kann die von Ihnen gesuchte Seite nicht gefunden werden."
-"United States","Vereinigte Staaten"
-"Update my preferences","Einstellungen aktualisieren"
-"Update my profile","Mein Konto aktualisieren"
-"Update my shipping details","Meine Versanddetails aktualisieren"
-"Use my billing data","Meine Rechnungsdaten verwenden"
-"Use my company's address details","Bitte die Unternehmensanschrift verwenden"
-"Use my company's address details","Verwenden Sie die Adressdaten meiner Firma"
-"Value","Wert"
-"View all","Alle ansehen"
-"View order","Bestellung ansehen"
-"We can't find the page","Die Seite kann nicht gefunden werden"
-"We found other products you might like","Wir haben andere Produkte gefunden, die Sie interessieren könnten"
-"We use cookies to give you the best shopping experience.","Wir verwenden Cookies, um Ihnen das beste Einkaufserlebnis zu bieten."
-"We will send you details regarding the order","Wir senden Ihnen Details bezüglich Ihrer Bestellung zu"
-"We will send you the invoice to given e-mail address","Wir senden Ihnen die Rechnung an die angegebene E-Mail-Adresse"
-"We've noticed Internal Server Error while rendering this request.","Bei der Verarbeitung Ihrer Anfrage gab es einen internen Fehler."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Wir haben Ihnen eine Anleitung zum Zurücksetzen des Passworts an Ihre E-Mail-Adresse gesendet. Bitte überprüfen Sie Ihren Posteingang und folgen Sie dem Link."
-"What we can improve?","Was können wir verbessern?"
-"Wishlist","Wunschliste"
-"Women fashion","Damenmode"
-"You are logged in as {firstname}","Sie sind als {firstname} eingeloggt"
-"You are offline. Some features might not be available.","Sie sind offline. Einige Funktionen sind daher nur eingeschränkt nutzbar"
-"You can also use","Sie können auch"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Sie können sich mit Ihrer angegebenen E-Mail-Adresse und Ihrem Passwort in Ihrem Konto anmelden. In Ihrem Konto können Sie Ihr Profil bearbeiten, Ihren Transaktionsverlauf prüfenund Ihre Newsletteranmeldung bearbeiten."
-"You have been successfully subscribed to our newsletter!","Sie haben unseren Newsletter erfolgreich abonniert!"
-"You have no items to compare.","Es gibt keine Artikel zum Vergleichen."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Sie haben erfolgreich eine Bestellung abgegeben. Sie können den Status Ihrer Bestellung abrufen, indem Sie unser Lieferstatus-Feature nutzen. Sie werden eine Bestellbestätigung per E-Mail erhalten mit den Details Ihrer Bestellung und einem Link zur Sendungsverfolgung."
-"You must accept the terms and conditions.","Sie müssen die Allgemeinen Geschäftsbedingungen akzeptieren."
-"You've entered an incorrect coupon code. Please try again.","Sie haben einen falschen Coupon-Code eingegeben. Bitte versuchen Sie es erneut."
-"Your Account","Ihr Account"
-"Your feedback is important for us. Let us know what we could improve.","Ihr Feedback ist wichtig für uns. Lassen Sie uns wissen, was wir verbessern können."
-"Your purchase","Ihr Einkauf"
-"Your shopping cart is empty.","Ihr Warenkorb ist leer."
-"Your wishlist is empty.","Ihre Wunschliste ist leer."
-"Zip-code *","Postleitzahl *"
-"Zip-code must have at least 3 letters.","Die Postleitzahl muss aus mindestens 3 Zeichen bestehen."
-"Zip-code","Postleitzahl"
-"a chat","den Chat"
-"a contact page","die Kontaktseite"
-"browse our catalog","durchsuchen Sie unseren Katalog,"
-"color","Farbe"
-"color_filter","Farbe"
-"erin_recommends_filter","Erin empfiehlt"
-"login to your account","mit Ihrem Konto anmelden"
-"login","Login"
-"or write to us through","oder schreiben Sie uns über"
-"or","oder"
-"price_filter","Preis"
-"register an account","registrieren Sie ein Konto"
-"return to log in","zurück zur Anmeldung"
-"search","die Suche"
-"size","Größe"
-"size_filter","Größe"
-"to find product you were looking for.","benutzen, um das gesuchte Produkt zu finden."
-"to find something beautiful for You!","um etwas Schönes für Sie zu finden!"
diff --git a/src/themes/default/resource/i18n/en-US.csv b/src/themes/default/resource/i18n/en-US.csv
deleted file mode 100644
index f6f2f5e5b..000000000
--- a/src/themes/default/resource/i18n/en-US.csv
+++ /dev/null
@@ -1,296 +0,0 @@
-"A customer with the same email already exists in an associated website.","A customer with the same email already exists in an associated website."
-"About us (Magento CMS)","About us (Magento CMS)"
-"About us","About us"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Add a discount code"
-"Add discount code","Apply"
-"Add to cart","Add to cart"
-"Add to compare","Add to compare"
-"Add to favorite","Add to favorite"
-"Allow notification about the order","Allow notification about the order"
-"Are you sure you would like to remove all the items from the shopping cart?","Are you sure you would like to remove all the items from the shopping cart?"
-"Are you sure you would like to remove all the items from the wishlist?","Are you sure you would like to remove all the items from the wishlist?"
-"Author","Author"
-"Authorization in progress ...","Authorization in progress ..."
-"Back to login","Back to login"
-"Back","Back"
-"Billing address","Billing address"
-"Cancel","Cancel"
-"Cash on delivery","Cash on delivery"
-"Change my password","Change my password"
-"Choose your country","Choose your country"
-"City *","City *"
-"City","City"
-"Clear cart","Clear cart"
-"Clear filters","Clear filters"
-"Clear wishlist","Clear wishlist"
-"Clear","Clear"
-"Close","Close"
-"Cms Page Sync","Cms Page Sync"
-"Columns","Columns"
-"Company name *","Company name *"
-"Compare products","Compare products"
-"Confirm your order","Confirm your order"
-"Contact us","Contact us"
-"Continue to payment","Continue to payment"
-"Continue to shipping","Continue to shipping"
-"Copy address data from shipping","Copy address data from shipping"
-"Country *","Country *"
-"Country","Country"
-"Create a new account","Create a new account"
-"Current password *","Current password *"
-"Custom Cms Page","Custom Cms Page"
-"Customer service (Magento CMS)","Customer service (Magento CMS)"
-"Customer service","Customer service"
-"DPD Courier","DPD Courier"
-"Date and time","Date and time"
-"Delivery","Delivery"
-"Departments","Departments"
-"Discount code","Discount code"
-"Discount","Discount"
-"Don't hesitate and","Don't hesitate and"
-"E-mail address *","E-mail address *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience"
-"Edit newsletter preferences","Edit newsletter preferences"
-"Edit payment","Edit payment"
-"Edit personal details","Edit personal details"
-"Edit shipping","Edit shipping"
-"Edit your profile","Edit your profile"
-"Edit your shipping details","Edit your shipping details"
-"Edit","Edit"
-"Email address *","Email address *"
-"Email address","Email address"
-"English","English"
-"Enter your email to receive instructions on how to reset your password.","Enter your email to receive instructions on how to reset your password."
-"Erin recommends","Erin recommends"
-"Error while sending reset password e-mail","Error while sending reset password e-mail"
-"Everything new","Everything new"
-"Extension developers would like to thank you for placing an order!","Extension developers would like to thank you for placing an order!"
-"Field is required","Field is required"
-"Filter by categories","Filter by categories"
-"Filter","Filter"
-"Filters","Filters"
-"First name *","First name *"
-"First name","First name"
-"Forgot the password?","Forgot the password?"
-"General agreement","General agreement"
-"German","German"
-"Germany","Germany"
-"Get inspired","Get inspired"
-"Give a feedback","Give a feedback"
-"Go review the order","Go review the order"
-"Go to Facebook","Go to Facebook"
-"Go to Instagram","Go to Instagram"
-"Go to Pinterest","Go to Pinterest"
-"Go to Youtube","Go to Youtube"
-"Go to checkout","Go to checkout"
-"Help","Help"
-"Home","Home"
-"House/Apartment number *","House/Apartment number *"
-"House/Apartment number","House/Apartment number"
-"I accept ","I accept "
-"I accept terms and conditions","I accept terms and conditions"
-"I agree to","I agree to"
-"I have a company and want to receive an invoice for every order","I have a company and want to receive an invoice for every order"
-"I want to create an account","I want to create an account"
-"I want to create an account'","I want to create an account'"
-"I want to generate an invoice for the company","I want to generate an invoice for the company"
-"I want to receive a newsletter, and agree to its terms","I want to receive a newsletter, and agree to its terms"
-"If you need an assistance you can drop us a line on","If you need an assistance you can drop us a line on"
-"Instant Checkout","Instant Checkout"
-"Internal Server Error 500","Internal Server Error 500"
-"Italian","Italian"
-"Italy","Italy"
-"Items ordered","Items ordered"
-"Kidswear","Kidswear"
-"Last name *","Last name *"
-"Last name","Last name"
-"Latest","Latest"
-"Legal notice","Legal notice"
-"Load more","Load more"
-"Log in to your account","Log in to your account"
-"Log in","Log in"
-"Login to your account","Login to your account"
-"Logout","Logout"
-"Magazine","Magazine"
-"Men's fashion","Men's fashion"
-"My Account","My Account"
-"My Recently viewed products","My Recently viewed products"
-"My account","My account"
-"My loyalty card","My loyalty card"
-"My newsletter","My newsletter"
-"My orders","My orders"
-"My product reviews","My product reviews"
-"My profile","My profile"
-"My shipping details","My shipping details"
-"Name must have at least 2 letters.","Name must have at least 2 letters."
-"Name must have at least 3 letters.","Name must have at least 3 letters."
-"New Luma Yoga Collection","New Luma Yoga Collection"
-"New password *","New password *"
-"Newsletter","Newsletter"
-"No orders yet","No orders yet"
-"No products found!","No products found!"
-"No products yet","No products yet"
-"No results were found.","No results were found."
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!"
-"Open menu","Open menu"
-"Open microcart","Open microcart"
-"Open my account","Open my account"
-"Open search panel","Open search panel"
-"Open wishlist","Open wishlist"
-"Order #{id}","Order #{id}"
-"Order ID","Order ID"
-"Order Summary","Order Summary"
-"Order confirmation","Order confirmation"
-"Order informations","Order informations"
-"Orders","Orders"
-"Password *","Password *"
-"Password must have at least 8 letters.","Password must have at least 8 letters."
-"Passwords must be identical","Passwords must be identical"
-"Passwords must be identical.","Passwords must be identical."
-"Payment method","Payment method"
-"Payment","Payment"
-"Personal Details","Personal Details"
-"Phone Number","Phone Number"
-"Phone number may be needed by carrier","Phone number may be needed by carrier"
-"Place the order","Place the order"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!"
-"Please check if all data are correct","Please check if all data are correct"
-"Please confirm order you placed when you was offline","Please confirm order you placed when you was offline"
-"Please provide valid e-mail address.","Please provide valid e-mail address."
-"Price {variant}","Price {variant}"
-"Price","Price"
-"Price: High to low","Price: High to low"
-"Price: Low to high","Price: Low to high"
-"Privacy policy","Privacy policy"
-"Product Name","Product Name"
-"Product details","Product details"
-"Products","Products"
-"Purchase","Purchase"
-"Qty","Qty"
-"Quantity","Quantity"
-"Register an account","Register an account"
-"Register","Register"
-"Remake order","Remake order"
-"Remember me","Remember me"
-"Remove from compare","Remove from compare"
-"Remove","Remove"
-"Repeat new password *","Repeat new password *"
-"Repeat password *","Repeat password *"
-"Reset password","Reset password"
-"Resetting the password ... ","Resetting the password ... "
-"Return policy","Return policy"
-"Return to shopping","Return to shopping"
-"Returns","Returns"
-"Review order","Review order"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","Safety"
-"Sale","Sale"
-"Search","Search"
-"Searched term should consist of at least 3 characters.","Searched term should consist of at least 3 characters."
-"See details","See details"
-"See our bestsellers","See our bestsellers"
-"Select No","Select No"
-"Select Yes","Select Yes"
-"Select color ","Select color "
-"Select size {variant}","Select size {variant}"
-"Ship to my default address","Ship to my default address"
-"Shipping address","Shipping address"
-"Shipping method","Shipping method"
-"Shipping","Shipping"
-"Shopping cart","Shopping cart"
-"Shopping summary","Shopping summary"
-"Show subcategories","Show subcategories"
-"Sign up to our newsletter and receive a coupon for 10% off!","Sign up to our newsletter and receive a coupon for 10% off!"
-"Similar products","Similar products"
-"Size guide","Size guide"
-"Something went wrong ...","Something went wrong ..."
-"Something went wrong. Try again in a few seconds.","Something went wrong. Try again in a few seconds."
-"Sort By","Sort By"
-"State / Province","State / Province"
-"Status","Status"
-"Store locator","Store locator"
-"Street name *","Street name *"
-"Street name","Street name"
-"Subscribe to the newsletter and receive a coupon for 10% off","Subscribe to the newsletter and receive a coupon for 10% off"
-"Subscribe","Subscribe"
-"Subtotal","Subtotal"
-"Tax ID *","Tax ID *"
-"Tax ID must have at least 3 letters.","Tax ID must have at least 3 letters."
-"Tax identification number *","Tax identification number *"
-"Tax identification number must have at least 3 letters.","Tax identification number must have at least 3 letters."
-"Tax","Tax"
-"Terms and conditions","Terms and conditions"
-"The OrderNumber is {id}","The Order Number is {id}"
-"The new account will be created with the purchase. You will receive details on e-mail.","The new account will be created with the purchase. You will receive details on e-mail."
-"The order can not be transfered because of server error. Order has been queued","The order can not be transfered because of server error. Order has been queued"
-"The server order id has been set to ","The server order id has been set to "
-"This product is out of stock.","This product is out of stock."
-"To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items","To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items"
-"Toggle password visibility","Toggle password visibility"
-"Track my order","Track my order"
-"Type your opinion","Type your opinion"
-"Type","Type"
-"Unfortunately we can't find the page you are looking for.","Unfortunately we can't find the page you are looking for."
-"United States","United States"
-"Update my preferences","Update my preferences"
-"Update my profile","Update my profile"
-"Update my shipping details","Update my shipping details"
-"Use my billing data","Use my billing data"
-"Use my company's address details","Use my company's address details"
-"Use my company's address details","Use my company's address details"
-"Value","Value"
-"View all","View all"
-"View order","View order"
-"Vuestore","Vuestore"
-"We can't find the page","We can't find the page"
-"We found other products you might like","We found other products you might like"
-"We use cookies to give you the best shopping experience.","We use cookies to give you the best shopping experience."
-"We will send you details regarding the order","We will send you details regarding the order"
-"We will send you the invoice to given e-mail address","We will send you the invoice to given e-mail address"
-"We've noticed Internal Server Error while rendering this request.","We've noticed Internal Server Error while rendering this request."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","We've sent password reset instructions to your email. Check your inbox and follow the link."
-"What we can improve?","What we can improve?"
-"Wishlist","Wishlist"
-"Women fashion","Women fashion"
-"You are logged in as {firstname}","You are logged in as {firstname}"
-"You are offline","You are offline"
-"You are offline. Some features might not be available.","You are offline. Some features might not be available."
-"You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm.","You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm."
-"You can also use","You can also use"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter."
-"You have been successfully subscribed to our newsletter!","You have been successfully subscribed to our newsletter!"
-"You have been successfully unsubscribed from our newsletter!","You have been successfully unsubscribed from our newsletter!"
-"You have no items to compare.","You have no items to compare."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress."
-"You must accept the terms and conditions.","You must accept the terms and conditions."
-"You will receive Push notification after coming back online. You can confirm the order by clicking on it","You will receive Push notification after coming back online. You can confirm the order by clicking on it"
-"You've entered an incorrect coupon code. Please try again.","You've entered an incorrect coupon code. Please try again."
-"Your Account","Your Account"
-"Your feedback is important for us. Let us know what we could improve.","Your feedback is important for us. Let us know what we could improve."
-"Your purchase","Your purchase"
-"Your shopping cart is empty.","Your shopping cart is empty."
-"Your wishlist is empty.","Your wishlist is empty."
-"Zip-code *","Zip-code *"
-"Zip-code must have at least 3 letters.","Zip-code must have at least 3 letters."
-"Zip-code","Zip-code"
-"a chat","a chat"
-"a contact page","a contact page"
-"browse our catalog","browse our catalog"
-"color","color"
-"color_filter","Color"
-"erin_recommends_filter","Erin Recommends"
-"login to your account","login to your account"
-"login","login"
-"or write to us through","or write to us through"
-"or","or"
-"price_filter","Price"
-"register an account","register an account"
-"return to log in","return to log in"
-"search","search"
-"size","size"
-"size_filter","Size"
-"to find product you were looking for.","to find product you were looking for."
-"to find something beautiful for You!","to find something beautiful for You!"
-"{count} items","{count} items"
diff --git a/src/themes/default/resource/i18n/es-ES.csv b/src/themes/default/resource/i18n/es-ES.csv
deleted file mode 100644
index 2a16725fd..000000000
--- a/src/themes/default/resource/i18n/es-ES.csv
+++ /dev/null
@@ -1,182 +0,0 @@
-"About us","Sobre nosotros"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add to cart","Añadir a la cesta"
-"Add to compare","Añadir a comparar"
-"Add to favorite","Agregar a Favoritos"
-"Authorization in progress ...","Autorización en progreso ..."
-"Back to login","Atrás para iniciar sesión"
-"Cancel","Cancelar"
-"Change my password","Cambiar mi contraseña"
-"City *","Ciudad *"
-"City","Ciudad"
-"Company name *","Nombre de empresa *"
-"Contact us","Contáctenos"
-"Continue to payment","Continuar con el pago"
-"Continue to shipping","Continuar con el envío"
-"Copy address data from shipping","Copie los datos de la dirección del envío"
-"Country","País"
-"Current password *","Contraseña actual *"
-"Customer service","Servicio al cliente"
-"Delivery","Entrega"
-"Departments","Departamentos"
-"Don't hesitate and","No lo dudes y"
-"E-mail address *","Dirección de correo electrónico *"
-"Edit newsletter preferences","Editar las preferencias del boletín"
-"Edit payment","Editar pago"
-"Edit personal details","Editar detalles personales"
-"Edit shipping","Editar envío"
-"Edit your profile","Edite su perfil"
-"Edit your shipping details","Edite sus detalles de envío"
-"Edit","Editar"
-"Email address *","Dirección de correo electrónico *"
-"Email address","Dirección de correo electrónico"
-"Enter your email to receive instructions on how to reset your password.","Ingrese su correo electrónico para recibir instrucciones sobre cómo restablecer su contraseña."
-"Erin recommends","Erin recomienda"
-"Error while sending reset password e-mail","Error al enviar el correo electrónico para el restablecimiento de contraseña"
-"Everything new","Todo nuevo"
-"Field is required","El campo es requerido"
-"Filter","Filtro"
-"Filters","Filtros"
-"First name *","Nombre *"
-"First name","Nombre"
-"Forgot the password?","Olvidaste la contraseña?"
-"General agreement","Acuerdo General"
-"Get inspired","Inspírate"
-"Go review the order","Revisa la orden"
-"Go to Facebook","Ir a Facebook"
-"Go to Instagram","Ir a Instagram"
-"Go to Pinterest","Ir a Pinterest"
-"Go to Youtube","Ir a Youtube"
-"Go to checkout","Ir a pagar"
-"Help","Ayuda"
-"Home","Inicio"
-"House/Apartment number *","Número de casa/apartamento *"
-"House/Apartment number","Número de casa/apartamento"
-"I accept ","Acepto "
-"I agree to","Estoy de acuerdo con"
-"I have a company and want to receive an invoice for every order","Tengo una empresa y quiero recibir una factura por cada pedido"
-"I want to create an account","Quiero crear una cuenta"
-"I want to create an account'","Quiero crear una cuenta'"
-"I want to generate an invoice for the company","Quiero generar una factura para la empresa"
-"If you need an assistance you can drop us a line on","Si necesita ayuda, puede enviarnos un mensaje en"
-"Kidswear","Ropa de niños"
-"Last name *","Apellido *"
-"Last name","Apellido"
-"Legal notice","Aviso Legal"
-"Log in to your account","Ingrese a su cuenta"
-"Log in","Iniciar sesión"
-"Login to your account","Ingrese a su cuenta"
-"Logout","Cerrar sesión"
-"Magazine","Revista"
-"Men's fashion","Moda de hombres"
-"My Account","Mi Cuenta"
-"My account","Mi cuenta"
-"My loyalty card","Mi tarjeta de lealtad"
-"My newsletter","Mi boletín"
-"My orders","Mis ordenes"
-"My product reviews","Mis Opiniones del producto"
-"My profile","Mi perfil"
-"My shipping details","Mis detalles de envío"
-"Name must have at least 3 letters.","El nombre debe tener al menos 3 letras."
-"New Luma Yoga Collection","Nueva colección Luma Yoga"
-"New password *","Nueva contraseña *"
-"Newsletter","Boletin de Noticias"
-"No products found!","No se encontraron productos!"
-"No results were found.","No se encontraron resultados."
-"Order Summary","Resumen del pedido"
-"Orders","Pedidos"
-"Password *","Contraseña *"
-"Passwords must be identical","Las contraseñas deben ser idénticas"
-"Passwords must be identical.","Las contraseñas deben ser idénticas."
-"Payment method","Método de pago"
-"Payment","Pago"
-"Personal Details","Detalles personales"
-"Phone Number","Número de teléfono"
-"Phone number may be needed by carrier","El operador puede necesitar el número de teléfono"
-"Place the order","Hacer el pedido"
-"Please check if all data are correct","Por favor verifique si todos los datos son correctos"
-"Please provide valid e-mail address.","Proporcione una dirección de correo electrónico válida."
-"Price {variant}","Precio {variant}"
-"Price","Precio"
-"Privacy policy","Política de privacidad"
-"Product details","Detalles del producto"
-"Qty","Cantidad"
-"Quantity","Cantidad"
-"Register an account","Registrar una cuenta"
-"Register","Registro"
-"Remember me","Recuérdame"
-"Remove from compare","Eliminar de comparar"
-"Remove","Quitar"
-"Repeat new password *","repita la nueva contraseña *"
-"Repeat password *","repite la contraseña *"
-"Reset password","Restablecer la contraseña"
-"Resetting the password ... ","Restableciendo la contraseña ... "
-"Return policy","Politica de devolucion"
-"Return to shopping","Regresar a comprar"
-"Returns","Devoluciones"
-"Review order","Orden de revisión"
-"Safety","Seguridad"
-"Sale","Venta"
-"Search","Buscar"
-"See details","Ver detalles"
-"See our bestsellers","Vea nuestros bestsellers"
-"Select color ","Seleccionar el color "
-"Select size {variant}","Selecciona el tamaño {variant}"
-"Ship to my default address","Enviar a mi dirección predeterminada"
-"Shipping method","Método de envío"
-"Shipping","Envío"
-"Shopping cart","Carrito de compras"
-"Shopping summary","Resumen de compras"
-"Sign up to our newsletter and receive a coupon for 10% off!","¡Suscríbete a nuestro boletín y recibe un cupón con un 10% de descuento!"
-"Similar products","Productos similares"
-"Size guide","Guia de tallas"
-"State / Province","Provincia del estado"
-"Store locator","Localizador de tiendas"
-"Street name *","Nombre de la calle *"
-"Street name","Nombre de la calle"
-"Subscribe to the newsletter and receive a coupon for 10% off","Suscríbase al boletín y reciba un cupón con un 10% de descuento"
-"Subscribe","Suscribir"
-"Tax ID *","Identificación del impuesto *"
-"Tax identification number *","Número de identificación de impuestos *"
-"Tax identification number must have at least 3 letters.","El número de identificación fiscal debe tener al menos 3 letras."
-"Terms and conditions","Términos y Condiciones"
-"The new account will be created with the purchase. You will receive details on e-mail.","La nueva cuenta se creará con la compra. Recibirá detalles sobre el correo electrónico."
-"Track my order","Seguir mi orden"
-"Unfortunately we can't find the page you are looking for.","Desafortunadamente no podemos encontrar la página que está buscando."
-"Update my preferences","Actualizar mis preferencias"
-"Update my profile","Actualizar mi perfil"
-"Update my shipping details","Actualiza mis detalles de envío"
-"Use my billing data","Usa mis datos de facturación"
-"Use my company's address details","Usar los detalles de la dirección de mi empresa"
-"We can't find the page","No podemos encontrar la página"
-"We use cookies to give you the best shopping experience.","Utilizamos cookies para ofrecerle la mejor experiencia de compra."
-"We will send you details regarding the order","Le enviaremos detalles sobre el pedido"
-"We will send you the invoice to given e-mail address","Le enviaremos la factura a la dirección de correo electrónico especificada"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Hemos enviado instrucciones de restablecimiento de contraseña a su correo electrónico. Revisa tu bandeja de entrada y sigue el enlace."
-"Wishlist","Lista de Deseo"
-"Women fashion","Moda femenina"
-"You are logged in as {firstname}","Has iniciado sesión como {firstname}"
-"You are offline. Some features might not be available.","Estás fuera de línea, algunas de las funcionalidades son limitadas"
-"You can also use","También puedes usar"
-"You have been successfully subscribed to our newsletter!","¡Se suscribió exitosamente a nuestro boletín informativo!"
-"You have no items to compare.","No tienes articulos para comparar."
-"You must accept the terms and conditions.","Debe aceptar los Términos y Condiciones."
-"Your shopping cart is empty.","Su cesta está vacía."
-"Your wishlist is empty.","Tu lista de deseos esta vacía."
-"Zip-code *","Código postal *"
-"Zip-code must have at least 3 letters.","El código postal debe tener al menos 3 letras."
-"Zip-code","Código postal"
-"a chat","un chat"
-"a contact page","una página de contacto"
-"browse our catalog","navega por nuestro catálogo"
-"color_filter","Color"
-"login to your account","ingrese a su cuenta"
-"or write to us through","o escríbanos a través de"
-"or","o"
-"price_filter","Precio"
-"register an account","registrar una cuenta"
-"return to log in","volver a iniciar sesión"
-"search","buscar"
-"size_filter","Tamaño"
-"to find product you were looking for.","para encontrar el producto que estabas buscando."
-"to find something beautiful for You!","para encontrar algo hermoso para ti!"
diff --git a/src/themes/default/resource/i18n/et-EE.csv b/src/themes/default/resource/i18n/et-EE.csv
deleted file mode 100644
index 9b0815d51..000000000
--- a/src/themes/default/resource/i18n/et-EE.csv
+++ /dev/null
@@ -1,295 +0,0 @@
-"A customer with the same email already exists in an associated website.","Sama e-postiga klient juba eksisteerib."
-"About us (Magento CMS)","About us (Magento CMS)"
-"About us",Meist
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Lisa sooduskood"
-"Add discount code","Lisa sooduskood"
-"Add to cart",Osta
-"Add to compare","Lisa võrdlusesse"
-"Add to favorite","Lisa lemmikuks"
-"Allow notification about the order","Luba tellimusge seotud teadete edastamine"
-"Are you sure you would like to remove all the items from the shopping cart?","Olete kindel, et soovi deemaldada kõik tooted ostukorvist?"
-"Are you sure you would like to remove all the items from the wishlist?","Olete kindel, et soovite kõik tooted soovikorvist eemaldada?"
-"Authorization in progress ...","Sisse logimine ..."
-"Back to login","Tagasi sisse logima"
-"Billing address",Makseaadress
-"Cash on delivery","Sularahas tellimuse kohaletoimetamisel"
-"Change my password","Muuda parooli"
-"Choose your country","Vali riik"
-"City *","Linn *"
-"Clear cart","Tühjenda ostukorv"
-"Clear filters","Tühista filtrid"
-"Clear wishlist","Tühjenda soovikorv"
-"Cms Page Sync","Sisulehekülgede sünkroniseerimine"
-"Company name *","Ettevõtte *"
-"Compare products","Võrdle tooteid"
-"Confirm your order","Kinnitage oma tellimus"
-"Contact us","Võta ühendust"
-"Continue to payment",Edasi
-"Continue to shipping",Edasi
-"Copy address data from shipping","Kopeeri aadress tarneaadressi väljadelt"
-"Country *","Riik *"
-"Create a new account","Loo konto"
-"Current password *","Kasutusel olev parool *"
-"Custom Cms Page","Kohandatud sisulehekülg"
-"Customer service (Magento CMS)","Customer service (Magento CMS)"
-"Customer service",Klienditugi
-"DPD Courier","DPD kuller"
-"Date and time","Kuupäev ja aeg"
-"Discount code",Sooduskood
-"Don't hesitate and","Ärge muretsege"
-"E-mail address *","E-mail *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Ootame Teie poolset tagasisidet oma teenuse täiendamiseks."
-"Edit newsletter preferences","Muuda uudiskirjaga liitumist"
-"Edit payment","Muuda makseviisi"
-"Edit personal details","Muuda kliendi infot"
-"Edit shipping","Muuda tarneviisi"
-"Edit your profile","Muuda oma kontot"
-"Edit your shipping details","Muuda oma aadresse"
-"Email address *","E-maili aadress *"
-"Email address",E-mail
-"Enter your email to receive instructions on how to reset your password.","Sisesta e-mail ja saadame sulle parooli muutmise juhendi."
-"Erin recommends",Sooviab
-"Error while sending reset password e-mail","Parooli muutmise e-maili edastamisel esines viga."
-"Everything new",Uued
-"Extension developers would like to thank you for placing an order!","Mooduli loojad tänavad tellimuse tegemise eest!"
-"Field is required",Kohustuslik
-"Filter by categories","Filtreeri kategooriate alusel"
-"First name *","Eesnimi *"
-"First name",Eesnimi
-"Forgot the password?","Unustasite parooli?"
-"General agreement","Üldine nõustumine"
-"Get inspired","Saa Inspiratsiooni"
-"Give a feedback","Anna tagasisidet"
-"Go review the order","Kontrolli tellimust"
-"Go to Facebook",Facebook
-"Go to Instagram",Instagram
-"Go to Pinterest",Pinterest
-"Go to Youtube",Youtube
-"Go to checkout","Ostu vormistama"
-"House/Apartment number *","Maja/korter *"
-"House/Apartment number",Maja/koter
-"I accept ",Kinnitan
-"I accept terms and conditions","Nõustun kasutustingimustega"
-"I agree to",Nõustun
-"I have a company and want to receive an invoice for every order","Soovin iga tellimusega ettevõttele arvet"
-"I want to create an account","Soovin kontot luua"
-"I want to create an account'","Tahan kontot luua'"
-"I want to generate an invoice for the company","Soovin ettevõtte nimele arvet"
-"I want to receive a newsletter, and agree to its terms","Soovin uudiskirja ja nõstund tingimustega"
-"If you need an assistance you can drop us a line on","Anna meile märku, kui vajad abi."
-"Instant Checkout","Kiir ostuvormistamine"
-"Internal Server Error 500","Serveriga seonduv viga 500"
-"Items ordered","Tellitud tooted"
-"Last name *","Perekonnanimi *"
-"Last name",Perekonnanimi
-"Legal notice","Õiguslik teade"
-"Load more","Lae veel"
-"Log in to your account","Logi sisse"
-"Log in","logi sisse"
-"Login to your account","Logige sisse"
-"Men's fashion","Meeste riided"
-"My Account","Minu konto"
-"My Recently viewed products","Minu viimati vaadatud tooted"
-"My account","Minu konto"
-"My loyalty card","Minu loyaalsus kaart"
-"My newsletter","Minu uudiskiri"
-"My orders","Minu tellimused"
-"My product reviews","Minu kommentaarid"
-"My profile","Minu konto"
-"My shipping details","Minu aadressid"
-"Name must have at least 2 letters.","Nimi peab sisaldama vähemalt kahte tähte"
-"Name must have at least 3 letters.","Nimi peab sisaldama vähemalt kolme tähte"
-"New Luma Yoga Collection","Uus Luma yoga kollektsioon"
-"New password *","Uus parool *"
-"No orders yet","Tellimusi ei ole veel"
-"No products found!","Tooteid ei leitud!"
-"No products yet","Ei ole veel tooteid kahjuks"
-"No results were found.","Vasteid ei leitud."
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","Keegi ei ole veel kommentaare lisanud. Ole esimene ja jaga oma kogemust."
-"Open menu",Menüü
-"Open microcart",Ostukorv
-"Open my account","Minu konto"
-"Open search panel",Otsing
-"Open wishlist",Soovikorv
-"Order #{id}","Tellimus #{id}"
-"Order ID","Tellimuse number"
-"Order Summary","Tellimuse kokkuvõte"
-"Order confirmation","Tellimuse kinnitus"
-"Order informations","Tellimuse informatsioon"
-"Password *","Parool *"
-"Password must have at least 8 letters.","Parool peab sisaldama vähemalt 8 tähte."
-"Passwords must be identical","Paroolid ei ole samad"
-"Passwords must be identical.","Paroolid peav identsed olema."
-"Payment method",Makseviis
-"Personal Details","Kliendi info"
-"Phone Number",Mobiilinumber
-"Phone number may be needed by carrier","Vajame toodete kohale toimetamiseks teie mobiilinumbrit"
-"Place the order",Telli
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Kahjuks ei leidnud me Teie otsingule vasteid."
-"Please check if all data are correct","Palun kontrollige, et kõik oleks õige"
-"Please confirm order you placed when you was offline","Palun kinnitage oma tellimuse, mille ilma interneti ühenduseta varasemalt tegite"
-"Please provide valid e-mail address.","Kontrolli e-maili aadressi"
-"Price {variant}","Hind {variant}"
-"Price: High to low","Kallimad eespool"
-"Price: Low to high","Odavamad eespool"
-"Privacy policy",Privaatsustingimused
-"Product Name",Tootenimi
-"Product details","Toote kirjeldus"
-"Register an account","Loo konto"
-"Remake order","Telli uuesti"
-"Remember me","Jäta mind meelde"
-"Remove from compare","Eemalda võrdlusest"
-"Repeat new password *","Siseta uus parool uuesti *"
-"Repeat password *","Sisesta parool uuesti *"
-"Reset password","Muuda parool"
-"Resetting the password ... ","Parooli tühistamine..."
-"Return policy",Tagasitamine
-"Return to shopping",Tagasi
-"Review order","Tellimuse ülevaade"
-"SKU: {sku}","SKU: {sku}"
-"Searched term should consist of at least 3 characters.","Otsingu sõna peab sisaldama vähemalt kolme tähte."
-"See details",Rohkem
-"See our bestsellers","Vaata meie enim müüdud tooteid"
-"Select No","Vali number"
-"Select Yes","Vali Jah"
-"Select color ","Vali värv"
-"Select size {variant}","Vali {variant} suurus"
-"Ship to my default address","Saada kaup mu vaikimisi aadressile"
-"Shipping address",Tarneaadress
-"Shipping method",Tarneviis
-"Shopping cart",Ostukorv
-"Shopping summary","Ostu kokkuvõte"
-"Show subcategories",Alamkategooriad
-"Sign up to our newsletter and receive a coupon for 10% off!","Liitu meie uudiskirjaga ja saad 10% sooduskupongi!"
-"Similar products","Sarnased tooted"
-"Size guide","Suuruste juhend"
-"Something went wrong ...","Midagi läks valesti ..."
-"Something went wrong. Try again in a few seconds.","Midagi läks valesti. Pooriv uuesti mõne aja pärast.."
-"Sort By",Sorteeri
-"State / Province",Maakond
-"Store locator","Otsi kauplus"
-"Street name *","Tänav *"
-"Street name",Tänav
-"Subscribe to the newsletter and receive a coupon for 10% off","Liitu uudiskirjaga ja saad 10% soodustusega kupongi"
-"Tax ID *","Käibemaksunumber *"
-"Tax ID must have at least 3 letters.","Käibemaksukood peab olema vähemalt 3 tähte."
-"Tax identification number *","Käibemaksu number *"
-"Tax identification number must have at least 3 letters.","Käibemaksukood peab olema vähemalt 3 tähte."
-"Terms and conditions",Kasutustingimused
-"The OrderNumber is {id}","Tellimuse number on {id}"
-"The new account will be created with the purchase. You will receive details on e-mail.","Konto luuakse koos tellimuse vormistamisega. Saadame Teile konto loomise kohta e-maili."
-"The order can not be transfered because of server error. Order has been queued","Tellimus pandi edastamise järjekorda, kuna tellimust ei saadud edasta serveri vea tõttu."
-"The server order id has been set to ","Serveri tellimuse number saadeti"
-"This product is out of stock.","See toode on laost otsas."
-"To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items","Tellimuse lõpetamiseks tulge tagasi meie poodi. Teie tellimus saadetakse serverisse niipea, kui te siia intenetiühenduse taastudes tagasi tulete."
-"Toggle password visibility","Vaata parooli"
-"Track my order","Jälgi oma tellimust"
-"Type your opinion","Kirjuta oma arvamus"
-"Unfortunately we can't find the page you are looking for.","Kahjuks me ei leia otsitavat lehekülge."
-"United States",USA
-"Update my preferences","Muuda oma eelistusi"
-"Update my profile","Uuenda oma kontot"
-"Update my shipping details","Uuenda oma aadresse"
-"Use my billing data","Kasutage minu makseinformatsiooni"
-"Use my company's address details","Kasuta ettevõtte aadressi"
-"View all","Vaata kõiki"
-"View order","Vaata tellimust"
-"We can't find the page","Me ei leia soovitud lehekülge"
-"We found other products you might like","Leidsime veel tooteid, mis Teile võiks meeldida"
-"We use cookies to give you the best shopping experience.","Meie e-pood kasutab küpsiseid. "
-"We will send you details regarding the order","Saadame Teile tellimuse kohta informatsiooni"
-"We will send you the invoice to given e-mail address","Saadame sisestatud e-mailile tellimuse kinnituse ja arve"
-"We've noticed Internal Server Error while rendering this request.","Päringut teostades tekkis serveriga seondub viga 500"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Saatsime Teile parooli muutmise juhendi. Palun kontrollige oma e-maili."
-"What we can improve?","Mida võiksime paremini teha?"
-"Women fashion","Naiste riided"
-"You are logged in as {firstname}","Olete sisse logitud {firstname}"
-"You are offline","Teil puudub internetiühendus. "
-"You are offline. Some features might not be available.","Teil puudub internetiühendus. Mõned funktsionaalsused võivad seetõttu mitte töötada."
-"You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm.","Pärast intenetiühenduse taastumist võite lasta endale tellimusest meelde tuletada. Kinnitage see brauseri dialoogis."
-"You can also use","või kasuta"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Saate oma kontole e-maili ja parooli abil siseneda."
-"You have been successfully subscribed to our newsletter!","Olete uudiskirja listi lisatud!"
-"You have been successfully unsubscribed from our newsletter!","Olete uudiskirja listist eemaldatud!"
-"You have no items to compare.","Võrdlus on tühi"
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Tellimus vormistamine õnnestus. Saadame teile tellimuse kinnituse e-mailile. "
-"You must accept the terms and conditions.","Nõustun kasutustingimustega"
-"You will receive Push notification after coming back online. You can confirm the order by clicking on it","Võite tellida intenetiühenduse taastumise teavituse. Kinnitage see brauseri dialoogis."
-"You've entered an incorrect coupon code. Please try again.","Kupongikood ei kehti või puudub. Kontrolli sisestatud koodi ja proovi uuesti."
-"Your Account",Konto
-"Your feedback is important for us. Let us know what we could improve.","Teie tagasiside on meile tähtis. Palun kirjutage meile, mida võiksime veelgi paremaks muuta."
-"Your purchase","Teie tellimus"
-"Your shopping cart is empty.","Teie ostukorv on tühi"
-"Your wishlist is empty.","Teie Sooviskord on tühi"
-"Zip-code *","Sihtnumber *"
-"Zip-code must have at least 3 letters.","Sihtnumber peab sisaldama vähemalt viite numbrit"
-"a chat","kirjuta sõnum"
-"a contact page","sõnumi vorm"
-"browse our catalog","Sirvi tooteid"
-"login to your account","logi sisse"
-"or write to us through","või kirjuta meile"
-"register an account","loo konto"
-"return to log in","tagasi sisse logima"
-"to find product you were looking for.",",et leida otsitav toode."
-"to find something beautiful for You!","leia midagi ilusat"
-"{count} items","{count} kirjeid"
-Author,Autor
-Back,Tagasi
-Cancel,Tühista
-City,Linn
-Clear,Tühista
-Close,Sulge
-Columns,Tulbad
-Country,Riik
-Delivery,Tarne
-Departments,Kategooria
-Discount,Allahindlus
-Edit,Muuda
-English,Inglismaa
-Filter,Filtreeri
-Filters,Filtreeri
-German,Sakslane
-Germany,Saksamaa
-Help,Abi
-Home,Esileht
-Italian,Itaallane
-Italy,Itaalia
-Kidswear,"Laste riided"
-Latest,Viimased
-Logout,"Logi välja"
-Magazine,Kataloog
-Newsletter,Uudiskiri
-Orders,Tellimused
-Payment,Makseviis
-Price,Hind
-Products,Tooted
-Purchase,Tellimus
-Qty,Kogus
-Quantity,Kogus
-Register,Registreeri
-Remove,Eemalda
-Returns,Tagastamine
-SKU,Kood
-Safety,Turvalisus
-Sale,Allahindlus
-Search,Otsi
-Shipping,Tarneviis
-Status,Staatus
-Subscribe,Liitu
-Subtotal,Kokku
-Tax,Käibemaks
-Type,Tüüp
-Value,Summa
-Vuestore,"Vue Storefront"
-Wishlist,Soovikorv
-Zip-code,Sihtnumber
-color,värvus
-color_filter,värv_filter
-erin_recommends_filter,soovitab_filter
-login,"logi sisse"
-or,või
-price_filter,hind_filter
-search,otsi
-size,suurus
-size_filter,suurus_filter
diff --git a/src/themes/default/resource/i18n/fi-FI.csv b/src/themes/default/resource/i18n/fi-FI.csv
deleted file mode 100644
index ef5f15f29..000000000
--- a/src/themes/default/resource/i18n/fi-FI.csv
+++ /dev/null
@@ -1,295 +0,0 @@
-"A customer with the same email already exists in an associated website.","Samalla sähköpostiosoitteella on jo asiakas samalla sivustolla."
-"About us (Magento CMS)","Tietoja verkkokaupasta (Magento CMS)"
-"About us","Tietoja verkkokaupasta"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Lisää alennuskoodi"
-"Add discount code","Lisää alennuskoodi"
-"Add to cart","Lisää koriin"
-"Add to compare","Lisää vertailuun"
-"Add to favorite","Lisää suosikiksi"
-"Allow notification about the order","Vastaanota ilmoitus tilauksesta"
-"Are you sure you would like to remove all the items from the shopping cart?",
-"Are you sure you would like to remove all the items from the wishlist?","Haluatko varmasti poistaa kaikki tuotteet toivelistalta?"
-"Authorization in progress ...","Tarkistetaan oikeuksia ..."
-"Back to login","Takaisin kirjautumiseen"
-"Billing address",Laskutusosoite
-"Cash on delivery","Maksu toimituksen yhteydessä"
-"Change my password","Vaihda salasanaa"
-"Choose your country","Valitse maa"
-"City *","Kaupunki *"
-"Clear cart","Tyhjennä ostoskori"
-"Clear filters","Tyhjennä suodattimet"
-"Clear wishlist","Tyhjennä toivelista"
-"Cms Page Sync","Sisältösivun synkronointi"
-"Company name *","Yrityksen nimi *"
-"Compare products","Vertaile tuotteita"
-"Confirm your order","Vahvista tilaus"
-"Contact us","Ota yhteyttä"
-"Continue to payment","Jatka maksuun"
-"Continue to shipping","Jatka toimitukseen"
-"Copy address data from shipping","Kopioi osoite toimitustiedoista"
-"Country *","Maa *"
-"Create a new account","Luo uusi tili"
-"Current password *","Salasana *"
-"Custom Cms Page","Muokattu sisältösivu"
-"Customer service (Magento CMS)","Asiakaspalvelu (Magento CMS)"
-"Customer service",Asiakaspalvelu
-"DPD Courier",DPD-kuriiri
-"Date and time","Päivä ja aika"
-"Discount code",Alennuskoodi
-"Don't hesitate and","Älä epäröi ja"
-"E-mail address *","Sähköposti *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Miten voisimme palvella vielä paremmin? Lähetä meille palautetta yhteydenottosivun kautta."
-"Edit newsletter preferences","Muokkaa uutiskirjeen asetuksia"
-"Edit payment","Muokkaa maksua"
-"Edit personal details","Muokkaa omia tietoja"
-"Edit shipping","Muokkaa toimitusta"
-"Edit your profile","Muokkaa omaa profiilia"
-"Edit your shipping details","Muokkaa omia toimitustietoja"
-"Email address *","Sähköposti *"""
-"Email address","Sähköposti"""
-"Enter your email to receive instructions on how to reset your password.","Syötä sähköpostiosoitteesi, johon haluat vastaanottaa ohjeet salasanan uusimisesta."
-"Erin recommends",Suosittelemme
-"Error while sending reset password e-mail","Sähköpostin lähetys epäonnistui."
-"Everything new",Uutuudet
-"Extension developers would like to thank you for placing an order!","Laajennuksen kehittäjät kiittävät tilauksesta!"
-"Field is required","Vaadittu tieto"
-"Filter by categories","Suodata kategorian mukaan"
-"First name *","Etunimi *"
-"First name",Etunimi
-"Forgot the password?","Unohditko salasanasi?"
-"General agreement","Yleiset sopimusehdot"
-"Get inspired",Inspiroidu
-"Give a feedback","Anna palautetta"
-"Go review the order","Tarkista tilaus"
-"Go to Facebook",Facebook
-"Go to Instagram",Instagram
-"Go to Pinterest",Pinterest
-"Go to Youtube",Youtube
-"Go to checkout",Kassalle
-"House/Apartment number *","Talon/huoneiston numero *"
-"House/Apartment number","Talon/huoneiston numero"
-"I accept ","Hyväksyn "
-"I accept terms and conditions","Hyväksyn ehdot"
-"I agree to","Hyväksyn, että"
-"I have a company and want to receive an invoice for every order","Minulla on yritys ja haluan laskun jokaisesta tilauksesta"
-"I want to create an account","Haluan luoda tilin"
-"I want to create an account'","Haluan luoda tilin"
-"I want to generate an invoice for the company","Haluan laskun yritykselle"
-"I want to receive a newsletter, and agree to its terms","Haluan tilata uutiskirjeen ja hyväksyn ehdot"
-"If you need an assistance you can drop us a line on","Jos tarvitset apua, voit kirjoittaa meille"
-"Instant Checkout",Pikakassa
-"Internal Server Error 500","Palvelinvirhe 500"
-"Items ordered","Tilatut tuotteet"
-"Last name *","Sukunimi *"
-"Last name",Sukunimi
-"Legal notice","Oikeudellinen huomautus"
-"Load more","Näytä lisää"
-"Log in to your account","Kirjaudu omalle tilille"
-"Log in","Kirjaudu sisään"
-"Login to your account","Kirjaudu omalle tilille"
-"Men's fashion","Miesten muoti"
-"My Account","Oma tili"
-"My Recently viewed products","Viimeksi katsomani tuotteet"
-"My account","Oma tili"
-"My loyalty card","Oma kanta-asiakaskortti"
-"My newsletter","Oma uutiskirje"
-"My orders","Omat tilaukset"
-"My product reviews","Omat tuotearvostelut"
-"My profile","Oma profiili"
-"My shipping details","Omat toimitustiedot"
-"Name must have at least 2 letters.","Nimen pitää olla vähintään 2 merkkiä pitkä"
-"Name must have at least 3 letters.","Nimen pitää olla vähintään 3 merkkiä pitkä"
-"New Luma Yoga Collection","Uusi Luma Yoga -valikoima"
-"New password *","Uusi salasana *"
-"No orders yet","Ei tilauksia"
-"No products found!","Tuotteita ei löytynyt."
-"No products yet","Ei vielä yhtään tuotetta"
-"No results were found.","Ei tuloksia"
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","Tuotteella ei ole vielä arvosteluja. Kirjoita rohkeasti ensimmäinen!"
-"Open menu",Menu
-"Open microcart",Ostoskori
-"Open my account","Oma tili"
-"Open search panel",Haku
-"Open wishlist",Toivelista
-"Order #{id}","Tilaus "
-"Order ID",Tilausnumero
-"Order Summary","Tilauksen yhteenveto"
-"Order confirmation",Tilausvahvistus
-"Order informations","Tilauksen tiedot"
-"Password *","Salasana *"
-"Password must have at least 8 letters.","Salasanan pitää sisältää vähintään 8 kirjainta"
-"Passwords must be identical","Salasanojen pitää täsmätä"
-"Passwords must be identical.","Salasanojen pitää täsmätä"
-"Payment method",Maksutapa
-"Personal Details","Omat tiedot"
-"Phone Number",Puhelinnumero
-"Phone number may be needed by carrier","Kuljettaja voi tarvita puhelinnumeroa"
-"Place the order",Tilaa
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Muuta hakua ja yritä uudelleen."
-"Please check if all data are correct","Tarkista, että tiedot ovat oikein"
-"Please confirm order you placed when you was offline","Vahvista yhteydettömässä tilassa tekemäsi tilaus"
-"Please provide valid e-mail address.","Anna toimiva sähköpostiosoite"
-"Price {variant}","Hinta {variant}"
-"Price: High to low","Hinta: Kallein ensin"
-"Price: Low to high","Hinta: Halvin ensin"
-"Privacy policy","Yksityisyyden suoja"
-"Product Name","Tuotteen nimi"
-"Product details","Tuotteen lisätiedot"
-"Register an account","Luo tili"
-"Remake order","Tilaa uudelleen"
-"Remember me","Muista minut"
-"Remove from compare","Poista vertailusta"
-"Repeat new password *","Toista uusi salasana *"""
-"Repeat password *","Toista salasana *"
-"Reset password","Uusi salasana"
-"Resetting the password ... ","Asetetaan salasanaa ... "
-"Return policy",Palautuskäytännöt
-"Return to shopping","Jatka ostoksia"
-"Review order","Tarkista tilaus"
-"SKU: {sku}","Tuotekoodi: {sku}"
-"Searched term should consist of at least 3 characters.","Haun pitää sisältää vähintään 3 merkkiä"
-"See details",Lisätiedot
-"See our bestsellers","Katso eniten myydyt"
-"Select No","Valitse ei"
-"Select Yes","Valitse kyllä"
-"Select color ","Valitse väri "
-"Select size {variant}","Valitse koko {variant}"
-"Ship to my default address","Lähetä oletusosoitteeseeni"
-"Shipping address",Toimitusosoite
-"Shipping method",Toimitustapa
-"Shopping cart",Ostoskori
-"Shopping summary","Ostosten yhteenveto"
-"Show subcategories","Näytä alakategoriat"
-"Sign up to our newsletter and receive a coupon for 10% off!","Tilaa uutiskirje. Saat 10% alennuskupungin."
-"Similar products","Samanlaisia tuotteita"
-"Size guide",Koko-opas
-"Something went wrong ...","Jotain meni pieleen ..."
-"Something went wrong. Try again in a few seconds.","Jotain meni pieleen. Yritä uudelleen muutaman sekunnin päästä."
-"Sort By",Järjestys
-"State / Province","Osavaltio / maakunta"
-"Store locator","Etsi myymälä"
-"Street name *","Katu *"
-"Street name",Katu
-"Subscribe to the newsletter and receive a coupon for 10% off","Tilaa uutiskirje. Saat 10% alennuskupungin."
-"Tax ID *",Verotunniste
-"Tax ID must have at least 3 letters.","Verotunnuksen pitää olla vähintään 3 merkkiä pitkä"
-"Tax identification number *","Verotunniste *"
-"Tax identification number must have at least 3 letters.","Verotunnisteen pitää olla vähintään 3 merkkiä pitkä"
-"Terms and conditions",Toimitusehdot
-"The OrderNumber is {id}","Tilausnumero on {id}"
-"The new account will be created with the purchase. You will receive details on e-mail.","Uusi tili luodaan tilauksen yhteydessä. Saat vahvistuksen sähköpostiisi."
-"The order can not be transfered because of server error. Order has been queued",
-"The server order id has been set to ","Palvelimelle tallennettu tilausnumero on "
-"This product is out of stock.","Tuote on loppu varastosta"
-"To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items",
-"Toggle password visibility","Näytä/piilota salasana"
-"Track my order","Seuraa tilausta"
-"Type your opinion","Kirjoita mielipiteesi"
-"Unfortunately we can't find the page you are looking for.","Valitettavasti etsimääsi sivua ei löydy"
-"United States",Yhdysvallat
-"Update my preferences","Päivitä omat asetukset"
-"Update my profile","Päivitä oma profiili"
-"Update my shipping details","Päivitä omat toimitustiedot"
-"Use my billing data","Käytä laskutustietojani"
-"Use my company's address details","Käytä yrityksen osoitetta"
-"View all","Näytä kaikki"
-"View order","Näytä tilaus"
-"We can't find the page","Sivua ei löydy"
-"We found other products you might like","Voisit olla kiinnostunut myös näistä tuotteista"
-"We use cookies to give you the best shopping experience.","Sivustolla käytetään evästeitä parhaan ostokokemuksen tarjoamiseksi"
-"We will send you details regarding the order","Lähetämme sinulle vahvistuksen tilauksesta"
-"We will send you the invoice to given e-mail address","Lähetämme sinulle laskun antamaasi sähköpostiosoitteeseen"
-"We've noticed Internal Server Error while rendering this request.","Palvelimella havaittiin virhe"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.",
-"What we can improve?","Miten voisimme palvella vielä paremmin?"
-"Women fashion","Naisten muoti"
-"You are logged in as {firstname}","Olet kirjautuneena, {firstname}"
-"You are offline","Verkkoyhteys ei ole saatavilla."
-"You are offline. Some features might not be available.","Verkkoyhteys ei ole saatavilla. Osa toiminnoista ei ole käytettävissä."
-"You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm.",
-"You can also use","Voit myös käyttää"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Voit kirjautua omalle tilillesi antamallasi sähköpostiosoitteella ja salasanalla. Omalla tililläsi voit muokata käyttäjäprofiilia, selata tapahtumahistoriaa ja muokata uutiskirjeen tilausasetuksia."
-"You have been successfully subscribed to our newsletter!","Uutiskirje on tilattu."
-"You have been successfully unsubscribed from our newsletter!","Uutiskirjeen tilaus on peruutettu."
-"You have no items to compare.","Vertailussa ei ole tuotteita"
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Tilauksesi vastaanotettiin onnistuneesti. Lähetämme sinulle vahvistusviestin sähköpostilla."
-"You must accept the terms and conditions.","Sinun täytyy hyväksyä toimitusehdot"
-"You will receive Push notification after coming back online. You can confirm the order by clicking on it","Saat ilmoituksen, kun verkkoyhteys palautuu. Voit vahvistaa tilauksen ilmoituksesta."
-"You've entered an incorrect coupon code. Please try again.","Syöttämäsi alennuskoodi ei kelpaa. Yritä uudelleen."
-"Your Account","Oma tili"
-"Your feedback is important for us. Let us know what we could improve.","Palautteesi on meille tärkeää. Kerro meille miten voisimme palvella vielä paremmin."
-"Your purchase",Tilauksesi
-"Your shopping cart is empty.","Ostoskori on tyhjä"
-"Your wishlist is empty.","Toivelista on tyhjä"
-"Zip-code *","Postinumero *"
-"Zip-code must have at least 3 letters.","Postinumeron pitää olla vähintään 3 merkkiä pitkä"
-"a chat",chatissä
-"a contact page","yhteydenottosivun kautta"
-"browse our catalog","selaa tuotteita"
-"login to your account","kirjaudu omalle tilille"
-"or write to us through","tai kirjoittaa meille viestin"
-"register an account","luo tili"
-"return to log in","palaa kirjautumiseen"
-"to find product you were looking for.","löytääksesi etsimäsi tuotteen"
-"to find something beautiful for You!","ja löydä jotain kaunista itsellesi!"
-"{count} items","{count} tuotetta"
-Author,Tekijä
-Back,Takaisin
-Cancel,Peruuta
-City,Kaupunki
-Clear,Tyhjennä
-Close,Sulje
-Columns,Sarakkeet
-Country,Maa
-Delivery,Lähetys
-Departments,Osastot
-Discount,Alennus
-Edit,Muokkaa
-English,Englanti
-Filter,Suodatin
-Filters,Suodattimet
-German,Sakasalainen
-Germany,Saksa
-Help,Apua
-Home,Koti
-Italian,Italialainen
-Italy,Italia
-Kidswear,Lastenvaatteet
-Latest,Uusin
-Logout,"Kirjaudu ulos"
-Magazine,Kuvasto
-Newsletter,Uutiskirje
-Orders,Tilaukset
-Payment,Maksu
-Price,Hinta
-Products,Tuotteet
-Purchase,Osto
-Qty,Määrä
-Quantity,Määrä
-Register,"Luo tili"
-Remove,Poista
-Returns,Palautukset
-SKU,Tuotekoodi
-Safety,Turvallisuus
-Sale,Alennus
-Search,Hae
-Shipping,Toimitus
-Status,Tila
-Subscribe,Tilaa
-Subtotal,Välisumma
-Tax,Vero
-Type,Tyyppi
-Value,Arvo
-Vuestore,Vuestore
-Wishlist,Toivelista
-Zip-code,Postinumero
-color,väri
-color_filter,väri_suodatin
-erin_recommends_filter,suosittelemme_suodatin
-login,"kirjaudu sisään"
-or,tai
-price_filter,hinta_suodatin
-search,haku
-size,koko
-size_filter,koko_suodatin
diff --git a/src/themes/default/resource/i18n/fr-FR.csv b/src/themes/default/resource/i18n/fr-FR.csv
deleted file mode 100644
index 128a522ac..000000000
--- a/src/themes/default/resource/i18n/fr-FR.csv
+++ /dev/null
@@ -1,252 +0,0 @@
-"About us","À propos"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Ajouter un code de réduction"
-"Add discount code","Appliquer"
-"Add to cart","Ajouter au panier"
-"Add to compare","Ajouter au comparateur"
-"Add to favorite","Ajouter aux favoris"
-"Allow notification about the order","Activer les notifications à propos de cette commande"
-"Author","Auteur"
-"Authorization in progress ...","Authentification en cours ..."
-"Back to login","Retour au login"
-"Back","Retour"
-"Billing address","Adresse de facturation"
-"Cancel","Annuler"
-"Change my password","Changer mon mot de passe"
-"Choose your country","Sélectionnez votre pays"
-"City *","Ville *"
-"City","Ville"
-"Close","Fermer"
-"Cms Page Sync","Page CMS synchronisée"
-"Company name *","Nom de l'entreprise *"
-"Compare products","Comparer les produits"
-"Confirm your order","Confirmer votre commande"
-"Contact us","Contactez-nous"
-"Continue to payment","Continuer vers le paiement"
-"Continue to shipping","Continuer vos achats"
-"Copy address data from shipping","Copier les données de l'adresse d'expédition"
-"Country *","Pays *"
-"Country","Pays"
-"Create a new account","Créer un compte"
-"Current password *","Mot de passe actuel *"
-"Custom Cms Page","Page CMS personnalisée"
-"Customer service","Service client"
-"Date and time","Date et heure"
-"Delivery","Livraison"
-"Departments","Départements"
-"Discount code","Code de réduction"
-"Discount","Réduction"
-"Don't hesitate and","N'hésitez pas et "
-"E-mail address *","Adresse e-mail *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Envoyez-nous un mail à demo@vuestorefront.io si vous avez la moindre question ou suggestion sur la façon d'améliorer nos produits ou notre expérience d'achat"
-"Edit newsletter preferences","Modifier les préférences de la newsletter"
-"Edit payment","Editer le paiement"
-"Edit personal details","Modifier les détails personnels"
-"Edit shipping","Modifier les options de livraison"
-"Edit your profile","Editer mon profil"
-"Edit your shipping details","Editer mes détails d'expédition"
-"Edit","Editer"
-"Email address *","Adresse e-mail *"
-"Email address","Adresse e-mail"
-"English","Anglais"
-"Enter your email to receive instructions on how to reset your password.","Saisissez votre adresse e-mail afin de recevoir les instructions nécessaires à la réinitialisation de votre mot de passe."
-"Erin recommends","Erin recommende"
-"Error while sending reset password e-mail","Erreur lors de l'envoi de l'Email de réinitialisation du mot de passe"
-"Everything new","Toutes les nouvautés"
-"Extension developers would like to thank you for placing an order!","Les développeurs de l'extension voudraient vous remercier d'avoir passé une commande !"
-"Field is required","Champ requis"
-"Filter","Filtres"
-"Filters","Filtres"
-"First name *","Prénom *"
-"First name","Prénom"
-"Forgot the password?","Mot de passe oublié ?"
-"General agreement","Accord général"
-"German","Allemand"
-"Germany","Allemagne"
-"Get inspired","Soyez inspiré"
-"Give a feedback","Envoyer un commentaire"
-"Go review the order","Vérifier le contenu de la commande"
-"Go to Facebook","Aller sur Facebook"
-"Go to Instagram","Aller sur Instagram"
-"Go to Pinterest","Aller sur Pinterest"
-"Go to Youtube","Aller sur Youtube"
-"Go to checkout","Commander"
-"Help","Aide"
-"Home","Accueil"
-"House/Apartment number *","Numéro d'appartement / maison *"
-"House/Apartment number","Numéro d'appartement / maison"
-"I accept ","J'accepte "
-"I accept terms and conditions","J'accepte les termes et conditions"
-"I agree to","J'accepte"
-"I have a company and want to receive an invoice for every order","J'ai une entreprise et je veux reçevoir une facture pour chaque commande"
-"I want to create an account","Je veux créer un compte"
-"I want to create an account'","Je veux créer un compte'"
-"I want to generate an invoice for the company","Je veux genérer une facture pour l'entreprise"
-"I want to receive a newsletter, and agree to its terms","Je veux recevoir la newsletter, et j'en accepte les termes"
-"If you need an assistance you can drop us a line on","Si vous avez besoin d'aide, vous pouvez nous laisser un message"
-"Italian","Italien"
-"Italy","Italie"
-"Items ordered","Produits commandés"
-"Kidswear","Enfant"
-"Last name *","Nom *"
-"Last name","Nom"
-"Legal notice","Mentions légales"
-"Load more","Charger plus"
-"Log in to your account","Connectez-vous"
-"Log in","Connexion"
-"Login to your account","Connectez-vous à votre compte"
-"Logout","Déconnexion"
-"Magazine","Magazine"
-"Men's fashion","Mode homme"
-"My Account","Mon compte"
-"My account","Mon compte"
-"My loyalty card","Ma carte de fidélité"
-"My newsletter","Ma newsletter"
-"My orders","Mes commandes"
-"My product reviews","Mes évaluations de produits"
-"My profile","Mon profil"
-"My shipping details","Mes détails d'expédition"
-"Name must have at least 3 letters.","Le nom doit contenir au moins 3 lettres."
-"New Luma Yoga Collection","Nouvelle collection Luma Yoga"
-"New password *","Nouveau mot de passe *"
-"Newsletter","Newsletter"
-"No orders yet","Aucune commande"
-"No products found!","Aucun produit trouvé !"
-"No results were found.","Aucun résultat n'a été trouvé."
-"Open menu","Ouvrir le menu"
-"Open microcart","Ouvrir le panier"
-"Open my account","Ouvrir mon compte"
-"Open search panel","Ouvrir le panneau de recherche"
-"Open wishlist","Ouvrir la liste de souhaits"
-"Order #{id}","Commande #{id}"
-"Order ID","ID de la commande"
-"Order Summary","Récapitulatif de la commande"
-"Order confirmation","Confirmation de la commande"
-"Order informations","Informations sur la commande"
-"Orders","Commandes"
-"Password *","Mot de passe *"
-"Password must have at least 8 letters.","Le mot de passe doit avoir au minimum 8 caractères."
-"Passwords must be identical","les mots de passe doivent correspondre"
-"Passwords must be identical.","les mots de passe doivent correspondre."
-"Payment method","Méthode de paiement"
-"Payment","Paiement"
-"Personal Details","Détails personnels"
-"Phone Number","Numéro de téléphone"
-"Phone number may be needed by carrier","Le numéro de téléphone peut être requis par le transporteur"
-"Place the order","Passer la commande"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Veuillez changer vos critères de recherche et réessayer. Si vous ne trouvez toujours rien d'intéressant, vous pouvez visiter notre page d'accueil et consulter nos meilleures ventes !"
-"Please check if all data are correct","Veuillez vérifier si toutes les données sont correctes"
-"Please confirm order you placed when you was offline","Veuillez confirmer la commande que vous avez passé lorsque vous étiez hors-ligne"
-"Please provide valid e-mail address.","Veuillez saisir une adresse e-mail valide."
-"Price {variant}","Prix {variant}"
-"Price","Prix"
-"Privacy policy","Politique de confidentialité"
-"Product Name","Nom du produit"
-"Product details","Détails du produit"
-"Purchase","Acheter"
-"Qty","Qté"
-"Quantity","Quantité"
-"Register an account","Créer un compte"
-"Register","S'enregistrer"
-"Remake order","Repasser la commande"
-"Remember me","Se souvenir de moi"
-"Remove from compare","Supprimer du comparateur"
-"Remove","Supprimer"
-"Repeat new password *","Confirmer le nouveau mot de passe *"
-"Repeat password *","Confirmer le mot de passe *"
-"Reset password","Réinitialiser le mot de passe"
-"Resetting the password ... ","Réinitialiser le mot de passe ... "
-"Return policy","Politique de retour"
-"Return to shopping","Poursuivre vos achats"
-"Returns","Retours"
-"Review order","Vérifier la commande"
-"SKU","UVC"
-"SKU: {sku}","UVC : {sku}"
-"Safety","Sécurité"
-"Sale","Vente"
-"Search","Recherche"
-"See details","En savoir plus"
-"See our bestsellers","Voir nos meilleures ventes"
-"Select color ","Sélectionner une couleur "
-"Select size {variant}","Sélectionner la taille {variant}"
-"Ship to my default address","Expédier à mon adresse par défaut"
-"Shipping address","Adresse d'expédition"
-"Shipping method","Méthode de livraison"
-"Shipping","Livraison"
-"Shopping cart","Panier"
-"Shopping summary","Résumé de votre commande"
-"Show subcategories","Afficher les sous-catégories"
-"Sign up to our newsletter and receive a coupon for 10% off!","Abonnez-vous à notre newsletter et recevez un coupon de réduction de 10% !"
-"Similar products","Produits similaires"
-"Size guide","Guide des tailles"
-"Sort By","Trier par"
-"State / Province","Département / Province"
-"Status","Statut"
-"Store locator","Trouver un magasin"
-"Street name *","Rue *"
-"Street name","Rue"
-"Subscribe to the newsletter and receive a coupon for 10% off","Abonnez-vous à la newsletter et recevez un coupon de réduction de 10%"
-"Subscribe","Abonnez-vous"
-"Subtotal","Sous-total"
-"Tax ID *","Numéro d'immatriculation fiscale *"
-"Tax ID must have at least 3 letters.","Le numéro d'immatriculation fiscale doit comporter au moins 3 lettres."
-"Tax identification number *","Numéro d'identification fiscale *"
-"Tax identification number must have at least 3 letters.","Le numéro d'identification fiscale doit comporter au moins 3 lettres."
-"Tax","Taxes"
-"Terms and conditions","Termes et conditions"
-"The new account will be created with the purchase. You will receive details on e-mail.","Le nouveau compte sera créé à l'achat. Vous recevrez les détails sur votre adresse e-mail."
-"Toggle password visibility","Afficher le mot de passe"
-"Track my order","Suivre ma commande"
-"Type your opinion","Exprimez votre opinion"
-"Type","Type"
-"Unfortunately we can't find the page you are looking for.","Malheureusement, nous ne pouvons pas trouver la page que vous recherchez."
-"United States","États-Unis"
-"Update my preferences","Mettre à jour mes préférences"
-"Update my profile","Mettre à jour mon profil"
-"Update my shipping details","Mettre à jour mes détails d'expédition"
-"Use my billing data","Utiliser mes données de paiement"
-"Use my company's address details","Utiliser les coordonnées de mon entreprise"
-"Use my company's address details","Utiliser les coordonnées de mon entreprise"
-"Value","Valeur"
-"View all","Voir tout"
-"View order","Voir la commande"
-"We can't find the page","Nous ne parvenons pas à trouver la page"
-"We found other products you might like","Nous avons trouvé d'autres produits que vous pourriez aimer"
-"We use cookies to give you the best shopping experience.","Nous utilisons des cookies pour vous offrir la meilleure expérience de vente."
-"We will send you details regarding the order","Nous vous enverrons les détails concernant la commande"
-"We will send you the invoice to given e-mail address","Nous vous enverrons la facture à l'adresse e-mail indiquée"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Nous avons envoyé les instructions de réinitialisation du mot de passe à votre adresse e-mail. Vérifiez votre boîte de réception et suivez le lien."
-"What we can improve?","Que pouvons-nous améliorer ?"
-"Wishlist","Liste de souhaits"
-"Women fashion","Mode femme"
-"You are logged in as {firstname}","Vous êtes connecté en tant que {firstname}"
-"You are offline. Some features might not be available.","Vous êtes hors ligne, certaines fonctionnalités sont limitées"
-"You can also use","Vous pouvez aussi utiliser"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter."
-"You have been successfully subscribed to our newsletter!","Vous avez été inscrit avec succès à notre newsletter !"
-"You have no items to compare.","Vous n'avez pas de produits à comparer."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Votre commande a été enregistrée avec succès. Vous pouvez suivre l'état de votre commande en utilisant notre fonctionnalité état de la commande. Vous allez recevoir un e-mail de confirmation avec les détails de votre commande et un lien pour suivre son avancée."
-"You must accept the terms and conditions.","Vous devez accepter les conditions générales."
-"You've entered an incorrect coupon code. Please try again.","Vous avez saisi un code coupon incorrect. Veuillez réessayer."
-"Your Account","Votre Compte"
-"Your feedback is important for us. Let us know what we could improve.","Vos retours nous intéressent. Faites-nous savoir ce que nous pouvons améliorer."
-"Your purchase","Votre achat"
-"Your shopping cart is empty.","Votre panier est vide."
-"Your wishlist is empty.","Votre liste de souhaits est vide."
-"Zip-code *","Code postal *"
-"Zip-code must have at least 3 letters.","Le code postal doit contenir au moins 3 chiffres."
-"Zip-code","Code postal"
-"a chat","sur le chat"
-"a contact page","la page de contact"
-"browse our catalog","parcourez notre catologue"
-"color_filter","Couleur"
-"login to your account","connectez-vous"
-"or write to us through","ou écrivez-nous via"
-"or","ou"
-"price_filter","Prix"
-"register an account","créer un compte"
-"return to log in","Retour au login"
-"search","la recherche"
-"size_filter","Taille"
-"to find product you were looking for.","pour trouver le produit que vous cherchiez."
-"to find something beautiful for You!","pour trouver de belles choses pour vous !"
diff --git a/src/themes/default/resource/i18n/it-IT.csv b/src/themes/default/resource/i18n/it-IT.csv
deleted file mode 100644
index 971ed4cad..000000000
--- a/src/themes/default/resource/i18n/it-IT.csv
+++ /dev/null
@@ -1,296 +0,0 @@
-"A customer with the same email already exists in an associated website.","Esiste già un account registrato con questo indirizzo email."
-"About us (Magento CMS)","Chi siamo (Magento CMS)"
-"About us","Chi siamo"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Aggiungi un codice sconto"
-"Add discount code","Applica"
-"Add to cart","Aggiungi al carrello"
-"Add to compare","Confronta"
-"Add to favorite","Aggiungi ai preferiti"
-"Allow notification about the order","Abilita le notifiche per quest'ordine"
-"Are you sure you would like to remove all the items from the shopping cart?","Confermi la rimozione di tutti i prodotti dal carrello?"
-"Are you sure you would like to remove all the items from the wishlist?","Confermi la rimozione di tutti i prodotti dalla lista dei desideri?"
-"Author","Autore"
-"Authorization in progress ...","Autorizzazione in corso..."
-"Back to login","Ritorna al login"
-"Back","Indietro"
-"Billing address","Indirizzo di fatturazione"
-"Cancel","Annulla"
-"Cash on delivery","Pagamento alla consegna"
-"Change my password","Modifica password"
-"Choose your country","Scegli il tuo paese"
-"City *","Città*"
-"City","Città"
-"Clear cart","Svuota carrello"
-"Clear filters","Azzera i filtri"
-"Clear wishlist","Svuota la lista"
-"Clear","Clear"
-"Close","Chiudi"
-"Cms Page Sync","Sincronizzazione pagina CMS"
-"Columns","Colonne"
-"Company name *","Ragione sociale*"
-"Compare products","Confronta prodotti"
-"Confirm your order","Conferma il tuo ordine"
-"Contact us","Contattaci"
-"Continue to payment","Continua con il pagamento"
-"Continue to shipping","Continua gli acquisti"
-"Copy address data from shipping","Utilizza l'indirizzo di spedizione"
-"Country *","Paese*"
-"Country","Paese"
-"Create a new account","Crea un nuovo account"
-"Current password *","Password attuale*"
-"Custom Cms Page","Pagina CMS personalizzata"
-"Customer service (Magento CMS)","Servizio clienti (Magento CMS)"
-"Customer service","Servizio clienti"
-"DPD Courier","Corriere DPD"
-"Date and time","Data e ora"
-"Delivery","Spedizione"
-"Departments","Dipartimenti"
-"Discount code","Codice sconto"
-"Discount","Sconto"
-"Don't hesitate and","Non esitare e "
-"E-mail address *","Indirizzo email*"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","Scrivici a demo@vuestorefront.io per qualsiasi domanda, suggerimenti su come migliorare i nostri prodotti o sulla tua esperienza di acquisto."
-"Edit newsletter preferences","Modifica le preferenze della newsletter"
-"Edit payment","Modifica"
-"Edit personal details","Modifica"
-"Edit shipping","Modifica"
-"Edit your profile","Modifica il profilo"
-"Edit your shipping details","Modifica le informazioni di spedizione"
-"Edit","Modifica"
-"Email address *","Email*"
-"Email address","Email"
-"English","Inglese"
-"Enter your email to receive instructions on how to reset your password.","Inserisci la tua mail per ricevere istruzioni su come reimpostare la password"
-"Erin recommends","Erin consiglia"
-"Error while sending reset password e-mail","Errore durante l'invio della mail per reimpostare la password"
-"Everything new","Tutte le novità"
-"Extension developers would like to thank you for placing an order!","Gli sviluppatori dell'estensione ti ringraziano per aver fatto un acquisto!"
-"Field is required","Campo obbligatorio"
-"Filter by categories","Filtra per categorie"
-"Filter","Filtri"
-"Filters","Filtri"
-"First name *","Nome*"
-"First name","Nome"
-"Forgot the password?","Hai dimenticato la password?"
-"General agreement","Condizioni generali"
-"German","Tedesco"
-"Germany","Germania"
-"Get inspired","Lasciati ispirare"
-"Give a feedback","Invia un riscontro"
-"Go review the order","Vai al riepilogo dell'ordine"
-"Go to Facebook","Vai su Facebook"
-"Go to Instagram","Vai su Instagram"
-"Go to Pinterest","Vai su Pinterest"
-"Go to Youtube","Vai su Youtube"
-"Go to checkout","Vai alla cassa"
-"Help","Aiuto"
-"Home","Home"
-"House/Apartment number *","Numero/interno*"
-"House/Apartment number","Numero/interno"
-"I accept ","Accetto "
-"I accept terms and conditions","Accetto i termini e le condizioni"
-"I agree to","Accetto"
-"I have a company and want to receive an invoice for every order","Ho una società e desidero ricevere una fattura per ogni ordine"
-"I want to create an account","Desidero creare un account"
-"I want to create an account'","Voglio creare un account'"
-"I want to generate an invoice for the company","Genera la fattura"
-"I want to receive a newsletter, and agree to its terms","Voglio ricevere la newsletter e accetto i suoi termini"
-"If you need an assistance you can drop us a line on","Se hai bisogno di aiuto scrivici un messaggio"
-"Instant Checkout","Checkout istantaneo"
-"Internal Server Error 500","Errore interno del server 500"
-"Italian","Italiano"
-"Italy","Italia"
-"Items ordered","Articoli ordinati"
-"Kidswear","Abbigliamento bimbo"
-"Last name *","Cognome*"
-"Last name","Cognome"
-"Latest","Dal più recente"
-"Legal notice","Note legali"
-"Load more","Carica altro"
-"Log in to your account","Accedi al tuo account"
-"Log in","Accedi"
-"Login to your account","Accedi al tuo account"
-"Logout","Esci"
-"Magazine","Rivista"
-"Men's fashion","Abbigliamento uomo"
-"My Account","Il mio account"
-"My Recently viewed products","Prodotti visti di recente"
-"My account","Il mio account"
-"My loyalty card","Carta fedeltà"
-"My newsletter","Newsletter"
-"My orders","I miei ordini"
-"My product reviews","Le mie recensioni"
-"My profile","Il mio profilo"
-"My shipping details","Informazioni di spedizione"
-"Name must have at least 2 letters.","Il nome deve essere composto da almeno 2 lettere"
-"Name must have at least 3 letters.","Il nome deve essere composto da almeno 3 lettere"
-"New Luma Yoga Collection","Nuova collezione Luma Yoga"
-"New password *","Nuova password*"
-"Newsletter","Newsletter"
-"No orders yet","Ancora nessun ordine"
-"No products found!","Nessun prodotto trovato!"
-"No products yet","Ancora nessun prodotto"
-"No results were found.","Non abbiamo trovato niente"
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","Non ci sono ancora recensioni per questo prodotto. Non esitare a condividere la tua opinione e scrivi la prima recensione!"
-"Open menu","Apri menu"
-"Open microcart","Apri il mini carrello"
-"Open my account","Apri il mio account"
-"Open search panel","Apri il pannello di ricerca"
-"Open wishlist","Apri la lista dei desideri"
-"Order #{id}","Ordine n.{id}"
-"Order ID","Numero ordine"
-"Order Summary","Riepilogo dell'ordine"
-"Order confirmation","Conferma ordine"
-"Order informations","Informazioni sull'ordine"
-"Orders","Ordini"
-"Password *","Password*"
-"Password must have at least 8 letters.","La password deve essere composta da almeno 8 lettere."
-"Passwords must be identical","Le password devono essere uguali"
-"Passwords must be identical.","Le due password devono essere uguali."
-"Payment method","Metodo di pagamento"
-"Payment","Pagamento"
-"Personal Details","Informazioni personali"
-"Phone Number","Telefono"
-"Phone number may be needed by carrier","Il numero di telefono potrebbe servire al corriere"
-"Place the order","Concludi l'ordine"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Per favore cambia i tuoi parametri di ricerca e riprova. Se ancora non trovi nulla di rilevante, torna in home page e prova qualcuno dei nostri prodotti più venduti!"
-"Please check if all data are correct","Controlla che tutte le informazioni siano corrette"
-"Please confirm order you placed when you was offline","Conferma l'ordine che hai creato quando eri offline"
-"Please provide valid e-mail address.","Inserisci un indirizzo email valido"
-"Price {variant}","Prezzo {variant}"
-"Price","Prezzo"
-"Price: High to low","Prezzo: dal più caro al meno caro"
-"Price: Low to high","Prezzo: dal meno caro al più caro"
-"Privacy policy","Privacy policy"
-"Product Name","Nome prodotto"
-"Product details","Dettagli sul prodotto"
-"Products","Prodotti"
-"Purchase","Acquisto"
-"Qty","Qtà"
-"Quantity","Quantità"
-"Register an account","Registra un account"
-"Register","Registrati"
-"Remake order","Ordina di nuovo"
-"Remember me","Ricordami"
-"Remove from compare","Rimuovi dal comparatore"
-"Remove","Elimina"
-"Repeat new password *","Ripeti la nuova password*"
-"Repeat password *","Ripeti la password*"
-"Reset password","Reimposta la password"
-"Resetting the password ... ","Reimpostazione della password... "
-"Return policy","Politica dei resi"
-"Return to shopping","Continua gli acquisti"
-"Returns","Resi"
-"Review order","Verifica dell'ordine"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","Sicurezza"
-"Sale","Sconti"
-"Search","Cerca"
-"Searched term should consist of at least 3 characters.","Il termine di ricerca deve contenere almeno 3 caratteri."
-"See details","Dettagli"
-"See our bestsellers","Visualizza i prodotti più venduti"
-"Select No","Seleziona No"
-"Select Yes","Seleziona Sì"
-"Select color ","Seleziona un colore "
-"Select size {variant}","Seleziona la taglia {variant}"
-"Ship to my default address","Spedisci all'indirizzo predefinito"
-"Shipping address","Indirizzo di spedizione"
-"Shipping method","Metodo di spedizione"
-"Shipping","Spedizione"
-"Shopping cart","Carrello"
-"Shopping summary","Riepilogo acquisti"
-"Show subcategories","Mostra sottocategorie"
-"Sign up to our newsletter and receive a coupon for 10% off!","Iscriviti alla newsletter e ricevi un codice sconto del 10% !"
-"Similar products","Prodotti simili"
-"Size guide","Guida alle taglie"
-"Something went wrong ...","Qualcosa è andato storto..."
-"Something went wrong. Try again in a few seconds.","Qualcosa è andato storto. Riprova fra qualche secondo."
-"Sort By","Ordina per"
-"State / Province","Provincia"
-"Status","Stato"
-"Store locator","Store locator"
-"Street name *","Via*"
-"Street name","Via"
-"Subscribe to the newsletter and receive a coupon for 10% off","Iscriviti alla newsletter e ricevi un codice sconto del 10%"
-"Subscribe","Iscriviti"
-"Subtotal","Subtotale"
-"Tax ID *","Partita IVA*"
-"Tax ID must have at least 3 letters.","Tax ID must have at least 3 letters."
-"Tax identification number *","Codice fiscale/Partita IVA*"
-"Tax identification number must have at least 3 letters.","Il codice fiscale deve essere composto da almeno 3 lettere"
-"Tax","Tasse"
-"Terms and conditions","Termini e condizioni"
-"The OrderNumber is {id}","Il numero dell'ordine è {id}"
-"The new account will be created with the purchase. You will receive details on e-mail.","Il nuovo account verrà creato con l'acquisto. Riceverai le informazioni tramite email"
-"The order can not be transfered because of server error. Order has been queued","L'ordine non può essere evaso a causa di un errore del server. L'ordine è stato messo in coda."
-"The server order id has been set to ","L'ID dell'ordine sul server è stato impostato a "
-"This product is out of stock.","Il prodotto non è disponibile."
-"To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items","Per completare l'ordine ritorna sul sito quando sarai online. Il tuo ordine verrà spedito al server appena tornerai qui con la linea internet attiva e poi confermato in accordo con le quantità rimanenti dei prodotti selezionati"
-"Toggle password visibility","Attiva/Disattiva visibilità password"
-"Track my order","Traccia il mio ordine"
-"Type your opinion","Scrivi la tua opinione"
-"Type","Tipo"
-"Unfortunately we can't find the page you are looking for.","Sfortunatamente non riusciamo a trovare la pagina che stai cercando."
-"United States","Stati Uniti"
-"Update my preferences","Aggiorna le preferenze"
-"Update my profile","Aggiorna il profilo"
-"Update my shipping details","Aggiorna le informazioni di spedizione"
-"Use my billing data","Utilizza l'indirizzo di fatturazione"
-"Use my company's address details","Usa l'indirizzo della società"
-"Use my company's address details","Utilizza l'indirizzo della mia azienda"
-"Value","Valore"
-"View all","Vedi tutto"
-"View order","Vedi ordine"
-"Vuestore","Vuestore"
-"We can't find the page","Non troviamo la pagina"
-"We found other products you might like","Abbiamo trovato dei prodotti che ti potrebbero piacere"
-"We use cookies to give you the best shopping experience.","Utilizziamo cookies per offrirvi la migliore esperienza d'acquisto possibile."
-"We will send you details regarding the order","Ti invieremo i dettagli dell'ordine"
-"We will send you the invoice to given e-mail address","La fattura verrà inviata all'indirizzo email inserito"
-"We've noticed Internal Server Error while rendering this request.","È stato rilevatao un errore interno del server durante questa richiesta."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Abbiamo inviato le istruzioni per reimpostare la password al tuo indirizzo email. Verifica la tua casella di posta e segui il link."
-"What we can improve?","Cosa possiamo migliorare?"
-"Wishlist","Lista dei desideri"
-"Women fashion","Abbigliamento donna"
-"You are logged in as {firstname}","Sei connesso come {firstname}"
-"You are offline","Non sei connesso"
-"You are offline. Some features might not be available.","Non sei connesso ad internet, alcune funzionalità potrebbero essere limitate"
-"You can allow us to remind you about the order via push notification after coming back online. You'll only need to click on it to confirm.","Puoi consentirci di informarti a proposito dell'ordine tramite notifiche push dopo essere tornato online. Dovrai cliccare per confermare."
-"You can also use","Puoi utilizzare anche"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Puoi accedere al tuo account con email e password definite prima. Con un account registrato puoi modificare i tuoi dati, controllare la cronologia delle transazioni, modificare l'iscrizione alla newsletter."
-"You have been successfully subscribed to our newsletter!","Ti sei iscritto alla newsletter con successo!"
-"You have been successfully unsubscribed from our newsletter!","Ti sei discritto con successo dalla nostra newsletter!"
-"You have no items to compare.","Non hai niente da confrontare."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Il tuo ordine è stato effettuato con successo. Puoi controllarne lo stato attraverso l'opzione stato della consegna. Riceverai un'email di conferma con tutti i dettagli dell'ordine ed un link per seguirne il progresso."
-"You must accept the terms and conditions.","È necessario accettare le condizioni generali del servizio."
-"You will receive Push notification after coming back online. You can confirm the order by clicking on it","Riceverai le notifiche push appena tornerai online. Conferma l'ordine cliccando su esso."
-"You've entered an incorrect coupon code. Please try again.","Hai inserito un coupon errato o inesistente. Riprova."
-"Your Account","Il tuo account"
-"Your feedback is important for us. Let us know what we could improve.","La tua opinione è importante per noi. Facci sapere cosa possiamo migliorare."
-"Your purchase","Il tuo acquisto"
-"Your shopping cart is empty.","Il tuo carrello è vuoto"
-"Your wishlist is empty.","La lista dei desideri è vuota."
-"Zip-code *","CAP*"
-"Zip-code must have at least 3 letters.","Il codice postale deve essere composto da almeno 3 lettere"
-"Zip-code","CAP"
-"a chat","una chat"
-"a contact page","la pagina dei contatti"
-"browse our catalog","Sfoglia il catalogo"
-"color","colore"
-"color_filter","Colore"
-"erin_recommends_filter","Raccomandato da Erin"
-"login to your account","accedi al tuo account"
-"login","accedi"
-"or write to us through","o scrivici tramite"
-"or","o"
-"price_filter","Prezzo"
-"register an account","registra un account"
-"return to log in","ritorna al login"
-"search","cerca"
-"size","taglia"
-"size_filter","Taglia"
-"to find product you were looking for.","per trovare il prodotto che cerchi."
-"to find something beautiful for You!","per trovare qualcosa di bello per te!"
-"{count} items","{count} elementi"
diff --git a/src/themes/default/resource/i18n/ja-JP.csv b/src/themes/default/resource/i18n/ja-JP.csv
deleted file mode 100644
index e2fa66941..000000000
--- a/src/themes/default/resource/i18n/ja-JP.csv
+++ /dev/null
@@ -1,224 +0,0 @@
-"About us","About us"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","ディスカウントコードを追加する"
-"Add discount code","申し込み"
-"Add to cart","カートに追加"
-"Add to compare","比較への追加"
-"Add to favorite","お気に入りへの追加"
-"Author","書いた人"
-"Authorization in progress ...","認証を実行中 ..."
-"Back to login","ログインに戻る"
-"Back","戻る"
-"Billing address","請求先住所"
-"Cancel","キャンセル"
-"Change my password","パスワードの変更"
-"City *","市町村 *"
-"City","市町村"
-"Close","閉じる"
-"Company name *","会社名 *"
-"Contact us","Contact us"
-"Continue to payment","支払いに進む"
-"Continue to shipping","買い物を続ける"
-"Copy address data from shipping","発送先の住所をコピーする"
-"Country *","国 *"
-"Country","国"
-"Create a new account","アカウントを作る"
-"Current password *","現在のパスワード *"
-"Customer service","カスタマーサービス"
-"Date and time","日時"
-"Delivery","発送"
-"Departments","部署"
-"Discount code","ディスカウントコード"
-"Discount","ディスカウント"
-"Don't hesitate and","躊躇しないで"
-"E-mail address *","メールアドレス *"
-"Edit newsletter preferences","ニュースレターの設定を変更する"
-"Edit payment","支払いの編集"
-"Edit personal details","個人の詳細の編集"
-"Edit shipping","発送方法の編集"
-"Edit your profile","プロフールの編集"
-"Edit your shipping details","買い物の詳細を編集"
-"Edit","編集"
-"Email address *","メールアドレス *"
-"Email address","メールアドレス"
-"Enter your email to receive instructions on how to reset your password.","メールアドレスを入力して、パスワードリセットの手順を受信してください。"
-"Erin recommends","エリンのおすすめ"
-"Error while sending reset password e-mail","リセットパスワードを送る際にエラー"
-"Everything new","全ての新しいもの"
-"Field is required","必須の項目です"
-"Filter","フィルター"
-"Filters","フィルター"
-"First name *","名 *"
-"First name","名"
-"Forgot the password?","パスワードを忘れましたか?"
-"General agreement","ユーザー同意"
-"Get inspired","インスピレーションをもらいましょう"
-"Go review the order","注文の確認"
-"Go to Facebook","Facebookへ行く"
-"Go to Instagram","Instagramへ行く"
-"Go to Pinterest","Pinterestへ行く"
-"Go to Youtube","Youtubeへ行く"
-"Go to checkout","チェックアウトに進む"
-"Help","ヘルプ"
-"Home","ホーム"
-"House/Apartment number *","部屋番号、建物名 *"
-"House/Apartment number","部屋番号、建物名"
-"I accept ","同意する "
-"I accept terms and conditions","利用規約に同意する"
-"I agree to","同意します"
-"I have a company and want to receive an invoice for every order","法人からなので、全ての注文に請求書を発行してください"
-"I want to create an account","アカウントを作成"
-"I want to create an account'","アカウントを作成する'"
-"I want to generate an invoice for the company","この会社への請求書を発行する"
-"I want to receive a newsletter, and agree to its terms","規約に同意して、ニュースレターを受け取ります"
-"If you need an assistance you can drop us a line on","お問合わせはこちらからお願いします"
-"Items ordered","注文されました"
-"Kidswear","子供服"
-"Last name *","姓 *"
-"Last name","姓"
-"Legal notice","法律のお知らせ"
-"Log in to your account","アカウントにログインする"
-"Log in","ログイン"
-"Login to your account","アカウントにログイン"
-"Logout","ログアウト"
-"Magazine","マガジン"
-"Men's fashion","男性のファッション"
-"My Account","自分のアカウント"
-"My account","自分のアカウント"
-"My loyalty card","自分のポイントカード"
-"My newsletter","自分のニュースレター"
-"My orders","自分の注文"
-"My product reviews","自分の商品レビュー"
-"My profile","自分のプロフィール"
-"My shipping details","買い物の詳細"
-"Name must have at least 3 letters.","名前は最低3文字必要です"
-"New Luma Yoga Collection","Luma Yogaの新しいコレクション"
-"New password *","新しいパスワード *"
-"Newsletter","ニュースレター"
-"No orders yet","また注文されていません"
-"No products found!","商品がありません!"
-"No results were found.","なにもありません"
-"Open menu","メニューを開く"
-"Open microcart","小さなカートを開く"
-"Open my account","自分のアカウントを開く"
-"Open search panel","検索パネルを開く"
-"Open wishlist","欲しいモノリストを開く"
-"Order #{id}","注文番号{id}"
-"Order ID","注文ID"
-"Order Summary","注文の詳細"
-"Order informations","注文の情報"
-"Orders","注文"
-"Password *","パスワード *"
-"Passwords must be identical","パスワードは同じである必要があります"
-"Passwords must be identical.","パスワードは同じである必要があります"
-"Payment method","決済方法"
-"Payment","支払い"
-"Personal Details","個人の詳細"
-"Phone Number","電話番号"
-"Phone number may be needed by carrier","発送に電話番号が必要な事があります"
-"Place the order","注文を行う"
-"Please check if all data are correct","全てのデータが正しいかどうか確認してください"
-"Please provide valid e-mail address.","正しいメールアドレスを入力してください"
-"Price {variant}","価格 {variant}"
-"Price","価格"
-"Privacy policy","プライバシー・ポリシー"
-"Product Name","商品名"
-"Product details","商品の詳細"
-"Purchase","購入"
-"Qty","数量"
-"Quantity","数量"
-"Register an account","アカウントの登録"
-"Register","登録"
-"Remake order","注文を再度行う"
-"Remember me","ログインを覚える"
-"Remove from compare","比較から削除する"
-"Remove","削除する"
-"Repeat new password *","もう一度新しいパスワード *"
-"Repeat password *","パスワードもう一度 *"
-"Reset password","リセットパスワード"
-"Resetting the password ... ","パスワードをリセットしていあmす ... "
-"Return policy","返品ポリシー"
-"Return to shopping","買い物に戻る"
-"Returns","返品"
-"Review order","注文の確認"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","安全性"
-"Sale","セール"
-"Search","検索"
-"See details","詳細を見る"
-"See our bestsellers","ベストセラーを見る"
-"Select color ","色の選択 "
-"Select size {variant}","サイズの選択 {variant}"
-"Ship to my default address","デフォルトの住所に発送する"
-"Shipping address","発送住所"
-"Shipping method","発送方法"
-"Shipping method","発送方法"
-"Shipping","送料"
-"Shopping cart","ショッピングカート"
-"Shopping summary","買い物の詳細"
-"Show subcategories","サブカテゴリーを開く"
-"Sign up to our newsletter and receive a coupon for 10% off!","ニュースレターに登録して、10%割引のクーポンをうけとりましょう!"
-"Similar products","似た商品"
-"Size guide","サイズガイド"
-"State / Province","都道府県"
-"Status","ステータス"
-"Store locator","Store locator"
-"Street name *","住所 *"
-"Street name","住所"
-"Subscribe to the newsletter and receive a coupon for 10% off","ニュースレターに登録して10%オフのクーポンをもらいましょう"
-"Subscribe","登録"
-"Subtotal","小計"
-"Tax ID *","税金ID *"
-"Tax ID must have at least 3 letters.","税金IDは3文字以上である必要があります"
-"Tax identification number *","税金ID番号 *"
-"Tax identification number must have at least 3 letters.","税金IDは最低3文字必要です"
-"Tax","税金"
-"Terms and conditions","利用規約"
-"The new account will be created with the purchase. You will receive details on e-mail.","この買い物の後にアカウントが作成されます。詳細の入ったメールが送られます"
-"Toggle password visibility","パスワードの見え方の切り替え"
-"Track my order","注文のトラッキング"
-"Type","タイプ"
-"Unfortunately we can't find the page you are looking for.","すみません、お探しのページはありません。"
-"Update my preferences","設定を更新する"
-"Update my profile","プロフィールを更新"
-"Update my shipping details","買い物の詳細の更新"
-"Use my billing data","請求書情報を使う"
-"Use my company's address details","自分の会社の住所の詳細を使う"
-"Use my company's address details","自分の会社の住所を使う"
-"Value","値"
-"View all","全てを見る"
-"View order","注文をみる"
-"We can't find the page","ページがありません"
-"We use cookies to give you the best shopping experience.","買い物をできるだけ良いものにするためクッキーを使っています"
-"We will send you details regarding the order","注文に関する詳細を送ります"
-"We will send you the invoice to given e-mail address","指定されたメールアドレスに請求書を送ります"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","パスワードリセットのメールを送りました。メールの中にあるリンクをクリックしてください。"
-"Wishlist","欲しいモノリスト"
-"Women fashion","女性のファッション"
-"You are logged in as {firstname}","ログイン中 {firstname}"
-"You are offline. Some features might not be available.","オフラインです、使えない機能があります"
-"You can also use","こちらを使うこともできます"
-"You have been successfully subscribed to our newsletter!","ニュースレターに購読しました!"
-"You have no items to compare.","比較するアイテムはありません"
-"You must accept the terms and conditions.","利用規格に同意してください"
-"You've entered an incorrect coupon code. Please try again.","クーポンコードが無効です。もう一度試してください。"
-"Your shopping cart is empty.","ショッピングカートは空です"
-"Your wishlist is empty.","欲しいモノリストは空です"
-"Zip-code *","郵便番号 *"
-"Zip-code must have at least 3 letters.","郵便番号は最低3文字必要です"
-"Zip-code","郵便番号"
-"a chat","チャット"
-"a contact page","コンタクトページ"
-"browse our catalog","カタログを見る"
-"color_filter","色"
-"login to your account","アカウントへのログイン"
-"or write to us through","かこちらから連絡ください"
-"or","か"
-"price_filter","価格"
-"register an account","アカウントの登録"
-"return to log in","ログインに戻る"
-"search","検索"
-"size_filter","サイズ"
-"to find product you were looking for.","から欲しい商品を見つける"
-"to find something beautiful for You!","あなたにあった素敵なものを見つけましょう!"
diff --git a/src/themes/default/resource/i18n/nl-NL.csv b/src/themes/default/resource/i18n/nl-NL.csv
deleted file mode 100644
index 1bce6a7e3..000000000
--- a/src/themes/default/resource/i18n/nl-NL.csv
+++ /dev/null
@@ -1,266 +0,0 @@
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Pas uw zoekcriteria aan en probeer het opnieuw. Als u nog steeds niks relevants kan vinden, bezoek dan de homepage en bekijk onze bestsellers!"
-"Select ","Selecteer "
-"You are offline. Some features might not be available.","U bent momenteel offline, niet alle onderdelen van de shop zullen volledig werken"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","U kunt zich aanmelden bij uw account met eerder gedefinieerd e-mailadres en wachtwoord. In uw account kunt u uw profielgegevens bewerken, de geschiedenis van transacties bekijken, het abonnement op de nieuwsbrief bewerken."
-"color_filter","Kleur"
-"not authorized","geen toegang"
-"price_filter","Prijs"
-"size_filter","Grootte"
-About us,Over ons
-Add a discount code,Voeg een kortingscode toe
-Add discount code,Voeg kortingscode toe
-Add review,Review toevoegen
-Add to cart,In winkelwagen
-Add to compare,Voeg toe aan de vergelijking
-Add to favorite,Voeg toe aan favorieten
-Allow notification about the order,Sta notificaties over uw order toe
-Apply,Toepassen
-Author,Auteur
-Authorization in progress ...,Bezig met autorizeren...
-Availability,Beschikbaarheid
-Back to login,Terug naar login
-Back,Terug
-Billing address,Factuuradres
-Cancel,Annuleren
-Change my password,Wijzig mijn wachtwoord
-Choose your country,Kies uw land
-City *,Stad *
-City,Stad
-Clear,Leegmaken
-Close,Sluiten
-Company name *,Bedrijfsnaam *
-Complete,Compleet
-Confirm your order,Bevestig uw bestelling
-Contact us,Neem contact met ons op
-Continue to payment,Ga door naar betaling
-Continue to shipping,Ga door naar verzending
-Copy address data from shipping,Kopieer adresgegevens van verzending
-Country,Land
-Current password *,Huidige wachtwoord *
-Customer service,Klantenservice
-Customer service,Klantenservice
-Date and time,Datum en tijd
-Delivery,Levering
-Departments,Afdelingen
-Description,Beschrijving
-Details,Details
-Discount code,Kortingscode
-Don't hesitate and,Aarzel niet en
-Dutch,Nederlands
-E-mail address *,Email adres *
-Edit newsletter preferences,Wijzig nieuwsbrief voorkeuren
-Edit payment,Wijzig betaalmethode
-Edit personal details,Wijzig persoonlijke gegevens
-Edit shipping,Wijzig verzending
-Edit your profile,Wijzig mijn profiel
-Edit your shipping details,Wijzig mijn verzendgegevens
-Edit,Wijzig
-Email address *,E-mailadres *
-Email address,E-mailadres
-Enter your email to receive instructions on how to reset your password.,Voer uw e-mailadres in om instructies te ontvangen over het opnieuw instellen van uw wachtwoord.
-Erin recommends,Erin beveelt aan
-Error while sending reset password e-mail,Fout bij het verzenden van de wachtwoord reset email
-Error: Error placing an order,Error: Er iets fout gegaan tijdens het verwerken van uw order
-Everything new,Nieuwe producten
-Field is required,Veld is verplicht
-Filter,Filter
-Filters,Filters
-First name *,Voornaam *
-First name,Voornaam
-Forgot the password?,Wachtwoord vergeten?
-General agreement,Algemene overeenkomst
-Get inspired,Krijg inspiratie
-Go review the order,Controleer de order
-Go to Facebook,Ga naar Facebook
-Go to Instagram,Ga naar Instagram
-Go to Pinterest,Ga naar Pinterest
-Go to Youtube,Ga naar Youtube
-Go to checkout,Ga naar afrekenen
-Grand Total,Totaal
-Help,Hulp
-Home,Home
-House/Apartment number *,Huisnummer/Toevoeging *
-House/Apartment number,Huisnummer/toevoeging
-I accept ,Ik accepteer
-I agree to,Ik ga akkoord met
-I have a company and want to receive an invoice for every order,Ik heb een bedrijf en ik wil voor iedere bestelling een factuur ontvangen
-I want to create an account',Ik wil een account aanmaken'
-I want to create an account,Ik wil een account aanmaken
-I want to generate an invoice for the company,Ik wil een zakelijke factuur genereren
-I want to receive a newsletter, and agree to its terms,Ik wil de nieuwsbrief ontvangen en ga akkoord met de voorwaarden
-If you need an assistance you can drop us a line on,"Als u hulp nodig heeft, kunt u ons een bericht sturen via"
-In stock!,Op voorraad!
-In stock,Op voorraad
-Information,Informatie
-Items ordered,Bestelde producten
-Kidswear,Kinderkleding
-Last name *,Achternaam *
-Last name,Achternaam
-Latest,Meest recent
-Legal notice,Juridische kennisgeving
-Log in to your account,Log in op uw account
-Log in,Log in
-Login to your account,Log in op jouw account
-Logout,Log uit
-Magazine,Magazine
-Men's fashion,Herenmode
-My Account,Mijn Account
-My Recently viewed products,Recent bekeken producten
-My account,Mijn account
-My loyalty card,Mijn klantenkaart
-My newsletter,Mijn nieuwsbrief
-My orders,Mijn bestellingen
-My product reviews,Mijn productreviews
-My profile,Mijn profiel
-My shipping details,Mijn verzendgegevens
-Name must have at least 3 letters.,Naam moet minimaal drie letters bevatten
-Name,Naam
-Netherlands,Nederland
-New Luma Yoga Collection,De nieuwe Luma Yoga collectie
-New password *,Nieuw wachtwoord *
-Newsletter,Nieuwsbrief
-No products found!,Geen producten gevonden
-No results were found.,Geen resultaten gevonden.
-No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!,Er zijn nog geen reviews geplaatst. Aarzel niet om uw mening te delen en de eerste recensie te schrijven!
-OK,Oke
-Open menu,Open menu
-Open microcart,Open winkelwagen
-Open my account,Open mijn account
-Open search panel,Open zoekvenster
-Open wishlist,Open verlanglijst
-Order ID,Order nummer
-Order Summary,Besteloverzicht
-Order confirmation,Bestel bevestiging
-Order informations,Order informatie
-Order now - sent tomorrow,Nu besteld - morgen verstuurd
-Orders,Bestellingen
-Out of stock,Niet op voorraad
-Password *,Wachtwoord *
-Passwords must be identical,Wachtwoord moet identiek zijn
-Passwords must be identical.,Wachtwoord moet identiek zijn
-Payment method,Betaalmethode
-Payment,Betaling
-Pending,In afwachting
-Personal Details,Persoonlijke gegevens
-Phone Number,Telefoonnummer
-Phone number may be needed by carrier,Telefoonnummer kan nodig zijn voor vervoerder
-Place the order,Plaats bestelling
-Please check if all data are correct,Controleer of alle gegevens correct zijn
-Please confirm order you placed when you was offline,Bevestig uw offline geplaatste order
-Please provide valid e-mail address.,Geef een geldig e-mailadres op.
-Price ,Prijs
-Price,Prijs
-Price,Prijs
-Price: High to low,Prijs: van hoog naar laag
-Price: Low to high,Prijs: van laag naar hoog
-Privacy policy,Privacybeleid
-Proceed to checkout,Ga door naar winkelwagen
-Processing order...,Order verwerken...
-Processing,Bezig met verwerken
-Product Name,Product naam
-Product details,Productdetails
-Product that you are trying to add is not available.,Het product dat u probeert toe te voegen is niet beschikbaar.
-Qty,Aantal
-Quantity,Aantal
-Quantity,Aantal
-Read more,Lees meer
-Recommended price,Adviesprijs
-Recommended retail price,Adviesprijs
-Register an account,Registreer een account
-Register,Registreren
-Remake order,Bestel opnieuw
-Remember me,Herinner mij
-Remove from compare,Verwijder van de vergelijking
-Remove,Verwijderen
-Repeat new password *,Herhaal nieuw wachtwoord *
-Repeat password *,Wachtwoord herhalen *
-Reset password,Wachtwoord opnieuw instellen
-Resetting the password ... ,Wachtwoord resetten ...
-Return policy,Retourvoorwaarden
-Return to shopping,Ga verder met winkelen
-Returns,Retouren
-Review order,Bestelling controleren
-Review,Review
-Reviews,Reviews
-Safety,Veiligheid
-Sale,Aanbiedingen
-Search,Zoeken
-See details,Bekijk details
-See our bestsellers,Bekijk onze bestsellers
-Select color ,Selecteer kleur
-Select size ,Selecteer grootte
-Ship to my default address,Verzend naar mijn standaard adres
-Shipping address,Verzendadres
-Shipping method,Verzendmethode
-Shipping,Verzending
-Shopping cart,Winkelwagen
-Shopping summary,Winkelwagen samenvatting
-Show subcategories,Laat subcategorieën zien
-Sign up to our newsletter and receive a coupon for 10% off!,Meld u aan voor onze nieuwsbrief en ontvang 10% korting!
-Similar products,Vergelijkbare producten
-Size guide,Maattabel
-Sort By,Sorteer op
-Sort by,Sorteer op
-Specifications,Specificaties
-State / Province,Provincie
-Store locator,Winkelzoeker
-Street name *,Straatnaam *
-Street name,Straatnaam
-Subscribe to the newsletter and receive a coupon for 10% off,Abonneer u op de nieuwsbrief en ontvang een kortingsbon van 10% korting
-Subscribe,Abbonneer
-Subtotal,Subtotaal
-Summary,Samenvatting
-Tax ID *,BTW Nummer *
-Tax ID must have at least 3 letters,BTW nummer moet minimaal drie letters bevatten
-Tax identification number *,BTW Nummer *
-Tax identification number must have at least 3 letters.,BTW nummer moet minimaal drie letters bevatten
-Tax,BTW
-Terms and conditions,Algemene voorwaarden
-The new account will be created with the purchase. You will receive details on e-mail.,Het nieuwe account wordt aangemaakt tijdens de aankoop. U ontvangt de details per e-mail.
-The product, category or CMS page is not available in Offline mode. Redirecting to Home.,Deze pagina is niet beschikbaar in offline modus. U wordt doorgestuurd naar de homepage.
-Track my order,Volg mijn bestelling
-Unfortunately we can't find the page you are looking for.,Helaas kunnen we niet vinden waar u naar op zoek bent
-Update my preferences,Update mijn voorkeuren
-Update my profile,Update mijn profiel
-Update my shipping details,Update mijn verzendgegevens
-Use my billing data,Gebruik mijn factuuradres
-Use my company's address details,Gebruik mijn bedrijfsadres
-Value,Waarde
-View all,Bekijk alle
-View order,Bekijk order
-We can't find the page,We kunnen de pagina niet vinden
-We use cookies to give you the best shopping experience.,Wij gebruiken cookies om u de beste winkelervaring te bieden.
-We will send you details regarding the order,We sturen u de details over de bestelling
-We will send you the invoice to given e-mail address,We zullen u de factuur sturen naar het opgegeven email-adres
-We've sent password reset instructions to your email. Check your inbox and follow the link.,We hebben instructies voor het opnieuw instellen van het wachtwoord verzonden naar uw e-mailadres. Controleer uw inbox en volg de link.
-Wishlist,Verlanglijst
-Women fashion,Damesmode
-You are logged in as,U bent ingelogd als
-You can also use,U kunt ook de
-You have been successfully subscribed to our newsletter!,U bent succesvol ingeschreven voor onze nieuwsbrief!!
-You have no items to compare.,U heeft geen producten om te vergelijken
-You have successfully placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.,U hebt de bestelling succesvol geplaatst. U kunt de status van onze bezorging controleren door onze bezorgstatus te gebruiken. U ontvangt een orderbevestiging per e-mail met de details van uw bestelling en een link naar het voortgangspad.
-You must accept the terms and conditions.,U moet de voorwaarden accepteren.
-Your Account,Uw Account
-Your choice,Jouw keuze
-Your purchase,Uw bestelling
-Your shopping cart is empty.,Uw winkelwagen is leeg.
-Your wishlist is empty.,Uw verlanglijst is leeg
-Zip-code *,Postode *
-Zip-code must have at least 3 letters.,Postcode moet minimaal drie letters bevatten
-Zip-code,Postcode
-a chat,de chat
-a contact page,de contact pagina
-available,beschikbaar
-browse our catalog,bekijk onze catalogus
-login to your account,log in op uw account
-login,login
-or write to us through,of
-or,of
-register an account,registreer een account
-return to log in,keer terug om in te loggen
-search,zoekfunctie
-to account,naar account
-to find product you were looking for.,gebruiken om een product te vinden.
-to find something beautiful for You!,om iets moois voor jou te vinden!
diff --git a/src/themes/default/resource/i18n/pl-PL.csv b/src/themes/default/resource/i18n/pl-PL.csv
deleted file mode 100644
index 8d0189dd0..000000000
--- a/src/themes/default/resource/i18n/pl-PL.csv
+++ /dev/null
@@ -1,245 +0,0 @@
-"About us","O nas"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Dodaj kod rabatowy"
-"Add discount code","Dodaj kod rabatowy"
-"Add to cart","Dodaj do koszyka"
-"Add to compare","Dodaj do porównania"
-"Add to favorite","Dodaj do listy życzeń"
-"Author","Autor"
-"Authorization in progress ...","Autoryzacja ..."
-"Back to login","Powrót do logowania"
-"Back","Powrót"
-"Billing address","Adres rozliczeniowy"
-"Cancel","Anuluj"
-"Change my password","Zmień hasło"
-"Choose your country","Wybierz kraj"
-"City *","Miasto *"
-"City","Miasto"
-"Close","Zamknij"
-"Company name *","Nazwa firmy *"
-"Confirm your order","Potwierdź swoje zamówienie"
-"Contact us","Kontakt"
-"Continue to payment","Przejdź do płatności"
-"Continue to shipping","Przejdź do opcji wysyłki"
-"Copy address data from shipping","Kopiuj dane adresowe z wysyłki"
-"Country *","Kraj *"
-"Country","Kraj"
-"Create a new account","Stwórz nowe konto"
-"Current password *","Obecne hasło *"
-"Customer service","Obsługa klienta"
-"Date and time","Data i godzina"
-"Delivery","Dostawa"
-"Departments","Departments"
-"Discount code","Kod rabatowy"
-"Discount","Zniżka"
-"Don't hesitate and","Nie wahaj się i"
-"E-mail address *","Adres email *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience"
-"Edit newsletter preferences","Edytuj swoje subskrypcje"
-"Edit payment","Edytuj płatność"
-"Edit personal details","Edytuj dane osobowe"
-"Edit shipping","Edytuj opcje wysyłki"
-"Edit your profile","Edytuj mój profil"
-"Edit your shipping details","Edytuj adres dostawy"
-"Edit","Edytuj"
-"Email address *","Adres email *"
-"Email address","Adres email"
-"English","Angielski"
-"Enter your email to receive instructions on how to reset your password.","Podaj swój email, aby otrzymać instrukcje dotyczące zresetowania hasła."
-"Erin recommends","Polecane"
-"Error while sending reset password e-mail","Błąd wysyłania emaila do zresetowania hasła"
-"Everything new","Nowości"
-"Field is required","Pole jest wymagane"
-"Filter","Filtruj"
-"Filters","Filtry"
-"First name *","Imię *"
-"First name","Imię"
-"Forgot the password?","Nie pamiętasz hasła?"
-"General agreement","Regulamin"
-"German","Niemiecki"
-"Germany","Niemcy"
-"Get inspired","Inspiracje"
-"Give a feedback","Prześlij opinię"
-"Go review the order","Przejrzyj zamówienie"
-"Go to Facebook","Przejdź do Facebook"
-"Go to Instagram","Przejdź do Instagram"
-"Go to Pinterest","Przejdź do Pinterest"
-"Go to Youtube","Przejdź do Youtube"
-"Go to checkout","Przejdź do płatności"
-"Help","Pomoc"
-"Home","Home"
-"House/Apartment number *","Numer domu/mieszkania *"
-"House/Apartment number","Numer domu/mieszkania"
-"I accept ","Akceptuję "
-"I accept terms and conditions","Akceptuję regulamin"
-"I agree to","Akceptuję"
-"I have a company and want to receive an invoice for every order","Chcę otrzymać fakturę za każde zamówienie"
-"I want to create an account","Chcę utworzyć konto"
-"I want to create an account'","Chcę utworzyć konto'"
-"I want to generate an invoice for the company","Chcę wygenerować fakturę dla firmy"
-"I want to receive a newsletter, and agree to its terms","Chcę otrzymywać newsletter"
-"If you need an assistance you can drop us a line on","Potrzebujesz przypisania, napisz do nas."
-"Italian","Włoski"
-"Italy","Włochy"
-"Items ordered","Zamówione przedmioty"
-"Kidswear","Odzież dziecięca"
-"Last name *","Nazwisko *"
-"Last name","Nazwisko"
-"Legal notice","Informacje prawne"
-"Log in to your account","Zaloguj się na swoje konto"
-"Log in","Zaloguj"
-"Login to your account","Zaloguj się"
-"Logout","Wyloguj"
-"Magazine","Magazyn"
-"Men's fashion","Moda męska"
-"My Account","Moje konto"
-"My account","Moje konto"
-"My loyalty card","Moja karta lojalnościowa"
-"My newsletter","Mój newsletter"
-"My orders","Moje zamówienia"
-"My product reviews","Moje opinie"
-"My profile","Mój profil"
-"My shipping details","Mój adres dostawy"
-"Name must have at least 3 letters.","Nazwa musi zawierać co najmniej 3 litery."
-"New Luma Yoga Collection","Nowa kolekcja"
-"New password *","Nowe hasło *"
-"Newsletter","Newsletter"
-"No orders yet","Brak zamówień"
-"No products found!","Brak produktów!"
-"No results were found.","Brak wyników wyszukiwania."
-"Open menu","Otwórz menu"
-"Open microcart","Otwórz mini koszyk"
-"Open my account","Otwórz moje konto"
-"Open search panel","Otwórz wyszukiwanie"
-"Open wishlist","Otwórz listę życzeń"
-"Order #{id}","Zamówienie #{id}"
-"Order ID","ID zamówienia"
-"Order Summary","Podsumowanie zamówienia"
-"Order confirmation","Potwierdzenie zamówienia"
-"Order informations","Informcje dotyczące zamówienia"
-"Orders","Zamówienia"
-"Password *","Hasło *"
-"Passwords must be identical","Hasła muszą być identyczne"
-"Passwords must be identical.","Hasła muszą być identyczne."
-"Payment method","Metoda płatności"
-"Payment","Płatność"
-"Personal Details","Dane osobowe"
-"Phone Number","Numer telefonu"
-"Phone number may be needed by carrier","Numer telefonu może być wymagany przez kuriera"
-"Place the order","Złóż zamówienie"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","Zmień kryteria wyszukiwania i spróbuj ponownie. Jeśli nadal nie znajdziesz niczego istotnego, odwiedź stronę główną i wypróbuj niektóre z naszych bestsellerów!"
-"Please check if all data are correct","Sprawdź czy wszystkie dane są poprawne"
-"Please confirm order you placed when you was offline","Potwierdź zamówienie, które złożyłeś będąc w trybie offline"
-"Please provide valid e-mail address.","Podaj prawidłowy adres e-mail."
-"Price {variant}","Cena {variant}"
-"Price","Cena"
-"Privacy policy","Polityka prywatności"
-"Product Name","Nazwa produktu"
-"Product details","Szczegóły produktu"
-"Purchase","Zakup"
-"Qty","Ilość"
-"Quantity","Ilość"
-"Register an account","Utwórz konto"
-"Register","Zarejestruj"
-"Remake order","Ponów zamówienie"
-"Remember me","Zapamiętaj mnie"
-"Remove from compare","Usuń z porównania"
-"Remove","Usuń"
-"Repeat new password *","Powtórz nowe hasło *"
-"Repeat password *","Powtórz hasło *"
-"Reset password","Zresetuj hasło"
-"Resetting the password ... ","Resetowanie hasła ... "
-"Return policy","Zasady dotyczące zwrotów"
-"Return to shopping","Kontynuuj zakupy"
-"Returns","Zwroty"
-"Review order","Przegląd zamówienia"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","Bezpieczeństwo"
-"Sale","Wyprzedaż"
-"Search","Szukaj"
-"Searched term should consist of at least 3 characters.","Szukana fraza powinna składać się z co najmniej 3 znaków."
-"See details","Szczegóły"
-"See our bestsellers","Zobacz bestsellery"
-"Select color ","Wybierz kolor "
-"Select size {variant}","Wybierz rozmiar {variant}"
-"Ship to my default address","Wyślij na mój domyślny adres"
-"Shipping address","Adres wysyłki"
-"Shipping method","Metoda wysyłki"
-"Shipping","Wysyłka"
-"Shopping cart","Koszyk"
-"Shopping summary","Podsumowanie zamówienia"
-"Show subcategories","Pokaż podkategorie"
-"Sign up to our newsletter and receive a coupon for 10% off!","Zapisz się do naszego newslettera!"
-"Similar products","Podobne produkty"
-"Size guide","Tabela rozmiarów"
-"State / Province","Województwo"
-"Status","Status"
-"Store locator","Znajdź sklep"
-"Street name *","Ulica *"
-"Street name","Ulica"
-"Subscribe to the newsletter and receive a coupon for 10% off","Zapisz się do naszego newslettera i otrzymaj 10% rabatu!"
-"Subscribe","Zapisz"
-"Subtotal","Suma częściowa"
-"Tax ID *","NIP *"
-"Tax ID must have at least 3 letters.","Identyfikator podatkowy musi składać się z co najmniej 3 liter."
-"Tax identification number *","NIP *"
-"Tax identification number must have at least 3 letters.","Numer identyfikacji podatkowej musi zawierać co najmniej 3 litery."
-"Tax","Podatek"
-"Terms and conditions","Regulamin"
-"The new account will be created with the purchase. You will receive details on e-mail.","Nowe konto zostanie utworzone wraz z zamówieniem. Szczegóły zostaną wysłane na adres email."
-"Toggle password visibility","Przełącz widoczność hasła"
-"Track my order","Śledź moje zamówienie"
-"Type your opinion","Twoja opinia"
-"Type","Typ"
-"Unfortunately we can't find the page you are looking for.","Niestety nie możemy znaleźć strony."
-"United States","Stany Zjednoczone"
-"Update my preferences","Zachowaj moje ustawienia"
-"Update my profile","Zachowaj zmiany"
-"Update my shipping details","Zapisz zmiany"
-"Use my billing data","Użyj moich danych rozliczeniowych"
-"Use my company's address details","Użyj danych adresowych mojej firmy"
-"Use my company's address details","Użyj danych adresowych mojej firmy"
-"Value","Wartość"
-"View all","Pokaż wszystkie"
-"View order","Wyświetl zamówienie"
-"We can't find the page","Nie ma takiej strony"
-"We found other products you might like","Może Zainteresuje Cię również"
-"We use cookies to give you the best shopping experience.","Używamy plików cookies."
-"We will send you details regarding the order","Wyślemy Ci szczegóły Twojego zamówienia"
-"We will send you the invoice to given e-mail address","Wyślemy Ci fakturę na podany adres e-mail"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Instrukcja dotycząca resetowania hasła została wysłana na Twój adres email. Sprawdź skrzynkę odbiorczą."
-"What we can improve?","Co możemy udoskonalić?"
-"Wishlist","Lista życzeń"
-"Women fashion","Moda damska"
-"You are logged in as {firstname}","Jesteś zalogowany jako {firstname}"
-"You are offline. Some features might not be available.","Jesteś offline, niektóre funkcje są ograniczone"
-"You can also use","Możesz również użyć"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","Możesz zalogować się na swoje konto, używając wcześniej zdefiniowanego adresu e-mail i hasła. Na swoim koncie możesz edytować swoje dane profilu, sprawdzić historię transakcji, edytować subskrypcję. "
-"You have been successfully subscribed to our newsletter!","Zostałeś dodany do listy naszych subskrybentów!"
-"You have no items to compare.","Nie wybrałeś produktów do porównania."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","Z powodzeniem złożyłeś zamówienie. Możesz sprawdzić status swojego zamówienia, korzystając z opcji status przesyłki.Otrzymasz e-mail z potwierdzeniem zamówienia ze szczegółami zamówienia oraz linkiem do śledzenia jego postępu."
-"You must accept the terms and conditions.","Musisz zaakceptować regulamin."
-"You've entered an incorrect coupon code. Please try again.","Podałeś nieprawidłowy kod kuponu. Proszę spróbuj ponownie."
-"Your Account","Twoje konto"
-"Your feedback is important for us. Let us know what we could improve.","Twoja opinia jest dla nas ważna. Daj nam znać, co możemy poprawić."
-"Your purchase","Twój zakup"
-"Your shopping cart is empty.","Twój koszyk jest pusty."
-"Your wishlist is empty.","Twoje lista życzeń jest pusta."
-"Zip-code *","Kod pocztowy *"
-"Zip-code must have at least 3 letters.","Kod pocztowy musi zawierać co najmniej 3 litery."
-"Zip-code","Kod pocztowy"
-"a chat","a chat"
-"a contact page","strona kontaktu"
-"browse our catalog","przejrzyj nasze produkty"
-"color_filter","Kolor"
-"login to your account","zaloguj się na swoje konto"
-"or write to us through","lub napisz do nas przez"
-"or","lub"
-"price_filter","Cena"
-"register an account","utwórz konto"
-"return to log in","powrót do logowania"
-"search","szukaj"
-"size_filter","Rozmiar"
-"to find product you were looking for.","aby znaleźć produkt, który szukasz."
-"to find something beautiful for You!","znajdź coś dla siebie!"
diff --git a/src/themes/default/resource/i18n/pt-BR.csv b/src/themes/default/resource/i18n/pt-BR.csv
deleted file mode 100644
index d373ec48a..000000000
--- a/src/themes/default/resource/i18n/pt-BR.csv
+++ /dev/null
@@ -1,223 +0,0 @@
-"About us","Sobre nós"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Adicionar cupom de desconto"
-"Add discount code","Aplicar"
-"Add to cart","Adicionar ao carrinho"
-"Add to compare","Adicionar para Comparação"
-"Add to favorite","Adicionar aos Favoritos"
-"Author","Autor"
-"Authorization in progress ...","Autorização em progresso ..."
-"Back to login","Voltar para Login"
-"Back","Voltar"
-"Billing address","Endereço de Cobrança"
-"Cancel","Cancelar"
-"Change my password","Mudar minha senha"
-"City *","Cidade *"
-"City","Cidade"
-"Close","Fechar"
-"Company name *","Nome Fantasia *"
-"Contact us","Entre em contato"
-"Continue to payment","Continuar para Pagamento"
-"Continue to shipping","Continuar para entrega"
-"Copy address data from shipping","Copiar endereço dos dados de entrega"
-"Country *","País *"
-"Country","País"
-"Create a new account","Criar uma nova conta"
-"Current password *","Senha atual *"
-"Customer service","Serviço de Atendimento ao Cliente"
-"Date and time","Data e hora"
-"Delivery","Entrega"
-"Departments","Departamentos"
-"Discount code","Código de desconto"
-"Discount","Desconto"
-"Don't hesitate and","Não hesite e"
-"E-mail address *","Endereço de E-mail *"
-"Edit newsletter preferences","Editar preferências de newsletter"
-"Edit payment","Editar pagamento"
-"Edit personal details","Editar dados pessoais"
-"Edit shipping","Editar entrega"
-"Edit your profile","Editar seu perfil"
-"Edit your shipping details","Editar detalhes de entrega"
-"Edit","Editar"
-"Email address *","Endereço de E-mail *"
-"Email address","Endereço E-mail"
-"Enter your email to receive instructions on how to reset your password.","Informe um e-mail para receber as instruções de como recuperar sua senha."
-"Erin recommends","Erin recomenda"
-"Error while sending reset password e-mail","Ocorreu um erro ao enviar o e-mail de recuperação de senha"
-"Everything new","Tudo novo"
-"Field is required","Campo é obrigatório"
-"Filter","Filtro"
-"Filters","Filtros"
-"First name *","Nome *"
-"First name","Nome"
-"Forgot the password?","Esqueceu a senha?"
-"General agreement","Acordo geral"
-"Get inspired","Se inspire"
-"Go review the order","Ir para revisão do pedido"
-"Go to Facebook","Ir para Facebook"
-"Go to Instagram","Ir para Instagram"
-"Go to Pinterest","Ir para Pinterest"
-"Go to Youtube","Ir para Youtube"
-"Go to checkout","Finalizar Compra"
-"Help","Ajuda"
-"Home","Início"
-"House/Apartment number *","Número *"
-"House/Apartment number","Número"
-"I accept ","Eu aceito "
-"I accept terms and conditions","Eu aceito os termos e condições"
-"I agree to","Eu concordo com"
-"I have a company and want to receive an invoice for every order","Eu possuo uma empresa e quero receber uma fatura para todos os pedidos"
-"I want to create an account","Eu quero criar uma conta"
-"I want to create an account'","Eu quero criar uma conta'"
-"I want to generate an invoice for the company","Eu quero criar uma fatura para uma empresa"
-"I want to receive a newsletter, and agree to its terms","Eu quero receber novidades, e concordo com os termos"
-"If you need an assistance you can drop us a line on","Caso precise de alguma ajuda você pode nos deixar uma mensagem"
-"Items ordered","Itens do pedido"
-"Kidswear","Moda Infantil"
-"Last name *","Sobrenome *"
-"Last name","Sobrenome"
-"Legal notice","Aviso Legal"
-"Log in to your account","Logar na sua conta"
-"Log in","Entrar"
-"Login to your account","Logar para sua conta"
-"Logout","Sair"
-"Magazine","Revista"
-"Men's fashion","Moda Masculina"
-"My Account","Minha Conta"
-"My account","Minha conta"
-"My loyalty card","Meu cartão de fidelidade"
-"My newsletter","Minha newsletter"
-"My orders","Meus pedidos"
-"My product reviews","Minhas avaliações de produtos"
-"My profile","Meu perfil"
-"My shipping details","Meus detalhes de entrega"
-"Name must have at least 3 letters.","Nome deve ter ao menos 3 letras."
-"New Luma Yoga Collection","Nova Coleção Luma Yoga"
-"New password *","Nova senha *"
-"Newsletter","Newsletter"
-"No orders yet","Nenhum pedido ainda"
-"No products found!","Nenhum produto encontrado!"
-"No results were found.","Nenhum resultado foi encontrado."
-"Open menu","Abrir menu"
-"Open microcart","Abrir mini-carrinho"
-"Open my account","Abrir minha conta"
-"Open search panel","Abrir painel de busca"
-"Open wishlist","Abrir lista de desejos"
-"Order #{id}","Pedido #{id}"
-"Order ID","ID do Pedido"
-"Order Summary","Resumo do Pedido"
-"Order informations","Informação do Pedido"
-"Orders","Pedidos"
-"Password *","Senha *"
-"Passwords must be identical","Senhas precisam ser iguais"
-"Passwords must be identical.","Senhas devem ser idênticas."
-"Payment method","Método de pagamento"
-"Payment","Pagamento"
-"Personal Details","Detalhes Pessoais"
-"Phone Number","Telefone"
-"Phone number may be needed by carrier","Telefone pode ser necessário pelo método de entrega"
-"Place the order","Enviar o pedido"
-"Please check if all data are correct","Por favor verifique se todos os dados informados estão corretos "
-"Please provide valid e-mail address.","Por favor informe um e-mail válido."
-"Price {variant}","Preço {variant}"
-"Price","Preço"
-"Privacy policy","Política de Privacidade"
-"Product Name","Nome do Produto"
-"Product details","Detalhes do Produto"
-"Purchase","Compra"
-"Qty","Qtd"
-"Quantity","Quantidade"
-"Register an account","Criar uma conta"
-"Register","Criar uma conta"
-"Remake order","Refazer pedido"
-"Remember me","Lembrar de mim"
-"Remove from compare","Remover da comparação"
-"Remove","Remover"
-"Repeat new password *","Repetir senha *"
-"Repeat password *","Repetir senha *"
-"Reset password","Recuperação de senha"
-"Resetting the password ... ","Resetando sua senha... "
-"Return policy","Política de Devolução"
-"Return to shopping","Voltar para loja"
-"Returns","Retornar"
-"Review order","Revisão do Pedido"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","Seguro"
-"Sale","Promoção"
-"Search","Busca"
-"See details","Veja detalhes"
-"See our bestsellers","Ver os mais vendidos"
-"Select color ","Selecione uma cor "
-"Select size {variant}","Selecione o tamanho {variant}"
-"Ship to my default address","Enviar para meu endereço padrão"
-"Shipping address","Endereço de Entrega"
-"Shipping method","Método de Entrega"
-"Shipping","Entrega"
-"Shopping cart","Carrinho de compras"
-"Shopping summary","Resumo da compra"
-"Show subcategories","Mostrar subcategorias"
-"Sign up to our newsletter and receive a coupon for 10% off!","Cadastre-se na nossa newsletter e receba um cupom de 10% de desconto!"
-"Similar products","Produtos parecidos"
-"Size guide","Guia de Tamanho"
-"State / Province","Estado"
-"Status","Status"
-"Store locator","Localizador de lojas"
-"Street name *","Endereço *"
-"Street name","Endereço"
-"Subscribe to the newsletter and receive a coupon for 10% off","Inscreva-se em nossa Newsletter e receba um cupom para 10% desconto"
-"Subscribe","Inscreva"
-"Subtotal","Subtotal"
-"Tax ID *","CPF/CNPJ *"
-"Tax ID must have at least 3 letters.","CPF/CNPJ deve possuir ao menos 3 caracteres."
-"Tax identification number *","CPF/CNPJ *"
-"Tax identification number must have at least 3 letters.","CPF/CNPJ precisa ter ao menos 3 caracteres."
-"Tax","Imposto"
-"Terms and conditions","Termos e condições"
-"The new account will be created with the purchase. You will receive details on e-mail.","Sua nova conta será criada junto com o pedido. Você recebará os detalhes no e-mail."
-"Toggle password visibility","Alternar visibilidade da senha"
-"Track my order","Rastrear meu pedido"
-"Type","Tipo"
-"Unfortunately we can't find the page you are looking for.","Infelizmente não conseguimos encontrar a página que você está procurando."
-"Update my preferences","Atualizar minhas preferências"
-"Update my profile","Atualizar meu perfil"
-"Update my shipping details","atualizar meus detalhes de entrega"
-"Use my billing data","Usar meus dados de cobrança"
-"Use my company's address details","Usar dados de pessoa jurídica"
-"Use my company's address details","Utilizar meus dados de pessoa jurídica."
-"Value","Valor"
-"View all","Ver tudo"
-"View order","Ver pedido"
-"We can't find the page","Não encontramos a página"
-"We use cookies to give you the best shopping experience.","Nós utilizamos cookies para dar a você a melhor experiência de compra."
-"We will send you details regarding the order","Nós iremos enviar um e-mail para você com os detalhes do pedido"
-"We will send you the invoice to given e-mail address","Nós iremos enviar para você a fatura no e-mail informado "
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Nós enviamos um e-mail com instruções de como recuperar sua senha. Verifique sua Caixa de Entrada e clique no link."
-"Wishlist","Lista de Desejos"
-"Women fashion","Moda Feminina"
-"You are logged in as {firstname}","Você logou como {firstname}"
-"You are offline. Some features might not be available.","Você está sem Internet, algumas funcionalidades estão limitadas"
-"You can also use","Você també pode usar"
-"You have been successfully subscribed to our newsletter!","Você foi inscrito com sucesso em nossa Newsletter!"
-"You have no items to compare.","Você não possui itens para comparar."
-"You must accept the terms and conditions.","Você deve aceitar os termos e condições."
-"You've entered an incorrect coupon code. Please try again.","Você informou um código de cupom inválido. Por favor tente novamente."
-"Your shopping cart is empty.","Seu carrinho de compra está vazio."
-"Your wishlist is empty.","Sua lista de desejos está vazia."
-"Zip-code *","CEP *"
-"Zip-code must have at least 3 letters.","CEP precisa ter ao menos 3 letras."
-"Zip-code","CEP"
-"a chat","no chat"
-"a contact page","página de contato"
-"browse our catalog","visualize nosso catálogo"
-"color_filter","Cor"
-"login to your account","Entrar na sua conta"
-"or write to us through","ou escrever para nos pela"
-"or","ou"
-"price_filter","Preço"
-"register an account","criar uma conta"
-"return to log in","Voltar para tela de login"
-"search","a busca"
-"size_filter","Tamanho"
-"to find product you were looking for.","para achar o produto que você está procurando."
-"to find something beautiful for You!","para encontrar algo bonito para Você!"
diff --git a/src/themes/default/resource/i18n/ru-RU.csv b/src/themes/default/resource/i18n/ru-RU.csv
deleted file mode 100644
index fad6daff1..000000000
--- a/src/themes/default/resource/i18n/ru-RU.csv
+++ /dev/null
@@ -1,223 +0,0 @@
-"About us","О нас"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","Ввести скидочный код"
-"Add discount code","Применить"
-"Add to cart","Добавить в корзину"
-"Add to compare","Добавить для сравнения"
-"Add to favorite","Добавить в избранное"
-"Author","Автор"
-"Authorization in progress ...","Производится авторизация ..."
-"Back to login","Назад на авторизацию"
-"Back","Назад"
-"Billing address","Платежный адрес"
-"Cancel","Отмена"
-"Change my password","Изменить мой пароль"
-"City *","Город *"
-"City","Город"
-"Close","Закрыть"
-"Company name *","Название компании *"
-"Contact us","Контакты"
-"Continue to payment","Перейти к платежу"
-"Continue to shipping","Перейти к доставке"
-"Copy address data from shipping","Скопировать адресные данные из доставки"
-"Country *","Страна *"
-"Country","Страна"
-"Create a new account","Создать новую учетную запись"
-"Current password *","Текущий пароль *"
-"Customer service","Служба поддержки"
-"Date and time","Дата и время"
-"Delivery","Доставка"
-"Departments","Отделы"
-"Discount code","Скидочный код"
-"Discount","Скидка"
-"Don't hesitate and","Не раздумывайте и"
-"E-mail address *","Электронный адрес *"
-"Edit newsletter preferences","Изменить новостные предпочтения"
-"Edit payment","Изменить платеж"
-"Edit personal details","Изменить личную информацию"
-"Edit shipping","Изменить доставку"
-"Edit your profile","Изменить личный кабинет"
-"Edit your shipping details","Изменить детали доставки"
-"Edit","Изменить"
-"Email address *","Электронный адрес *"
-"Email address","Электронный адрес"
-"Enter your email to receive instructions on how to reset your password.","Введите свой электронный адрес для получения инструкции как переустановить свой пароль."
-"Erin recommends","Erin рекомендует"
-"Error while sending reset password e-mail","Произошла ошибка при отправке письма для переустановки пароля"
-"Everything new","Новинки"
-"Field is required","Обязательное поле"
-"Filter","Фильтр"
-"Filters","Фильтры"
-"First name *","Имя *"
-"First name","Имя"
-"Forgot the password?","Забыли пароль?"
-"General agreement","Генеральное соглашение"
-"Get inspired","Воодушевись"
-"Go review the order","Перейдите к обзору заказа"
-"Go to Facebook","Перейти в Facebook"
-"Go to Instagram","Перейти в Instagram"
-"Go to Pinterest","Перейти в Pinterest"
-"Go to Youtube","Перейти в Youtube"
-"Go to checkout","Перейти к оформлению заказа"
-"Help","Помощь"
-"Home","Главная"
-"House/Apartment number *","Дом/Номер квартиры *"
-"House/Apartment number","Дом/Номер квартиры"
-"I accept ","Я принимаю "
-"I accept terms and conditions","Я принимаю условия и положения"
-"I agree to","Я согласен с"
-"I have a company and want to receive an invoice for every order","У меня есть компания и я хотел бы получать счет по каждому заказу"
-"I want to create an account","Я хочу создать учетную запись"
-"I want to create an account'","Я хочу создать учетную запись'"
-"I want to generate an invoice for the company","Я хочу сгенерировать счет для компании"
-"I want to receive a newsletter, and agree to its terms","Я хочу получать новости, и принимаю условия и положения"
-"If you need an assistance you can drop us a line on","Если Вам нужна помощь, Вы можете написать нам в"
-"Items ordered","Заказанные товары"
-"Kidswear","Детская одежда"
-"Last name *","Фамилия *"
-"Last name","Фамилия"
-"Legal notice","Официальное уведомление"
-"Log in to your account","Войти под своей учетной записью"
-"Log in","Войти"
-"Login to your account","Войти под своей учетной записью"
-"Logout","Выйти"
-"Magazine","Журнал"
-"Men's fashion","Мужская одежда"
-"My Account","Личный кабинет"
-"My account","Личный кабинет"
-"My loyalty card","Моя дисконтная карта"
-"My newsletter","Мои новости"
-"My orders","Мои заказы"
-"My product reviews","Мои обзоры товаров"
-"My profile","Мой личный кабинет"
-"My shipping details","Мои детали доставки"
-"Name must have at least 3 letters.","Имя должно состоять из минимум 3 букв."
-"New Luma Yoga Collection","Новая коллекция Luma Yoga"
-"New password *","Новый пароль *"
-"Newsletter","Новости"
-"No orders yet","Заказов пока нет"
-"No products found!","Товары не найдены!"
-"No results were found.","Ничего не найдено."
-"Open menu","Открыть меню"
-"Open microcart","Открыть корзину"
-"Open my account","Открыть личный кабинет"
-"Open search panel","Открыть панель поиска"
-"Open wishlist","Открыть список пожеланий"
-"Order #{id}","Заказ #{id}"
-"Order ID","ID заказа"
-"Order Summary","Итог заказа"
-"Order informations","Информация о заказе"
-"Orders","Заказы"
-"Password *","Пароль *"
-"Passwords must be identical","Пароли должны быть идентичными"
-"Passwords must be identical.","Пароли должны быть идентичными."
-"Payment method","Способ оплаты"
-"Payment","Платеж"
-"Personal Details","Личная информация"
-"Phone Number","Номер телефона"
-"Phone number may be needed by carrier","Номер телефона может понадобиться курьеру"
-"Place the order","Оформить заказ"
-"Please check if all data are correct","Пожалуйста проверьте корректность всех данных"
-"Please provide valid e-mail address.","Пожалуйста укажите валидный электронный адрес."
-"Price {variant}","Цена {variant}"
-"Price","Цена"
-"Privacy policy","Политика конфиденциальности"
-"Product Name","Название товара"
-"Product details","Детали товара"
-"Purchase","Заказ"
-"Qty","К-во"
-"Quantity","Количество"
-"Register an account","Создать учетную запись"
-"Register","Регистрация"
-"Remake order","Повторить заказ"
-"Remember me","Запомнить меня"
-"Remove from compare","Удалить из страницы сравнения"
-"Remove","Удалить"
-"Repeat new password *","Повторите новый пароль *"
-"Repeat password *","Повторите пароль *"
-"Reset password","Переустановить пароль"
-"Resetting the password ... ","Пароль переустанавливается ... "
-"Return policy","Политика возврата"
-"Return to shopping","Вернуться в магазин"
-"Returns","Возвраты"
-"Review order","Обзор заказа"
-"SKU","Идентификационный номер товара"
-"SKU: {sku}","Идентификационный номер товара: {sku}"
-"Safety","Безопасность"
-"Sale","Распродажа"
-"Search","Поиск"
-"See details","Посмотреть детали"
-"See our bestsellers","Посмотрите наши бестселлеры"
-"Select color ","Выберите цвет "
-"Select size {variant}","Выберите размер {variant}"
-"Ship to my default address","Отправить на мой адрес по умолчанию"
-"Shipping address","Адрес доставки"
-"Shipping method","Способ доставки"
-"Shipping","Доставка"
-"Shopping cart","Корзина"
-"Shopping summary","Резюме покупок"
-"Show subcategories","Показать подкатегории"
-"Sign up to our newsletter and receive a coupon for 10% off!","Подпишитесь к нашим новостям и получите купон на 10% скидку!"
-"Similar products","Похожие товары"
-"Size guide", "Помощник по размерам"
-"State / Province","Область / Район"
-"Status","Статус"
-"Store locator","Расположение магазинов"
-"Street name *","Название улицы *"
-"Street name","Название улицы"
-"Subscribe to the newsletter and receive a coupon for 10% off","Подпишитесь к новостям и получите купон на 10% скидку"
-"Subscribe","Подписка"
-"Subtotal","Подитог"
-"Tax ID *","ИНН *"
-"Tax ID must have at least 3 letters.","ИНН должен состоять из минимум 3 символов."
-"Tax identification number *","Идентификационный номер налогоплательщика *"
-"Tax identification number must have at least 3 letters.","Идентификационный номер налогоплательщика должен состоять из минимум 3 символов."
-"Tax","Налог"
-"Terms and conditions","Условия и положения"
-"The new account will be created with the purchase. You will receive details on e-mail.","Новая учетная запись будет создана вместе с заказом. Детали будут высланы Вам на электронный адрес."
-"Toggle password visibility","Переключить видимость пароля"
-"Track my order","Отследить заказ"
-"Type","Тип"
-"Unfortunately we can't find the page you are looking for.","К сожалению, мы не смогли найти запрашиваемую Вами страницу."
-"Update my preferences","Обновить мои предпочтения"
-"Update my profile","Обновить личный кабинет"
-"Update my shipping details","Обновить детали доставки"
-"Use my billing data","Использовать мои платежные данные"
-"Use my company's address details","Использовать адресные данные моей компании"
-"Use my company's address details","Использовать адресные данные моей компании"
-"Value","Значение"
-"View all","Посмотреть все"
-"View order","Посмотреть заказ"
-"We can't find the page","Мы не смогли найти страницу"
-"We use cookies to give you the best shopping experience.","Для предоставления Вам лучшего сервиса мы используем cookies."
-"We will send you details regarding the order","Мы вышлем Вам детали заказа"
-"We will send you the invoice to given e-mail address","Мы вышлем Вам счет на указанный электронный адрес"
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","Мы выслали на Ваш электронный адрес инструкцию по переустановке пароля. Проверьте почту и следуйте указанной там ссылке."
-"Wishlist","Список пожеланий"
-"Women fashion","Женская одежда"
-"You are logged in as {firstname}","Вы авторизованы как {firstname}"
-"You are offline. Some features might not be available.","Отсутствует соединение с интернетом, некоторые возможности будут ограничены"
-"You can also use","Также Вы можете воспользоваться"
-"You have been successfully subscribed to our newsletter!","Вы успешно подписались к нашим новостям!"
-"You have no items to compare.","У Вас нет товаров для сравнения."
-"You must accept the terms and conditions.","Вам необходимо принять условия и положения."
-"You've entered an incorrect coupon code. Please try again.","Вы ввели неверный код купона. Пожалуйста попробуйте еще раз."
-"Your shopping cart is empty.","Ваша корзина пустая."
-"Your wishlist is empty.","Ваш список пожеланий пуст."
-"Zip-code *","Почтовый индекс *"
-"Zip-code must have at least 3 letters.","Почтовый индекс должен состоять из минимум 3 символов."
-"Zip-code","Почтовый индекс"
-"a chat","чат"
-"a contact page","страницу контактов"
-"browse our catalog","пролистайте наш каталог"
-"color_filter","Цвет"
-"login to your account","войти под своей учетной записью"
-"or write to us through","или написать нам через"
-"or","или"
-"price_filter","Цена"
-"register an account","создать учетную запись"
-"return to log in","вернуться для авторизации"
-"search","поиском"
-"size_filter","Размер"
-"to find product you were looking for.","чтобы найти искомый Вами товар."
-"to find something beautiful for You!","чтобы найти что-нибудь красивое для Вас!"
diff --git a/src/themes/default/resource/i18n/zh-cn.csv b/src/themes/default/resource/i18n/zh-cn.csv
deleted file mode 100644
index 7c4e7b64d..000000000
--- a/src/themes/default/resource/i18n/zh-cn.csv
+++ /dev/null
@@ -1,264 +0,0 @@
-"About us (Magento CMS)","关于我们(Magento CMS)"
-"About us","关于我们"
-"Accepts only alphabet characters.","Accepts only alphabet characters."
-"Add a discount code","A添加折扣代码"
-"Add to cart","加入购物车"
-"Add to compare","商品比较"
-"Add to favorite","收藏"
-"Allow notification about the order","允许有关订单的通知"
-"Apply","应用"
-"Author","作者"
-"Authorization in progress ...","正在验证身份 ..."
-"Back to login","返回登录"
-"Back","返回"
-"Billing address","发票地址"
-"Cancel","取消"
-"Cash on delivery","货到付款"
-"Change my password","修改密码"
-"Choose your country","请选择你的国家"
-"City *","城市 *"
-"City","城市"
-"Clear","清除"
-"Close","关闭"
-"Cms Page Sync","内容管理页面数据同步"
-"Company name *","公司名称 *"
-"Compare products","比较商品清单"
-"Confirm your order","订单确认"
-"Contact us","联系我们"
-"Continue to payment","继续付款"
-"Continue to shipping","继续发货"
-"Copy address data from shipping","从发货中复制地址数据"
-"Country *","国家 *"
-"Country","国家"
-"Create a new account","创建账号"
-"Current password *","当前密码 *"
-"Custom Cms Page","自定义内容管理页面"
-"Customer service","顾客服务"
-"DPD Courier","DPD Courier"
-"Date and time","日期和时间"
-"Delivery","Delivery"
-"Departments","部门"
-"Discount code","优惠码"
-"Discount","折扣"
-"Don't hesitate and","不要犹豫"
-"E-mail address *","电邮 *"
-"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","如有任何问题,建议我们如何改进产品或购物体验,请发送电子邮件至 demo@vuestorefront.io "
-"Edit newsletter preferences","编辑新闻稿首选项"
-"Edit payment","修改付款方式"
-"Edit personal details","编辑个人资料"
-"Edit shipping","修改运费"
-"Edit your profile","修改头像"
-"Edit your shipping details","修改送货明细"
-"Edit","编辑"
-"Email address","电邮"
-"English","English"
-"Enter your email to receive instructions on how to reset your password.","请输入邮箱,我们将发送邮件告诉你如何重置密码."
-"Error while sending reset password e-mail","发送重置邮件出错"
-"Everything new","最新上架"
-"Extension developers would like to thank you for placing an order!","拓展开发人员要感谢您下订单!"
-"Field is required","必填字段"
-"Filter","过滤"
-"Filters","过滤条件"
-"First name","姓"
-"Forgot the password?","忘记密码?"
-"General agreement","通用协议"
-"German","German"
-"Germany","Germany"
-"Get inspired","得到灵感"
-"Give a feedback","给出反馈"
-"Go review the order","返回查看订单"
-"Go to Facebook","Go to Facebook"
-"Go to Instagram","Go to Instagram"
-"Go to Pinterest","Go to Pinterest"
-"Go to Youtube","Go to Youtube"
-"Go to checkout","结算"
-"Help","帮助"
-"Home","主页"
-"House/Apartment number *","房子/公寓号码 *"
-"I accept ","我接受 "
-"I accept terms and conditions","我接受所有的条件和协议"
-"I agree to","我同意"
-"I have a company and want to receive an invoice for every order","我公司想要收到每张订单的发票"
-"I want to create an account'","我想创建帐户'"
-"I want to generate an invoice for the company","我想开公司发票"
-"I want to receive a newsletter, and agree to its terms","我同意相关条款,并订阅相关新闻"
-"If you need an assistance you can drop us a line on","如果您需要帮助,可以给我们打电话"
-"If you need an assistance you can drop us a line on","如果您需要帮助,可以给我们打电话"
-"Internal Server Error 500","内部服务器错误 500"
-"Italian","意大利"
-"Italy","意大利"
-"Items ordered","订购物品"
-"Kidswear","童装"
-"Last name","名"
-"Latest","最新"
-"Legal notice","法律申明"
-"Load more","更多"
-"Log in to your account","登录"
-"Log in","登录"
-"Login to your account","登录"
-"Logout","注销"
-"Magazine","杂志"
-"Men's fashion","男性潮流"
-"My Account","我的账户"
-"My Recently viewed products","最近浏览"
-"My account","我的账户"
-"My loyalty card","我的幸运卡"
-"My newsletter","我的新闻"
-"My orders","我的订单"
-"My product reviews","我的产品评论"
-"My profile","我的头像"
-"My shipping details","我的送货明细"
-"Name must have at least 3 letters.","名字至少3个字母."
-"New Luma Yoga Collection","新的Luma瑜伽系列"
-"New password *","新密码 *"
-"Newsletter","新闻列表"
-"No orders yet","还没订单"
-"No products found!","没找到商品!"
-"No products yet","还没有商品"
-"No results were found.","没有找到."
-"No reviews have been posted yet. Please don't hesitate to share Your opinion and write the first review!","还没有评论发布。 请不要犹豫,分享您的意见并撰写第一篇评论!"
-"Open menu","打开菜单"
-"Open microcart","打开微型购物车"
-"Open my account","打开我的帐户"
-"Open search panel","打开搜索面板"
-"Open wishlist","打开心愿单"
-"Order #{id}","订单 #{id}"
-"Order ID","订单编号"
-"Order Summary","订单摘要"
-"Order confirmation","订单确认"
-"Order informations","订单信息"
-"Orders","订单"
-"Password *","密码 *"
-"Password must have at least 8 letters.","密码至少为8个字符."
-"Passwords must be identical","密码必须相同"
-"Payment method","付款方式"
-"Payment","支付"
-"Personal Details","个人资料"
-"Phone Number","电话号码"
-"Phone number may be needed by carrier","配送需要电话号码"
-"Place the order","下订单"
-"Please change Your search criteria and try again. If still not finding anything relevant, please visit the Home page and try out some of our bestsellers!","请更改您的搜索条件,然后重试。 如果仍然没有找到任何相关内容,请访问主页并阅读一些畅销书!"
-"Please check if all data are correct","请检查所有数据是否正确"
-"Please confirm order you placed when you was offline","请确认您离线时的订单"
-"Please provide valid e-mail address.","请提供有效的电子邮件地址."
-"Price {variant}","价格 {variant}"
-"Price","价格"
-"Price: High to low","价格:从高到低"
-"Price: Low to high","价格: 从低到高"
-"Privacy policy","隐私政策"
-"Product Name","商品名称"
-"Product details","商品明细"
-"Purchase","购买"
-"Qty","数量"
-"Quantity","数量"
-"Register an account","注册"
-"Register","注册"
-"Remake order","重新订购"
-"Remember me","记住我的登录状态"
-"Remove from compare","移除比较"
-"Remove","移除"
-"Repeat new password *","重复输入密码 *"
-"Reset password","重置密码"
-"Resetting the password ... ","重置密码 ... "
-"Return policy","退货政策"
-"Return to shopping","返回购物"
-"Returns","退货"
-"Review order","查看订单"
-"SKU","SKU"
-"SKU: {sku}","SKU: {sku}"
-"Safety","安全"
-"Sale","销售"
-"Search","搜索"
-"See details","查看详细"
-"See our bestsellers","查看我们的畅销书"
-"Select No","否"
-"Select Yes","是"
-"Select color ","颜色"
-"Select size {variant}","尺码 {variant}"
-"Ship to my default address","发送到我的默认地址"
-"Shipping address","配送地址"
-"Shipping method","配送方式"
-"Shipping","配送"
-"Shopping cart","购物车"
-"Shopping summary","购物车摘要"
-"Show subcategories","显示子类别"
-"Sign up to our newsletter and receive a coupon for 10% off!","订阅我们的时事新闻并获得10%的优惠券!"
-"Similar products","相似商品"
-"Size guide","尺码向导"
-"Something went wrong ...","有些不对劲 ..."
-"Sort By","排序"
-"State / Province","州 / 省"
-"Status","状态"
-"Store locator","商店定位"
-"Street name *","街道名称 *"
-"Subscribe to the newsletter and receive a coupon for 10% off","订阅时事新闻并获得10%的优惠券"
-"Subscribe","订阅"
-"Subtotal","分类汇总"
-"Tax ID *","税码 *"
-"Tax ID must have at least 3 letters.","税号至少为3位."
-"Tax identification number *","税号 *"
-"Tax identification number must have at least 3 letters.","税号必须至少有3个字母."
-"Tax","Tax"
-"Terms and conditions","条款和条件"
-"The new account will be created with the purchase. You will receive details on e-mail.","购买时将创建新帐户。 您将收到有关电子邮件的详细信息。"
-"The server order id has been set to ","服务器订单ID已设置为 "
-"This product is out of stock.","该商品缺货."
-"Toggle password visibility","切换密码可见性"
-"Track my order","跟踪订单"
-"Type your opinion","输入你的意见"
-"Type","分类"
-"Unfortunately we can't find the page you are looking for.","很遗憾,我们找不到您要查找的页面."
-"United States","United States"
-"Update my preferences","更新我的参数"
-"Update my profile","更新头像"
-"Update my shipping details","更新送货明细"
-"Use my billing data","使用我的结算数据"
-"Use my company's address details","使用我公司的详细地址"
-"Use my company's address details","使用我公司的详细地址信息"
-"Value","值"
-"View all","查看"
-"View order","查看订单"
-"We can't find the page","我们找不到该页面"
-"We found other products you might like","猜你喜欢"
-"We use cookies to give you the best shopping experience.","我们使用cookies为您提供最佳的购物体验."
-"We will send you details regarding the order","我们会向您发送有关订单的详细信息"
-"We will send you the invoice to given e-mail address","我们将发票发送到你的邮箱"
-"We've noticed Internal Server Error while rendering this request.","我们在呈现此请求时注意到内部服务器错误."
-"We've sent password reset instructions to your email. Check your inbox and follow the link.","W已经将密码重置说明发送到您的电子邮箱。 请检查您的收件箱,并点击重置密码链接"
-"What we can improve?","我们可以改进什么?"
-"Wishlist","心愿单"
-"Women fashion","女性时尚"
-"You are logged in as {firstname}","你已登录 {firstname}"
-"You are offline. Some features might not be available.","您处于离线状态,部分功能有限"
-"You can also use","你也可以使用"
-"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","您可以使用之前定义的电子邮件和密码登录您的帐户。 在您的帐户中,您可以修改个人资料数据,检查交易历史记录,编辑订阅简报。"
-"You have been successfully subscribed to our newsletter!","你已成功订阅该新闻组!"
-"You have no items to compare.","您没有可比较的项目."
-"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","您已成功下订单。 您可以使用我们的投放状态功能查看订单状态。 您将收到订单确认电子邮件,其中包含订单的详细信息以及跟踪其进度的链接。"
-"You must accept the terms and conditions.","您必须接受条款和条件."
-"You've entered an incorrect coupon code. Please try again.","您输入了错误的优惠券代码。请再试一次。"
-"Your Account","你的账户"
-"Your feedback is important fo us. Let us know what we could improve.","您的反馈对我们很重要。 让我们知道我们可以改进什么."
-"Your purchase","你的购买"
-"Your shopping cart is empty.","购物车空空荡荡."
-"Your wishlist is empty.","你还没心仪的商品."
-"Zip-code *","邮编 *"
-"Zip-code must have at least 3 letters.","邮编至少3个字符."
-"a chat","聊天"
-"a contact page","联系页面"
-"browse our catalog","浏览我们的目录"
-"color","颜色"
-"color_filter","颜色"
-"erin_recommends_filter","Erin推荐"
-"login to your account","登录"
-"login","登录"
-"or write to us through","或写信给我们"
-"or","或者"
-"price_filter","价格"
-"register an account","注册"
-"return to log in","返回登录"
-"search","搜索"
-"size","尺码"
-"size_filter","尺码"
-"to find product you were looking for.","找到你想要的产品."
-"to find something beautiful for You!","为你寻找美好的东西!"
diff --git a/src/themes/default/resource/main-image.json b/src/themes/default/resource/main-image.json
deleted file mode 100644
index b156168d9..000000000
--- a/src/themes/default/resource/main-image.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "image": {
- "title": "Walk the walk.",
- "subtitle": "A fashion can become the prevailing style in behaviour or manifest the newest creations of designers, technologists, engineers, and design managers.",
- "image": "/assets/full_width_banner.jpg",
- "link": "/women/women-20"
- }
-}
diff --git a/src/themes/default/resource/promoted_offers.json b/src/themes/default/resource/promoted_offers.json
deleted file mode 100644
index d08d39eaf..000000000
--- a/src/themes/default/resource/promoted_offers.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "mainBanners": [
- {
- "title": "Office casual",
- "subtitle": "Collection",
- "image": "/assets/ban1.jpg",
- "link": "/women/women-20"
- }
- ],
- "smallBanners": [
- {
- "title": "Shine on",
- "subtitle": "Accessories",
- "image": "/assets/ban2.jpg",
- "link": "/men/men-11"
- },
- {
- "title": "Spring is coming",
- "subtitle": "Hats",
- "image": "/assets/ban3.jpg",
- "link": "/gear/gear-3"
- }
- ],
- "productBanners": [
- {
- "title": "Spring is coming",
- "subtitle": "Hats",
- "image": "/assets/ban3.jpg",
- "link": "/gear/gear-3"
- }
- ]
-}
diff --git a/src/themes/default/resource/slider.json b/src/themes/default/resource/slider.json
deleted file mode 100644
index 45beb6b38..000000000
--- a/src/themes/default/resource/slider.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "slides": [
- {
- "title": "Luma Yoga",
- "subtitle": "New collection",
- "button_text": "Shop now",
- "image": "/assets/slide_01.jpg",
- "link": "/women/women-20"
- },
- {
- "title": "Luma Fitness",
- "subtitle": "Collection",
- "button_text": "Shop now",
- "image": "/assets/slide_02.jpg",
- "link": "/men/men-11"
- },
- {
- "title": "Luma Fitness",
- "subtitle": "What's new",
- "button_text": "Shop now",
- "image": "/assets/slide_03.jpg",
- "link": "/training/training-9"
- }
- ],
- "total": "3"
-}
\ No newline at end of file
diff --git a/src/themes/default/router/index.js b/src/themes/default/router/index.js
deleted file mode 100644
index db52f1294..000000000
--- a/src/themes/default/router/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const Home = () => import(/* webpackChunkName: "vsf-home" */ 'theme/pages/Home.vue')
-const PageNotFound = () => import(/* webpackChunkName: "vsf-not-found" */ 'theme/pages/PageNotFound.vue')
-const ErrorPage = () => import(/* webpackChunkName: "vsf-error" */ 'theme/pages/Error.vue')
-const Product = () => import(/* webpackChunkName: "vsf-product" */ 'theme/pages/Product.vue')
-const Category = () => import(/* webpackChunkName: "vsf-category" */ 'theme/pages/Category.vue')
-const CmsPage = () => import(/* webpackChunkName: "vsf-cms" */ 'theme/pages/CmsPage.vue')
-const Checkout = () => import(/* webpackChunkName: "vsf-checkout" */ 'theme/pages/Checkout.vue')
-const Compare = () => import(/* webpackChunkName: "vsf-compare" */ 'theme/pages/Compare.vue')
-const MyAccount = () => import(/* webpackChunkName: "vsf-my-account" */ 'theme/pages/MyAccount.vue')
-const Static = () => import(/* webpackChunkName: "vsf-static" */ 'theme/pages/Static.vue')
-
-let routes = [
- { name: 'home', path: '/', component: Home, alias: '/pwa.html' },
- { name: 'checkout', path: '/checkout', component: Checkout },
- { name: 'legal', path: '/legal', component: Static, props: { page: 'lorem', title: 'Legal Notice' }, meta: { title: 'Legal Notice', description: 'Legal Notice - example of description usage' } },
- { name: 'privacy', path: '/privacy', component: Static, props: { page: 'lorem', title: 'Privacy' } },
- { name: 'magazine', path: '/magazine', component: Static, props: { page: 'lorem', title: 'Magazine' } },
- { name: 'sale', path: '/sale', component: Static, props: { page: 'lorem', title: 'Sale' } },
- { name: 'order-tracking', path: '/order-tracking', component: Static, props: { page: 'lorem', title: 'Track my Order' } },
- { name: 'my-account', path: '/my-account', component: MyAccount },
- { name: 'my-shipping-details', path: '/my-account/shipping-details', component: MyAccount, props: { activeBlock: 'MyShippingDetails' } },
- { name: 'my-newsletter', path: '/my-account/newsletter', component: MyAccount, props: { activeBlock: 'MyNewsletter' } },
- { name: 'my-orders', path: '/my-account/orders', component: MyAccount, props: { activeBlock: 'MyOrders' } },
- { name: 'my-order', path: '/my-account/orders/:orderId', component: MyAccount, props: { activeBlock: 'MyOrder' } },
- { name: 'my-recently-viewed', path: '/my-account/recently-viewed', component: MyAccount, props: { activeBlock: 'MyRecentlyViewed' } },
- { name: 'about-us', path: '/about-us', component: Static, props: { page: 'lorem', title: 'About us' } },
- { name: 'customer-service', path: '/customer-service', component: Static, props: { page: 'lorem', title: 'Customer service' } },
- { name: 'store-locator', path: '/store-locator', component: Static, props: { page: 'lorem', title: 'Store locator' } },
- { name: 'size-guide', path: '/size-guide', component: Static, props: { page: 'lorem', title: 'Size guide' } },
- { name: 'gift-card', path: '/gift-card', component: Static, props: { page: 'lorem', title: 'Gift card' } },
- { name: 'delivery', path: '/delivery', component: Static, props: { page: 'lorem', title: 'Delivery' } },
- { name: 'returns', path: '/returns', component: Static, props: { page: 'lorem', title: 'Returns policy' } },
- { name: 'order-from-catalog', path: '/order-from-catalog', component: Static, props: { page: 'lorem', title: 'Order from catalog' } },
- { name: 'contact', path: '/contact', component: Static, props: { page: 'contact', title: 'Contact' } },
- { name: 'compare', path: '/compare', component: Compare, props: { title: 'Compare Products' } },
- { name: 'error', path: '/error', component: ErrorPage, meta: { layout: 'minimal' } },
- { name: 'virtual-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'bundle-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'simple-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'downloadable-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'grouped-product', path: '/p/:parentSku/:slug', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'configurable-product', path: '/p/:parentSku/:slug/:childSku', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'product', path: '/p/:parentSku/:slug/:childSku', component: Product }, // :sku param can be marked as optional with ":sku?" (https://github.com/vuejs/vue-router/blob/dev/examples/route-matching/app.js#L16), but it requires a lot of work to adjust the rest of the site
- { name: 'category', path: '/c/:slug', component: Category },
- { name: 'cms-page', path: '/i/:slug', component: CmsPage },
- { name: 'page-not-found', path: '*', component: PageNotFound }
-]
-
-export default routes
diff --git a/src/themes/default/service-worker/index.js b/src/themes/default/service-worker/index.js
deleted file mode 100644
index d77566ec1..000000000
--- a/src/themes/default/service-worker/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-Service worker extension
-
-Add your own Service worker code here - for example using sw-toolbox library:
-
-toolbox.router.get("/", toolbox.cacheFirst, {});
-toolbox.router.get("/catalog", toolbox.fastest, {});
-
-The code will be merged with default Service Worker
-*/
diff --git a/src/themes/default/store/cart/actions.ts b/src/themes/default/store/cart/actions.ts
deleted file mode 100644
index ccc3f7ca5..000000000
--- a/src/themes/default/store/cart/actions.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as types from './mutation-types'
-import getCurrentConfigurationFromTotals from './helpers/getCurrentConfigurationFromTotals'
-
-const actions = {
- async configureProduct (context, { product }) {
- if (product.type_id === 'simple') {
- const configuration = getCurrentConfigurationFromTotals(product)
- const parentProduct = await context.dispatch('product/findConfigurableParent', { product, configuration }, { root: true })
- return context.dispatch('cart/updateItem', { product: parentProduct }, { root: true })
- }
-
- return Promise.resolve()
- },
- openEditMode (context, { product, selectedOptions }) {
- context.commit(types.CART_OPEN_EDIT_MODE, { productId: product.id, qty: product.qty, selectedOptions })
- },
- editModeSetFilters ({ commit }, { filterOptions }) {
- commit(types.CART_EDIT_MODE_SET_FILTERS, { filterOptions })
- },
- editModeSetQty ({ commit }, { qty }) {
- commit(types.CART_EDIT_QTY, { qty })
- },
- closeEditMode ({ commit }) {
- commit(types.CART_CLOSE_EDIT_MODE)
- }
-}
-
-export default actions
diff --git a/src/themes/default/store/cart/getters.ts b/src/themes/default/store/cart/getters.ts
deleted file mode 100644
index 6e95a3127..000000000
--- a/src/themes/default/store/cart/getters.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { GetterTree } from 'vuex'
-import RootState from '@vue-storefront/core/types/RootState'
-import CartState from './types/CartState'
-
-const getters: GetterTree = {
- isEditMode (state) {
- return state.editMode !== null
- },
- getEditingProductId (state, getters) {
- return getters.isEditMode && state.editMode.productId
- },
- getSelectedOptions (state, getters) {
- return getters.isEditMode && state.editMode.selectedOptions
- },
- getEditingQty (state, getters) {
- return getters.isEditMode && state.editMode.qty
- }
-}
-
-export default getters
diff --git a/src/themes/default/store/cart/helpers/getCurrentConfigurationFromTotals.ts b/src/themes/default/store/cart/helpers/getCurrentConfigurationFromTotals.ts
deleted file mode 100644
index ac0e57935..000000000
--- a/src/themes/default/store/cart/helpers/getCurrentConfigurationFromTotals.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-const getCurrentConfigurationFromTotals = (product) => {
- const { options } = product.totals
- const colorLabel = options.find(opt => opt.label === 'Color').value
- const sizeLabel = options.find(opt => opt.label === 'Size').value
-
- return {
- size: { id: product.size, attribute_code: 'size', label: sizeLabel },
- color: { id: product.color, attribute_code: 'color', label: colorLabel }
- }
-}
-
-export default getCurrentConfigurationFromTotals
diff --git a/src/themes/default/store/cart/index.ts b/src/themes/default/store/cart/index.ts
deleted file mode 100644
index e6ca2c7f6..000000000
--- a/src/themes/default/store/cart/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Module } from 'vuex'
-import getters from './getters'
-import actions from './actions'
-import mutations from './mutations'
-import CartState from './types/CartState'
-
-export const module: Module = {
- namespaced: true,
- state: {
- editMode: null
- },
- getters,
- actions,
- mutations
-}
diff --git a/src/themes/default/store/cart/mutation-types.ts b/src/themes/default/store/cart/mutation-types.ts
deleted file mode 100644
index 20185d7ea..000000000
--- a/src/themes/default/store/cart/mutation-types.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export const SN_CART = 'cart'
-export const CART_OPEN_EDIT_MODE = SN_CART + '/CART_OPEN_EDIT_MODE'
-export const CART_EDIT_MODE_SET_FILTERS = SN_CART + '/CART_EDIT_MODE_SET_FILTERS'
-export const CART_EDIT_QTY = SN_CART + '/CART_EDIT_QTY'
-export const CART_CLOSE_EDIT_MODE = SN_CART + '/CART_CLOSE_EDIT_MODE'
diff --git a/src/themes/default/store/cart/mutations.ts b/src/themes/default/store/cart/mutations.ts
deleted file mode 100644
index 60c1db483..000000000
--- a/src/themes/default/store/cart/mutations.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as types from './mutation-types'
-import { MutationTree } from 'vuex'
-import CartState from './types/CartState'
-
-const mutations: MutationTree = {
- [types.CART_OPEN_EDIT_MODE] (state, { productId, selectedOptions, qty }) {
- state.editMode = { productId, selectedOptions, qty }
- },
- [types.CART_EDIT_MODE_SET_FILTERS] (state, { filterOptions }) {
- state.editMode.selectedOptions[filterOptions.type] = filterOptions
- },
- [types.CART_EDIT_QTY] (state, { qty }) {
- state.editMode.qty = qty
- },
- [types.CART_CLOSE_EDIT_MODE] (state) {
- state.editMode = null
- }
-}
-
-export default mutations
diff --git a/src/themes/default/store/cart/types/CartState.ts b/src/themes/default/store/cart/types/CartState.ts
deleted file mode 100644
index 80bc71b6a..000000000
--- a/src/themes/default/store/cart/types/CartState.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import EditMode from './EditMode'
-
-export default interface CartState {
- editMode: EditMode | null
-}
diff --git a/src/themes/default/store/cart/types/EditMode.ts b/src/themes/default/store/cart/types/EditMode.ts
deleted file mode 100644
index 34c7d651a..000000000
--- a/src/themes/default/store/cart/types/EditMode.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-
-export default interface EditMode {
- productId: number | string,
- selectedOptions: Record,
- qty: number
-}
diff --git a/src/themes/default/store/claims.ts b/src/themes/default/store/claims.ts
deleted file mode 100644
index d0c47bcdb..000000000
--- a/src/themes/default/store/claims.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { StorageManager } from '@vue-storefront/core/lib/storage-manager'
-import { Logger } from '@vue-storefront/core/lib/logger'
-
-export const claimsStore = {
- namespaced: true,
- actions: {
- set (context, { claimCode, value, description }) {
- const claimCollection = StorageManager.get('claims')
- claimCollection.setItem(claimCode, {
- code: claimCode,
- created_at: new Date(),
- value: value,
- description: description
- }).catch((reason) => {
- Logger.error(reason) // it doesn't work on SSR
- })
- },
-
- unset (context, { claimCode }) {
- const claimCollection = StorageManager.get('claims')
- claimCollection.removeItem(claimCode).catch((reason) => {
- Logger.error(reason) // it doesn't work on SSR
- })
- },
-
- check (context, { claimCode }) {
- const claimCollection = StorageManager.get('claims')
- return claimCollection.getItem(claimCode).catch((reason) => {
- Logger.error(reason) // it doesn't work on SSR
- })
- }
- }
-}
diff --git a/src/themes/default/store/homepage.ts b/src/themes/default/store/homepage.ts
deleted file mode 100644
index 5607cec17..000000000
--- a/src/themes/default/store/homepage.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { prepareQuery } from '@vue-storefront/core/modules/catalog/queries/common'
-
-export const homepageStore = {
- namespaced: true,
- state: {
- new_collection: [],
- bestsellers: []
- },
- actions: {
- async fetchNewCollection ({ commit, dispatch }) {
- const newProductsQuery = prepareQuery({ queryConfig: 'newProducts' })
-
- const newProductsResult = await dispatch('product/list', {
- query: newProductsQuery,
- size: 8,
- sort: 'created_at:desc'
- }, { root: true })
- const configuredProducts = await dispatch(
- 'category-next/configureProducts',
- { products: newProductsResult.items
- }, { root: true })
- commit('SET_NEW_COLLECTION', configuredProducts)
- },
- async loadBestsellers ({ commit, dispatch }) {
- const response = await dispatch('product/list', {
- query: prepareQuery({ queryConfig: 'bestSellers' }),
- size: 8,
- sort: 'created_at:desc'
- }, { root: true })
-
- commit('SET_BESTSELLERS', response.items)
- }
- },
- mutations: {
- SET_NEW_COLLECTION (state, products) {
- state.new_collection = products || []
- },
- SET_BESTSELLERS (state, bestsellers) {
- state.bestsellers = bestsellers
- }
- },
- getters: {
- getEverythingNewCollection (state) {
- return state.new_collection
- },
- getBestsellers (state) {
- return state.bestsellers
- }
- }
-}
diff --git a/src/themes/default/store/promoted-offers.ts b/src/themes/default/store/promoted-offers.ts
deleted file mode 100644
index c83bba6e2..000000000
--- a/src/themes/default/store/promoted-offers.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Logger } from '@vue-storefront/core/lib/logger'
-
-export default interface PromotedOffersState {
- banners: {
- mainBanners: any[],
- smallBanners: any[],
- productBanners: any[]
- },
- headImage: Record
-}
-
-export const promotedStore = {
- namespaced: true,
- state: {
- banners: {
- mainBanners: [],
- smallBanners: [],
- productBanners: []
- },
- headImage: null
- },
- getters: {
- getPromotedOffers: state => {
- return state.banners
- },
- getHeadImage: state => state.headImage
- },
- actions: {
- async updatePromotedOffers ({ commit, rootState }, data) {
- let promotedBannersResource = rootState.storeView && rootState.storeView.storeCode ? `banners/${rootState.storeView.storeCode}_promoted_offers` : `promoted_offers`
- try {
- // Workaround to get jest --watch to work so don't change the import sting to a template string
- const promotedOffersModule = await import(/* webpackChunkName: "vsf-promoted-offers-[request]" */ 'theme/resource/' + promotedBannersResource + '.json')
- commit('updatePromotedOffers', promotedOffersModule)
- } catch (err) {
- Logger.debug('Unable to load promotedOffers' + err)()
- }
- },
- async updateHeadImage ({ commit, rootState }, data) {
- let mainImageResource = rootState.storeView && rootState.storeView.storeCode ? `banners/${rootState.storeView.storeCode}_main-image` : `main-image`
- try {
- // Workaround to get jest --watch to work so don't change the import sting to a template string
- const imageModule = await import(/* webpackChunkName: "vsf-head-img-[request]" */ 'theme/resource/' + mainImageResource + '.json')
- commit('SET_HEAD_IMAGE', imageModule.image)
- } catch (err) {
- Logger.debug('Unable to load headImage' + err)()
- }
- }
- },
- mutations: {
- updatePromotedOffers (state, data) {
- state.banners = data
- },
- SET_HEAD_IMAGE (state, headImage) {
- state.headImage = headImage
- }
- }
-}
diff --git a/src/themes/default/store/ui.ts b/src/themes/default/store/ui.ts
deleted file mode 100644
index a92769c46..000000000
--- a/src/themes/default/store/ui.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-export const uiStore = {
- namespaced: true,
- state: {
- sidebar: false,
- microcart: false,
- wishlist: false,
- searchpanel: false,
- newsletterPopup: false,
- overlay: false,
- loader: false,
- authElem: 'login',
- checkoutMode: false,
- openMyAccount: false,
- submenu: {
- depth: false,
- path: []
- }
- },
- mutations: {
- setCheckoutMode (state, action) {
- state.checkoutMode = action === true
- },
- setMicrocart (state, action) {
- state.microcart = action === true
- state.overlay = action === true
- },
- setSidebar (state, action) {
- state.sidebar = action === true
- state.overlay = action === true
- },
- setSubmenu (state, { id, depth }) {
- if (id) {
- state.submenu.path.push(id)
- } else if (state.submenu.path.length) {
- setTimeout(() => {
- state.submenu.path.pop()
- }, 300)
- }
- state.submenu.depth = state.submenu.depth > 0 && depth
- },
- setSearchpanel (state, action) {
- state.searchpanel = action === true
- state.overlay = action === true
- },
- setWishlist (state, action) {
- state.wishlist = action === true
- state.overlay = action === true
- },
- setOverlay (state, action) {
- state.overlay = action === true
- },
- setLoader (state, action) {
- state.loader = action === true
- },
- setAuthElem (state, action) {
- state.authElem = action
- }
- },
- actions: {
- toggleMicrocart ({ commit, state }) {
- commit('setMicrocart', !state.microcart)
- },
- toggleWishlist ({ commit, state }) {
- commit('setWishlist', !state.wishlist)
- },
- closeMicrocart ({ commit, state }) {
- if (state.microcart) commit('setMicrocart', false)
- },
- closeWishlist ({ commit, state }) {
- if (state.wishlist) commit('setWishlist', false)
- }
- }
-}
diff --git a/src/themes/default/templates/index.basic.template.html b/src/themes/default/templates/index.basic.template.html
deleted file mode 100755
index c653630f3..000000000
--- a/src/themes/default/templates/index.basic.template.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- {{{ meta.inject().title.text() }}}
- {{{ meta.inject().meta.text() }}}
-
-
- {{{ meta.inject().link.text() }}}
- {{{ renderStyles() }}}
- {{{ output.appendHead() }}}
-
-
-
-
-
diff --git a/src/themes/default/templates/index.minimal.template.html b/src/themes/default/templates/index.minimal.template.html
deleted file mode 100755
index 40387d1b5..000000000
--- a/src/themes/default/templates/index.minimal.template.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- {{{ meta.inject().title.text() }}}
- {{{ meta.inject().meta.text() }}}
-
-
- {{{ meta.inject().link.text() }}}
- {{{ output.appendHead() }}}
-
-
-
-
-
diff --git a/src/themes/default/templates/index.template.html b/src/themes/default/templates/index.template.html
deleted file mode 100755
index ccc431bbd..000000000
--- a/src/themes/default/templates/index.template.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- {{{ meta.inject().title.text() }}}
- {{{ meta.inject().meta.text() }}}
-
-
- {{{ meta.inject().link.text() }}}
- {{{ meta.inject().script.text() }}}
- {{{ renderResourceHints() }}}
- {{{ renderStyles() }}}
- {{{ output.appendHead() }}}
-
-
-
- {{{ renderState() }}}
- {{{ renderScripts() }}}
-
-
diff --git a/src/themes/default/webpack.config.js b/src/themes/default/webpack.config.js
deleted file mode 100644
index 4aea92873..000000000
--- a/src/themes/default/webpack.config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// You can extend default webpack build here. Read more on docs: https://github.com/DivanteLtd/vue-storefront/blob/master/docs/guide/core-themes/webpack.md
-module.exports = function (config, { isClient, isDev }) {
- return config
-}
diff --git a/test/e2e/integration/add-to-cart.js b/test/e2e/integration/add-to-cart.js
index edb1ea89d..796e17ba9 100644
--- a/test/e2e/integration/add-to-cart.js
+++ b/test/e2e/integration/add-to-cart.js
@@ -1,26 +1,63 @@
+/* eslint no-undef: 0 */
describe('add to cart', () => {
it('verify that the configurable product is added to cart', () => {
- cy.visit('/p/WS01/gwyn-endurance-tee-1577/WS01')
- cy.get('[aria-label="Select color Green"]').click()
- cy.get('[aria-label="Select color Green"]').click()
- cy.get('[aria-label="Select size L"]').click()
- cy.get('[data-testid=addToCart]').click()
- cy.get('[data-testid=notificationMessage]').contains('Product has been added to the cart!')
- cy.get('[data-testid=openMicrocart]').click({ force: true })
- cy.get('[data-testid=productSku]').contains('WS01-L-Green')
- cy.get('[data-testid=closeMicrocart]').click()
- })
+ cy.visit('/p/WS01/gwyn-endurance-tee-1577/WS01');
+ cy.get('[data-testid=productName]').contains('Gwyn Endurance Tee')
+ cy.wait(1000)
+ cy.get('[aria-label="Select color Green"]').click();
+ cy.get('[aria-label="Select size L"]').click();
+ cy.get('[data-testid=addToCart]').click();
+ cy.get('[data-testid=notificationMessage]').should('contain',
+ 'Product has been added to the cart!');
+ cy.get('[data-testid=openMicrocart]').click({ force: true });
+ cy.get('[data-testid=productSku]').contains('WS01-L-Green');
+ cy.get('[data-testid=closeMicrocart]').click();
+ });
+
+ it('verify that quantity is updated', () => {
+ cy.get('[data-testid=addToCart]').click();
+ cy.get('[data-testid=notificationMessage]').should('contain',
+ 'Product quantity has been updated!'
+ );
+ cy.get('[data-testid=openMicrocart]').click({ force: true });
+ cy.get('input[type=number]').should('have.value', '2');
+ });
it('verify that the bundle product is added to cart', () => {
- cy.visit('/p/24-WG080/sprite-yoga-companion-kit-45')
- cy.get('#bundleOption_2').click({ force: true })
- cy.get('#bundleOptionQty_1').clear().type('2')
- cy.get('#bundleOption_6').click({ force: true })
- cy.get('#bundleOptionQty_4').clear().type('3')
- cy.get('[data-testid=addToCart]').click()
- cy.get('[data-testid=notificationMessage]').contains('Product has been added to the cart!')
- cy.get('[data-testid=openMicrocart]').click({ force: true })
- cy.get('[data-testid=productPrice]').contains('163.59')
- cy.get('[data-testid=closeMicrocart]').click()
- })
-})
+ cy.visit('/p/24-WG080/sprite-yoga-companion-kit-45');
+ cy.get('[data-testid=addToCart]').click();
+ cy.get('[data-testid=notificationMessage]', { timeout: 10000 }).contains(
+ 'This product is out of stock.'
+ );
+ cy.get('[data-testid=notificationAction1]').click();
+
+ cy.get('[data-testid=bundle-options]').children().as('allOptions')
+ cy.get('@allOptions').first().find('[data-testid=bundle-single-option]').its(1).as('ballOptions')
+ .find('input[type=radio]').click({ force: true })
+ cy.get('#bundleOptionQty_1')
+ .clear()
+ .type('2');
+ cy.get('@ballOptions').should(($ballOptions) => {
+ const text = $ballOptions.text()
+ expect(text).to.include('Sprite Stasis Ball 65')
+ })
+ cy.get('@allOptions').eq(-2).find('[data-testid=custom-single-option]').its(2).as('strapOptions')
+ .find('input[type=radio]').click({ force: true })
+ cy.get('@strapOptions').should(($strapOptions) => {
+ const text = $strapOptions.text()
+ expect(text).to.include('Sprite Yoga Strap 10 foot')
+ })
+ cy.get('#bundleOptionQty_4')
+ .clear()
+ .type('3');
+ cy.get('[data-testid=addToCart]').click();
+ cy.get('[data-testid=notificationMessage]').contains(
+ 'Product has been added to the cart!'
+ );
+ cy.get('[data-testid=notificationAction1]').click();
+ cy.get('[data-testid=openMicrocart]').click({ force: true });
+ cy.get('[class=prices]').contains('168.51');
+ cy.get('[data-testid="productLink"]').should('contain', 'Sprite Yoga Companion Kit')
+ cy.get('[data-testid=closeMicrocart]').click();
+ });
+});
diff --git a/test/e2e/integration/add-to-compare.js b/test/e2e/integration/add-to-compare.js
index 622f9392b..7590dc354 100644
--- a/test/e2e/integration/add-to-compare.js
+++ b/test/e2e/integration/add-to-compare.js
@@ -1,14 +1,14 @@
+/* eslint no-undef: 0 */
describe('add to compare', () => {
it('Two products should be added to comparison table', () => {
- cy.visit('/c/jackets-23')
- cy.get(':nth-child(1) > .product > .no-underline > .product-image > img').click()
- cy.get('.py40 > :nth-child(2) > .p0').click()
- cy.go('back')
- cy.get('[data-testid="productImage"]').eq(1).click()
- cy.get('[data-testid="addToCompare"]').click()
- cy.scrollTo(0, 0)
- cy.get('.compare-icon').click()
- cy.get('[data-testid="comparedProduct"]').eq(0)
- cy.get('[data-testid="comparedProduct"]').eq(1)
- })
-})
+ cy.visit('/c/jackets-23');
+ cy.get('[data-testid="productLink"]').eq(1).as('firstProduct')
+ cy.get('@firstProduct').click().should('have.attr', 'href').and('include', 'olivia-14-zip-light-jacket')
+ cy.get('[data-testid="addToCompare"]').click();
+ cy.go('back').wait(1000)
+ cy.get('[data-testid="addToCompare"]').eq(2).click()
+ cy.scrollTo('top');
+ cy.get('[data-testid="compare-list-icon"]').click();
+ cy.get('[data-testid="comparedProduct"]').should('have.length', 2)
+ });
+});
diff --git a/test/e2e/integration/basic-client-path.js b/test/e2e/integration/basic-client-path.js
index 0cab39e0d..d946fcbd6 100644
--- a/test/e2e/integration/basic-client-path.js
+++ b/test/e2e/integration/basic-client-path.js
@@ -1,24 +1,35 @@
+/* eslint no-undef: 0 */
describe('basic client path', () => {
it('should go through basic user flow', () => {
- cy.visit('/')
- cy.get('[data-testid=productLink]').eq(7).click()
- cy.get('[data-testid=addToCart]').click()
- cy.get('[data-testid=notificationAction2]').click()
- cy.get('[name=first-name]').type('Firstname')
- cy.get('[name=last-name]').type('Lastname')
- cy.get('[name=email-address]').type('e2e@vuestorefront.io')
- cy.get('[data-testid=personalDetailsSubmit]').click({ force: true })
- cy.get('[name=street-address]').type('Streetname')
- cy.get('[name=apartment-number]').type('28')
- cy.get('[name=city]').type('Wroclaw')
- cy.get('[name=state]').type('Lowersilesian')
- cy.get('[name=zip-code').type('50-000')
- cy.get('[name=countries]').select('PL')
- cy.get('[name=phone-number]').type('111 222 333')
- cy.get('[data-testid=shippingSubmit]').click({ force: true })
- cy.get('#sendToShippingAddressCheckbox').check({ force: true })
- cy.get('[data-testid=paymentSubmit]').click()
- cy.get('#acceptTermsCheckbox').check({ force: true })
- cy.get('[data-testid="errorMessage"]').should('not.exist')
- })
-})
+ cy.visit('/');
+ cy.get('[data-testid=productLink]')
+ .eq(7)
+ .click({ force: true });
+ cy.get('[data-testid=addToCart]').click({ force: true });
+ cy.get('[data-testid=notificationMessage]').contains(
+ 'This product is out of stock.'
+ );
+ cy.get('[data-testid=notificationAction1]').click();
+ cy.get('[aria-label="Select color Red"]').click();
+ cy.get('[aria-label="Select size S"]').click();
+ cy.wait(1000);
+ cy.get('[data-testid=addToCart]').click({ force: true });
+ cy.get('[data-testid=notificationAction2]').click();
+ cy.get('[name=first-name]').type('Firstname');
+ cy.get('[name=last-name]').type('Lastname');
+ cy.get('[name=email-address]').type('e2e@vuestorefront.io');
+ cy.get('[data-testid=personalDetailsSubmit]').click({ force: true });
+ cy.get('[name=street-address]').type('Streetname');
+ cy.get('[name=apartment-number]').type('28');
+ cy.get('[name=city]').type('Wroclaw');
+ cy.get('[name=state]').type('Lowersilesian');
+ cy.get('[name=zip-code]').type('50-000');
+ cy.get('[name="countries"]').select('PL');
+ cy.get('[name=phone-number]').type('111 222 333');
+ cy.get('[data-testid=shippingSubmit]').click({ force: true });
+ cy.get('#sendToShippingAddressCheckbox').check({ force: true });
+ cy.get('[data-testid=paymentSubmit]').click();
+ cy.get('#acceptTermsCheckbox').check({ force: true });
+ cy.get('[data-testid="errorMessage"]').should('not.exist');
+ });
+});
diff --git a/test/e2e/integration/category-page.js b/test/e2e/integration/category-page.js
index a35d77537..e72a3864e 100644
--- a/test/e2e/integration/category-page.js
+++ b/test/e2e/integration/category-page.js
@@ -1,21 +1,49 @@
+/* eslint no-undef: 0 */
describe('Category page', () => {
it('verification of filters in the Women category', () => {
- cy.visit('/')
- cy.get('[data-testid=menuButton]').click()
- cy.get('[data-testid=categoryButton]').contains('Women').click()
- cy.get('[data-testid=categoryLink][href="/c/women-20"]').click()
- cy.url().should('include', '/c/women-20')
- cy.get('[aria-label="Select color Red"]').first().click().should('have.class', 'active')
- cy.wait(500)
- cy.get('[data-testid=productImage]').first().should('have.attr', 'src').and('include', 'red')
- cy.get('[aria-label="Select size S"]').first().click().contains('S')
- cy.get('[aria-label="Select size 30"]').first().click().should('have.class', 'active')
- cy.get('[aria-label="Select size XL"]').first().click().contains('XL')
- cy.get('[data-testid=productImage]').should('have.length', 17)
- cy.get('[label="< $50"] > .price-selector').first().click().should('have.class', 'active')
- cy.get('[label="> $150"] > .price-selector').first().click().should('have.class', 'active')
- cy.get('[data-testid=noProductsInfo]').contains('No products found!')
- cy.get('[label="> $150"] > .price-selector').first().click()
- cy.get('[data-testid=noProductsInfo]').should('not.exist')
- })
-})
+ cy.visit('/');
+ cy.get('[data-testid=menuButton]').click();
+ cy.get('[data-testid=categoryButton]')
+ .contains('Women')
+ .click({ force: true });
+ cy.get('[data-testid=categoryLink][href="/women/women-20"]').click();
+ cy.url().should('include', '/women/women-20');
+ cy.get('[aria-label="Select color Red"]')
+ .first()
+ .click()
+ .should('have.class', 'active');
+ cy.wait(500);
+ cy.get('[data-testid=productImage]', { timeout: 10000 })
+ .first()
+ .find('img')
+ .eq(2)
+ .should('have.attr', 'src')
+ .and('include', 'red');
+ cy.get('[aria-label="Select size S"]')
+ .first()
+ .click()
+ .contains('S');
+ cy.get('[aria-label="Select size 30"]')
+ .first()
+ .click()
+ .should('have.class', 'active');
+ cy.get('[aria-label="Select size XL"]')
+ .first()
+ .click()
+ .contains('XL');
+ cy.get('[data-testid=productImage]').should('have.length', 2);
+ cy.get('[aria-label="Price < $50"]')
+ .first()
+ .click()
+ .should('have.class', 'active');
+ cy.get('[aria-label="Price > $150"]')
+ .first()
+ .click()
+ .should('have.class', 'active');
+ cy.get('[data-testid=noProductsInfo]').contains('No products found!');
+ cy.get('[aria-label="Price > $150"]')
+ .first()
+ .click();
+ cy.get('[data-testid=noProductsInfo]').should('not.exist');
+ });
+});
diff --git a/test/e2e/integration/checkout-page.js b/test/e2e/integration/checkout-page.js
index aea461e2c..6ae75d48f 100644
--- a/test/e2e/integration/checkout-page.js
+++ b/test/e2e/integration/checkout-page.js
@@ -1,26 +1,43 @@
+/* eslint no-undef: 0 */
describe('checkout page', () => {
it('Default shipping/billing address should be changed', () => {
- cy.visit('/')
- cy.get('[data-testid=accountButton]').click()
- cy.get('[name=email]').type('logintest@user.co')
- cy.get('[name=password]').type('123qwe!@#')
- cy.get('#remember').check({ force: true })
- cy.get('[data-testid=loginSubmit]').click()
- cy.get('[data-testid=notificationMessage]').contains('You are logged in!')
- cy.get('[data-testid=productLink]').eq(3).click()
- cy.get('[data-testid=addToCart]').click()
- cy.get('[data-testid=notificationAction2]').click()
- cy.get('[data-testid=personalDetailsSubmit]').click()
- cy.get('#shipToMyAddressCheckbox').check({ force: true })
- cy.get('[name=street-address]').clear().type('Dmowskiego street')
- cy.get('[name=apartment-number]').clear().type('17')
- cy.get('[name=city]').clear().type('Wroclaw')
- cy.get('[data-testid=shippingSubmit]').click()
- cy.get('[data-testid=shippingAddressSummary]').contains('Wroclaw')
- cy.get('#sendToShippingAddressCheckbox').check({ force: true })
- cy.get('[value=checkmo]').check()
- cy.get('[data-testid=paymentSubmit]').click()
- cy.get('#acceptTermsCheckbox').check({ force: true })
- cy.get('[data-testid="errorMessage"]').should('not.exist')
- })
-})
+ cy.visit('/');
+ cy.get('[data-testid=accountButton]').click();
+ cy.get('[name=email]').type('logintest@user.co');
+ cy.get('[name=password]').type('123qwe!@#');
+ cy.get('#remember').check({ force: true });
+ cy.get('[data-testid=loginSubmit]').click();
+ cy.wait(500);
+ cy.get('[data-testid=notificationMessage]').contains('You are logged in!');
+ cy.get('[data-testid=productLink]')
+ .eq(3)
+ .click();
+ cy.get('[aria-label="Select size L"]').click();
+ cy.get('[data-testid=addToCart]').click();
+ cy.get('[data-testid=notificationAction2]').click();
+ cy.get('[data-testid=personalDetailsSubmit]').click();
+ cy.get('#shipToMyAddressCheckbox').check({ force: true });
+ cy.get('[name=street-address]')
+ .clear()
+ .type('Dmowskiego street');
+ cy.get('[name=apartment-number]')
+ .clear()
+ .type('17');
+ cy.get('[name=city]')
+ .clear()
+ .type('Wroclaw');
+ cy.get('[name=zip-code]')
+ .clear()
+ .type('50-555');
+ cy.get('[data-testid=shippingSubmit]').click();
+ cy.get('[data-testid=shippingAddressSummary]').contains('Wroclaw');
+ cy.get('#sendToShippingAddressCheckbox').check({ force: true });
+ cy.get('[value=checkmo]').check();
+ cy.get('[data-testid=paymentSubmit]').click();
+ cy.get('#acceptTermsCheckbox').check({ force: true });
+ cy.get('[data-testid="errorMessage"]').should('not.exist');
+ cy.get('[data-testid=orderReviewSubmit]').click();
+ cy.url().should('include', '/checkout#orderReview');
+ cy.get('.category-title').contains('Order confirmation');
+ });
+});
diff --git a/test/e2e/integration/home-page.js b/test/e2e/integration/home-page.js
index 3aa5d9965..ce7da1951 100644
--- a/test/e2e/integration/home-page.js
+++ b/test/e2e/integration/home-page.js
@@ -1,17 +1,24 @@
-describe('home page', () => {
+/* eslint no-undef: 0 */
+describe('home page+privacy', () => {
it('verify the content of the homepage', () => {
- cy.visit('/')
- cy.get('[data-testid=mainSliderTitle]').first().contains('Luma Yoga')
- cy.get('.VueCarousel-dot-button').eq(1).click()
- cy.get('[data-testid=mainSliderTitle]').eq(1).contains('Luma Fitness')
- cy.get('.VueCarousel-dot-button').eq(2).click()
- cy.get('[data-testid=mainSliderSubtitle]').eq(2).contains("What's new")
- cy.get('[data-testid=closeCookieButton]').click()
- cy.get('[data-testid=bottomLinks').should('be.visible')
- cy.get('[data-testid=openNewsletterButton').click().contains('Subscribe')
- cy.get('[data-testid=subscribeSubmit]').contains('Subscribe')
- cy.get('[data-testid=closeModalButton]').click()
- cy.get('[data-testid=mainSliderTitle]').first().contains('Luma Yoga')
- cy.get('.new-collection').should('be.visible')
- })
-})
+ cy.visit('/');
+ cy.get('[data-testid=mainSliderTitle]')
+ .first()
+ .contains('Walk the walk.');
+ cy.get('.header')
+ .should('be.visible')
+ .find('[data-testid="accountButton"]');
+ cy.get('[title="See details"]').click();
+ cy.get('.static-content').contains('Luma Privacy Policy');
+ cy.get('[title="Home Page"]').click();
+ cy.get('[data-testid=closeCookieButton]').click();
+ cy.get('[data-testid=bottomLinks').should('be.visible');
+ cy.get('[data-testid=openNewsletterButton')
+ .click()
+ .contains('Subscribe');
+ cy.get('[data-testid=subscribeSubmit]').contains('Subscribe');
+ cy.get('[data-testid=closeModalButton]').click();
+ cy.get('.new-collection').should('be.visible');
+ cy.scrollTo(0, 0);
+ });
+});
diff --git a/test/e2e/integration/local-storage.js b/test/e2e/integration/local-storage.js
index 4a1000a31..f89f11076 100644
--- a/test/e2e/integration/local-storage.js
+++ b/test/e2e/integration/local-storage.js
@@ -1,27 +1,28 @@
+/* eslint no-undef: 0 */
describe('local-storage', () => {
it('Items added to the cart should be kept.', () => {
cy.visit('p/WS11/diva-gym-tee-1545/WS11')
+ cy.wait(1000)
cy.get('[aria-label="Select color Yellow"]').click().should('have.class', 'active')
cy.get('[aria-label="Select size S"]').click()
cy.get('[data-testid=variantsLabel]').first().contains('Yellow')
cy.get('[data-testid=variantsLabel]').last().contains('S')
cy.get('[data-testid=addToCart]').click()
cy.get('[data-testid=notificationMessage]').contains(
- 'Product has been added to the cart!'
- )
+ 'Product has been added to the cart!')
cy.get('[data-testid=openMicrocart]').click({ force: true })
cy.get('[data-testid=microcart]').should('be.visible')
- cy.reload()
+ cy.reload().wait(500)
cy.get('[data-testid=openMicrocart]').click({ force: true })
cy.get('[data-testid=productSku]').contains('WS11-S-Yellow')
- cy.get('[data-testid=productQty]').contains('1')
- cy.get('[data-testid=editButton').click()
- cy.get('[data-testid=productQtyInput]').clear().type(2).blur()
- cy.get('.summary').click()
+ cy.get('input[type=number]').should('have.value', '1');
+ cy.get('.col-xs > .actions > :nth-child(1)').click()
+ cy.get('#input_164').clear({ force: true }).type(2).blur()
+ cy.get('.pb15 > [data-testid=subscribeSubmit]').click()
cy.wait(500)
cy.reload()
cy.get('[data-testid=openMicrocart]').click({ force: true })
- cy.get('[data-testid=productQty]').contains('2')
+ cy.get('input[type=number]').should('have.value', '2');
cy.get('[data-testid=closeMicrocart]').click()
cy.get('[data-testid=minicartCount]').contains('2')
})
diff --git a/test/e2e/integration/login-path.js b/test/e2e/integration/login-path.js
index 0868ac080..8099799e4 100644
--- a/test/e2e/integration/login-path.js
+++ b/test/e2e/integration/login-path.js
@@ -1,10 +1,26 @@
+/* eslint no-undef: 0 */
describe('login path', () => {
- it('should login user', () => {
+ it('not existing user', () => {
cy.visit('/')
cy.get('[data-testid=accountButton]').click()
- cy.get('[name=email]').type('test@test.com')
+ cy.get('[name=email]').type('test@false')
+ cy.get('[name=password]').type('Password123')
+ cy.get('#remember').check({ force: true })
+ cy.get('[data-testid="errorMessage"]').should('exist').contains(
+ 'Please provide valid e-mail address.')
+ cy.get('[data-testid=loginSubmit]').click()
+ cy.get(`[data-testid=notificationMessage]`).should('exist').contains(
+ 'Please fix the validation errors')
+ })
+ it('successfull login', () => {
+ cy.visit('/')
+ cy.get('[data-testid=accountButton]').click()
+ cy.get('[name=email]').type('test@vue.co')
cy.get('[name=password]').type('Password123')
cy.get('#remember').check({ force: true })
cy.get('[data-testid="errorMessage"]').should('not.exist')
+ cy.get('[data-testid=loginSubmit]').click()
+ cy.get(`[data-testid=notificationMessage]`).should('exist').contains(
+ 'You are logged in!')
})
})
diff --git a/test/e2e/integration/product-page.js b/test/e2e/integration/product-page.js
index c0ccf90ee..02328db80 100644
--- a/test/e2e/integration/product-page.js
+++ b/test/e2e/integration/product-page.js
@@ -2,11 +2,11 @@
describe('product page', () => {
it('should verify that all information are visible', () => {
cy.visit('/p/WS01/gwyn-endurance-tee-1577/WS01')
- cy.get('[data-testid=productName]').contains('Gwyn Endurance Tee')
- cy.get('[data-testid=productGalleryImage]').first().should(
+ cy.get('[data-testid=productName]').contains('Gwyn Endurance Tee').wait(1000)
+ cy.get('.product-image__thumb').eq(1).should(
'have.attr',
'src',
- 'https://demo.vuestorefront.io/img/600/744/resize/w/s/ws01-black_main.jpg'
+ 'https://next.storefrontcloud.io/img/600/744/resize/w/s/ws01-black_main.jpg'
)
cy.get('[aria-label="Select color Green"]')
.click()
@@ -21,18 +21,18 @@ describe('product page', () => {
.should('have.class', 'active')
cy.get('[data-testid=variantsLabel]').last().contains('M')
cy.get('[aria-label="Select size L"]')
- .click()
- .should('have.class', 'active')
+ .click()
+ .should('have.class', 'active')
cy.get('[data-testid=variantsLabel]').last().contains('L')
})
it('should add and remove product from wishlist', () => {
- cy.get('[data-testid=addToWishlist]').click()
+ cy.get('[data-testid=addToWishlist]').first().click()
cy.get('[data-testid=notificationMessage]').contains(
'Product Gwyn Endurance Tee has been added to wishlist!'
)
cy.get('[data-testid=notificationAction1]').click()
- cy.get('[data-testid=addToWishlist]').click()
+ cy.get('[data-testid=addToWishlist]').first().click()
cy.get('[data-testid=notificationMessage]').contains(
'Product Gwyn Endurance Tee has been removed from wishlist!'
)
@@ -40,12 +40,12 @@ describe('product page', () => {
})
it('should add and remove product from compare', () => {
- cy.get('[data-testid=addToCompare]').click()
+ cy.get('[data-testid=addToCompare]').first().click()
cy.get('[data-testid=notificationMessage]').contains(
'Product Gwyn Endurance Tee has been added to the compare!'
)
cy.get('[data-testid=notificationAction1]').click()
- cy.get('[data-testid=addToCompare]').click()
+ cy.get('[data-testid=addToCompare]').first().click()
cy.get('[data-testid=notificationMessage]').contains(
'Product Gwyn Endurance Tee has been removed from compare!'
)
diff --git a/test/e2e/integration/search-results.js b/test/e2e/integration/search-results.js
index 90c3336ca..d2f2e6387 100644
--- a/test/e2e/integration/search-results.js
+++ b/test/e2e/integration/search-results.js
@@ -1,3 +1,4 @@
+/* eslint no-undef: 0 */
describe('Search results', () => {
it('verification of the search results', () => {
cy.visit('/')
@@ -8,11 +9,11 @@ describe('Search results', () => {
cy.get('[data-testid=searchPanel] [data-testid=productLink]').first().as('firstResult')
cy.get('@firstResult')
.should('be.visible')
- .find('[data-testid=productImage]')
+ .find('.product-image__thumb').eq(1)
.should(
'have.attr',
'src',
- 'https://demo.vuestorefront.io/img/310/300/resize/w/g/wg02-bk-0.jpg'
+ 'https://next.storefrontcloud.io/img/310/300/resize/w/g/wg02-bk-0.jpg'
)
cy.get('@firstResult').contains('Didi Sport Watch')
cy.get('#search').clear()
@@ -28,11 +29,11 @@ describe('Search results', () => {
cy.get('[data-testid=searchPanel] [data-testid=productLink]').first().as('firstResult')
cy.get('@firstResult')
.should('be.visible')
- .find('[data-testid=productImage]')
+ .find('.product-image__thumb').eq(1)
.should(
'have.attr',
'src',
- 'https://demo.vuestorefront.io/img/310/300/resize/w/g/wg02-bk-0.jpg'
+ 'https://next.storefrontcloud.io/img/310/300/resize/w/g/wg02-bk-0.jpg'
)
cy.get('@firstResult').contains('Didi Sport Watch')
cy.get('[data-testid=closeSearchPanel]').click()
diff --git a/test/e2e/support/commands.js b/test/e2e/support/commands.js
index c1f5a772e..31735e082 100644
--- a/test/e2e/support/commands.js
+++ b/test/e2e/support/commands.js
@@ -1,3 +1,23 @@
+/* eslint no-undef: 0 */
+
+Cypress.Commands.add('internalNavigation', (selector) => {
+ // create random numbre which will be checked after page change
+ cy.window().then(win => {
+ // add random number to window object
+ win.e2eInternalNavigationKey = 'test-key'
+ })
+ // change page by clicking link
+ cy.get(selector).click()
+ cy.window()
+ .then(win => {
+ if (!win.e2eInternalNavigationKey) {
+ cy.log(`After clicking on link ${selector} page was reloaded. Please use 'router-link' or 'nuxt-link' instead of native link`)
+ }
+ // check if page was reloaded, if it wasn't reloaded then it should pass, because window should be still the same
+ cy.wrap({ e2eInternalNavigationKey: win.e2eInternalNavigationKey }).should('have.property', 'e2eInternalNavigationKey', 'test-key')
+ cy.log(`Internal page navigation was successfull`)
+ })
+})
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
diff --git a/test/unit/jest.conf.js b/test/unit/jest.conf.js
index dadde07ca..538927ee9 100644
--- a/test/unit/jest.conf.js
+++ b/test/unit/jest.conf.js
@@ -32,6 +32,7 @@ module.exports = {
'^.+\\.(css|less)$': '/test/unit/cssStub.js'
},
transformIgnorePatterns: [
+ '(.*)storefront-query-builder/node_modules/(.*)',
'/node_modules/(?!lodash)',
'/node_modules/(?!lodash-es/.*)'
],
diff --git a/test/unit/package.json b/test/unit/package.json
index 32a0c11c3..39acef06c 100644
--- a/test/unit/package.json
+++ b/test/unit/package.json
@@ -1,5 +1,5 @@
{
"name": "@vue-storefront/unit-tests",
"private": true,
- "version": "1.11.4"
+ "version": "1.12.0"
}
diff --git a/test/unit/utils/index.ts b/test/unit/utils/index.ts
index 1f1ca3349..6c923a850 100644
--- a/test/unit/utils/index.ts
+++ b/test/unit/utils/index.ts
@@ -1,11 +1,12 @@
import Vuex from 'vuex'
-import {shallowMount, createLocalVue} from '@vue/test-utils'
-
-export const mountMixin = (
- component: object,
- mountOptions: object = {},
- template: string = ''
-) => {
+import { shallowMount, createLocalVue, Wrapper, ThisTypedShallowMountOptions } from '@vue/test-utils';
+import Vue, { ComponentOptions } from 'vue';
+
+export const mountMixin = (
+ component: ComponentOptions,
+ mountOptions: ThisTypedShallowMountOptions = {},
+ template = ''
+): Wrapper => {
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -19,12 +20,12 @@ export const mountMixin = (
})
};
-export const mountMixinWithStore = (
- component: object,
+export const mountMixinWithStore = (
+ component: ComponentOptions,
storeOptions: object = {},
- mountOptions: object = {},
- template: string = ''
-) => {
+ mountOptions: ThisTypedShallowMountOptions = {},
+ template = ''
+): Wrapper => {
const localVue = createLocalVue();
localVue.use(Vuex);
diff --git a/tsconfig.json b/tsconfig.json
index 367159fc4..2a49df230 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
"module": "esnext",
"strict": false,
"allowJs": true,
- "importHelpers": true,
+ "importHelpers": false,
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
diff --git a/yarn.lock b/yarn.lock
index 0fb37ad06..31afa85dc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,6 +9,22 @@
dependencies:
"@babel/highlight" "^7.8.3"
+"@babel/code-frame@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
+ integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
+ dependencies:
+ "@babel/highlight" "^7.10.1"
+
+"@babel/compat-data@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db"
+ integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw==
+ dependencies:
+ browserslist "^4.12.0"
+ invariant "^2.2.4"
+ semver "^5.5.0"
+
"@babel/compat-data@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35"
@@ -18,7 +34,7 @@
invariant "^2.2.4"
semver "^5.5.0"
-"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4", "@babel/core@^7.8.6":
+"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.6.tgz#27d7df9258a45c2e686b6f18b6c659e563aa4636"
integrity sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg==
@@ -39,6 +55,38 @@
semver "^5.4.1"
source-map "^0.5.0"
+"@babel/core@^7.9.0":
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e"
+ integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@babel/generator" "^7.9.0"
+ "@babel/helper-module-transforms" "^7.9.0"
+ "@babel/helpers" "^7.9.0"
+ "@babel/parser" "^7.9.0"
+ "@babel/template" "^7.8.6"
+ "@babel/traverse" "^7.9.0"
+ "@babel/types" "^7.9.0"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.13"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
+"@babel/generator@^7.10.1":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9"
+ integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==
+ dependencies:
+ "@babel/types" "^7.10.2"
+ jsesc "^2.5.1"
+ lodash "^4.17.13"
+ source-map "^0.5.0"
+
"@babel/generator@^7.4.0", "@babel/generator@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.6.tgz#57adf96d370c9a63c241cd719f9111468578537a"
@@ -49,6 +97,23 @@
lodash "^4.17.13"
source-map "^0.5.0"
+"@babel/generator@^7.9.0", "@babel/generator@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9"
+ integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==
+ dependencies:
+ "@babel/types" "^7.9.5"
+ jsesc "^2.5.1"
+ lodash "^4.17.13"
+ source-map "^0.5.0"
+
+"@babel/helper-annotate-as-pure@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268"
+ integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-annotate-as-pure@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee"
@@ -56,6 +121,14 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059"
+ integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw==
+ dependencies:
+ "@babel/helper-explode-assignable-expression" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503"
@@ -73,6 +146,17 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-compilation-targets@^7.10.2":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285"
+ integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA==
+ dependencies:
+ "@babel/compat-data" "^7.10.1"
+ browserslist "^4.12.0"
+ invariant "^2.2.4"
+ levenary "^1.1.1"
+ semver "^5.5.0"
+
"@babel/helper-compilation-targets@^7.8.4", "@babel/helper-compilation-targets@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.6.tgz#015b85db69e3a34240d5c2b761fc53eb9695f09c"
@@ -84,6 +168,18 @@
levenary "^1.1.1"
semver "^5.5.0"
+"@babel/helper-create-class-features-plugin@^7.10.1":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz#7474295770f217dbcf288bf7572eb213db46ee67"
+ integrity sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/helper-member-expression-to-functions" "^7.10.1"
+ "@babel/helper-optimise-call-expression" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-replace-supers" "^7.10.1"
+ "@babel/helper-split-export-declaration" "^7.10.1"
+
"@babel/helper-create-class-features-plugin@^7.8.3":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0"
@@ -96,6 +192,15 @@
"@babel/helper-replace-supers" "^7.8.6"
"@babel/helper-split-export-declaration" "^7.8.3"
+"@babel/helper-create-regexp-features-plugin@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd"
+ integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.1"
+ "@babel/helper-regex" "^7.10.1"
+ regexpu-core "^4.7.0"
+
"@babel/helper-create-regexp-features-plugin@^7.8.3":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz#7fa040c97fb8aebe1247a5c645330c32d083066b"
@@ -105,6 +210,15 @@
"@babel/helper-regex" "^7.8.3"
regexpu-core "^4.6.0"
+"@babel/helper-define-map@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d"
+ integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/types" "^7.10.1"
+ lodash "^4.17.13"
+
"@babel/helper-define-map@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15"
@@ -114,6 +228,14 @@
"@babel/types" "^7.8.3"
lodash "^4.17.13"
+"@babel/helper-explode-assignable-expression@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e"
+ integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg==
+ dependencies:
+ "@babel/traverse" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-explode-assignable-expression@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982"
@@ -122,6 +244,15 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-function-name@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4"
+ integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.1"
+ "@babel/template" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-function-name@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
@@ -131,6 +262,22 @@
"@babel/template" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-function-name@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
+ integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.8.3"
+ "@babel/template" "^7.8.3"
+ "@babel/types" "^7.9.5"
+
+"@babel/helper-get-function-arity@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d"
+ integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-get-function-arity@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
@@ -138,6 +285,13 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-hoist-variables@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077"
+ integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-hoist-variables@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134"
@@ -145,6 +299,13 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-member-expression-to-functions@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15"
+ integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-member-expression-to-functions@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c"
@@ -159,6 +320,26 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-module-imports@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876"
+ integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
+"@babel/helper-module-transforms@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622"
+ integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.1"
+ "@babel/helper-replace-supers" "^7.10.1"
+ "@babel/helper-simple-access" "^7.10.1"
+ "@babel/helper-split-export-declaration" "^7.10.1"
+ "@babel/template" "^7.10.1"
+ "@babel/types" "^7.10.1"
+ lodash "^4.17.13"
+
"@babel/helper-module-transforms@^7.8.3":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4"
@@ -172,6 +353,26 @@
"@babel/types" "^7.8.6"
lodash "^4.17.13"
+"@babel/helper-module-transforms@^7.9.0":
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5"
+ integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.8.3"
+ "@babel/helper-replace-supers" "^7.8.6"
+ "@babel/helper-simple-access" "^7.8.3"
+ "@babel/helper-split-export-declaration" "^7.8.3"
+ "@babel/template" "^7.8.6"
+ "@babel/types" "^7.9.0"
+ lodash "^4.17.13"
+
+"@babel/helper-optimise-call-expression@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543"
+ integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-optimise-call-expression@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9"
@@ -184,6 +385,18 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==
+"@babel/helper-plugin-utils@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127"
+ integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==
+
+"@babel/helper-regex@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96"
+ integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g==
+ dependencies:
+ lodash "^4.17.13"
+
"@babel/helper-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965"
@@ -191,6 +404,17 @@
dependencies:
lodash "^4.17.13"
+"@babel/helper-remap-async-to-generator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432"
+ integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.1"
+ "@babel/helper-wrap-function" "^7.10.1"
+ "@babel/template" "^7.10.1"
+ "@babel/traverse" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-remap-async-to-generator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86"
@@ -202,6 +426,16 @@
"@babel/traverse" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-replace-supers@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d"
+ integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.10.1"
+ "@babel/helper-optimise-call-expression" "^7.10.1"
+ "@babel/traverse" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
@@ -212,6 +446,14 @@
"@babel/traverse" "^7.8.6"
"@babel/types" "^7.8.6"
+"@babel/helper-simple-access@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e"
+ integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw==
+ dependencies:
+ "@babel/template" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-simple-access@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae"
@@ -220,6 +462,13 @@
"@babel/template" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-split-export-declaration@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f"
+ integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==
+ dependencies:
+ "@babel/types" "^7.10.1"
+
"@babel/helper-split-export-declaration@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
@@ -227,6 +476,26 @@
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-validator-identifier@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
+ integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
+
+"@babel/helper-validator-identifier@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
+ integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
+
+"@babel/helper-wrap-function@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9"
+ integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/template" "^7.10.1"
+ "@babel/traverse" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/helper-wrap-function@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610"
@@ -246,6 +515,24 @@
"@babel/traverse" "^7.8.4"
"@babel/types" "^7.8.3"
+"@babel/helpers@^7.9.0":
+ version "7.9.2"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f"
+ integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==
+ dependencies:
+ "@babel/template" "^7.8.3"
+ "@babel/traverse" "^7.9.0"
+ "@babel/types" "^7.9.0"
+
+"@babel/highlight@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
+ integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.1"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/highlight@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
@@ -260,6 +547,25 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c"
integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==
+"@babel/parser@^7.10.1":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0"
+ integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==
+
+"@babel/parser@^7.9.0":
+ version "7.9.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
+ integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
+
+"@babel/plugin-proposal-async-generator-functions@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55"
+ integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-remap-async-to-generator" "^7.10.1"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+
"@babel/plugin-proposal-async-generator-functions@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f"
@@ -269,6 +575,14 @@
"@babel/helper-remap-async-to-generator" "^7.8.3"
"@babel/plugin-syntax-async-generators" "^7.8.0"
+"@babel/plugin-proposal-class-properties@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01"
+ integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-proposal-class-properties@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e"
@@ -286,6 +600,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-decorators" "^7.8.3"
+"@babel/plugin-proposal-dynamic-import@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0"
+ integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+
"@babel/plugin-proposal-dynamic-import@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054"
@@ -294,6 +616,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-dynamic-import" "^7.8.0"
+"@babel/plugin-proposal-json-strings@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09"
+ integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+
"@babel/plugin-proposal-json-strings@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b"
@@ -302,6 +632,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-json-strings" "^7.8.0"
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78"
+ integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+
"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2"
@@ -310,6 +648,23 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+"@babel/plugin-proposal-numeric-separator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123"
+ integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.1"
+
+"@babel/plugin-proposal-object-rest-spread@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6"
+ integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-transform-parameters" "^7.10.1"
+
"@babel/plugin-proposal-object-rest-spread@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb"
@@ -318,6 +673,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+"@babel/plugin-proposal-optional-catch-binding@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2"
+ integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+
"@babel/plugin-proposal-optional-catch-binding@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9"
@@ -326,6 +689,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+"@babel/plugin-proposal-optional-chaining@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c"
+ integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+
"@babel/plugin-proposal-optional-chaining@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543"
@@ -334,6 +705,22 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-optional-chaining" "^7.8.0"
+"@babel/plugin-proposal-private-methods@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598"
+ integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f"
+ integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-proposal-unicode-property-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz#b646c3adea5f98800c9ab45105ac34d06cd4a47f"
@@ -356,6 +743,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-class-properties@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5"
+ integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-syntax-decorators@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz#8d2c15a9f1af624b0025f961682a9d53d3001bda"
@@ -391,6 +785,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-numeric-separator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99"
+ integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
@@ -412,6 +813,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-top-level-await@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362"
+ integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-syntax-top-level-await@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391"
@@ -419,6 +827,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-arrow-functions@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b"
+ integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-arrow-functions@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6"
@@ -426,6 +841,15 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-async-to-generator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062"
+ integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-remap-async-to-generator" "^7.10.1"
+
"@babel/plugin-transform-async-to-generator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086"
@@ -435,6 +859,13 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-remap-async-to-generator" "^7.8.3"
+"@babel/plugin-transform-block-scoped-functions@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d"
+ integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-block-scoped-functions@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3"
@@ -442,6 +873,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-block-scoping@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e"
+ integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ lodash "^4.17.13"
+
"@babel/plugin-transform-block-scoping@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a"
@@ -450,6 +889,20 @@
"@babel/helper-plugin-utils" "^7.8.3"
lodash "^4.17.13"
+"@babel/plugin-transform-classes@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f"
+ integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.1"
+ "@babel/helper-define-map" "^7.10.1"
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/helper-optimise-call-expression" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-replace-supers" "^7.10.1"
+ "@babel/helper-split-export-declaration" "^7.10.1"
+ globals "^11.1.0"
+
"@babel/plugin-transform-classes@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d"
@@ -464,6 +917,13 @@
"@babel/helper-split-export-declaration" "^7.8.3"
globals "^11.1.0"
+"@babel/plugin-transform-computed-properties@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07"
+ integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-computed-properties@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b"
@@ -471,6 +931,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-destructuring@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907"
+ integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-destructuring@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b"
@@ -478,6 +945,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee"
+ integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-dotall-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e"
@@ -486,6 +961,13 @@
"@babel/helper-create-regexp-features-plugin" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-duplicate-keys@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9"
+ integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-duplicate-keys@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1"
@@ -493,6 +975,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-exponentiation-operator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3"
+ integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-exponentiation-operator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7"
@@ -501,6 +991,13 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-for-of@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5"
+ integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-for-of@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085"
@@ -508,6 +1005,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-function-name@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d"
+ integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-function-name@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b"
@@ -516,6 +1021,13 @@
"@babel/helper-function-name" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-literals@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a"
+ integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-literals@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1"
@@ -523,6 +1035,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-member-expression-literals@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39"
+ integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-member-expression-literals@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410"
@@ -530,6 +1049,15 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-modules-amd@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a"
+ integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
"@babel/plugin-transform-modules-amd@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5"
@@ -539,6 +1067,16 @@
"@babel/helper-plugin-utils" "^7.8.3"
babel-plugin-dynamic-import-node "^2.3.0"
+"@babel/plugin-transform-modules-commonjs@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301"
+ integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-simple-access" "^7.10.1"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
"@babel/plugin-transform-modules-commonjs@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5"
@@ -549,6 +1087,16 @@
"@babel/helper-simple-access" "^7.8.3"
babel-plugin-dynamic-import-node "^2.3.0"
+"@babel/plugin-transform-modules-systemjs@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6"
+ integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.10.1"
+ "@babel/helper-module-transforms" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
"@babel/plugin-transform-modules-systemjs@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420"
@@ -559,6 +1107,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
babel-plugin-dynamic-import-node "^2.3.0"
+"@babel/plugin-transform-modules-umd@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595"
+ integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-modules-umd@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a"
@@ -574,6 +1130,13 @@
dependencies:
"@babel/helper-create-regexp-features-plugin" "^7.8.3"
+"@babel/plugin-transform-new-target@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324"
+ integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-new-target@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43"
@@ -581,6 +1144,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-object-super@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde"
+ integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-replace-supers" "^7.10.1"
+
"@babel/plugin-transform-object-super@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725"
@@ -589,6 +1160,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-replace-supers" "^7.8.3"
+"@babel/plugin-transform-parameters@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd"
+ integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-parameters@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz#1d5155de0b65db0ccf9971165745d3bb990d77d3"
@@ -598,6 +1177,13 @@
"@babel/helper-get-function-arity" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-property-literals@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d"
+ integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-property-literals@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263"
@@ -605,6 +1191,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-regenerator@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490"
+ integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw==
+ dependencies:
+ regenerator-transform "^0.14.2"
+
"@babel/plugin-transform-regenerator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz#b31031e8059c07495bf23614c97f3d9698bc6ec8"
@@ -612,6 +1205,13 @@
dependencies:
regenerator-transform "^0.14.0"
+"@babel/plugin-transform-reserved-words@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86"
+ integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-reserved-words@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5"
@@ -629,6 +1229,13 @@
resolve "^1.8.1"
semver "^5.5.1"
+"@babel/plugin-transform-shorthand-properties@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3"
+ integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-shorthand-properties@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8"
@@ -636,6 +1243,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-spread@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8"
+ integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-spread@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8"
@@ -643,6 +1257,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-sticky-regex@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00"
+ integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/helper-regex" "^7.10.1"
+
"@babel/plugin-transform-sticky-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100"
@@ -651,6 +1273,14 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/helper-regex" "^7.8.3"
+"@babel/plugin-transform-template-literals@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628"
+ integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-template-literals@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80"
@@ -659,6 +1289,13 @@
"@babel/helper-annotate-as-pure" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-typeof-symbol@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e"
+ integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-typeof-symbol@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412"
@@ -666,6 +1303,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
+"@babel/plugin-transform-unicode-escapes@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940"
+ integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.1"
+
+"@babel/plugin-transform-unicode-regex@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f"
+ integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+
"@babel/plugin-transform-unicode-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad"
@@ -682,7 +1334,7 @@
core-js "^2.6.5"
regenerator-runtime "^0.13.2"
-"@babel/preset-env@^7.8.4", "@babel/preset-env@^7.8.6":
+"@babel/preset-env@^7.8.4":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.6.tgz#2a0773b08589ecba4995fc71b1965e4f531af40b"
integrity sha512-M5u8llV9DIVXBFB/ArIpqJuvXpO+ymxcJ6e8ZAmzeK3sQeBNOD1y+rHvHCGG4TlEmsNpIrdecsHGHT8ZCoOSJg==
@@ -745,6 +1397,87 @@
levenary "^1.1.1"
semver "^5.5.0"
+"@babel/preset-env@^7.9.0":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb"
+ integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA==
+ dependencies:
+ "@babel/compat-data" "^7.10.1"
+ "@babel/helper-compilation-targets" "^7.10.2"
+ "@babel/helper-module-imports" "^7.10.1"
+ "@babel/helper-plugin-utils" "^7.10.1"
+ "@babel/plugin-proposal-async-generator-functions" "^7.10.1"
+ "@babel/plugin-proposal-class-properties" "^7.10.1"
+ "@babel/plugin-proposal-dynamic-import" "^7.10.1"
+ "@babel/plugin-proposal-json-strings" "^7.10.1"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1"
+ "@babel/plugin-proposal-numeric-separator" "^7.10.1"
+ "@babel/plugin-proposal-object-rest-spread" "^7.10.1"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.10.1"
+ "@babel/plugin-proposal-optional-chaining" "^7.10.1"
+ "@babel/plugin-proposal-private-methods" "^7.10.1"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.10.1"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+ "@babel/plugin-syntax-class-properties" "^7.10.1"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.1"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+ "@babel/plugin-syntax-top-level-await" "^7.10.1"
+ "@babel/plugin-transform-arrow-functions" "^7.10.1"
+ "@babel/plugin-transform-async-to-generator" "^7.10.1"
+ "@babel/plugin-transform-block-scoped-functions" "^7.10.1"
+ "@babel/plugin-transform-block-scoping" "^7.10.1"
+ "@babel/plugin-transform-classes" "^7.10.1"
+ "@babel/plugin-transform-computed-properties" "^7.10.1"
+ "@babel/plugin-transform-destructuring" "^7.10.1"
+ "@babel/plugin-transform-dotall-regex" "^7.10.1"
+ "@babel/plugin-transform-duplicate-keys" "^7.10.1"
+ "@babel/plugin-transform-exponentiation-operator" "^7.10.1"
+ "@babel/plugin-transform-for-of" "^7.10.1"
+ "@babel/plugin-transform-function-name" "^7.10.1"
+ "@babel/plugin-transform-literals" "^7.10.1"
+ "@babel/plugin-transform-member-expression-literals" "^7.10.1"
+ "@babel/plugin-transform-modules-amd" "^7.10.1"
+ "@babel/plugin-transform-modules-commonjs" "^7.10.1"
+ "@babel/plugin-transform-modules-systemjs" "^7.10.1"
+ "@babel/plugin-transform-modules-umd" "^7.10.1"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3"
+ "@babel/plugin-transform-new-target" "^7.10.1"
+ "@babel/plugin-transform-object-super" "^7.10.1"
+ "@babel/plugin-transform-parameters" "^7.10.1"
+ "@babel/plugin-transform-property-literals" "^7.10.1"
+ "@babel/plugin-transform-regenerator" "^7.10.1"
+ "@babel/plugin-transform-reserved-words" "^7.10.1"
+ "@babel/plugin-transform-shorthand-properties" "^7.10.1"
+ "@babel/plugin-transform-spread" "^7.10.1"
+ "@babel/plugin-transform-sticky-regex" "^7.10.1"
+ "@babel/plugin-transform-template-literals" "^7.10.1"
+ "@babel/plugin-transform-typeof-symbol" "^7.10.1"
+ "@babel/plugin-transform-unicode-escapes" "^7.10.1"
+ "@babel/plugin-transform-unicode-regex" "^7.10.1"
+ "@babel/preset-modules" "^0.1.3"
+ "@babel/types" "^7.10.2"
+ browserslist "^4.12.0"
+ core-js-compat "^3.6.2"
+ invariant "^2.2.2"
+ levenary "^1.1.1"
+ semver "^5.5.0"
+
+"@babel/preset-modules@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72"
+ integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
"@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
@@ -752,6 +1485,15 @@
dependencies:
regenerator-runtime "^0.13.2"
+"@babel/template@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
+ integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==
+ dependencies:
+ "@babel/code-frame" "^7.10.1"
+ "@babel/parser" "^7.10.1"
+ "@babel/types" "^7.10.1"
+
"@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
@@ -776,6 +1518,36 @@
globals "^11.1.0"
lodash "^4.17.13"
+"@babel/traverse@^7.10.1":
+ version "7.10.1"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27"
+ integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.1"
+ "@babel/generator" "^7.10.1"
+ "@babel/helper-function-name" "^7.10.1"
+ "@babel/helper-split-export-declaration" "^7.10.1"
+ "@babel/parser" "^7.10.1"
+ "@babel/types" "^7.10.1"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.13"
+
+"@babel/traverse@^7.9.0":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
+ integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@babel/generator" "^7.9.5"
+ "@babel/helper-function-name" "^7.9.5"
+ "@babel/helper-split-export-declaration" "^7.8.3"
+ "@babel/parser" "^7.9.0"
+ "@babel/types" "^7.9.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.13"
+
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01"
@@ -785,6 +1557,24 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d"
+ integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.1"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.9.0", "@babel/types@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
+ integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.9.5"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -2321,6 +3111,16 @@
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-scope "^4.0.0"
+"@typescript-eslint/experimental-utils@2.27.0":
+ version "2.27.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a"
+ integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw==
+ dependencies:
+ "@types/json-schema" "^7.0.3"
+ "@typescript-eslint/typescript-estree" "2.27.0"
+ eslint-scope "^5.0.0"
+ eslint-utils "^2.0.0"
+
"@typescript-eslint/parser@^1.7.1-alpha.17":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355"
@@ -2331,6 +3131,16 @@
"@typescript-eslint/typescript-estree" "1.13.0"
eslint-visitor-keys "^1.0.0"
+"@typescript-eslint/parser@^2.26.0":
+ version "2.27.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287"
+ integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg==
+ dependencies:
+ "@types/eslint-visitor-keys" "^1.0.0"
+ "@typescript-eslint/experimental-utils" "2.27.0"
+ "@typescript-eslint/typescript-estree" "2.27.0"
+ eslint-visitor-keys "^1.1.0"
+
"@typescript-eslint/typescript-estree@1.13.0":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
@@ -2339,6 +3149,19 @@
lodash.unescape "4.0.1"
semver "5.5.0"
+"@typescript-eslint/typescript-estree@2.27.0":
+ version "2.27.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8"
+ integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg==
+ dependencies:
+ debug "^4.1.1"
+ eslint-visitor-keys "^1.1.0"
+ glob "^7.1.6"
+ is-glob "^4.0.1"
+ lodash "^4.17.15"
+ semver "^6.3.0"
+ tsutils "^3.17.1"
+
"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
@@ -3654,6 +4477,13 @@ babel-plugin-dynamic-import-node@^2.2.0, babel-plugin-dynamic-import-node@^2.3.0
dependencies:
object.assign "^4.1.0"
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
babel-plugin-istanbul@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854"
@@ -4426,6 +5256,16 @@ browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.8.5:
electron-to-chromium "^1.3.363"
node-releases "^1.1.50"
+browserslist@^4.12.0:
+ version "4.12.0"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d"
+ integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==
+ dependencies:
+ caniuse-lite "^1.0.30001043"
+ electron-to-chromium "^1.3.413"
+ node-releases "^1.1.53"
+ pkg-up "^2.0.0"
+
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
@@ -4693,6 +5533,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000864, can
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001031.tgz#76f1bdd39e19567b855302f65102d9a8aaad5930"
integrity sha512-DpAP5a1NGRLgYfaNCaXIRyGARi+3tJA2quZXNNA1Du26VyVkqvy2tznNu5ANyN1Y5aX44QDotZSVSUSi2uMGjg==
+caniuse-lite@^1.0.30001043:
+ version "1.0.30001066"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz#0a8a58a10108f2b9bf38e7b65c237b12fd9c5f04"
+ integrity sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -6415,6 +7260,11 @@ electron-to-chromium@^1.3.363, electron-to-chromium@^1.3.47:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.367.tgz#48abffcaa6591051b612ae70ddc657763ede2662"
integrity sha512-GCHQreWs4zhKA48FNXCjvpV4kTnKoLu2PSAfKX394g34NPvTs2pPh1+jzWitNwhmOYI8zIqt36ulRVRZUgqlfA==
+electron-to-chromium@^1.3.413:
+ version "1.3.455"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.455.tgz#fd65a3f5db6ffa83eb7c84f16ea9b1b7396f537d"
+ integrity sha512-4lwnxp+ArqOX9hiLwLpwhfqvwzUHFuDgLz4NTiU3lhygUzWtocIJ/5Vix+mWVNE2HQ9aI1k2ncGe5H/0OktMvA==
+
elegant-spinner@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
@@ -6756,6 +7606,14 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3:
esrecurse "^4.1.0"
estraverse "^4.1.1"
+eslint-scope@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
+ integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
+ dependencies:
+ esrecurse "^4.1.0"
+ estraverse "^4.1.1"
+
eslint-utils@^1.3.1:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
@@ -6763,6 +7621,13 @@ eslint-utils@^1.3.1:
dependencies:
eslint-visitor-keys "^1.1.0"
+eslint-utils@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd"
+ integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==
+ dependencies:
+ eslint-visitor-keys "^1.1.0"
+
eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
@@ -9958,6 +10823,13 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
+json5@^2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+ integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
+ dependencies:
+ minimist "^1.2.5"
+
jsonfile@^2.1.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
@@ -10805,6 +11677,11 @@ memory-fs@^0.5.0:
errno "^0.1.3"
readable-stream "^2.0.1"
+memorystream@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+ integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
+
meow@^3.3.0, meow@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -11007,6 +11884,11 @@ minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
minimist@~0.0.1:
version "0.0.10"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
@@ -11356,6 +12238,11 @@ node-releases@^1.1.50:
dependencies:
semver "^6.3.0"
+node-releases@^1.1.53:
+ version "1.1.57"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.57.tgz#f6754ce225fad0611e61228df3e09232e017ea19"
+ integrity sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw==
+
node-sass@^4.12.0:
version "4.13.1"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3"
@@ -11508,6 +12395,21 @@ npm-pick-manifest@^3.0.0:
npm-package-arg "^6.0.0"
semver "^5.4.1"
+npm-run-all@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
+ integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ chalk "^2.4.1"
+ cross-spawn "^6.0.5"
+ memorystream "^0.3.1"
+ minimatch "^3.0.4"
+ pidtree "^0.3.0"
+ read-pkg "^3.0.0"
+ shell-quote "^1.6.1"
+ string.prototype.padend "^3.0.0"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -12207,6 +13109,11 @@ picomatch@^2.0.4, picomatch@^2.0.5:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
+pidtree@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b"
+ integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==
+
pidusage@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92"
@@ -12274,6 +13181,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
+pkg-up@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f"
+ integrity sha1-yBmscoBZpGHKscOImivjxJoATX8=
+ dependencies:
+ find-up "^2.1.0"
+
please-upgrade-node@^3.0.2, please-upgrade-node@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
@@ -13355,6 +14269,13 @@ regenerate-unicode-properties@^8.1.0:
dependencies:
regenerate "^1.4.0"
+regenerate-unicode-properties@^8.2.0:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec"
+ integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==
+ dependencies:
+ regenerate "^1.4.0"
+
regenerate@^1.2.1, regenerate@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
@@ -13391,6 +14312,14 @@ regenerator-transform@^0.14.0:
dependencies:
private "^0.1.6"
+regenerator-transform@^0.14.2:
+ version "0.14.4"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7"
+ integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+ private "^0.1.8"
+
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -13438,6 +14367,18 @@ regexpu-core@^4.6.0:
unicode-match-property-ecmascript "^1.0.4"
unicode-match-property-value-ecmascript "^1.1.0"
+regexpu-core@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938"
+ integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==
+ dependencies:
+ regenerate "^1.4.0"
+ regenerate-unicode-properties "^8.2.0"
+ regjsgen "^0.5.1"
+ regjsparser "^0.6.4"
+ unicode-match-property-ecmascript "^1.0.4"
+ unicode-match-property-value-ecmascript "^1.2.0"
+
register-service-worker@^1.5.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.6.2.tgz#9297e54c205c371c6e49bfa88f6997e8dd315f4c"
@@ -13482,6 +14423,11 @@ regjsgen@^0.5.0:
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c"
integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==
+regjsgen@^0.5.1:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+ integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
+
regjsparser@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c"
@@ -13496,6 +14442,13 @@ regjsparser@^0.6.0:
dependencies:
jsesc "~0.5.0"
+regjsparser@^0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272"
+ integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==
+ dependencies:
+ jsesc "~0.5.0"
+
relateurl@0.2.x, relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
@@ -14010,19 +14963,6 @@ semver-diff@^3.1.1:
dependencies:
semver "^6.3.0"
-semver-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9"
- integrity sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=
-
-semver-sort@^0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/semver-sort/-/semver-sort-0.0.4.tgz#34fdbddc6a6b2b4161398c3c4dba56243bfeaa8b"
- integrity sha1-NP293GprK0FhOYw8TbpWJDv+qos=
- dependencies:
- semver "^5.0.3"
- semver-regex "^1.0.0"
-
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@5.*, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
@@ -14048,6 +14988,11 @@ semver@^7.0.0, semver@^7.1.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6"
integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==
+semver@^7.1.3:
+ version "7.3.2"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
+ integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
+
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -14174,6 +15119,11 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+shell-quote@^1.6.1:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
+ integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
+
shelljs@0.7.8:
version "0.7.8"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
@@ -14582,6 +15532,13 @@ stealthy-require@^1.1.1:
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
+"storefront-query-builder@https://github.com/DivanteLtd/storefront-query-builder.git":
+ version "1.0.1"
+ resolved "https://github.com/DivanteLtd/storefront-query-builder.git#3984d3972162d2aa407a7435522066a73959aa21"
+ dependencies:
+ "@typescript-eslint/parser" "^2.26.0"
+ clone-deep "^4.0.1"
+
stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -14684,6 +15641,14 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
+string.prototype.padend@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3"
+ integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.0-next.1"
+
string.prototype.trimleft@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
@@ -15403,7 +16368,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
-tsutils@^3.7.0:
+tsutils@^3.17.1, tsutils@^3.7.0:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
@@ -15546,6 +16511,11 @@ unicode-match-property-value-ecmascript@^1.1.0:
resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
+unicode-match-property-value-ecmascript@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531"
+ integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==
+
unicode-property-aliases-ecmascript@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"