diff --git a/.all-contributorsrc b/.all-contributorsrc
index 8b7a1f4fa..8811c0c4b 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -177,7 +177,14 @@
"avatar_url": "https://avatars.githubusercontent.com/u/5359825?v=4",
"profile": "https://github.com/sethidden",
"contributions": [
- "code"
+ "code",
+ "question",
+ "ideas",
+ "infra",
+ "maintenance",
+ "review",
+ "test",
+ "tool"
]
},
{
@@ -206,6 +213,33 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "AlexanderDevitsky",
+ "name": "Alexander Devitsky",
+ "avatar_url": "https://avatars.githubusercontent.com/u/14941520?v=4",
+ "profile": "https://github.com/AlexanderDevitsky",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "Diegoalbag",
+ "name": "Diego Alba",
+ "avatar_url": "https://avatars.githubusercontent.com/u/72459310?v=4",
+ "profile": "https://github.com/Diegoalbag",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "aelmizeb",
+ "name": "Abdellatif EL MIZEB",
+ "avatar_url": "https://avatars.githubusercontent.com/u/19288561?v=4",
+ "profile": "https://github.com/aelmizeb",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 5
diff --git a/.eslintrc.js b/.eslintrc.js
index 5327c1907..976fd049c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -35,7 +35,7 @@ module.exports = {
"jest/expect-expect": [
"error",
{
- "assertFunctionNames": ["expect", "getByRole", "getByTestId"],
+ "assertFunctionNames": ["expect", "getByRole", "getByTestId", "getByText"],
}
],
"no-plusplus": "off",
diff --git a/.github/workflows/deploy-vue-storefront-cloud.yml b/.github/workflows/deploy-vue-storefront-cloud.yml
index a4c29ad40..d98374dbf 100644
--- a/.github/workflows/deploy-vue-storefront-cloud.yml
+++ b/.github/workflows/deploy-vue-storefront-cloud.yml
@@ -34,10 +34,10 @@ jobs:
NPM_PASS: ${{ secrets.CLOUD_PASSWORD }}
NPM_REGISTRY: https://registrynpm.storefrontcloud.io
- VSF_STORE_URL: '' # TODO
+ VSF_STORE_URL: ''
- VSF_MAGENTO_BASE_URL: https://magento2demo.frodigo.com/
- VSF_MAGENTO_GRAPHQL_URL: https://magento2demo.frodigo.com/graphql
+ VSF_MAGENTO_BASE_URL: https://magento2-instance.vuestorefront.io/
+ VSF_MAGENTO_GRAPHQL_URL: https://magento2-instance.vuestorefront.io/graphql
VSF_MAGENTO_EXTERNAL_CHECKOUT_ENABLED: false
VSF_MAGENTO_EXTERNAL_CHECKOUT_URL: ''
VSF_MAGENTO_EXTERNAL_CHECKOUT_SYNC_PATH: ''
diff --git a/README.md b/README.md
index a3e32dc57..6004b322a 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ This project is a Magento 2 integration for Vue Storefront 2.
-[](#contributors-)
+[](#contributors-)
## How to start if you want to try out the integration
@@ -110,6 +110,7 @@ If you have any questions about this integration we will be happy to answer them
## Contributors β¨
### Honorable Mentions
+- [Caravel x](https://www.caravelx.com/)
- [Cyberfuze](https://cyberfuze.com/)
- [Leonex](https://www.leonex.de/)
@@ -128,18 +129,29 @@ Thanks go to these wonderful people π:
Patrick Monteiro π»
- Kevin Gorjan π»
- Bartosz Herba π»
- Marcin Kwiatkowski π» π
+ Kevin Gorjan π» π
+ Bartosz Herba π» π π§ π§βπ« π
+ Marcin Kwiatkowski π» π πΌ π π€ π§ π§βπ« π
Filip Rakowski π¬ π§βπ« π
- Filip Sobol π¬ π§βπ« π
+ Filip Sobol π¬ π§βπ« π π
Patryk Andrzejewski π¬ π§βπ« π
Renan Oliveira π§ π
Dominik Deimel π» π
Lior Lindvor π»
+
+ Artur Tagisow π» π¬ π€ π π§ π β οΈ π§
+ Jonathan Ribas π»
+ Ali Ghanei π»
+ Maya Shavin π
+ Alexander Devitsky π»
+
+
+ Diego Alba π»
+ Abdellatif EL MIZEB π»
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 900793535..8d0efcddc 100755
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -6,7 +6,6 @@ module.exports = {
description: 'Documentation for the Magento connector for Vue Storefront 2',
head: [
['link', { rel: 'icon', href: '/favicon.png' }],
-
// Google Tag Manager
['script', {}, [`
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
@@ -16,24 +15,39 @@ module.exports = {
})(window,document,'script','dataLayer','${GTM_TAG}');
`]],
],
- configureWebpack: (config) => {
- config.module.rules = config.module.rules.map((rule) => ({
- ...rule,
- use:
- rule.use &&
- rule.use.map((useRule) => ({
- ...useRule,
- options:
- useRule.loader === 'url-loader'
- ? /**
- Hack for loading images properly.
- ref: https://github.com/vuejs/vue-loader/issues/1612#issuecomment-559366730
- */
- { ...useRule.options, esModule: false }
- : useRule.options,
- })),
- }));
+
+ /**
+ * RefοΌhttps://v1.vuepress.vuejs.org/config/#configurewebpack
+ */
+ configureWebpack: (config) => {
+ // Add support for webp images
+ config.module.rules.push({
+ test: /\.(webp)(\?.*)?$/,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 10000,
+ name: 'assets/img/[name].[hash:8].[ext]'
+ }
+ }
+ ]
+ });
+
+ // Fix image loading. Ref: https://github.com/vuejs/vue-loader/issues/1612#issuecomment-559366730
+ config.module.rules = config.module.rules.map((rule) => {
+ rule.use = rule.use && rule.use.map((useRule) => {
+ if (useRule.loader === 'url-loader') {
+ useRule.options.esModule = false;
+ }
+
+ return useRule;
+ });
+
+ return rule;
+ });
},
+
/**
* RefοΌhttps://v1.vuepress.vuejs.org/plugin/
*/
@@ -80,14 +94,39 @@ module.exports = {
title: 'Getting started',
collapsable: false,
children: [
+ ['/getting-started/installation', 'Installation'],
['/getting-started/configure-magento', 'Configuring Magento'],
['/getting-started/configure-integration', 'Configuring Vue Storefront'],
],
},
+ {
+ title: 'Composition',
+ collapsable: false,
+ children: [
+ ['/composition/composables', 'Composables'],
+ ['/composition/list-of-composables', 'List of composables'],
+ ],
+ },
+ {
+ title: 'Modules',
+ collapsable: false,
+ sidebarDepth: 2,
+ children: [
+ {
+ title: 'Catalog',
+ collapsable: true,
+ children: [
+ ['/modules/catalog/filters', 'Filters'],
+ ['/modules/catalog/product-types', 'Product Types'],
+ ],
+ },
+ ],
+ },
{
title: 'Guides',
collapsable: false,
children: [
+ ['/guide/global-state-management', 'Global state management'],
['/guide/image-optimization', 'Image optimization'],
['/guide/override-queries', 'Override queries'],
['/guide/testing', 'Testing'],
@@ -102,52 +141,12 @@ module.exports = {
['/guide/ssr', 'Server Side Rendering Cache'],
],
},
- {
- title: 'Composables',
- children: [
- ['/api-reference/magento-theme.useaddresses', 'useAddresses()'],
- ['/api-reference/magento-theme.useapi', 'useApi()'],
- ['/api-reference/magento-theme.usebilling', 'useBilling()'],
- ['/api-reference/magento-theme.usecart', 'useCart()'],
- ['/api-reference/magento-theme.usecategory', 'useCategory()'],
- ['/api-reference/magento-theme.usecategorysearch', 'useCategorySearch()'],
- ['/api-reference/magento-theme.useconfig', 'useConfig()'],
- ['/api-reference/magento-theme.usecontent', 'useContent()'],
- ['/api-reference/magento-theme.usecountrysearch', 'useCountrySearch()'],
- ['/api-reference/magento-theme.usecurrency', 'useCurrency()'],
- ['/api-reference/magento-theme.useexternalcheckout', 'useExternalCheckout()'],
- ['/api-reference/magento-theme.usefacet', 'useFacet()'],
- ['/api-reference/magento-theme.useforgotpassword', 'useForgotPassword()'],
- ['/api-reference/magento-theme.usegetshippingmethods', 'useGetShippingMethods()'],
- ['/api-reference/magento-theme.useguestuser', 'useGuestUser()'],
- ['/api-reference/magento-theme.useimage', 'useImage()'],
- ['/api-reference/magento-theme.usemagentoconfiguration', 'useMagentoConfiguration()'],
- ['/api-reference/magento-theme.usemakeorder', 'useMakeOrder()'],
- ['/api-reference/magento-theme.usenewsletter', 'useNewsletter()'],
- ['/api-reference/magento-theme.usepaymentprovider', 'usePaymentProvider()'],
- ['/api-reference/magento-theme.useproduct', 'useProduct()'],
- ['/api-reference/magento-theme.userelatedproducts', 'useRelatedProducts()'],
- ['/api-reference/magento-theme.usereview', 'useReview()'],
- ['/api-reference/magento-theme.useshipping', 'useShipping()'],
- ['/api-reference/magento-theme.useshippingprovider', 'useShippingProvider()'],
- ['/api-reference/magento-theme.usestore', 'useStore()'],
- ['/api-reference/magento-theme.useuihelpers', 'useUiHelpers()'],
- ['/api-reference/magento-theme.useuinotification', 'useUiNotification()'],
- ['/api-reference/magento-theme.useuistate', 'useUiState()'],
- ['/api-reference/magento-theme.useupsellproducts', 'useUpsellProducts()'],
- ['/api-reference/magento-theme.useurlresolver', 'useUrlResolver()'],
- ['/api-reference/magento-theme.useuser', 'useUser()'],
- ['/api-reference/magento-theme.useuseraddress', 'useUserAddress()'],
- ['/api-reference/magento-theme.useuserorder', 'useUserOrder()'],
- ['/api-reference/magento-theme.usewishlist', 'useWishlist()'],
- ],
- },
{
title: 'Reference',
children: [
['/plugins/', 'Plugins'],
['/api-reference/', 'API Reference'],
- ['/migration-guides/', 'Migration guides'],
+ ['/migration-guides/', 'Migration Guides'],
],
},
{
diff --git a/docs/assets/images/magento-marketplace-access-keys.webp b/docs/assets/images/magento-marketplace-access-keys.webp
new file mode 100644
index 000000000..940b5b83e
Binary files /dev/null and b/docs/assets/images/magento-marketplace-access-keys.webp differ
diff --git a/docs/assets/images/useUser-composable-anatomy.webp b/docs/assets/images/useUser-composable-anatomy.webp
new file mode 100644
index 000000000..75cdbc981
Binary files /dev/null and b/docs/assets/images/useUser-composable-anatomy.webp differ
diff --git a/docs/assets/images/useUser-load-flow.webp b/docs/assets/images/useUser-load-flow.webp
new file mode 100644
index 000000000..017e3eb49
Binary files /dev/null and b/docs/assets/images/useUser-load-flow.webp differ
diff --git a/docs/composition/composables.md b/docs/composition/composables.md
new file mode 100644
index 000000000..198e5eeaf
--- /dev/null
+++ b/docs/composition/composables.md
@@ -0,0 +1,112 @@
+# Composables
+
+## Prerequisites
+
+Composables use the Composition API introduced in Vue 3 but are also made available via plugins in Vue 2. If you are not familiar with it, see the [Composition API guide](https://docs.vuestorefront.io/v2/composition/composition-api.html).
+
+## What are composables?
+
+Composables are functions with an **internal state** that changes over time and **methods** that modify this state. You cannot directly modify the state. The only way to change the state is by calling one of the composable's methods. However, because the state is reactive β thanks to Vue's Composition API β you can watch and react to these changes when necessary to update the UI or perform other operations.
+
+This pattern encapsulates the state and business logic and exposes it through easy-to-use methods.
+
+## Anatomy of a composable
+
+Most composables consist of one or more of the following:
+
+- **Primary state** - read-only state of the composable, which you cannot update directly.
+- **Supportive state** - additional read-only state for values such as the status of the requests or errors.
+- **Methods** - functions that update the primary and supportive states. These methods usually call API endpoints but can also manage cookies or call methods from other composables.
+
+To make composables easily distinguishable from standard methods, we follow the popular convention of names starting with "use".
+
+### What does it look like in practice?
+
+Let's take a closer look at how it might look like using the [useUser](/api-reference/magento-theme.useuserinterface.html) composable as an example:
+
+
+
+In this example:
+
+- the `user` property is the primary state,
+- the `loading` and `error` properties represent the supportive state,
+- the `load`, `register`, `login`, `logout`, and `changePassword` are methods.
+
+## Usage
+
+Let's see how you can use the [useUser](/api-reference/magento-theme.useuserinterface.html) composable to load the current user's data:
+
+```vue
+
+```
+
+Let's go step by step through this example to understand what's going on:
+
+1. We begin by extracting needed methods and state variables from the composable.
+2. Next, we call the asynchronous `load` method within the `useFetch` hook to load user data.
+3. Finally, we return the `user` object from the `setup` method to make it available in the components ``.
+
+While it's okay to destructure a composable as we did in step 1, you should **not** destructure read-only states, such as the `user` or `error` properties. Doing it this way will create variables that are not reactive and don't update.
+
+```javascript
+// β Destructuring `user` will create variables that aren't reactive and don't update
+const { user: { value: { firstname } } } = useUser();
+
+// βοΈ Using `computed` will make the variable react to changes in the `user` object
+const { user } = useUser();
+const firstname = computed(() => user.value.firstname);
+```
+
+This raises two questions:
+
+1. What is the `useFetch`, and what does it do?
+2. What happened when we called the `load` method?
+
+### `useFetch` and other hooks for fetching data
+
+There are many hooks available in Composition API, but let's only focus on the most common ones used for fetching data:
+
+- The [useFetch](https://composition-api.nuxtjs.org/lifecycle/usefetch/) and [useAsync](https://composition-api.nuxtjs.org/API/useAsync) are Nuxt-specific hooks called on the server-side when rendering the route and on the client-side when navigating between pages.
+- The `onMounted` is a lifecycle hook called **only on the client-side** after the browser loads the page.
+- The `onServerPrefetch` is a lifecycle hook called **only on the server-side** when rendering the route.
+
+You can use one or more hooks simultaneously, even in the same component.
+
+### Internals of the `load` method
+
+You might be wondering what happened within the composable when we called the `load` method in the example above. The behavior of methods is different between composables. Still, in the case of the `useUser` composable, the `load` method updated the `loading`, `error`, and `user` properties to reflect the current state, made an API call, and then updated the state with the API's response.
+
+
+
+1. It set the `loading` property to `true`.
+2. It called the corresponding API endpoint to load the current user's data.
+ - if the request succeeded, it updated the `user` property,
+ - otherwise, it added the error message to the `error` property.
+3. It set the `loading` property to `false`.
+
+## What's next?
+
+Now that you understand what composables are, you should see the [List of composables](./list-of-composables.html) and their options.
diff --git a/docs/composition/list-of-composables.md b/docs/composition/list-of-composables.md
new file mode 100644
index 000000000..a170371ea
--- /dev/null
+++ b/docs/composition/list-of-composables.md
@@ -0,0 +1,211 @@
+# List of composables
+
+## `useAddresses`
+
+Allows loading and manipulating addresses of the current user. These addresses can be used for both billing and shipping.
+
+See the [UseAddressesInterface](/api-reference/magento-theme.useaddressesinterface.html) for more information.
+
+## `useApi`
+
+Allows executing arbitrary GraphQL queries and mutations.
+
+See the [UseApiInterface](/api-reference/magento-theme.useapiinterface.html) for more information.
+
+## `useBilling`
+
+Allows loading and saving billing information of the current cart.
+
+See the [UseBillingInterface](/api-reference/magento-theme.usebillinginterface.html) for more information.
+
+## `useCart`
+
+Allows loading and manipulating cart of the current user.
+
+See the [UseCartInterface](/api-reference/magento-theme.usecartinterface.html) for more information.
+
+## `useCategory`
+
+Allows loading categories from Magento API. It is commonly used in navigation menus, and provides the load function and refs for the categories, loading and error.
+
+See the [UseCategoryInterface](/api-reference/magento-theme.usecategoryinterface.html) for more information.
+
+## `useCategorySearch`
+
+Allows searching for categories. It is commonly used in subtrees navigation.
+
+See the [UseCategorySearchInterface](/api-reference/magento-theme.usecategorysearchinterface.html) for more information.
+
+## `useConfig`
+
+Allows interacting with the store configuration.
+
+See the [UseConfigInterface](/api-reference/magento-theme.useconfiginterface.html) for more information.
+
+## `useContent`
+
+Allows loading CMS Pages or Blocks from Magento API.
+
+See the [UseContentInterface](/api-reference/magento-theme.usecontentinterface.html) for more information.
+
+## `useCountrySearch`
+
+Allows fetching a list of countries or a single country by ID
+
+See the [UseCountrySearchInterface](/api-reference/magento-theme.usecountrysearchinterface.html) for more information.
+
+## `useCurrency`
+
+Allows loading and changing the currency.
+
+See the [UseCurrencyInterface](/api-reference/magento-theme.usecurrencyinterface.html) for more information.
+
+## `useExternalCheckout` (Work in progress)
+
+Allows redirecting to external checkout process. It depends on the [magento2-external-checkout repository](https://github.com/Vendic/magento2-external-checkout).
+
+See the [UseExternalCheckoutInterface](/api-reference/magento-theme.useexternalcheckoutinterface.html) for more information.
+
+## `useFacet`
+
+Allows searching for products with pagination, totals and sorting options.
+
+See the [UseFacetInterface](/api-reference/magento-theme.usefacetinterface.html) for more information.
+
+## `useForgotPassword`
+
+Allows to request a password reset email and to set a new password to a user.
+
+Se the [UseForgotPasswordInterface](/api-reference/magento-theme.useforgotpasswordinterface.html) for more information.
+
+## `useGetShippingMethods`
+
+Allows fetching shipping methods available for a given cart.
+
+See the [UseGetShippingMethodsInterface](/api-reference/magento-theme.usegetshippingmethodsinterface.html) for more information.
+
+## `useGuestUser`
+
+Allows to attach a guest cart to a user.
+
+See the [UseGuestUserInterface](/api-reference/magento-theme.useguestuserinterface.html) for more information.
+
+## `useImage`
+
+Allows extracting image paths from magento URL.
+
+See the [UseImageInterface](/api-reference/magento-theme.useimageinterface.html) for more information.
+
+## `useMagentoConfiguration`
+
+Allows getting the Magento's major definitions, e.g., the selected currency, store, locale, and config object.
+
+See the [UseMagentoConfigurationInterface](/api-reference/magento-theme.usemagentoconfigurationinterface.html) for more information.
+
+## `useMakeOrder`
+
+Allows making an order from a cart.
+
+See the [UseMakeOrderInterface](/api-reference/magento-theme.usemakeorderinterface.html) for more information.
+
+## `useNewsletter`
+
+Allows updating the subscription status of an email in the newsletter.
+
+See the [UseNewsletterInterface](/api-reference/magento-theme.usenewsletterinterface.html) for more information.
+
+## `usePaymentProvider`
+
+Allows loading the available payment methods for current cart, and selecting (saving) one of them.
+
+See the [UsePaymentProviderInterface](/api-reference/magento-theme.usepaymentproviderinterface.html) for more information.
+
+## `useProduct`
+
+Allows loading product details or list with params for sorting, filtering and pagination.
+
+See the [UseProductInterface](/api-reference/magento-theme.useproductinterface.html) for more information.
+
+## `useRelatedProducts`
+
+Allows searching for related products with params for sort, filter and pagination.
+
+See the [UseRelatedProductsInterface](/api-reference/magento-theme.userelatedproductsinterface.html) for more information.
+
+## `useReview`
+
+Allows loading and adding product reviews.
+
+See the [UseReviewInterface](/api-reference/magento-theme.usereviewinterface.html) for more information.
+
+## `useShipping`
+
+Allows loading the shipping information for the current cart and saving (selecting) other shipping information for the same cart.
+
+See the [UseShippingInterface](/api-reference/magento-theme.useshippinginterface.html) for more information.
+
+## `useShippingProvider`
+
+Allows loading the shipping provider for the current cart and saving (selecting) other shipping provider for the same cart.
+
+See the [UseShippingProviderInterface](/api-reference/magento-theme.useshippingproviderinterface.html) for more information.
+
+## `useStore`
+
+Allows loading and changing currently active store.
+
+See the [UseStoreInterface](/api-reference/magento-theme.usestoreinterface.html) for more information.
+
+## `useUiHelpers`
+
+Allows handling the parameters for filtering, searching, sorting and pagination in the URL search/query params.
+
+See the [UseUiHelpersInterface](/api-reference/magento-theme.useuihelpersinterface.html) for more information.
+
+## `useUiNotification`
+
+Allows managing and showing notifications to the user.
+
+See the [UseUiNotificationInterface](/api-reference/magento-theme.useuinotificationinterface.html) for more information.
+
+## `useUiState`
+
+Global store for managing UI state.
+
+See the [UseUiStateInterface](/api-reference/magento-theme.useuistateinterface.html) for more information.
+
+## `useUpsellProducts`
+
+Allows loading upsell products.
+
+See the [UseUpsellProductsInterface](/api-reference/magento-theme.useupsellproductsinterface.html) for more information.
+
+## `useUrlResolver`
+
+Allows searching the resolver for current route path (URL).
+
+See the [UseUrlResolverInterface](/api-reference/magento-theme.useurlresolverinterface.html) for more information.
+
+## `useUser`
+
+Allows loading and manipulating data of the current user.
+
+See the [UseUserInterface](/api-reference/magento-theme.useuserinterface.html) for more information.
+
+## `useUserAddress`
+
+Allows loading and manipulating addresses of the current user.
+
+See the [UseUserAddressInterface](/api-reference/magento-theme.useuseraddressinterface.html) for more information.
+
+## `useUserOrder`
+
+Allows fetching customer orders.
+
+See the [UseUserOrderInterface](/api-reference/magento-theme.useuserorderinterface.html) for more information.
+
+## `useWishlist`
+
+Allows loading and manipulating wishlist of the current user.
+
+See the [UseWishlistInterface](/api-reference/magento-theme.usewishlistinterface.html) for more information.
diff --git a/docs/getting-started/configure-integration.md b/docs/getting-started/configure-integration.md
index a6799cd81..89a911fc5 100644
--- a/docs/getting-started/configure-integration.md
+++ b/docs/getting-started/configure-integration.md
@@ -1,28 +1,16 @@
# Configuring Vue Storefront for Magento 2
-This guide explains the steps needed to install and set up Vue Storefront for Magento 2.
+This guide explains the steps needed to set up Vue Storefront for Magento 2.
## Prerequisites
-Before you can get started, you need the following:
+Before you can get started, you need:
-- **Node.js 16** installed,
+- Vue Storefront project created following the [Installation](/getting-started/installation.html) guide.
- Magento 2 server configured following the [Configuring Magento](/getting-started/configure-magento.html) guide.
-To check the Node version you are using, run the following command:
-
-```bash
-node -v
-```
-
## Creating the Vue Storefront for Magento 2
-To install Vue Storefront, run the command below. It will ask you for the project name and the integration of your choice. Keep in mind that the project name will also be used as the folder name, and be sure to select the Magento 2 integration.
-
-```sh
-npx @vue-storefront/cli init
-```
-
### 1. Configure environment variables
After installation, the first step is configuring the integration using the environment variables.
diff --git a/docs/getting-started/configure-magento.md b/docs/getting-started/configure-magento.md
index 122c92853..f394b7eff 100644
--- a/docs/getting-started/configure-magento.md
+++ b/docs/getting-started/configure-magento.md
@@ -4,7 +4,7 @@ This guide explains the step needed to install and set up Magento 2 store for Vu
## Prerequisites
-Before you can get started, you need the following:
+Before you can get started, you need:
- **Docker Desktop** to setup Magento 2 locally,
- **Magento Marketplace account** to download Magento 2. To create it, visit [this page](https://account.magento.com/customer/account/create/).
@@ -24,7 +24,7 @@ Registry that stores Magento and other third-party packages require authenticati
Follow the [Get your authentication keys](https://devdocs.magento.com/guides/v2.4/install-gde/prereq/connect-auth.html) guide to generate new access keys.
-
+
### 2. Install Magento 2 store
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
new file mode 100644
index 000000000..e39aff446
--- /dev/null
+++ b/docs/getting-started/installation.md
@@ -0,0 +1,66 @@
+# Installation
+
+[[toc]]
+
+## Prerequisites
+
+Before proceeding, make sure you have [Node 16](https://nodejs.org/en/) installed. You can check this by running the following command:
+
+```bash
+node -v
+```
+
+## Installation steps
+
+### Step 1: Generate a new project
+
+The easiest way to get started with Vue Storefront is to set up your project using our CLI. You can run it using the following command:
+
+```bash
+npx @vue-storefront/cli init
+```
+
+It will ask you to enter the project's name and select the e-commerce platform you wish to use. Once selected, the CLI will create project files in the directory matching your project name.
+
+::: warning
+CLI will use the project name you enter to create a new directory, so avoid using special characters and spaces.
+:::
+
+### Step 2: Install dependencies
+
+Go to the newly created directory and install the required dependencies:
+
+```bash
+cd
+
+npm install
+```
+
+### Step 2. Setup and configure Magento
+
+Before you can configure the project, you need to set up and configure a new Magento instance that Vue Storefront will connect to. To do so, follow the [Configuring Magento](/getting-started/configure-magento.html) guide.
+
+### Step 3. Configure Vue Storefront
+
+With the Magento instance setup and configured, you can connect your project to it. To do so, follow the [Configuring Vue Storefront](/getting-started/configure-integration.html) guide.
+
+### Step 4. Start the project
+
+The project is now ready. You can start the application in development mode using the command below. You can read more about available commands and environments on the [Commands and deployment](https://nuxtjs.org/docs/2.x/get-started/commands/) page in Nuxt.js documentation.
+
+```bash
+npm run dev
+```
+
+## Recommended tools
+
+Below are the tools we use to make the development and debugging easier, and we recommend you use them too.
+
+### Vue.js Devtools
+
+We strongly recommend installing [Vue.js Devtools](https://devtools.vuejs.org/guide/installation.html) in your browser. It's an excellent tool for viewing component structures and their current state, inspecting events, routes, and much more.
+
+### Vetur for VS Code
+
+For those using Visual Studio Code as their primary code editor, we also recommend using [Vetur extension](https://marketplace.visualstudio.com/items?itemName=octref.vetur).
+It speeds up the development of Vue.js-based applications by providing features like Vue.js code autocompletion and syntax highlighting.
diff --git a/docs/guide/filters.md b/docs/guide/filters.md
new file mode 100644
index 000000000..cd2002137
--- /dev/null
+++ b/docs/guide/filters.md
@@ -0,0 +1,29 @@
+# Filters
+Filters are filterable attributes available on the category page to narrow the result set. Natively Magetno 2 supports a lot of configuration options for each attribute including renderer, where to display the attribute or how to do that. Unfortunately GraphQL has its limitations and most of the filter options are unavailable for headless fronted purposes. That is why we introduced extensive and configurable implementation to address that issue.
+
+### Renderers
+In the core we implemented four most commonly used renderer types:
+
+* Checkbox
+* Radio
+* Color Swatch
+* Yes No
+
+You can find all of them in `/modules/catalog/category/components/filters/renderer`
+
+### Configuration
+Filter's config file is located in `/modules/catalog/category/config/config.ts`. You can configure what filters you want to display, which renderer to use, what is the type of filter and eventually disable some of them. The list of filters is tha actual set of filter that will be displayed in the sidebar on the category page. If there is no filter configured - at least its code must be set - it will be not displayed.
+The default configuration fallback is as follows
+
+```javascript
+const defaultCfg = {
+ attrCode,
+ type: FilterTypeEnum.CHECKBOX,
+ component: RendererTypesEnum.CHECKBOX,
+ disabled: false,
+};
+```
+As you can see only the `attrCode` is required because it is not possible to guess or assume this parameter.
+
+### Query
+The command responsible for fetching all filters is `>/modules/catalog/category/components/filters/command/getProductFilterByCategoryCommand.ts` and the query `/modules/catalog/category/components/filters/command/getProductFilterByCategory.gql.ts`. This is the best place to modify filters fetching logic.
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
deleted file mode 100644
index 74e016beb..000000000
--- a/docs/guide/getting-started.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Getting Started
-
-## How to start if you want to try out the integration
-
-```
-yarn global add @vue-storefront/cli
-```
-```
-vsf init && cd && yarn && yarn dev
-```
-
-## How to start if you want to contribute?
-
-Want to contribute? Ping us on `magento2` channel on [our Discord](https://discord.vuestorefront.io)!
-
diff --git a/docs/guide/global-state-management.md b/docs/guide/global-state-management.md
new file mode 100644
index 000000000..dabbe88c6
--- /dev/null
+++ b/docs/guide/global-state-management.md
@@ -0,0 +1,165 @@
+# Global state management
+
+We use the [Pinia](https://pinia.vuejs.org/ssr/nuxt.html) for global state management. However, to make the application performant and easy to work with, we use it only to store a specific set of data.
+
+This document describes what data we store inside the global state and how we do it.
+
+## Local and global state
+
+Before we can dive deeper into the details, we need to answer one key question - what is the difference between global and local states?
+
+**Local state** is the state loaded and available only in one view. It might be a single component or group of components that share the data using component props. An example could be product information loaded from the API because you likely only need this data on one page.
+
+**Global state** is the state re-used across multiple views or components that are not directly related. This includes, but is not limited to:
+
+- store configuration,
+- categories in the navigation,
+- customer (user) data,
+- cart and checkout data.
+
+## Pinia stores
+
+Let's look at stores available out of the box:
+
+- **Config store** - provides information about available Magento stores, currency, and active store configuration.
+- **Category store** - provides information about product categories.
+- **Customer store** - provides information about the currently logged-in customer. Initially, it contains a boolean flag that indicates if there's a valid login session. The application loads more detailed data (shipping, billing addresses, etc.) if necessary.
+- **Cart store** - provides information about the cart, such as added items, quantity, and totals
+
+## How to create a store
+
+In the following example, we define a new store named `useWishlistStore`.
+
+```typescript
+import { defineStore } from 'pinia';
+import type { Wishlist } from '~/modules/GraphQL/types';
+
+interface WishlistStore {
+ wishlist: Wishlist[] | null;
+}
+
+export const useWishlistStore = defineStore('wishlist', {
+ state: (): WishlistStore => ({
+ wishlist: null,
+ }),
+});
+```
+
+The `useWishlistStore` has one state field: `wishlist` that is initially `null`.
+
+### Consuming data from the store
+
+Consuming data from the store is straightforward. All you need to do is to import it and use it similarly to composables:
+
+```typescript
+import { useWishlistStore } from '~/stores/wishlistStore';
+
+export default {
+ setup() {
+ const wishlistStore = useWishlistStore();
+
+ return {
+ // you can return the whole store instance to use it in the template
+ wishlistStore
+ };
+ },
+}
+```
+
+If you want to return a specific set of fields, you need to wrap the field with `computed()`. If you donβt do that, reactivity will break.
+
+```javascript
+import { useWishlistStore } from '~/stores/wishlistStore';
+
+export default {
+ setup() {
+ const { wishlist } = useWishlistStore();
+
+ return {
+ wishlistItems: computed(() => wishlist.items),
+ };
+ },
+}
+```
+
+### Modifying data in the store
+
+To write data to the store, we recommend using $patch like this:
+
+```javascript
+const { data } = await app.$vsf.$magento.api.wishlist();
+ wishlistStore.$patch((state) => {
+ state.wishlist = data.data?.customer?.wishlists ?? [];
+ });
+```
+
+## How to call the API in the store
+
+If you want to communicate with an API directly from the store, you need to use the `$graphql` Nuxt plugin accessible in the `this` keyword. It provides two objects:
+
+- The `query` object for fetching data. It contains the `request` method that accepts GraphQL query as an argument.
+- The `mutate` for submitting data. It contains the `mutate` method that accepts a GraphQL mutation as an argument
+
+Both methods return a **Promise**.
+
+Let's see an example in which we define a new `load` action in the store:
+
+```javascript
+import { defineStore } from 'pinia';
+import type { Wishlist } from '~/modules/GraphQL/types';
+import wishlistGql from "./categoryList.gql";
+
+interface WishlistStore {
+ wishlist: Wishlist[] | null;
+}
+
+export const useWishlistStore = defineStore('wishlist', {
+ state: (): WishlistStore => ({
+ wishlist: null,
+ }),
+ actions: {
+ async load() {
+ const response = await this.$nuxt.$graphql.query.request<{ wishlist: Wishlist }>(wishlistGql);
+ this.wishlist = response.wishlist;
+ },
+ },
+});
+```
+
+The `wishlistGql` is the GraphQL query that can look like this:
+
+```javascript
+import { gql } from 'graphql-request';
+
+export default gql`
+ query wishlist {
+ customer {
+ wishlists {
+ id
+ items_count
+ }
+ }
+ }
+`;
+```
+
+When you want to use the `load` action from the example above in a component, you can do it like so:
+
+```javascript
+import { useWishlistStore } from '~/stores/wishlistStore';
+
+export default {
+ setup() {
+ const wishlistStore = useWishlistStore();
+ const wishlist = computed(() => wishlistStore.wishlist);
+
+ onMounted(() => {
+ wishlistStore.load(); // loading wishlist
+ });
+
+ return {
+ wishlist,
+ };
+ },
+}
+```
diff --git a/docs/guide/override-queries.md b/docs/guide/override-queries.md
index dcdf05eee..988a97552 100644
--- a/docs/guide/override-queries.md
+++ b/docs/guide/override-queries.md
@@ -82,11 +82,10 @@ Make sure you have `graphgl-tag` installed as dependency prior using this sample
},
},
};
- ```
4. Now you can restart your dev environment and view the updated data queried.
-:::warning
+::: warning
`thumbnail` is a must-have field to query. It is used for our default image rendering (for Nuxt image). DO NOT remove it from the query in any circumstance.
:::
diff --git a/docs/migration-guides/1.0.0-rc.8/index.md b/docs/migration-guides/1.0.0-rc.8/index.md
index d2b59374e..4f318e915 100644
--- a/docs/migration-guides/1.0.0-rc.8/index.md
+++ b/docs/migration-guides/1.0.0-rc.8/index.md
@@ -2,7 +2,7 @@
Vue Storefront for Magento 1.0.0.rc.8 contains backward-incompatible changes. To review these backward-incompatible changes, see
-[1.0.0-rc.7 **Backward incompatible changes reference**](./rc.8-bic.md)
+[1.0.0-rc.8 **Backward incompatible changes reference**](./rc.8-bic.md)
## Vue Storefront for Magento 1.0.0-rc.8 highlights
diff --git a/docs/migration-guides/1.0.0-rc.9/index.md b/docs/migration-guides/1.0.0-rc.9/index.md
new file mode 100644
index 000000000..ddae70a2c
--- /dev/null
+++ b/docs/migration-guides/1.0.0-rc.9/index.md
@@ -0,0 +1,64 @@
+# Vue Storefront for Magento 1.0.0-rc.9 release notes
+
+Vue Storefront for Magento 1.0.0.rc.9 contains backward-incompatible changes. To review these backward-incompatible changes, see
+
+[1.0.0-rc.9 **Backward incompatible changes reference**](./rc.9-bic.md)
+
+## Vue Storefront for Magento 1.0.0-rc.9 highlights
+
+### New f**eatures**
+
+- feat: added productSkeleton component [#1097](https://github.com/vuestorefront/magento2/pull/1097)
+- feat: add configurable filters [#1060](https://github.com/vuestorefront/magento2/pull/1060)
+
+### **Bugfix**
+
+- fix!: search bar not returning results [#1087](https://github.com/vuestorefront/magento2/pull/1087)
+- fix: total price and discount calculation [#1090](https://github.com/vuestorefront/magento2/pull/1090)
+- fix: removed filters skeleton on mobile devices [#1100](https://github.com/vuestorefront/magento2/pull/1100)
+- fix: category page filters are taken off after using pagination [#1093](https://github.com/vuestorefront/magento2/pull/1093)
+- fix: bundle product option change special price calculation
+- fix: grouped product special price calculation [#1069](https://github.com/vuestorefront/magento2/pull/1069)
+- fix: category page header invalid title
+- fix: disable body scroll lock [#1059](https://github.com/vuestorefront/magento2/pull/1059)
+- fix: fetch new orders on each orders history visit AND move order information to separate section [#1046](https://github.com/vuestorefront/magento2/pull/1046)
+
+### Refactors
+
+- refactor!: refactor the useApi composable [#1104](https://github.com/vuestorefront/magento2/pull/1107 https://github.com/vuestorefront/magento2/pull/1104)
+- refactor: make unit tests typing work even when theme is moved to template-magento [#1091](https://github.com/vuestorefront/magento2/pull/1091)
+- refactor: remove some instances of implicit any [#1066](https://github.com/vuestorefront/magento2/pull/1066)
+- refactor: double-check types in composables [#1085](https://github.com/vuestorefront/magento2/pull/1085)
+- refactor!: refactored useUrlResolver to use the route query [#1078](https://github.com/vuestorefront/magento2/pull/1078)
+- refactor!: break down login modal into separate components [#1095](https://github.com/vuestorefront/magento2/pull/1095)
+- refactor: use null instead of {} [#1068](https://github.com/vuestorefront/magento2/pull/1068)
+- refactor: improve typing of sorting in facetGetters/category.vue [#1080](https://github.com/vuestorefront/magento2/pull/1080)
+- refactor: add interface for useProductsWithCommonCardProps [#1086](https://github.com/vuestorefront/magento2/pull/1086)
+- refactor: remove a few more instances of implicit any [#1071](https://github.com/vuestorefront/magento2/pull/1071)
+- refactor: add types for HeaderNavigation*.vue components [#1079](https://github.com/vuestorefront/magento2/pull/1079)
+- refactor!: add typing for VsfPaymentProvider.vue [#1077](https://github.com/vuestorefront/magento2/pull/1077)
+- refactor: connection to api refactor [https://github.com/vuestorefront/magento2/pull/1101](https://github.com/vuestorefront/magento2/pull/1101)
+- refactor: remove useless function [#1083](https://github.com/vuestorefront/magento2/pull/1083)
+- refactor: fixed customer logging and authorization checking [#1081](https://github.com/vuestorefront/magento2/pull/1081)
+- refactor: resolve all todos [#1064](https://github.com/vuestorefront/magento2/pull/1064)
+
+### Tests
+
+- test: added tests for selectedfilters component [#1067](https://github.com/vuestorefront/magento2/pull/1067)
+- test: add tests for filter renderers [#1065](https://github.com/vuestorefront/magento2/pull/1065)
+- test: added tests for categoryfilters component [#1076](https://github.com/vuestorefront/magento2/pull/1076)
+- test: added tests for category sidebar component [#1028](https://github.com/vuestorefront/magento2/pull/1028)
+- test: add CategorySidebar tests [#1103](https://github.com/vuestorefront/magento2/pull/1103)
+
+### Chore
+
+- chore: fix all remaining .vue lang="ts" errors [#1043](https://github.com/vuestorefront/magento2/pull/1043)
+- chore: updated contributors list [#1074](https://github.com/vuestorefront/magento2/pull/1074)
+
+### Documentation
+
+- docs: added documentation about global state management [#1072](https://github.com/vuestorefront/magento2/pull/1072)
+- docs: add catalog module documentation [#1082](https://github.com/vuestorefront/magento2/pull/1082)
+- docs: fix typos in Composables document [#1102](https://github.com/vuestorefront/magento2/pull/1102)
+- docs: new Installation document and image optimization [#1084](https://github.com/vuestorefront/magento2/pull/1084)
+- docs: add composables docs [#1062](https://github.com/vuestorefront/magento2/pull/1062)
diff --git a/docs/migration-guides/1.0.0-rc.9/rc.9-bic.md b/docs/migration-guides/1.0.0-rc.9/rc.9-bic.md
new file mode 100644
index 000000000..4042f89ed
--- /dev/null
+++ b/docs/migration-guides/1.0.0-rc.9/rc.9-bic.md
@@ -0,0 +1,21 @@
+# 1.0.0-rc.9 Backward incompatible changes reference
+
+In this document, you can see crucial breaking changes in the `1.0.0-rc.9` comparing to `1.0.0-rc.8` release. To see all changes, please take a look at the [release pull request](https://github.com/vuestorefront/magento2/pull/1110)
+
+## Theme
+
+| File | what and how it changed |
+|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
+| packages/theme/components/Checkout/VsfPaymentProvider.vue | names of variables have been changed |
+| packages/theme/components/LoginModal.vue | The file has been moved to packages/theme/modules/customer/components/LoginModal/LoginModal.vue |
+| packages/theme/components/NewProducts.vue | :is-added-to-cart event argument has been changed |
+| packages/theme/composables/useApi/index.ts | From now the useApi use methods from ApiClient (customQuery, customMutation) to communicate with Magento API |
+| packages/theme/composables/useUrlResolver/index.ts | changed GraphQL query to the route query |
+| packages/theme/modules/catalog/category/stores/category.ts | from now it uses API client to fetch data instead of graphql plugin |
+| packages/theme/modules/catalog/pages/category.vue | SfPagination is replaced by the new CategoryPagination components, values of props and variables have been changed |
+| All *.gql.ts files in the theme package | changed the `gql` dependency import from graphq-request to graphql-tag |
+| packages/theme/modules/catalog/product/components/ProductsCarousel.vue | :is-added-to-cart event argument has been changed |
+| packages/theme/modules/customer/getters/userShippingGetters.ts | getters names have been changed |
+| packages/theme/modules/customer/pages/MyAccount/MyWishlist.vue | :is-added-to-cart event argument has been changed |
+| packages/theme/nuxt.config.js | GraphQL plugin has been removed |
+| packages/theme/plugins/storeConfigPlugin.ts | from now it uses API client to fetch data instead of graphql plugin |
diff --git a/docs/migration-guides/index.md b/docs/migration-guides/index.md
index ffd32ebb3..af51b5602 100644
--- a/docs/migration-guides/index.md
+++ b/docs/migration-guides/index.md
@@ -2,3 +2,4 @@
- [1.0.0-rc.7](./1.0.0-rc.7/)
- [1.0.0-rc.8](./1.0.0-rc.8/)
+- [1.0.0-rc.9](./1.0.0-rc.9/)
diff --git a/docs/modules/catalog/filters.md b/docs/modules/catalog/filters.md
new file mode 100644
index 000000000..995e470a2
--- /dev/null
+++ b/docs/modules/catalog/filters.md
@@ -0,0 +1,117 @@
+# Filters
+
+The category page contains filters in the sidebar. Each of these filters narrows down the list of product results and renders differently.
+
+We introduced renderers for the most commonly used types and the mechanism to help you add and configure the filter list.
+
+## Renderers
+
+Before we discuss the mechanism that powers the filters list and how to configure it, let's explore the available renderers and when to use them:
+
+- **Checkbox** for selecting multiple options from a list,
+- **Radio** for selecting a single option from a list,
+- **Color swatch** for displaying a list of colors and selecting multiple options,
+- **Yes/No** for toggling between the `true` and `false` options.
+
+You can find all of them in the `modules/catalog/category/components/filters/renderer` folder. The naming convention we used for these renderers is their type name followed by the `Type` keyword, e.g.: `RadioType` or `CheckboxType`.
+
+## Configuration
+
+Filters sidebar on the Category page is configured in the `modules/catalog/category/config/config.ts` file. It exports the `config()` function that returns an array of filter configurations. By modifying this array, you can configure filters, disable them, decide which renderers to use, etc.
+
+Each filter can be customized using the following properties:
+
+- `attrCode` (required) - attribute code and that must match Magento's attribute code,
+- `type` (default: `checkbox`) - attribute type,
+- `component` (default: `checkbox`) - component to render,
+- `disabled` (default: `false`) - when set to `true` the attribute will not be displayed, nor affect the results.
+
+Although the `type` and `component` fields might have the same values, they serve different purposes. It is best illustrated in the `yesno` renderer, whose type is `yes_no`, but the component used is `radio`. Even if renderers are the same, it's worth checking which component renders in the application.
+
+### Examples
+
+To change the filters, open the `modules/catalog/category/config/config.ts` configuration file and update the array returned from the `config()` function.
+
+#### Adding new filters
+
+To add a new filter, add a new object to the array with at least the `attrCode` property. Such configuration will generate a `checkbox` filter because this is the default value for both the `types` and `component` properties.
+
+```diff
+return [
++ {
++ attrCode: 'capacity',
++ },
+ // Other filters
+]
+```
+
+#### Disabling filters
+
+To disable a filter, set that filter's `disabled` property to `true`.
+
+```diff
+return [
+ {
+ attrCode: 'material',
+- disabled: false,
++ disabled: true,
+ },
+ // Other filters
+]
+```
+
+## GraphQL query
+
+If you want to see or modify the way the application loads the filters, see the following files:
+
+- `modules/catalog/category/components/filters/command/getProductFilterByCategoryCommand.ts` file for request handler,
+- `modules/catalog/category/components/filters/command/getProductFilterByCategory.gql.ts` for GraphQL query.
+
+## Adding new filter type and renderer
+
+### Creating a new renderer
+
+To create a new renderer, open the `modules/catalog/category/components/filters/renderer` folder and add a new file following the naming convention mentioned in the [Renderers](#renderers) section, for example `CustomType.vue`.
+
+Every renderer must:
+
+- Emit `selectFilter` event whenever an option is selected (type: [AggregationOption](/api-reference/magento-theme.aggregationoption.html)). Otherwise parent component will not know about the change.
+
+ ```html
+
+ ```
+
+- Accept the `filter` prop (type: [Aggregation](/api-reference/magento-theme.aggregation.html))
+
+ ```typescript
+ export default {
+ props: {
+ filter: {
+ type: Object as PropType,
+ required: true
+ }
+ }
+ }
+ ```
+
+Once you have your custom renderer in place, you have to add a new renderer type to the enum located in the `modules/catalog/category/components/filters/renderer/RendererTypesEnum.ts` file.
+
+```diff
+enum RendererTypesEnum {
++ CUSTOM = 'CustomType',
+ // Other types
+}
+```
+
+Finally, to use the new renderer, open the `modules/catalog/category/config/config.ts` configuration file and update the array returned from the `config()` function.
+
+```diff
+return [
++ {
++ attrCode: 'custom',
++ type: FilterTypeEnum.RADIO, // Value of this property might be different
++ component: RendererTypesEnum.CUSTOM,
++ }
+ // Other filters
+]
+```
diff --git a/docs/modules/catalog/product-types.md b/docs/modules/catalog/product-types.md
new file mode 100644
index 000000000..d89631a7a
--- /dev/null
+++ b/docs/modules/catalog/product-types.md
@@ -0,0 +1,73 @@
+# Product Types
+
+Magento has different types of products, and each of them requires a different way of presenting data. To address this problem, we introduced Vue components for the four most commonly used product types:
+
+- Simple
+- Grouped
+- Configurable
+- Bundle
+
+These components are located in the `modules/catalog/product/components/product-types` folder.
+
+## Product rendering
+
+The `modules/catalog/pages/product.vue` component that powers the `/product` route dynamically switches between components based on the type of the current product.
+
+Firstly, components for all product types are registered using dynamic imports:
+
+```typescript
+export default {
+ components: {
+ SimpleProduct: () => import('...'),
+ BundleProduct: () => import('...'),
+ ConfigurableProduct: () => import('...'),
+ GroupedProduct: () => import('...'),
+ }
+}
+```
+
+Then, the `renderer` variable reads the type of the current product based on the data from Magento API (with a fallback to the `simple` type).
+
+```typescript
+const renderer = computed(() => product.value?.__typename ?? ProductTypeEnum.SIMPLE_PRODUCT);
+```
+
+Component passes this variable to the `:is` attribute of the dynamic `` to switch between product components.
+
+```html{2}
+
+```
+
+## How to add a custom product type?
+
+To add support for custom product types, create a new component inside the `modules/catalog/product/components/product-types` folder.
+
+It should accept two props:
+
+- `product` (type: [Product](http://localhost:8080/api-reference/magento-theme.product.html)) - product object fetched from the API,
+- `isFetching` (type: `boolean`) - a flag indicating whether the `product.vue` components currently fetches product data .
+
+```typescript
+export default {
+ props: {
+ product: {
+ type: [Object, null] as PropType,
+ default: null,
+ },
+ isFetching: {
+ type: Boolean,
+ default: true,
+ },
+ }
+};
+```
+
+Then, open the `modules/catalog/pages/product.vue` file and register the newly created component in the `components` object using dynamic import. Make sure that the name of this component matches the `__typename` value returned from the Magento API.
+
+Once done, the page should dynamically import the component and feed it with product data.
diff --git a/internals/eslint-import/package.json b/internals/eslint-import/package.json
index d0434c369..55c9a4997 100644
--- a/internals/eslint-import/package.json
+++ b/internals/eslint-import/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/eslint-config-import",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"author": "Heitor Ramon Ribeiro ",
"license": "MIT",
"scripts": {
diff --git a/internals/eslint-jest/package.json b/internals/eslint-jest/package.json
index 29cb2b5bb..347a386f7 100644
--- a/internals/eslint-jest/package.json
+++ b/internals/eslint-jest/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/eslint-config-jest",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"author": "Heitor Ramon Ribeiro ",
"license": "MIT",
"scripts": {
diff --git a/internals/eslint-typescript/package.json b/internals/eslint-typescript/package.json
index 8891b7565..017e17fb7 100644
--- a/internals/eslint-typescript/package.json
+++ b/internals/eslint-typescript/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/eslint-config-typescript",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"author": "Heitor Ramon Ribeiro ",
"license": "MIT",
"scripts": {
diff --git a/internals/eslint-vue/package.json b/internals/eslint-vue/package.json
index 990636ff1..3c6f321f7 100644
--- a/internals/eslint-vue/package.json
+++ b/internals/eslint-vue/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/eslint-config-vue",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"author": "Heitor Ramon Ribeiro ",
"license": "MIT",
"scripts": {
diff --git a/internals/eslint/package.json b/internals/eslint/package.json
index 348c9e22d..8f8a3f2b2 100644
--- a/internals/eslint/package.json
+++ b/internals/eslint/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/eslint-config-base",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"author": "Heitor Ramon Ribeiro ",
"license": "MIT",
"scripts": {
diff --git a/packages/api-client/README.md b/packages/api-client/README.md
index 87ba9b077..7a5e5f8bf 100644
--- a/packages/api-client/README.md
+++ b/packages/api-client/README.md
@@ -16,9 +16,10 @@ This integration developed by superheroes from [Caravel](https://github.com/cara
-[](#contributors-)
+[](#contributors-)
+
## How to start if you want to try out the integration
```
@@ -132,6 +133,10 @@ Thanks go to these wonderful people π:
Jonathan Ribas π»
Ali Ghanei π»
Maya Shavin π
+ Alexander Devitsky π»
+
+
+ Diego Alba π»
diff --git a/packages/api-client/package.json b/packages/api-client/package.json
index e9be8c4a8..1a071eed6 100644
--- a/packages/api-client/package.json
+++ b/packages/api-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/magento-api",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"sideEffects": false,
"homepage": "https://github.com/vuestorefront/magento2",
"bugs": {
diff --git a/packages/api-client/src/api/addBundleProductsToCart/index.ts b/packages/api-client/src/api/addBundleProductsToCart/index.ts
index cdf8c021e..64290e3eb 100644
--- a/packages/api-client/src/api/addBundleProductsToCart/index.ts
+++ b/packages/api-client/src/api/addBundleProductsToCart/index.ts
@@ -7,6 +7,7 @@ import {
AddBundleProductsToCartInput,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -27,5 +28,8 @@ export default async (
.mutate({
mutation: addBundleProductsToCartGQL.query,
variables: addBundleProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/addConfigurableProductsToCart/index.ts b/packages/api-client/src/api/addConfigurableProductsToCart/index.ts
index 0bceff723..715078ec0 100644
--- a/packages/api-client/src/api/addConfigurableProductsToCart/index.ts
+++ b/packages/api-client/src/api/addConfigurableProductsToCart/index.ts
@@ -5,6 +5,7 @@ import {
AddConfigurableProductsToCartInput, AddConfigurableProductsToCartMutation, AddConfigurableProductsToCartMutationVariables,
} from '../../types/GraphQL';
import addConfigurableProductsToCartMutation from './addConfigurableProductsToCart';
+import getHeaders from '../getHeaders';
/**
* Adds a set of configurable products to a specified cart
@@ -29,5 +30,8 @@ export default async function addConfigurableProductsToCart(
return context.client.mutate({
mutation: addConfigurableProductsToCartGQL.query,
variables: addConfigurableProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/addDownloadableProductsToCart/index.ts b/packages/api-client/src/api/addDownloadableProductsToCart/index.ts
index f70a27f9a..e2b52b11d 100644
--- a/packages/api-client/src/api/addDownloadableProductsToCart/index.ts
+++ b/packages/api-client/src/api/addDownloadableProductsToCart/index.ts
@@ -7,6 +7,7 @@ import {
AddDownloadableProductsToCartMutationVariables,
} from '../../types/GraphQL';
import addDownloadableProductsToCartMutation from './addDownloadableProductsToCart';
+import getHeaders from '../getHeaders';
/**
* Adds a set of downloadable products to a specified cart
@@ -31,5 +32,8 @@ export default async function addDownloadableProductsToCart(
return context.client.mutate({
mutation: addDownloadableProductsToCartGQL.query,
variables: addDownloadableProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/addProductToWishList/index.ts b/packages/api-client/src/api/addProductToWishList/index.ts
index 2768a2b88..13d28e199 100644
--- a/packages/api-client/src/api/addProductToWishList/index.ts
+++ b/packages/api-client/src/api/addProductToWishList/index.ts
@@ -6,6 +6,7 @@ import {
AddProductsToWishlistMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -24,5 +25,8 @@ export default async (
return context.client.mutate({
mutation: addProductsToWishlistGQL.query,
variables: addProductsToWishlistGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/addProductsToCart/index.ts b/packages/api-client/src/api/addProductsToCart/index.ts
index 6db9bce1a..9c29cdfe4 100644
--- a/packages/api-client/src/api/addProductsToCart/index.ts
+++ b/packages/api-client/src/api/addProductsToCart/index.ts
@@ -5,6 +5,7 @@ import {
AddProductsToCartMutation, CartItemInput,
} from '../../types/GraphQL';
import addProductsToCartMutation from './addProductsToCart';
+import getHeaders from '../getHeaders';
export type AddProductsToCartInput = {
cartId: string;
@@ -34,5 +35,8 @@ export default async function addProductsToCart(
return context.client.mutate({
mutation: addProductsToCartGQL.query,
variables: addProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/addSimpleProductsToCart/index.ts b/packages/api-client/src/api/addSimpleProductsToCart/index.ts
index 688c2eb72..d66048806 100644
--- a/packages/api-client/src/api/addSimpleProductsToCart/index.ts
+++ b/packages/api-client/src/api/addSimpleProductsToCart/index.ts
@@ -7,6 +7,7 @@ import {
AddSimpleProductsToCartMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -25,5 +26,8 @@ export default async (
return context.client.mutate({
mutation: addSimpleProductsToCartGQL.query,
variables: addSimpleProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/addVirtualProductsToCart/index.ts b/packages/api-client/src/api/addVirtualProductsToCart/index.ts
index 4b118def8..f031b79d4 100644
--- a/packages/api-client/src/api/addVirtualProductsToCart/index.ts
+++ b/packages/api-client/src/api/addVirtualProductsToCart/index.ts
@@ -7,6 +7,7 @@ import {
AddVirtualProductsToCartMutationVariables,
} from '../../types/GraphQL';
import addVirtualProductsToCartMutation from './addVirtualProductsToCart';
+import getHeaders from '../getHeaders';
/**
* Adds a set of virtual products to a specified cart
@@ -31,5 +32,8 @@ export default async function addVirtualProductsToCart(
return context.client.mutate({
mutation: addVirtualProductsToCartGQL.query,
variables: addVirtualProductsToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/applyCouponToCart/index.ts b/packages/api-client/src/api/applyCouponToCart/index.ts
index 15c6c21ee..fff6bd8bc 100644
--- a/packages/api-client/src/api/applyCouponToCart/index.ts
+++ b/packages/api-client/src/api/applyCouponToCart/index.ts
@@ -7,7 +7,7 @@ import {
ApplyCouponToCartMutationVariables,
} from '../../types/GraphQL';
import applyCouponToCartMutation from './applyCouponToCart';
-// @TODO : Add Mutation FROM CodeGEN
+import getHeaders from '../getHeaders';
/**
* Applies a coupon to a given card
@@ -32,5 +32,8 @@ export default async function applyCouponToCart(
return context.client.mutate({
mutation: applyCouponToCartGQL.query,
variables: applyCouponToCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/availableStores/index.ts b/packages/api-client/src/api/availableStores/index.ts
index 7c016163b..82a2ddc48 100644
--- a/packages/api-client/src/api/availableStores/index.ts
+++ b/packages/api-client/src/api/availableStores/index.ts
@@ -5,6 +5,7 @@ import {
} from '../../types/GraphQL';
import availableStores from './availableStores';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Returns list of available stores
@@ -24,5 +25,8 @@ export default async (
return context.client.query({
query: availableStoresGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/cart/index.ts b/packages/api-client/src/api/cart/index.ts
index 1d0e18a6b..2c722bc43 100644
--- a/packages/api-client/src/api/cart/index.ts
+++ b/packages/api-client/src/api/cart/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { Context } from '../../types/context';
import type { CartQuery, CartQueryVariables } from '../../types/GraphQL';
import cartQuery from './cart';
+import getHeaders from '../getHeaders';
/**
* Fetches a cart by its ID
@@ -27,5 +28,8 @@ export default async function cart(
return context.client.query({
query: cartGQL.query,
variables: cartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/cartTotalQty/index.ts b/packages/api-client/src/api/cartTotalQty/index.ts
index 2620f8486..62902ebbb 100644
--- a/packages/api-client/src/api/cartTotalQty/index.ts
+++ b/packages/api-client/src/api/cartTotalQty/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CartQuery, CartQueryVariables } from '../../types/GraphQL';
import query from './cartTotalQty';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -21,5 +22,8 @@ export default async (
return context.client.query({
query: cartTotalQty.query,
variables: cartTotalQty.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/categoryList/index.ts b/packages/api-client/src/api/categoryList/index.ts
index edba70589..c59c9bc69 100644
--- a/packages/api-client/src/api/categoryList/index.ts
+++ b/packages/api-client/src/api/categoryList/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CategoryListQuery, CategoryListQueryVariables } from '../../types/GraphQL';
import categoryListQuery from './categoryList';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches the category list.
@@ -26,5 +27,8 @@ export default async function categoryList(
return context.client.query({
query: categoryListGQL.query,
variables: categoryListGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/categorySearch/index.ts b/packages/api-client/src/api/categorySearch/index.ts
index 115e1bd98..fa35fce5a 100644
--- a/packages/api-client/src/api/categorySearch/index.ts
+++ b/packages/api-client/src/api/categorySearch/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CategorySearchQuery, CategorySearchQueryVariables } from '../../types/GraphQL';
import categorySearchQuery from './categorySearch';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Searches for categories using received filters.
@@ -28,5 +29,8 @@ export default async function categorySearch(
return context.client.query({
query: categorySearchGQL.query,
variables: categorySearchGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/changeCustomerPassword/index.ts b/packages/api-client/src/api/changeCustomerPassword/index.ts
index 8d3473859..8a7abd645 100644
--- a/packages/api-client/src/api/changeCustomerPassword/index.ts
+++ b/packages/api-client/src/api/changeCustomerPassword/index.ts
@@ -6,6 +6,7 @@ import {
ChangeCustomerPasswordMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Changes password of the current customer. To override the default query, use the `changeCustomerPassword` query key.
@@ -29,6 +30,9 @@ export default async (
.mutate({
mutation: changeCustomerPasswordGQL.query,
variables: changeCustomerPasswordGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
// For error in data we don't throw 500, because it's not server error
diff --git a/packages/api-client/src/api/cmsBlocks/index.ts b/packages/api-client/src/api/cmsBlocks/index.ts
index fd425ae6d..eb6ee5f77 100644
--- a/packages/api-client/src/api/cmsBlocks/index.ts
+++ b/packages/api-client/src/api/cmsBlocks/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CmsBlockQuery, CmsBlockQueryVariables } from '../../types/GraphQL';
import cmsBlocks from './cmsBlocks';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetch CMS Blocks from Magento Api.
@@ -28,5 +29,8 @@ export default async function getCmsBlocks(
return context.client.query({
query: cmsBlocksGQL.query,
variables: cmsBlocksGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/cmsPage/index.ts b/packages/api-client/src/api/cmsPage/index.ts
index 893b01cd0..661708957 100644
--- a/packages/api-client/src/api/cmsPage/index.ts
+++ b/packages/api-client/src/api/cmsPage/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery, Logger } from '@vue-storefront/core';
import { CmsPageQueryVariables, CmsPageQuery } from '../../types/GraphQL';
import cmsPage from './cmsPage';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetch CMS Page from Magento
@@ -30,6 +31,9 @@ export default async function getCmsPage(
.query({
query: cmsPageGQL.query,
variables: cmsPageGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
// For error in data we don't throw 500, because it's not server error
diff --git a/packages/api-client/src/api/countries/index.ts b/packages/api-client/src/api/countries/index.ts
index 1290fa452..db1584234 100644
--- a/packages/api-client/src/api/countries/index.ts
+++ b/packages/api-client/src/api/countries/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CountriesListQuery } from '../../types/GraphQL';
import countriesListQuery from './countriesList';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Loads the list of countries
@@ -23,5 +24,8 @@ export default async function countries(
);
return context.client.query({
query: countriesGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/country/index.ts b/packages/api-client/src/api/country/index.ts
index cd48e15df..14d79abe6 100644
--- a/packages/api-client/src/api/country/index.ts
+++ b/packages/api-client/src/api/country/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CountryInformationQuery, CountryInformationQueryVariables } from '../../types/GraphQL';
import countryInformation from './countryInformation';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches the information about a country given its ID
@@ -27,5 +28,8 @@ export default async function country(
return context.client.query({
query: countryGQL.query,
variables: countryGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/createCustomer/index.ts b/packages/api-client/src/api/createCustomer/index.ts
index 456b16080..4578c5a10 100644
--- a/packages/api-client/src/api/createCustomer/index.ts
+++ b/packages/api-client/src/api/createCustomer/index.ts
@@ -9,6 +9,7 @@ import {
} from '../../types/GraphQL';
import createCustomer from './createCustomer';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Registers a new customer. To override the default query, use the `createCustomer` query key.
@@ -47,11 +48,12 @@ export default async (
},
);
- return await context
- .client
- .mutate({
+ return await context.client.mutate({
mutation: createCustomerGQL.query,
variables: createCustomerGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
// For error in data we don't throw 500, because it's not server error
diff --git a/packages/api-client/src/api/createCustomerAddress/index.ts b/packages/api-client/src/api/createCustomerAddress/index.ts
index 098c12700..e3f45e3e5 100644
--- a/packages/api-client/src/api/createCustomerAddress/index.ts
+++ b/packages/api-client/src/api/createCustomerAddress/index.ts
@@ -7,6 +7,7 @@ import {
CustomerAddressInput,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Creates a customer address.
@@ -33,5 +34,8 @@ export default async function createCustomerAddress(
return context.client.mutate({
mutation: createCustomerAddressGQL.query,
variables: createCustomerAddressGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/createEmptyCart/index.ts b/packages/api-client/src/api/createEmptyCart/index.ts
index 41a6bb3a9..1e5394987 100644
--- a/packages/api-client/src/api/createEmptyCart/index.ts
+++ b/packages/api-client/src/api/createEmptyCart/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CreateEmptyCartMutation } from '../../types/GraphQL';
import createEmptyCart from './createEmptyCart';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -19,5 +20,8 @@ export default async (
return context.client.mutate({
mutation: createEmptyCartGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/createProductReview/index.ts b/packages/api-client/src/api/createProductReview/index.ts
index 217a0a49a..7671b69bf 100644
--- a/packages/api-client/src/api/createProductReview/index.ts
+++ b/packages/api-client/src/api/createProductReview/index.ts
@@ -5,6 +5,7 @@ import { CreateProductReviewMutation, CreateProductReviewInput } from '../../typ
import createProductReview from './createProductReview';
import { Context } from '../../types/context';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';
+import getHeaders from '../getHeaders';
/**
* Creates a new product review
@@ -45,5 +46,8 @@ export default async (
return context.client.mutate({
mutation: createProductReviewGQL.query,
variables: createProductReviewGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/currency/index.ts b/packages/api-client/src/api/currency/index.ts
index a42c70d7b..2513d6a93 100644
--- a/packages/api-client/src/api/currency/index.ts
+++ b/packages/api-client/src/api/currency/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CurrencyQuery } from '../../types/GraphQL';
import currencyQuery from './currency';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches the currency information.
@@ -22,5 +23,8 @@ export default async function currency(
return context.client.query({
query: currencyGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/customMutation/index.ts b/packages/api-client/src/api/customMutation/index.ts
index 562442c79..dbe9d36cc 100644
--- a/packages/api-client/src/api/customMutation/index.ts
+++ b/packages/api-client/src/api/customMutation/index.ts
@@ -1,21 +1,24 @@
-import gql from 'graphql-tag';
import { FetchPolicy, FetchResult } from '@apollo/client/core';
+import { DocumentNode } from 'graphql';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
- { client }: Context,
+ context: Context,
{
mutation,
mutationVariables,
fetchPolicy,
}: {
- mutation: MUTATION,
+ mutation: DocumentNode,
mutationVariables: MUTATION_VARIABLES,
fetchPolicy?: Extract,
},
-): Promise> => client
- .mutate({
- mutation: gql`${mutation}`,
+): Promise> => context.client.mutate({
+ mutation,
variables: { ...mutationVariables },
fetchPolicy: fetchPolicy || 'no-cache',
+ context: {
+ headers: getHeaders(context),
+ },
});
diff --git a/packages/api-client/src/api/customQuery/index.ts b/packages/api-client/src/api/customQuery/index.ts
index 4e41cdccf..970f3c1e7 100644
--- a/packages/api-client/src/api/customQuery/index.ts
+++ b/packages/api-client/src/api/customQuery/index.ts
@@ -1,21 +1,24 @@
-import gql from 'graphql-tag';
import { ApolloQueryResult, FetchPolicy } from '@apollo/client/core';
+import { DocumentNode } from 'graphql';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
- { client }: Context,
+ context: Context,
{
query,
queryVariables,
fetchPolicy,
}: {
- query: QUERY,
+ query: DocumentNode,
queryVariables?: QUERY_VARIABLES,
fetchPolicy?: FetchPolicy,
},
-): Promise> => client
- .query({
- query: gql`${query}`,
+): Promise> => context.client.query({
+ query,
variables: { ...queryVariables },
fetchPolicy: fetchPolicy || 'no-cache',
+ context: {
+ headers: getHeaders(context),
+ },
});
diff --git a/packages/api-client/src/api/customer/index.ts b/packages/api-client/src/api/customer/index.ts
index 2a72aa6c0..d1462c3b5 100644
--- a/packages/api-client/src/api/customer/index.ts
+++ b/packages/api-client/src/api/customer/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { CustomerQuery } from '../../types/GraphQL';
import customer from './customer';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Returns the information about the current customer. To override the default query, use the `customer` query key.
@@ -22,5 +23,8 @@ export default async (
return context.client.query({
query: customerGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/customerCart/index.ts b/packages/api-client/src/api/customerCart/index.ts
index 443642722..6f44612be 100644
--- a/packages/api-client/src/api/customerCart/index.ts
+++ b/packages/api-client/src/api/customerCart/index.ts
@@ -3,6 +3,7 @@ import type { CustomQuery } from '@vue-storefront/core';
import type { Context } from '../../types/context';
import type { CustomerCartQuery } from '../../types/GraphQL';
import customerCartQuery from './customerCart';
+import getHeaders from '../getHeaders';
/**
* Fetches the cart of the current logged in user
@@ -24,5 +25,8 @@ export default async function customerCart(
return context.client.query({
query: customerCartGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/customerOrders/index.ts b/packages/api-client/src/api/customerOrders/index.ts
index 4f8c8eec3..d44cd42a1 100644
--- a/packages/api-client/src/api/customerOrders/index.ts
+++ b/packages/api-client/src/api/customerOrders/index.ts
@@ -8,6 +8,7 @@ import {
import customerOrdersQuery from './customerOrders';
import { Context } from '../../types/context';
import { GetOrdersSearchParams } from '../../types/API';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -44,10 +45,12 @@ export default async (
});
try {
- return await context.client
- .query({
+ return await context.client.query({
query: customerOrders.query,
variables: customerOrders.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/customerProductReview/index.ts b/packages/api-client/src/api/customerProductReview/index.ts
index bc21cfd39..8ec83bcf1 100644
--- a/packages/api-client/src/api/customerProductReview/index.ts
+++ b/packages/api-client/src/api/customerProductReview/index.ts
@@ -6,6 +6,7 @@ import {
} from '../../types/GraphQL';
import customerProductReview from './customerProductReview';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export type CustomerProductReviewParams = {
pageSize: number;
@@ -41,6 +42,9 @@ export default async (
return await context.client.query({
query: reviews.query,
variables: reviews.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/deleteCustomerAddress/index.ts b/packages/api-client/src/api/deleteCustomerAddress/index.ts
index 455b3ab3d..14ff195b9 100644
--- a/packages/api-client/src/api/deleteCustomerAddress/index.ts
+++ b/packages/api-client/src/api/deleteCustomerAddress/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import deleteCustomerAddressMutation from './deleteCustomerAddress';
import { Context } from '../../types/context';
import { DeleteCustomerAddressMutation, DeleteCustomerAddressMutationVariables } from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
/**
* Deletes a customer address.
@@ -29,5 +30,8 @@ export default async function deleteCustomerAddress(
return context.client.mutate({
mutation: deleteCustomerAddressGQL.query,
variables: deleteCustomerAddressGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/generateCustomerToken/index.ts b/packages/api-client/src/api/generateCustomerToken/index.ts
index b9c9e2dce..4e97293d3 100644
--- a/packages/api-client/src/api/generateCustomerToken/index.ts
+++ b/packages/api-client/src/api/generateCustomerToken/index.ts
@@ -8,6 +8,7 @@ import {
GenerateCustomerTokenMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Logs in the customer based on provided username and password. To override the default query, use the `generateCustomerToken` query key.
@@ -49,11 +50,15 @@ export default async (
},
);
- return await context.client
- .mutate({
- mutation: generateCustomerTokenGQL.query,
- variables: generateCustomerTokenGQL.variables,
- });
+ return await context.client.mutate(
+ {
+ mutation: generateCustomerTokenGQL.query,
+ variables: generateCustomerTokenGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
+ },
+ );
} catch (error) {
// For error in data we don't throw 500, because it's not server error
if (error.graphQLErrors) {
diff --git a/packages/api-client/src/api/getAvailableCustomerPaymentMethods/index.ts b/packages/api-client/src/api/getAvailableCustomerPaymentMethods/index.ts
index 2e7f06c4f..c1ad829c8 100644
--- a/packages/api-client/src/api/getAvailableCustomerPaymentMethods/index.ts
+++ b/packages/api-client/src/api/getAvailableCustomerPaymentMethods/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { Context } from '../../types/context';
import CustomerAvailablePaymentMethods from './CustomerPaymentMethods';
import { CustomerAvailablePaymentMethodsQuery } from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -23,6 +24,9 @@ export default async (
try {
return await context.client.query({
query: paymentMethods.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
diff --git a/packages/api-client/src/api/getAvailableCustomerShippingMethods/index.ts b/packages/api-client/src/api/getAvailableCustomerShippingMethods/index.ts
index 88fb2d6f0..486645974 100644
--- a/packages/api-client/src/api/getAvailableCustomerShippingMethods/index.ts
+++ b/packages/api-client/src/api/getAvailableCustomerShippingMethods/index.ts
@@ -6,6 +6,7 @@ import CustomerAvailableShippingMethods from './CustomerShippingMethods';
import {
CustomerAvailableShippingMethodsQuery,
} from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
/**
* Retrive available shipping methods for current customer
@@ -28,6 +29,9 @@ export default async function getAvailableCustomerShippingMethods(
try {
return await context.client.query({
query: shippingMethods.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/getAvailablePaymentMethods/index.ts b/packages/api-client/src/api/getAvailablePaymentMethods/index.ts
index c28bec00a..0a940fe37 100644
--- a/packages/api-client/src/api/getAvailablePaymentMethods/index.ts
+++ b/packages/api-client/src/api/getAvailablePaymentMethods/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { Context } from '../../types/context';
import GuestAvailablePaymentMethods from './GuestAvailablePaymentMethods';
import type { GuestAvailablePaymentMethodsQuery, GuestAvailablePaymentMethodsQueryVariables } from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
/**
* Fetches the available payment methods for the received cart.
@@ -33,6 +34,9 @@ export default async function getAvailablePaymentMethods(
return await context.client.query({
query: paymentMethods.query,
variables: paymentMethods.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/getAvailableShippingMethods/index.ts b/packages/api-client/src/api/getAvailableShippingMethods/index.ts
index 73a0f7cd2..6e42fda61 100644
--- a/packages/api-client/src/api/getAvailableShippingMethods/index.ts
+++ b/packages/api-client/src/api/getAvailableShippingMethods/index.ts
@@ -6,6 +6,7 @@ import {
GuestAvailableShippingMethodsQuery,
GuestAvailableShippingMethodsQueryVariables,
} from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -29,6 +30,9 @@ export default async (
GuestAvailableShippingMethodsQueryVariables>({
query: shippingMethods.query,
variables: shippingMethods.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/getCustomerAddresses/index.ts b/packages/api-client/src/api/getCustomerAddresses/index.ts
index 96542bcfd..6786d38af 100644
--- a/packages/api-client/src/api/getCustomerAddresses/index.ts
+++ b/packages/api-client/src/api/getCustomerAddresses/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { GetCustomerAddressesQuery } from '../../types/GraphQL';
import getCustomerAddressesQuery from './getCustomerAddresses';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches customer addresses.
@@ -23,6 +24,9 @@ export default async function getCustomerAddresses(
try {
return await context.client.query({
query: getCustomerAddressesGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/getHeaders.ts b/packages/api-client/src/api/getHeaders.ts
new file mode 100644
index 000000000..1f040b4b2
--- /dev/null
+++ b/packages/api-client/src/api/getHeaders.ts
@@ -0,0 +1,11 @@
+import { Context } from '../types/context';
+
+export default function getHeaders(context: Context) {
+ const { getCustomerToken, getStore, getCurrency } = context.config.state;
+
+ return {
+ ...(getCustomerToken() && { Authorization: `Bearer ${getCustomerToken()}` }),
+ ...(getStore() && { store: getStore() }),
+ ...(getCurrency() && { 'Content-Currency': getCurrency() }),
+ };
+}
diff --git a/packages/api-client/src/api/index.ts b/packages/api-client/src/api/index.ts
index 4e9dd5573..8ac66260a 100644
--- a/packages/api-client/src/api/index.ts
+++ b/packages/api-client/src/api/index.ts
@@ -60,5 +60,6 @@ export { default as updateCustomerAddress } from './updateCustomerAddress';
export { default as updateCustomerEmail } from './updateCustomerEmail';
export { default as upsellProduct } from './upsellProduct';
export { default as urlResolver } from './urlResolver';
+export { default as route } from './route';
export { default as wishlist } from './wishlist';
export { default as wishlistItemsCount } from './wishlistItemsCount';
diff --git a/packages/api-client/src/api/mergeCarts/index.ts b/packages/api-client/src/api/mergeCarts/index.ts
index 892430ce8..3248edc20 100644
--- a/packages/api-client/src/api/mergeCarts/index.ts
+++ b/packages/api-client/src/api/mergeCarts/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import mergeCarts from './mergeCarts';
import { MergeCartsMutation, MergeCartsMutationVariables } from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -28,5 +29,8 @@ export default async (
return context.client.mutate({
mutation: mergeCartsGQL.query,
variables: mergeCartsGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/placeOrder/index.ts b/packages/api-client/src/api/placeOrder/index.ts
index 1b00bb2ac..59519a228 100644
--- a/packages/api-client/src/api/placeOrder/index.ts
+++ b/packages/api-client/src/api/placeOrder/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import placeOrderMutation from './placeOrder';
import { PlaceOrderInput, PlaceOrderMutation, PlaceOrderMutationVariables } from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Places an order for received cart.
@@ -27,6 +28,9 @@ export default async function placeOrder(
return await context.client.mutate({
mutation: placeOrderGQL.query,
variables: placeOrderGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/productDetail/index.ts b/packages/api-client/src/api/productDetail/index.ts
index 216820ecd..95400b36d 100644
--- a/packages/api-client/src/api/productDetail/index.ts
+++ b/packages/api-client/src/api/productDetail/index.ts
@@ -9,6 +9,7 @@ import type {
} from '../../types/GraphQL';
import type { Context } from '../../types/context';
import type { GetProductSearchParams } from '../../types/API';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -59,6 +60,9 @@ export default async function productDetail(
const result = await context.client.query({
query: productDetailGQL.query,
variables: productDetailGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
if (result.data.products.items.length === 0) throw new Error('No products found');
diff --git a/packages/api-client/src/api/productReview/index.ts b/packages/api-client/src/api/productReview/index.ts
index f508c0156..2eb91b226 100644
--- a/packages/api-client/src/api/productReview/index.ts
+++ b/packages/api-client/src/api/productReview/index.ts
@@ -9,6 +9,7 @@ import {
import productReview from './productReview';
import { Context } from '../../types/context';
import { GetProductSearchParams } from '../../types/API';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -54,6 +55,9 @@ export default async (
return await context.client.query({
query: productReviewGQL.query,
variables: productReviewGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/productReviewRatingsMetadata/index.ts b/packages/api-client/src/api/productReviewRatingsMetadata/index.ts
index 4f630838e..6b0f47f87 100644
--- a/packages/api-client/src/api/productReviewRatingsMetadata/index.ts
+++ b/packages/api-client/src/api/productReviewRatingsMetadata/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { ProductReviewRatingsMetadataQuery } from '../../types/GraphQL';
import productReviewRatingsMetadata from './productReviewRatingsMetadata';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Returns additional product reviews data
@@ -22,5 +23,8 @@ export default async (
return context.client.query({
query: productReviewRatingsMetadataGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/products/index.ts b/packages/api-client/src/api/products/index.ts
index 6424b83d6..2b8036296 100644
--- a/packages/api-client/src/api/products/index.ts
+++ b/packages/api-client/src/api/products/index.ts
@@ -9,6 +9,7 @@ import {
import productsListQuery from './productsList';
import { Context } from '../../types/context';
import { GetProductSearchParams } from '../../types/API';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -59,6 +60,9 @@ export default async function products(
return await context.client.query({
query: productsGQL.query,
variables: productsGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/relatedProduct/index.ts b/packages/api-client/src/api/relatedProduct/index.ts
index 054021d1c..7f1a80ec3 100644
--- a/packages/api-client/src/api/relatedProduct/index.ts
+++ b/packages/api-client/src/api/relatedProduct/index.ts
@@ -9,6 +9,7 @@ import type {
import relatedProductQuery from './relatedProduct';
import type { Context } from '../../types/context';
import type { GetProductSearchParams } from '../../types/API';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -59,6 +60,9 @@ export default async function relatedProduct(
return await context.client.query({
query: relatedProductGQL.query,
variables: relatedProductGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/removeCouponFromCart/index.ts b/packages/api-client/src/api/removeCouponFromCart/index.ts
index ef40bb40b..b01585b70 100644
--- a/packages/api-client/src/api/removeCouponFromCart/index.ts
+++ b/packages/api-client/src/api/removeCouponFromCart/index.ts
@@ -7,6 +7,7 @@ import {
RemoveCouponFromCartMutationVariables,
} from '../../types/GraphQL';
import removeCouponFromCartMutation from './removeCouponFromCart';
+import getHeaders from '../getHeaders';
/**
* Removes a coupon from a cart
@@ -32,5 +33,8 @@ export default async function removeCouponFromCart(
return context.client.mutate({
mutation: removeCouponFromCartGQL.query,
variables: removeCouponFromCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/removeItemFromCart/index.ts b/packages/api-client/src/api/removeItemFromCart/index.ts
index 3d43970e0..4741707cf 100644
--- a/packages/api-client/src/api/removeItemFromCart/index.ts
+++ b/packages/api-client/src/api/removeItemFromCart/index.ts
@@ -7,6 +7,7 @@ import {
RemoveItemFromCartMutationVariables,
} from '../../types/GraphQL';
import removeItemFromCartMutation from './removeItemFromCart';
+import getHeaders from '../getHeaders';
/**
* Removes an item from the given cart
@@ -32,5 +33,8 @@ export default async function removeItemFromCart(
return context.client.mutate({
mutation: removeItemFromCartGQL.query,
variables: removeItemFromCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/removeProductsFromWishlist/index.ts b/packages/api-client/src/api/removeProductsFromWishlist/index.ts
index b8037330d..2d5f6c1d7 100644
--- a/packages/api-client/src/api/removeProductsFromWishlist/index.ts
+++ b/packages/api-client/src/api/removeProductsFromWishlist/index.ts
@@ -6,6 +6,7 @@ import {
RemoveProductsFromWishlistMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -25,5 +26,8 @@ export default async (
return context.client.mutate({
mutation: removeProductsFromWishlistGQL.query,
variables: removeProductsFromWishlistGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/requestPasswordResetEmail/index.ts b/packages/api-client/src/api/requestPasswordResetEmail/index.ts
index acdd60873..5de4b9b2d 100644
--- a/packages/api-client/src/api/requestPasswordResetEmail/index.ts
+++ b/packages/api-client/src/api/requestPasswordResetEmail/index.ts
@@ -8,6 +8,7 @@ import {
RequestPasswordResetEmailMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Requests a password reset email to be sent to the user
@@ -50,6 +51,9 @@ export default async function requestPasswordResetEmail(
.mutate({
mutation: extendedMutation.query,
variables: extendedMutation.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
if (!result.data.requestPasswordResetEmail) throw new Error('Email was not found, or not available.');
diff --git a/packages/api-client/src/api/resetPassword/index.ts b/packages/api-client/src/api/resetPassword/index.ts
index 1f2b8803d..bd30dd234 100644
--- a/packages/api-client/src/api/resetPassword/index.ts
+++ b/packages/api-client/src/api/resetPassword/index.ts
@@ -8,6 +8,7 @@ import {
} from '../../types/GraphQL';
import { Context } from '../../types/context';
import recaptchaValidator from '../../helpers/recaptcha/recaptchaValidator';
+import getHeaders from '../getHeaders';
/**
* Resets a user's password
@@ -50,6 +51,9 @@ export default async function resetPassword(
.mutate({
mutation: extendedResetPasswordMutation.query,
variables: extendedResetPasswordMutation.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
if (!result.data.resetPassword) throw new Error('It was not possible to change the user password.');
diff --git a/packages/api-client/src/api/revokeCustomerToken/index.ts b/packages/api-client/src/api/revokeCustomerToken/index.ts
index e3f26ebe7..035d0c2ea 100644
--- a/packages/api-client/src/api/revokeCustomerToken/index.ts
+++ b/packages/api-client/src/api/revokeCustomerToken/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import revokeCustomerToken from './revokeCustomerToken';
import { Context } from '../../types/context';
import { RevokeCustomerTokenMutation } from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
/**
* Logs out the current customer. To override the default query, use the `revokeCustomerToken` query key.
@@ -19,5 +20,8 @@ export default async (
return context.client.mutate({
mutation: revokeCustomerTokenGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/route/index.ts b/packages/api-client/src/api/route/index.ts
new file mode 100644
index 000000000..835b8f71e
--- /dev/null
+++ b/packages/api-client/src/api/route/index.ts
@@ -0,0 +1,38 @@
+import type { ApolloQueryResult } from '@apollo/client/core';
+import type { CustomQuery } from '@vue-storefront/core';
+import type { QueryRouteArgs, RoutableInterface } from '../../types/GraphQL';
+import routeQuery from './route';
+import type { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
+
+export type RouteQuery = {
+ route: ROUTE_TYPE
+};
+
+/**
+ * Returns the canonical URL for a specified product, category, or CMS page
+ *
+ * @param context VSF Context
+ * @param url the URL to be resolved
+ * @param [customQuery] (optional) - custom GraphQL query that extends the default one
+ */
+export default async function route(
+ context: Context,
+ url: string,
+ customQuery: CustomQuery = { route: 'route' },
+): Promise>> {
+ const { route: routeGQL } = context.extendQuery(customQuery, {
+ route: {
+ query: routeQuery,
+ variables: { url },
+ },
+ });
+
+ return context.client.query, QueryRouteArgs>({
+ query: routeGQL.query,
+ variables: routeGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
+ });
+}
diff --git a/packages/api-client/src/api/route/route.ts b/packages/api-client/src/api/route/route.ts
new file mode 100644
index 000000000..df37c5c2f
--- /dev/null
+++ b/packages/api-client/src/api/route/route.ts
@@ -0,0 +1,16 @@
+import gql from 'graphql-tag';
+
+/** GraphQL Query that fetches the resolver for received URL. */
+export default gql`
+ query route($url: String!) {
+ route(url: $url) {
+ relative_url
+ redirect_code
+ type
+ ... on CategoryTree {
+ uid
+ id
+ }
+ }
+ }
+`;
diff --git a/packages/api-client/src/api/setBillingAddressOnCart/index.ts b/packages/api-client/src/api/setBillingAddressOnCart/index.ts
index e0d58b6eb..76b3f15d6 100644
--- a/packages/api-client/src/api/setBillingAddressOnCart/index.ts
+++ b/packages/api-client/src/api/setBillingAddressOnCart/index.ts
@@ -7,6 +7,7 @@ import {
SetBillingAddressOnCartMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -26,5 +27,8 @@ export default async (
return context.client.mutate({
mutation: setBillingAddressOnCartGQL.query,
variables: setBillingAddressOnCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/setGuestEmailOnCart/index.ts b/packages/api-client/src/api/setGuestEmailOnCart/index.ts
index 4d63036ed..313ae1c5f 100644
--- a/packages/api-client/src/api/setGuestEmailOnCart/index.ts
+++ b/packages/api-client/src/api/setGuestEmailOnCart/index.ts
@@ -5,6 +5,7 @@ import {
SetGuestEmailOnCartInput, SetGuestEmailOnCartMutation, SetGuestEmailOnCartMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Set the guest user email on the cart
@@ -30,5 +31,8 @@ export default async function setGuestEmailOnCart(
return context.client.mutate({
mutation: setGuestEmailOnCartGQL.query,
variables: setGuestEmailOnCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/setPaymentMethodOnCart/index.ts b/packages/api-client/src/api/setPaymentMethodOnCart/index.ts
index 68d6a4910..12995aec8 100644
--- a/packages/api-client/src/api/setPaymentMethodOnCart/index.ts
+++ b/packages/api-client/src/api/setPaymentMethodOnCart/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import setPaymentMethodOnCartMutation from './setPaymentMethodOnCart';
import type { SetPaymentMethodOnCartInput, SetPaymentMethodOnCartMutation, SetPaymentMethodOnCartMutationVariables } from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export interface SetPaymentMethodOnCartInputs extends SetPaymentMethodOnCartInput {
[k: string]: any;
@@ -30,5 +31,8 @@ export default async function setPaymentMethodOnCart(
return context.client.mutate({
mutation: setPaymentMethodOnCartGQL.query,
variables: setPaymentMethodOnCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/setShippingAddressesOnCart/index.ts b/packages/api-client/src/api/setShippingAddressesOnCart/index.ts
index 0eb86b4f3..6a146f2a6 100644
--- a/packages/api-client/src/api/setShippingAddressesOnCart/index.ts
+++ b/packages/api-client/src/api/setShippingAddressesOnCart/index.ts
@@ -7,6 +7,7 @@ import type {
SetShippingAddressesOnCartMutationVariables,
} from '../../types/GraphQL';
import type { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Sets a shipping address on received cart.
@@ -30,5 +31,8 @@ export default async function setShippingAddressesOnCart(
return context.client.mutate({
mutation: setShippingAddressesOnCartGQL.query,
variables: setShippingAddressesOnCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/setShippingMethodsOnCart/index.ts b/packages/api-client/src/api/setShippingMethodsOnCart/index.ts
index 1c2b2f6e8..e4483632f 100644
--- a/packages/api-client/src/api/setShippingMethodsOnCart/index.ts
+++ b/packages/api-client/src/api/setShippingMethodsOnCart/index.ts
@@ -3,6 +3,7 @@ import type { CustomQuery } from '@vue-storefront/core';
import setShippingMethodsOnCartMutation from './setShippingMethodsOnCart';
import type { SetShippingMethodsOnCartInput, SetShippingMethodsOnCartMutation, SetShippingMethodsOnCartMutationVariables } from '../../types/GraphQL';
import type { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Sets a shipping method on received cart.
@@ -26,5 +27,8 @@ export default async function setShippingMethodsOnCart(
return context.client.mutate({
mutation: setShippingMethodsOnCartGQL.query,
variables: setShippingMethodsOnCartGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/storeConfig/index.ts b/packages/api-client/src/api/storeConfig/index.ts
index 3546fef5c..034254159 100644
--- a/packages/api-client/src/api/storeConfig/index.ts
+++ b/packages/api-client/src/api/storeConfig/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import { StoreConfigQuery } from '../../types/GraphQL';
import storeConfigMutation from './storeConfig';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches the store configuration from the API
@@ -24,5 +25,8 @@ export default async function storeConfig(
return context.client.query({
query: storeConfigGQL.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/subscribeEmailToNewsletter/index.ts b/packages/api-client/src/api/subscribeEmailToNewsletter/index.ts
index c9a7b8d64..3c26d3849 100644
--- a/packages/api-client/src/api/subscribeEmailToNewsletter/index.ts
+++ b/packages/api-client/src/api/subscribeEmailToNewsletter/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import subscribeEmailToNewsletterMutation from './subscribeEmailToNewsletter';
import type { SubscribeEmailToNewsletterMutation, SubscribeEmailToNewsletterMutationVariables } from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Subscribes an email in the newsletter.
@@ -27,5 +28,8 @@ export default async function subscribeEmailToNewsletter(
return context.client.mutate({
mutation: subscribeEmailToNewsletterGQL.query,
variables: subscribeEmailToNewsletterGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/updateCartItems/index.ts b/packages/api-client/src/api/updateCartItems/index.ts
index 866a83e54..b28a89cb8 100644
--- a/packages/api-client/src/api/updateCartItems/index.ts
+++ b/packages/api-client/src/api/updateCartItems/index.ts
@@ -7,6 +7,7 @@ import {
UpdateCartItemsMutationVariables,
} from '../../types/GraphQL';
import updateCartItemsMutation from './updateCartItems';
+import getHeaders from '../getHeaders';
/**
* Updates the contents of the given cart
@@ -32,5 +33,8 @@ export default async function updateCartItems(
return context.client.mutate({
mutation: updateCartItemsGQL.query,
variables: updateCartItemsGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/updateCustomer/index.ts b/packages/api-client/src/api/updateCustomer/index.ts
index 2f1162fda..c25c63b91 100644
--- a/packages/api-client/src/api/updateCustomer/index.ts
+++ b/packages/api-client/src/api/updateCustomer/index.ts
@@ -7,6 +7,7 @@ import {
UpdateCustomerMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Updates the data of the current customer. To override the default query, use the `updateCustomer` query key.
@@ -29,5 +30,8 @@ export default async (
return context.client.mutate({
mutation: updateCustomerGQL.query,
variables: updateCustomerGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/updateCustomerAddress/index.ts b/packages/api-client/src/api/updateCustomerAddress/index.ts
index 256fc54c8..d085bacf9 100644
--- a/packages/api-client/src/api/updateCustomerAddress/index.ts
+++ b/packages/api-client/src/api/updateCustomerAddress/index.ts
@@ -7,6 +7,7 @@ import {
UpdateCustomerAddressMutationVariables,
} from '../../types/GraphQL';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Updates a customer address.
@@ -36,5 +37,8 @@ export default async function updateCustomerAddress(
return context.client.mutate({
mutation: updateCustomerAddressGQL.query,
variables: updateCustomerAddressGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/updateCustomerEmail/index.ts b/packages/api-client/src/api/updateCustomerEmail/index.ts
index 20e6278cb..fd513cdd6 100644
--- a/packages/api-client/src/api/updateCustomerEmail/index.ts
+++ b/packages/api-client/src/api/updateCustomerEmail/index.ts
@@ -3,6 +3,7 @@ import { CustomQuery } from '@vue-storefront/core';
import updateCustomerEmail from './updateCustomerEmail';
import { Context } from '../../types/context';
import { UpdateCustomerEmailMutation, UpdateCustomerEmailMutationVariables } from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -22,5 +23,8 @@ export default async (
return context.client.mutate({
mutation: updateCustomerEmailGQL.query,
variables: updateCustomerEmailGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
};
diff --git a/packages/api-client/src/api/upsellProduct/index.ts b/packages/api-client/src/api/upsellProduct/index.ts
index 09da6ee02..4f46bbed9 100644
--- a/packages/api-client/src/api/upsellProduct/index.ts
+++ b/packages/api-client/src/api/upsellProduct/index.ts
@@ -9,6 +9,7 @@ import type {
UpsellProductsQuery,
UpsellProductsQueryVariables,
} from '../../types/GraphQL';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -54,6 +55,9 @@ export default async (
return await context.client.query({
query: upsellProductsGQL.query,
variables: upsellProductsGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/urlResolver/index.ts b/packages/api-client/src/api/urlResolver/index.ts
index 6a0d26779..3838e4a30 100644
--- a/packages/api-client/src/api/urlResolver/index.ts
+++ b/packages/api-client/src/api/urlResolver/index.ts
@@ -3,6 +3,7 @@ import type { CustomQuery } from '@vue-storefront/core';
import type { UrlResolverQuery, UrlResolverQueryVariables } from '../../types/GraphQL';
import urlResolverQuery from './urlResolver';
import type { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
/**
* Fetches the resolver for received URL.
@@ -10,6 +11,7 @@ import type { Context } from '../../types/context';
* @param context VSF Context
* @param url the URL to be resolved
* @param [customQuery] (optional) - custom GraphQL query that extends the default one
+ * @deprecated - use route instead
*/
export default async function urlResolver(
context: Context,
@@ -26,5 +28,8 @@ export default async function urlResolver(
return context.client.query({
query: urlResolverGQL.query,
variables: urlResolverGQL.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
}
diff --git a/packages/api-client/src/api/wishlist/index.ts b/packages/api-client/src/api/wishlist/index.ts
index 31e24c14a..1af25bc23 100644
--- a/packages/api-client/src/api/wishlist/index.ts
+++ b/packages/api-client/src/api/wishlist/index.ts
@@ -6,6 +6,7 @@ import {
} from '../../types/GraphQL';
import wishlistQuery from './wishlist';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
type Variables = {
pageSize: number;
@@ -39,6 +40,9 @@ export default async (
.query({
query: wishlist.query,
variables: wishlist.variables,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/api/wishlistItemsCount/index.ts b/packages/api-client/src/api/wishlistItemsCount/index.ts
index 53a3c5b82..3fc031da3 100644
--- a/packages/api-client/src/api/wishlistItemsCount/index.ts
+++ b/packages/api-client/src/api/wishlistItemsCount/index.ts
@@ -6,6 +6,7 @@ import {
} from '../../types/GraphQL';
import query from './wishlistItemsCount';
import { Context } from '../../types/context';
+import getHeaders from '../getHeaders';
export default async (
context: Context,
@@ -17,9 +18,11 @@ export default async (
},
});
try {
- return await context.client
- .query({
+ return await context.client.query({
query: wishlistItemsCount.query,
+ context: {
+ headers: getHeaders(context),
+ },
});
} catch (error) {
throw error.graphQLErrors?.[0].message || error.networkError?.result || error;
diff --git a/packages/api-client/src/index.server.ts b/packages/api-client/src/index.server.ts
index b29d7a17e..8604aa622 100644
--- a/packages/api-client/src/index.server.ts
+++ b/packages/api-client/src/index.server.ts
@@ -6,7 +6,7 @@ import { createMagentoConnection } from './helpers/magentoLink';
import { defaultSettings } from './helpers/apiClient/defaultSettings';
import { apolloClientFactory } from './helpers/magentoLink/graphQl';
-const onCreate = (settings: Config): { config: Config; client: ClientInstance } => {
+const init = (settings: Config) => {
const config = {
...defaultSettings,
...settings,
@@ -48,6 +48,14 @@ const onCreate = (settings: Config): { config: Config; client: ClientInstance }
};
};
+const onCreate = (settings: Config): { config: Config; client: ClientInstance } => {
+ if (!settings?.client) {
+ return init(settings);
+ }
+
+ return { config: settings, client: settings.client };
+};
+
const tokenExtension: ApiClientExtension = {
name: 'tokenExtension',
hooks: (req, res) => ({
@@ -110,4 +118,5 @@ const { createApiClient } = apiClientFactory({
export {
createApiClient,
+ init,
};
diff --git a/packages/api-client/src/types/API.ts b/packages/api-client/src/types/API.ts
index aeb85396f..a01a1f4dd 100644
--- a/packages/api-client/src/types/API.ts
+++ b/packages/api-client/src/types/API.ts
@@ -1,5 +1,5 @@
import { ApolloQueryResult, FetchPolicy, FetchResult } from '@apollo/client/core';
-import { ExecutionResult } from 'graphql';
+import { DocumentNode, ExecutionResult } from 'graphql';
import { CustomQuery } from '@vue-storefront/core';
import {
AddConfigurableProductsToCartInput,
@@ -107,11 +107,12 @@ import {
ChangeCustomerPasswordMutation,
CreateCustomerAddressMutation,
DownloadableProduct,
- VirtualProduct, CustomerOrdersFilterInput,
+ VirtualProduct, CustomerOrdersFilterInput, RoutableInterface,
} from './GraphQL';
import { SetPaymentMethodOnCartInputs } from '../api/setPaymentMethodOnCart';
import { CustomerProductReviewParams } from '../api/customerProductReview';
import { AddProductsToCartInput } from '../api/addProductsToCart';
+import type { RouteQuery } from '../api/route';
export interface Product extends ProductInterface, ConfigurableProduct, Omit, Omit, Omit, Omit {
}
@@ -277,14 +278,14 @@ export interface MagentoApiMethods {
customQuery?: CustomQuery,
): Promise>;
- customQuery(params: {
- query: QUERY,
+ customQuery(params: {
+ query: DocumentNode,
queryVariables?: QUERY_VARIABLES,
fetchPolicy?: FetchPolicy,
}): Promise>;
- customMutation(params: {
- mutation: MUTATION,
+ customMutation(params: {
+ mutation: DocumentNode,
mutationVariables: MUTATION_VARIABLES,
fetchPolicy?: Extract,
}): Promise>;
@@ -440,6 +441,11 @@ export interface MagentoApiMethods {
customQuery?: CustomQuery
): Promise>;
+ route(
+ url: string,
+ customQuery?: CustomQuery
+ ): Promise>>;
+
wishlist(
searchParams: WishlistQueryVariables,
customQuery?: CustomQuery,
diff --git a/packages/api-client/src/types/setup.ts b/packages/api-client/src/types/setup.ts
index cebc7b295..7fe7f2fd0 100644
--- a/packages/api-client/src/types/setup.ts
+++ b/packages/api-client/src/types/setup.ts
@@ -96,6 +96,8 @@ export interface Config extends ClientConfig {
magentoApiEndpoint: string;
overrides: MagentoApiMethods;
recaptcha: RecaptchaConfig;
+ imageProvider: string;
+ magentoBaseUrl: string;
}
export interface ClientInstance extends ApolloClient {
diff --git a/packages/composables/README.md b/packages/composables/README.md
index 8c0ad9648..c4d4d3bb7 100644
--- a/packages/composables/README.md
+++ b/packages/composables/README.md
@@ -16,9 +16,10 @@ This integration developed by superheroes from [Caravel](https://github.com/cara
-[](#contributors-)
+[](#contributors-)
+
## How to start if you want to try out the integration
```
@@ -132,6 +133,10 @@ Thanks go to these wonderful people π:
Jonathan Ribas π»
Ali Ghanei π»
Maya Shavin π
+ Alexander Devitsky π»
+
+
+ Diego Alba π»
diff --git a/packages/composables/nuxt/plugin.js b/packages/composables/nuxt/plugin.js
index 669d6994e..81902f18a 100644
--- a/packages/composables/nuxt/plugin.js
+++ b/packages/composables/nuxt/plugin.js
@@ -7,7 +7,6 @@ import cookie from '@vue-storefront/magento/nuxt/cookie';
const moduleOptions = JSON.parse('<%= JSON.stringify(options) %>');
-// TODO should be moved to THEME and expose consistent cookie management API
export default integrationPlugin(({
app, res, req, integration,
}) => {
diff --git a/packages/composables/package.json b/packages/composables/package.json
index f16b21171..1939ac0ad 100644
--- a/packages/composables/package.json
+++ b/packages/composables/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/magento",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"license": "MIT",
"homepage": "https://github.com/vuestorefront/magento2",
"bugs": {
@@ -29,7 +29,7 @@
},
"dependencies": {
"@vue-storefront/core": "~2.5.6",
- "@vue-storefront/magento-api": "^1.0.0-rc.7",
+ "@vue-storefront/magento-api": "^1.0.0-rc.9",
"@vue/composition-api": "^1.4.1",
"cookie-universal": "^2.1.5",
"vue": "^2.6.14",
diff --git a/packages/composables/src/composables/useCart/index.ts b/packages/composables/src/composables/useCart/index.ts
index 3409fa882..16c55ffbd 100644
--- a/packages/composables/src/composables/useCart/index.ts
+++ b/packages/composables/src/composables/useCart/index.ts
@@ -254,7 +254,6 @@ const factoryParams: UseCartFactoryParams = {
.addVirtualProductsToCart
.cart as unknown as Cart;
default:
- // todo implement other options
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
throw new Error(`Product Type ${product.__typename} not supported in add to cart yet`);
diff --git a/packages/composables/src/composables/useExternalCheckout/index.ts b/packages/composables/src/composables/useExternalCheckout/index.ts
index e655aa072..e9ffce80a 100644
--- a/packages/composables/src/composables/useExternalCheckout/index.ts
+++ b/packages/composables/src/composables/useExternalCheckout/index.ts
@@ -25,7 +25,6 @@ const factoryParams: UseExternalCheckoutFactoryParams = {
if (externalCheckout.enable) {
if (userToken && cartToken) {
- // @TODO: Implements Multiple Store
/* if (Object.keys(externalCheckout.stores).length) {
} */
diff --git a/packages/composables/src/composables/useWishlist/index.ts b/packages/composables/src/composables/useWishlist/index.ts
index 182be3a56..fc03c3cdc 100644
--- a/packages/composables/src/composables/useWishlist/index.ts
+++ b/packages/composables/src/composables/useWishlist/index.ts
@@ -114,7 +114,6 @@ const factoryParams: UseWishlistFactoryParams = {
return bundleProductData?.addProductsToWishlist?.wishlist ?? {};
default:
- // todo implement other options
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
throw new Error(`Product Type ${product.__typename} not supported in add to wishlist yet`);
diff --git a/packages/composables/src/getters/cartGetters.ts b/packages/composables/src/getters/cartGetters.ts
index 7aede9417..9a7ae33b6 100644
--- a/packages/composables/src/getters/cartGetters.ts
+++ b/packages/composables/src/getters/cartGetters.ts
@@ -55,7 +55,6 @@ export const getItemPrice = (product: CartItem): AgnosticPrice => {
special: specialPrice || 0,
// @ts-ignore
credit: Math.round(specialPrice / 10),
- // @TODO: Who set this installment value?
installment: Math.round((specialPrice * 1.1046) / 10),
discountPercentage: 100 - Math.round((specialPrice / regularPrice) * 100),
total: product.prices?.row_total?.value,
diff --git a/packages/composables/src/getters/productGetters.ts b/packages/composables/src/getters/productGetters.ts
index b158918c1..89e1c38e7 100644
--- a/packages/composables/src/getters/productGetters.ts
+++ b/packages/composables/src/getters/productGetters.ts
@@ -202,7 +202,6 @@ export const getFormattedPrice = (price: number) => {
return null;
}
- // TODO get correct data from api
const locale = 'en';
const country = 'en';
const currency = 'USD';
diff --git a/packages/theme/README.md b/packages/theme/README.md
index c5247cf11..4542b3a3b 100644
--- a/packages/theme/README.md
+++ b/packages/theme/README.md
@@ -4,6 +4,11 @@
# Magento 2.x theme
+
+[](#contributors-)
+
+
+
### Vue Storefront 2 integration with Magento
### Requirements:
@@ -91,6 +96,10 @@ Thanks go to these wonderful people π:
Jonathan Ribas π»
Ali Ghanei π»
Maya Shavin π
+ Alexander Devitsky π»
+
+
+ Diego Alba π»
@@ -100,3 +109,4 @@ Thanks go to these wonderful people π:
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
+
diff --git a/packages/theme/components/AppHeader.vue b/packages/theme/components/AppHeader.vue
index 28ce31e78..046bec65e 100644
--- a/packages/theme/components/AppHeader.vue
+++ b/packages/theme/components/AppHeader.vue
@@ -4,7 +4,6 @@
class="sf-header--has-mobile-search"
:class="{ 'header-on-top': isSearchOpen }"
>
-
@@ -87,15 +86,16 @@
@@ -116,16 +116,16 @@ import {
useFetch,
} from '@nuxtjs/composition-api';
import HeaderNavigation from '~/components/Header/Navigation/HeaderNavigation.vue';
-import useCategory from '~/modules/catalog/category/composables/useCategory';
+import { useCategory } from '~/modules/catalog/category/composables/useCategory';
import {
useUiHelpers,
useUiState,
} from '~/composables';
-import useCart from '~/modules/checkout/composables/useCart';
-import useWishlist from '~/modules/wishlist/composables/useWishlist';
+import { useCart } from '~/modules/checkout/composables/useCart';
+import { useWishlist } from '~/modules/wishlist/composables/useWishlist';
import { useUser } from '~/modules/customer/composables/useUser';
import { useWishlistStore } from '~/modules/wishlist/store/wishlistStore';
-import type { CategoryTree } from '~/modules/GraphQL/types';
+import type { CategoryTree, ProductInterface } from '~/modules/GraphQL/types';
import CurrencySelector from '~/components/CurrencySelector.vue';
import HeaderLogo from '~/components/HeaderLogo.vue';
import SvgImage from '~/components/General/SvgImage.vue';
@@ -157,9 +157,10 @@ export default defineComponent({
const { loadItemsCount: loadWishlistItemsCount } = useWishlist();
const { categories: categoryList, load: categoriesListLoad } = useCategory();
- const wishlistStore = useWishlistStore();
const isSearchOpen = ref(false);
- const result = ref(null);
+ const productSearchResults = ref(null);
+
+ const wishlistStore = useWishlistStore();
const wishlistItemsQty = computed(() => wishlistStore.wishlist?.items_count ?? 0);
const wishlistHasProducts = computed(() => wishlistItemsQty.value > 0);
@@ -185,7 +186,7 @@ export default defineComponent({
if (app.$device.isDesktop) {
await loadCartTotalQty();
// eslint-disable-next-line promise/catch-or-return
- await loadWishlistItemsCount({});
+ await loadWishlistItemsCount();
}
});
@@ -197,7 +198,7 @@ export default defineComponent({
handleAccountClick,
isAuthenticated,
isSearchOpen,
- result,
+ productSearchResults,
setTermForUrl,
toggleCartSidebar,
toggleWishlistSidebar,
diff --git a/packages/theme/components/BottomNavigation.vue b/packages/theme/components/BottomNavigation.vue
index 4157049e6..2c389fdd9 100644
--- a/packages/theme/components/BottomNavigation.vue
+++ b/packages/theme/components/BottomNavigation.vue
@@ -1,5 +1,4 @@
-
Yes
@@ -68,6 +69,7 @@
@@ -90,6 +92,7 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -218,6 +248,7 @@
@@ -228,6 +259,7 @@
{{ $t('Go back shopping') }}
@@ -269,14 +301,13 @@ import {
useExternalCheckout,
useImage,
} from '~/composables';
-import useCart from '~/modules/checkout/composables/useCart';
+import { useCart } from '~/modules/checkout/composables/useCart';
import { useUser } from '~/modules/customer/composables/useUser';
import stockStatusEnum from '~/enums/stockStatusEnum';
import SvgImage from '~/components/General/SvgImage.vue';
+import type { ConfigurableCartItem, BundleCartItem, CartItemInterface } from '~/modules/GraphQL/types';
import CouponCode from './CouponCode.vue';
-import type { ConfigurableCartItem } from '~/modules/GraphQL/types';
-
export default defineComponent({
name: 'CartSidebar',
components: {
@@ -323,9 +354,10 @@ export default defineComponent({
},
})));
const totals = computed(() => cartGetters.getTotals(cart.value));
+ const discount = computed(() => -cartGetters.getDiscountAmount(cart.value));
const totalItems = computed(() => cartGetters.getTotalItems(cart.value));
- const getAttributes = (product) => product.configurable_options || [];
- const getBundles = (product) => product.bundle_options?.map((b) => b.values).flat() || [];
+ const getAttributes = (product: ConfigurableCartItem) => product.configurable_options || [];
+ const getBundles = (product: BundleCartItem) => product.bundle_options?.map((b) => b.values).flat() || [];
const visible = ref(false);
const tempProduct = ref();
@@ -336,13 +368,11 @@ export default defineComponent({
});
const goToCheckout = async () => {
- const redirectUrl = initializeCheckout({
- baseUrl: '/checkout/user-account',
- });
- await router.push(`${app.localePath(redirectUrl)}`);
+ const redirectUrl = initializeCheckout({ baseUrl: '/checkout/user-account' });
+ await router.push(app.localePath(redirectUrl));
};
- const sendToRemove = ({ product }) => {
+ const sendToRemove = ({ product }: { product: CartItemInterface }) => {
if (notifications.value.length > 0) {
notifications.value[0].dismiss();
}
@@ -351,7 +381,7 @@ export default defineComponent({
tempProduct.value = product;
};
- const actionRemoveItem = async (product) => {
+ const actionRemoveItem = async (product: CartItemInterface) => {
await removeItem({ product });
visible.value = false;
@@ -370,7 +400,7 @@ export default defineComponent({
(params) => updateItemQty(params),
1000,
);
- const isInStock = (product) => cartGetters.getStockStatus(product) === stockStatusEnum.inStock;
+ const isInStock = (product: CartItemInterface) => cartGetters.getStockStatus(product) === stockStatusEnum.inStock;
return {
sendToRemove,
@@ -394,6 +424,7 @@ export default defineComponent({
isInStock,
imageSizes,
getMagentoImage,
+ discount,
};
},
});
@@ -452,10 +483,14 @@ export default defineComponent({
margin: 0;
}
+ &__subtotal, &__discount {
+ --price-font-weight: var(--font-weight--light);
+ }
+
&__total-price {
- --price-font-size: var(--font-size--xl);
+ --price-font-size: var(--font-size--lg);
--price-font-weight: var(--font-weight--medium);
- margin: 0 0 var(--spacer-base) 0;
+ margin: var(--spacer-base) 0 var(--spacer-base) 0;
}
}
diff --git a/packages/theme/components/Checkout/CartPreview.vue b/packages/theme/components/Checkout/CartPreview.vue
index a09fde007..3cf375e7d 100644
--- a/packages/theme/components/Checkout/CartPreview.vue
+++ b/packages/theme/components/Checkout/CartPreview.vue
@@ -21,7 +21,7 @@
cartGetters.getItems(cart.value));
const totalItems = computed(() => cartGetters.getTotalItems(cart.value));
const totals = computed(() => cartGetters.getTotals(cart.value));
- const discounts = computed(() => cartGetters.getDiscounts(cart.value));
- const hasDiscounts = computed(() => discounts.value.length > 0);
- const discountsAmount = computed(
- () => -1 * discounts.value.reduce((a, el) => el.value + a, 0),
- );
+ const discount = computed(() => -cartGetters.getDiscountAmount(cart.value));
+ const hasDiscounts = computed(() => Math.abs(discount.value) > 0);
const selectedShippingMethod = computed(() => cartGetters.getSelectedShippingMethod(cart.value));
return {
cart,
- discounts,
- discountsAmount,
+ discount,
hasDiscounts,
totalItems,
listIsHidden,
diff --git a/packages/theme/components/Checkout/VsfPaymentProvider.vue b/packages/theme/components/Checkout/VsfPaymentProvider.vue
index 73ccd2b10..618c73945 100644
--- a/packages/theme/components/Checkout/VsfPaymentProvider.vue
+++ b/packages/theme/components/Checkout/VsfPaymentProvider.vue
@@ -2,17 +2,17 @@
- {{ method.label }}
+ {{ method.title }}
@@ -20,57 +20,40 @@
diff --git a/packages/theme/components/NewProducts.vue b/packages/theme/components/NewProducts.vue
index 76158edf3..79f222af6 100644
--- a/packages/theme/components/NewProducts.vue
+++ b/packages/theme/components/NewProducts.vue
@@ -45,7 +45,7 @@
:score-rating="productGetters.getAverageRating(product)"
:reviews-count="productGetters.getTotalReviews(product)"
:is-in-wishlist="isInWishlist({ product })"
- :is-added-to-cart="isInCart({ product })"
+ :is-added-to-cart="isInCart(product)"
:wishlist-icon="isAuthenticated ? 'heart' : ''"
:is-in-wishlist-icon="isAuthenticated ? 'heart_fill' : ''"
@click:wishlist="addItemToWishlist(product)"
@@ -98,7 +98,7 @@ export default defineComponent({
setup() {
const { isAuthenticated } = useUser();
const { getProductList, loading } = useProduct();
- const { isInWishlist, addItem, removeItem } = useWishlist();
+ const { isInWishlist, addOrRemoveItem } = useWishlist();
const { addItemToCart, isInCart } = useAddToCart();
const products = ref([]);
@@ -108,9 +108,7 @@ export default defineComponent({
})));
const addItemToWishlist = async (product) => {
- await (isInWishlist({ product })
- ? removeItem({ product })
- : addItem({ product }));
+ await addOrRemoveItem({ product });
};
const { getMagentoImage, imageSizes } = useImage();
diff --git a/packages/theme/components/SkeletonLoader/index.vue b/packages/theme/components/SkeletonLoader/index.vue
index 991e2dd12..2c7357f0e 100644
--- a/packages/theme/components/SkeletonLoader/index.vue
+++ b/packages/theme/components/SkeletonLoader/index.vue
@@ -11,6 +11,8 @@
diff --git a/packages/theme/modules/catalog/product/components/ProductsCarousel.vue b/packages/theme/modules/catalog/product/components/ProductsCarousel.vue
index a38d24c58..660b61355 100644
--- a/packages/theme/modules/catalog/product/components/ProductsCarousel.vue
+++ b/packages/theme/modules/catalog/product/components/ProductsCarousel.vue
@@ -44,7 +44,7 @@
:score-rating="productGetters.getAverageRating(product)"
:reviews-count="productGetters.getTotalReviews(product)"
:is-in-wishlist="isInWishlist({ product })"
- :is-added-to-cart="isInCart({ product })"
+ :is-added-to-cart="isInCart(product)"
:wishlist-icon="isAuthenticated ? 'heart' : ''"
:is-in-wishlist-icon="isAuthenticated ? 'heart_fill' : ''"
@click:wishlist="addItemToWishlist(product)"
@@ -125,7 +125,7 @@ export default defineComponent({
},
setup(props) {
const { isAuthenticated } = useUser();
- const { isInWishlist, addItem, removeItem } = useWishlist();
+ const { isInWishlist, addOrRemoveItem } = useWishlist();
const { addItemToCart, isInCart } = useAddToCart();
const mappedProducts = computed(() => props.products.map((product) => ({
@@ -135,9 +135,7 @@ export default defineComponent({
})));
const addItemToWishlist = async (product) => {
- await (isInWishlist({ product })
- ? removeItem({ product })
- : addItem({ product }));
+ await addOrRemoveItem({ product });
};
const { getMagentoImage, imageSizes } = useImage();
diff --git a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue
index fc24a26a3..9da480ab3 100644
--- a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProduct.vue
@@ -186,7 +186,7 @@ export default defineComponent({
const { productGallery, imageSizes } = useProductGallery(product);
const { activeTab, setActiveTab, openNewReviewTab } = useProductTabs();
const { isAuthenticated } = useUser();
- const { addItem: addItemToWishlist, isInWishlist } = useWishlist();
+ const { addOrRemoveItem, isInWishlist } = useWishlist();
const productShortDescription = computed(
() => props.product?.short_description?.html || '',
);
@@ -205,7 +205,7 @@ export default defineComponent({
return {
addItem,
- addItemToWishlist,
+ addItemToWishlist: addOrRemoveItem,
averageRating,
canAddToCart,
isAuthenticated,
diff --git a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductSelector.vue b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductSelector.vue
index 73c2b6f8c..1af5ef4eb 100644
--- a/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductSelector.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/bundle/BundleProductSelector.vue
@@ -39,7 +39,7 @@
@change="
selectedOptions[bundle.uid].price = getProductPrice(
option.product
- ).regular
+ ).final
"
>
diff --git a/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue b/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue
index df0b906ed..f9b9bfdf0 100644
--- a/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/configurable/ConfigurableProduct.vue
@@ -235,7 +235,7 @@ export default defineComponent({
const { activeTab, setActiveTab, openNewReviewTab } = useProductTabs();
const { isAuthenticated } = useUser();
- const { addItem: addItemToWishlist, isInWishlist } = useWishlist();
+ const { addOrRemoveItem, isInWishlist } = useWishlist();
const { app } = useContext();
const productShortDescription = computed(
@@ -282,7 +282,7 @@ export default defineComponent({
};
return {
addItem,
- addItemToWishlist,
+ addItemToWishlist: addOrRemoveItem,
canAddToCart,
configurableOptions,
updateProductConfiguration,
diff --git a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProduct.vue b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProduct.vue
index 759761e3c..ee78db5c8 100644
--- a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProduct.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProduct.vue
@@ -175,7 +175,7 @@ export default defineComponent({
const { activeTab, setActiveTab, openNewReviewTab } = useProductTabs();
const { isAuthenticated } = useUser();
- const { addItem: addItemToWishlist, isInWishlist } = useWishlist();
+ const { addOrRemoveItem, isInWishlist } = useWishlist();
const basePrice = ref(0);
const openTab = ref(1);
@@ -184,13 +184,13 @@ export default defineComponent({
);
const productPrice = computed(() => getGroupedProductPriceCommand(props.product));
- const productSpecialPrice = 0; // TODO add logic for special price calculation;
+ const productSpecialPrice = 0;
const totalReviews = computed(() => getTotalReviews(props.product));
const averageRating = computed(() => getAverageRating(props.product));
return {
addItem,
- addItemToWishlist,
+ addItemToWishlist: addOrRemoveItem,
averageRating,
basePrice,
canAddToCart,
diff --git a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue
index d75a082ec..41799a92c 100644
--- a/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/grouped/GroupedProductSelector.vue
@@ -115,9 +115,8 @@ export default defineComponent({
watch(
() => props.product,
- (newValue) => {
- const price = getGroupedProductPriceCommand(newValue);
-
+ (product) => {
+ const price = getGroupedProductPriceCommand(product);
emit('update-price', price);
},
{
diff --git a/packages/theme/modules/catalog/product/components/product-types/simple/SimpleProduct.vue b/packages/theme/modules/catalog/product/components/product-types/simple/SimpleProduct.vue
index 9c884fa87..1517bf448 100644
--- a/packages/theme/modules/catalog/product/components/product-types/simple/SimpleProduct.vue
+++ b/packages/theme/modules/catalog/product/components/product-types/simple/SimpleProduct.vue
@@ -175,7 +175,7 @@ export default defineComponent({
const { addItem, loading: isCartLoading, canAddToCart } = useCart();
const { productGallery, imageSizes } = useProductGallery(product);
const { isAuthenticated } = useUser();
- const { addItem: addItemToWishlist, isInWishlist } = useWishlist();
+ const { addOrRemoveItem, isInWishlist } = useWishlist();
const { activeTab, setActiveTab, openNewReviewTab } = useProductTabs();
const productShortDescription = computed(
@@ -189,7 +189,7 @@ export default defineComponent({
return {
addItem,
- addItemToWishlist,
+ addItemToWishlist: addOrRemoveItem,
averageRating,
totalReviews,
canAddToCart,
diff --git a/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductDetailsCommand.ts b/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductDetailsCommand.ts
index 247288f30..e8f5db63e 100644
--- a/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductDetailsCommand.ts
+++ b/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductDetailsCommand.ts
@@ -1,10 +1,8 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { GetProductSearchParams } from '~/modules/catalog/product/types';
-type Context = ReturnType;
-
export const getProductDetailsCommand = {
- execute: async (context: Context, searchParams: GetProductSearchParams, customQuery = { productDetail: 'productDetail' }) => {
+ execute: async (context: UseContextReturn, searchParams: GetProductSearchParams, customQuery = { productDetail: 'productDetail' }) => {
const { data } = await context.app.$vsf.$magento.api.productDetail(searchParams, customQuery);
return data?.products ?? null;
diff --git a/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductListCommand.ts b/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductListCommand.ts
index 44a30cbe8..5cb3496c0 100644
--- a/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductListCommand.ts
+++ b/packages/theme/modules/catalog/product/composables/useProduct/commands/getProductListCommand.ts
@@ -1,10 +1,8 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { GetProductSearchParams } from '~/modules/catalog/product/types';
-type Context = ReturnType;
-
export const getProductListCommand = {
- execute: async (context: Context, searchParams: GetProductSearchParams, customQuery = { products: 'products' }) => {
+ execute: async (context: UseContextReturn, searchParams: GetProductSearchParams, customQuery = { products: 'products' }) => {
const { data } = await context.app.$vsf.$magento.api.products(searchParams, customQuery);
return data?.products ?? null;
diff --git a/packages/theme/modules/catalog/product/composables/useProduct/index.ts b/packages/theme/modules/catalog/product/composables/useProduct/index.ts
index 687fa3a3e..4663090dd 100644
--- a/packages/theme/modules/catalog/product/composables/useProduct/index.ts
+++ b/packages/theme/modules/catalog/product/composables/useProduct/index.ts
@@ -11,8 +11,10 @@ import type {
} from './useProduct';
/**
- * The `useProduct()` composable allows loading product details or list with
+ * Allows loading product details or list with
* params for sorting, filtering and pagination.
+ *
+ * See the {@link UseProductInterface} for a list of methods and values available in this composable.
*/
export function useProduct(id?: string): UseProductInterface {
const loading = ref(false);
diff --git a/packages/theme/modules/catalog/product/composables/useProduct/useProduct.ts b/packages/theme/modules/catalog/product/composables/useProduct/useProduct.ts
index 8c9c6c08e..d04adc5f7 100644
--- a/packages/theme/modules/catalog/product/composables/useProduct/useProduct.ts
+++ b/packages/theme/modules/catalog/product/composables/useProduct/useProduct.ts
@@ -18,7 +18,9 @@ export interface UseProductErrors {
getProductDetails: Error | null;
}
-/** The interface provided by {@link useProduct} composable. */
+/**
+ * Data and methods returned from the {@link useProduct} composable.
+ */
export interface UseProductInterface {
/**
* Contains errors from any of the composable methods.
diff --git a/packages/theme/modules/catalog/product/composables/useRelatedProducts/index.ts b/packages/theme/modules/catalog/product/composables/useRelatedProducts/index.ts
index 32a20634a..b4bbe888b 100644
--- a/packages/theme/modules/catalog/product/composables/useRelatedProducts/index.ts
+++ b/packages/theme/modules/catalog/product/composables/useRelatedProducts/index.ts
@@ -9,8 +9,10 @@ import type {
} from './useRelatedProducts';
/**
- * The `useRelatedProducts()` composable allows searching for related products
+ * Allows searching for related products
* with params for sort, filter and pagination.
+ *
+ * See the {@link UseRelatedProductsInterface} for a list of methods and values available in this composable.
*/
export function useRelatedProducts(): UseRelatedProductsInterface {
const { app } = useContext();
diff --git a/packages/theme/modules/catalog/product/composables/useRelatedProducts/useRelatedProducts.ts b/packages/theme/modules/catalog/product/composables/useRelatedProducts/useRelatedProducts.ts
index c8a1bb9e0..1e8aa071b 100644
--- a/packages/theme/modules/catalog/product/composables/useRelatedProducts/useRelatedProducts.ts
+++ b/packages/theme/modules/catalog/product/composables/useRelatedProducts/useRelatedProducts.ts
@@ -1,6 +1,7 @@
import type { DeepReadonly, Ref } from '@nuxtjs/composition-api';
-import type { ComposableFunctionArgs, ProductsSearchParams } from '~/composables/types';
+import type { ComposableFunctionArgs } from '~/composables/types';
import type { RelatedProductQuery } from '~/modules/GraphQL/types';
+import type { GetProductSearchParams } from '~/modules/catalog/product/types';
export type RelatedProduct = RelatedProductQuery['products']['items'][number]['related_products'];
@@ -14,9 +15,11 @@ export interface UseRelatedProductsErrors {
}
/** The params received by {@link useRelatedProducts}'s `search` method. */
-export type UseRelatedProductsSearchParams = ComposableFunctionArgs;
+export type UseRelatedProductsSearchParams = ComposableFunctionArgs;
-/** The interface provided by {@link useRelatedProducts} composable. */
+/**
+ * Data and methods returned from the {@link useRelatedProducts} composable.
+ */
export interface UseRelatedProductsInterface {
/**
* Contains errors from any of the composable methods.
diff --git a/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts b/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts
index 598090f70..b95f6a2ef 100644
--- a/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts
+++ b/packages/theme/modules/catalog/product/composables/useUpsellProducts/index.ts
@@ -8,9 +8,9 @@ import type {
} from './useUpsellProducts';
/**
- * The `useUpsellProducts()` composable allows loading upsell products.
+ * Allows loading upsell products.
*
- * See the {@link UseUpsellProductsInterface} page for more information.
+ * See the {@link UseUpsellProductsInterface} for a list of methods and values available in this composable.
*/
export function useUpsellProducts(): UseUpsellProductsInterface {
const { app } = useContext();
diff --git a/packages/theme/modules/catalog/product/composables/useUpsellProducts/useUpsellProducts.ts b/packages/theme/modules/catalog/product/composables/useUpsellProducts/useUpsellProducts.ts
index f364e7a61..9ee0d8d73 100644
--- a/packages/theme/modules/catalog/product/composables/useUpsellProducts/useUpsellProducts.ts
+++ b/packages/theme/modules/catalog/product/composables/useUpsellProducts/useUpsellProducts.ts
@@ -1,7 +1,7 @@
import type { Ref, DeepReadonly } from '@nuxtjs/composition-api';
import type { Maybe, UpsellProductsQuery } from '~/modules/GraphQL/types';
import type { ComposableFunctionArgs } from '~/composables/types';
-import type { ProductsSearchParams } from '~/modules/catalog/product/types';
+import type { GetProductSearchParams } from '~/modules/catalog/product/types';
/**
* Errors that occured in the `useUpsellProducts` composable
@@ -15,10 +15,10 @@ export type UpsellProducts = UpsellProductsQuery['products']['items'][0]['upsell
/**
* Parameters accepted by the `search` method in the `useUpsellProducts` composable
*/
-export type UseUpsellProductsSearchParams = ComposableFunctionArgs;
+export type UseUpsellProductsSearchParams = ComposableFunctionArgs;
/**
- * Represents the data returned from and functions available in the `useUpsellProducts()` composable.
+ * Data and methods returned from the {@link useUpsellProducts|useUpsellProducts()} composable
*/
export interface UseUpsellProductsInterface {
/**
diff --git a/packages/theme/modules/catalog/product/getters/productGetters.ts b/packages/theme/modules/catalog/product/getters/productGetters.ts
index 607694656..f3fb243d9 100644
--- a/packages/theme/modules/catalog/product/getters/productGetters.ts
+++ b/packages/theme/modules/catalog/product/getters/productGetters.ts
@@ -1,10 +1,7 @@
-import type {
- AgnosticAttribute,
- AgnosticBreadcrumb,
- AgnosticMediaGalleryItem,
- AgnosticPrice,
-} from '~/composables/types';
-import type { Product } from '~/modules/catalog/product/types';
+import type { Price, MediaGalleryItem } from '~/modules/catalog/types';
+import type { Product, ProductAttribute } from '~/modules/catalog/product/types';
+import { Breadcrumb } from '~/modules/catalog/types';
+
import type {
BundleProduct,
CategoryInterface,
@@ -19,17 +16,16 @@ import { getTotalReviews, getAverageRating } from '~/getters/reviewGetters';
export interface ProductGetters {
getName: (product: ProductInterface) => string;
getSlug(product: ProductInterface, category?: CategoryInterface): string;
- getPrice: (product: ProductInterface) => AgnosticPrice;
- getGallery: (product: ProductInterface) => AgnosticMediaGalleryItem[];
+ getPrice: (product: ProductInterface) => Price;
+ getGallery: (product: ProductInterface) => MediaGalleryItem[];
getCoverImage: (product: ProductInterface) => string;
- getAttributes: (products: ProductInterface[] | ProductInterface, filters?: Array) => Record;
+ getAttributes: (products: ProductInterface[] | ProductInterface, filters?: Array) => Record;
getDescription: (product: ProductInterface) => string;
getCategoryIds: (product: ProductInterface) => string[];
getId: (product: ProductInterface) => string;
- getFormattedPrice: (price: number) => string;
getTotalReviews: (product: ProductInterface) => number;
getAverageRating: (product: ProductInterface) => number;
- getBreadcrumbs?: (product: ProductInterface, category?: CategoryInterface) => AgnosticBreadcrumb[];
+ getBreadcrumbs?: (product: ProductInterface, category?: CategoryInterface) => Breadcrumb[];
getCategory(product: Product, currentUrlPath: string): CategoryTree | null;
getProductRelatedProduct(product: ProductInterface): Product[];
getProductSku(product: ProductInterface): string;
@@ -70,14 +66,14 @@ export const getSlug = (product: ProductInterface, category?: CategoryTree | Cat
return url;
};
-export const getPrice = (product: ProductInterface): AgnosticPrice => {
+export const getPrice = (product: ProductInterface): Price => {
let regular = 0;
let special = null;
let maximum = null;
-
+ let final = null;
if (product?.price_range) {
regular = product.price_range.minimum_price.regular_price.value;
- const final = product.price_range.minimum_price.final_price.value;
+ final = product.price_range.minimum_price.final_price.value;
maximum = product.price_range.maximum_price.final_price.value;
if (final < regular) {
@@ -89,10 +85,11 @@ export const getPrice = (product: ProductInterface): AgnosticPrice => {
regular,
special,
maximum,
+ final,
};
};
-export const getGallery = (product: Product): AgnosticMediaGalleryItem[] => {
+export const getGallery = (product: Product): MediaGalleryItem[] => {
const images = [];
if (!product?.media_gallery && !product?.configurable_product_options_selection?.media_gallery) {
@@ -134,7 +131,7 @@ export const getProductThumbnailImage = (product: Product): string => {
export const getAttributes = (
products: Product,
_filterByAttributeName?: string[],
-): Record => {
+): Record => {
if (!products || !products?.configurable_options) {
return {};
}
@@ -145,14 +142,14 @@ export const getAttributes = (
// eslint-disable-next-line no-restricted-syntax
for (const option of configurableOptions) {
attributes[option.attribute_code] = {
- name: option.attribute_code,
+ code: option.attribute_code,
label: option.label,
value: option.values.map((value) => {
const obj = {};
obj[value.value_index] = value.label;
return obj;
}),
- } as AgnosticAttribute;
+ } as ProductAttribute;
}
return attributes;
};
@@ -215,23 +212,7 @@ export const getProductSku = (product: Product): string => product.sku;
// eslint-disable-next-line no-underscore-dangle
export const getTypeId = (product: Product): string => product.__typename;
-export const getFormattedPrice = (price: number) => {
- if (price === null) {
- return null;
- }
-
- // TODO get correct data from api
- const locale = 'en';
- const country = 'en';
- const currency = 'USD';
-
- return new Intl.NumberFormat(`${locale}-${country}`, {
- style: 'currency',
- currency,
- }).format(price);
-};
-
-const getCategoryBreadcrumbs = (category: CategoryInterface): AgnosticBreadcrumb[] => {
+const getCategoryBreadcrumbs = (category: CategoryInterface): Breadcrumb[] => {
let breadcrumbs = [];
if (!category) {
@@ -242,18 +223,18 @@ const getCategoryBreadcrumbs = (category: CategoryInterface): AgnosticBreadcrumb
breadcrumbs = category.breadcrumbs.map((breadcrumb) => ({
text: breadcrumb.category_name,
link: `/c/${breadcrumb.category_url_path}${category.url_suffix || ''}`,
- } as AgnosticBreadcrumb));
+ } as Breadcrumb));
}
breadcrumbs.push({
text: category.name,
link: `/c/${category.url_path}${category.url_suffix || ''}`,
- } as AgnosticBreadcrumb);
+ } as Breadcrumb);
return breadcrumbs;
};
-export const getBreadcrumbs = (product: ProductInterface, category?: CategoryInterface): AgnosticBreadcrumb[] => {
+export const getBreadcrumbs = (product: ProductInterface, category?: CategoryInterface): Breadcrumb[] => {
let breadcrumbs = [];
if (!product) {
@@ -261,7 +242,7 @@ export const getBreadcrumbs = (product: ProductInterface, category?: CategoryInt
}
if (category) {
- breadcrumbs = getCategoryBreadcrumbs(category) as AgnosticBreadcrumb[];
+ breadcrumbs = getCategoryBreadcrumbs(category) as Breadcrumb[];
}
breadcrumbs.push({
@@ -296,7 +277,6 @@ const productGetters: ProductGetters = {
getCategoryIds,
getCoverImage,
getDescription,
- getFormattedPrice,
getGallery,
getId,
getName,
diff --git a/packages/theme/modules/catalog/product/types.ts b/packages/theme/modules/catalog/product/types.ts
index 7aba0213c..1765d5e52 100644
--- a/packages/theme/modules/catalog/product/types.ts
+++ b/packages/theme/modules/catalog/product/types.ts
@@ -7,6 +7,12 @@ import {
VirtualProduct,
} from '~/modules/GraphQL/types';
+export interface ProductAttribute {
+ name?: string;
+ value: string | Record;
+ label: string;
+}
+
/**
* There is no __typename in GraphQL definitions but type_id is marked as a deprecated
*/
@@ -17,7 +23,6 @@ export interface Product extends ProductInterface, ConfigurableProduct, Omit = TProduct & { __typename: string };
-// TODO I am sure that we do not need both interfaces, both are used in the same queries types and both share the same fields
export declare type GetProductSearchParams = {
search?: string;
filter?: ProductAttributeFilterInput;
@@ -26,12 +31,3 @@ export declare type GetProductSearchParams = {
sort?: ProductAttributeSortInput;
configurations?: string[];
};
-
-export interface ProductsSearchParams {
- perPage?: number;
- page?: number;
- sort?: any;
- term?: any;
- filters?: any;
- [x: string]: any;
-}
diff --git a/packages/theme/modules/catalog/types.d.ts b/packages/theme/modules/catalog/types.d.ts
new file mode 100644
index 000000000..0182206d1
--- /dev/null
+++ b/packages/theme/modules/catalog/types.d.ts
@@ -0,0 +1,17 @@
+export interface Breadcrumb {
+ text: string;
+ link: string;
+}
+
+export interface Price {
+ regular: number | null;
+ special?: number | null;
+ maximum?: number | null;
+ final?: number | null;
+}
+
+export interface MediaGalleryItem {
+ small: string;
+ normal: string;
+ big: string;
+}
diff --git a/packages/theme/modules/checkout/composables/useBilling/index.ts b/packages/theme/modules/checkout/composables/useBilling/index.ts
index 872f8cd6d..fd6985138 100644
--- a/packages/theme/modules/checkout/composables/useBilling/index.ts
+++ b/packages/theme/modules/checkout/composables/useBilling/index.ts
@@ -12,7 +12,7 @@ import type {
} from './useBilling';
/**
- * The `useBilling` composable allows loading and saving billing information
+ * Allows loading and saving billing information
* of the current cart.
*
* See the {@link UseBillingInterface} for a list of methods and values available in this composable.
diff --git a/packages/theme/modules/checkout/composables/useBilling/useBilling.ts b/packages/theme/modules/checkout/composables/useBilling/useBilling.ts
index cfdf2fae1..2fed2597a 100644
--- a/packages/theme/modules/checkout/composables/useBilling/useBilling.ts
+++ b/packages/theme/modules/checkout/composables/useBilling/useBilling.ts
@@ -49,7 +49,7 @@ export interface UseBillingSaveParams {
}
/**
- * The refs and methods returned by the {@link useBilling|useBilling()} composable
+ * Data and methods returned from the {@link useBilling|useBilling()} composable
*/
export interface UseBillingInterface {
/**
diff --git a/packages/theme/modules/checkout/composables/useCart/commands/addItemCommand.ts b/packages/theme/modules/checkout/composables/useCart/commands/addItemCommand.ts
index beccb2a71..9c693288f 100644
--- a/packages/theme/modules/checkout/composables/useCart/commands/addItemCommand.ts
+++ b/packages/theme/modules/checkout/composables/useCart/commands/addItemCommand.ts
@@ -35,7 +35,6 @@ export const addItemCommand = {
if (!product) {
return;
}
- // @ts-ignore
// eslint-disable-next-line no-underscore-dangle
switch (product.__typename) {
case 'SimpleProduct':
@@ -85,7 +84,6 @@ export const addItemCommand = {
.cart as unknown as Cart;
case 'BundleProduct':
const createEnteredOptions = () =>
- // @ts-ignore
// eslint-disable-next-line implicit-arrow-linebreak
product.bundle_options.map((bundleOption) => ({
...bundleOption,
@@ -159,8 +157,6 @@ export const addItemCommand = {
.addVirtualProductsToCart
.cart as unknown as Cart;
default:
- // todo implement other options
- // @ts-ignore
// eslint-disable-next-line no-underscore-dangle
throw new Error(`Product Type ${product.__typename} not supported in add to cart yet`);
}
diff --git a/packages/theme/modules/checkout/composables/useCart/commands/removeItemCommand.ts b/packages/theme/modules/checkout/composables/useCart/commands/removeItemCommand.ts
index c255ebf62..380117635 100644
--- a/packages/theme/modules/checkout/composables/useCart/commands/removeItemCommand.ts
+++ b/packages/theme/modules/checkout/composables/useCart/commands/removeItemCommand.ts
@@ -2,7 +2,6 @@ import { Logger } from '~/helpers/logger';
import { Cart, RemoveItemFromCartInput } from '~/modules/GraphQL/types';
import { VsfContext } from '~/composables/context';
-// TODO refactoring point
export const removeItemCommand = {
execute: async (
context: VsfContext,
diff --git a/packages/theme/modules/checkout/composables/useCart/index.ts b/packages/theme/modules/checkout/composables/useCart/index.ts
index dedc6148d..e7c16ee6c 100644
--- a/packages/theme/modules/checkout/composables/useCart/index.ts
+++ b/packages/theme/modules/checkout/composables/useCart/index.ts
@@ -17,9 +17,9 @@ import { UseCartErrors, UseCartInterface } from './useCart';
import { Product } from '~/modules/catalog/product/types';
/**
- * The `useCart` composable provides functions and refs to deal with a user's cart from Magento API.
+ * Allows loading and manipulating cart of the current user.
*
- * See the {@link UseCartInterface} page for more information.
+ * See the {@link UseCartInterface} for a list of methods and values available in this composable.
*/
export function useCart(): UseCartInterface<
CART,
@@ -65,8 +65,7 @@ PRODUCT
*
* @return boolean
*/
- // TODO rework parameter {product} => product, wrapping obj is not necessary
- const isInCart = ({ product }: { product: PRODUCT }): boolean => !!cart.value?.items?.find((cartItem) => cartItem?.product?.uid === product.uid);
+ const isInCart = (product: PRODUCT): boolean => !!cart.value?.items?.find((cartItem) => cartItem?.product?.uid === product.uid);
const load = async ({ customQuery = {}, realCart = false } = { customQuery: { cart: 'cart' } }): Promise => {
Logger.debug('useCart.load');
@@ -130,9 +129,9 @@ PRODUCT
loading.value = true;
if (!apiState.getCartId()) {
- // TODO if cart is not loaded throw error instead to decouple this method
await load({ realCart: true });
}
+
const updatedCart = await addItemCommand.execute(context, {
currentCart: cart.value,
product,
diff --git a/packages/theme/modules/checkout/composables/useCart/useCart.ts b/packages/theme/modules/checkout/composables/useCart/useCart.ts
index 2873536b2..96073ff91 100644
--- a/packages/theme/modules/checkout/composables/useCart/useCart.ts
+++ b/packages/theme/modules/checkout/composables/useCart/useCart.ts
@@ -7,7 +7,7 @@ import { Product } from '~/modules/catalog/product/types';
*/
export type UseCartAddItemParams = ComposableFunctionArgs<{
product: PRODUCT;
- quantity: any; // TODO: Update type
+ quantity: number;
}>;
/**
@@ -40,14 +40,7 @@ type UseCartApplyCouponParams = ComposableFunctionArgs<{
}>;
/**
- * Parameters accepted by the `isInCart` method in the {@link useCart} composable
- */
-type UseCartIsInCartParams = {
- product: PRODUCT
-};
-
-/**
- * Represents data and methods returned from the {@link useCart} composable
+ * Data and methods returned from the {@link useCart} composable
*/
export interface UseCartInterface {
/** Loads the current cart */
@@ -68,8 +61,8 @@ export interface UseCartInterface {
applyCoupon(params: UseCartApplyCouponParams): Promise;
/** Removes applied coupon from the cart */
removeCoupon(params: ComposableFunctionArgs<{}>): Promise;
- /** Checks wheter a `product` is in the `cart` */
- isInCart(params: UseCartIsInCartParams): boolean;
+ /** Checks whether a `product` is in the `cart` */
+ isInCart(PRODUCT): boolean;
/** Sets the contents of the cart */
setCart(newCart: CART): void;
/** Returns the Items in the Cart as a `computed` property */
diff --git a/packages/theme/modules/checkout/composables/useGetShippingMethods/index.ts b/packages/theme/modules/checkout/composables/useGetShippingMethods/index.ts
index c69462913..aa6799183 100644
--- a/packages/theme/modules/checkout/composables/useGetShippingMethods/index.ts
+++ b/packages/theme/modules/checkout/composables/useGetShippingMethods/index.ts
@@ -9,9 +9,9 @@ import { UseGetShippingMethodsInterface } from '~/modules/checkout/composables/u
import { AvailableShippingMethod } from '~/modules/GraphQL/types';
/**
- * The `useGetShippingMethods` composable returns the shipping methods for a given cart
+ * Allows fetching shipping methods available for a given cart.
*
- * Se the {@link UseGetShippingMethodsInterface} page for more information
+ * See the {@link UseGetShippingMethodsInterface} for a list of methods and values available in this composable.
*/
export function useGetShippingMethods(): UseGetShippingMethodsInterface {
const loading = ref(false);
diff --git a/packages/theme/modules/checkout/composables/useGetShippingMethods/useGetShippingMethods.ts b/packages/theme/modules/checkout/composables/useGetShippingMethods/useGetShippingMethods.ts
index 11c376920..03276803f 100644
--- a/packages/theme/modules/checkout/composables/useGetShippingMethods/useGetShippingMethods.ts
+++ b/packages/theme/modules/checkout/composables/useGetShippingMethods/useGetShippingMethods.ts
@@ -8,7 +8,9 @@ export interface UseGetShippingMethodsErrors {
load: Error;
}
-/** Represents data and methods returned by the {@link useGetShippingMethods} composable */
+/**
+ * Data and methods returned from the {@link useGetShippingMethods} composable
+ */
export interface UseGetShippingMethodsInterface {
/** Loads the shipping methods for a cart */
load (params: ComposableFunctionArgs<{ cartId: string }>): Promise;
diff --git a/packages/theme/modules/checkout/composables/useMakeOrder/commands/placeOrderCommand.ts b/packages/theme/modules/checkout/composables/useMakeOrder/commands/placeOrderCommand.ts
index 4a23c95c3..23e9c9d0c 100644
--- a/packages/theme/modules/checkout/composables/useMakeOrder/commands/placeOrderCommand.ts
+++ b/packages/theme/modules/checkout/composables/useMakeOrder/commands/placeOrderCommand.ts
@@ -1,10 +1,8 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { PlaceOrderOutput } from '~/modules/GraphQL/types';
-type Context = ReturnType;
-
export const placeOrderCommand = {
- execute: async (context: Context, cartId: string): Promise => {
+ execute: async (context: UseContextReturn, cartId: string): Promise => {
const { data } = await context.app.$vsf.$magento.api.placeOrder({ cart_id: cartId });
return data?.placeOrder ?? null;
diff --git a/packages/theme/modules/checkout/composables/useMakeOrder/index.ts b/packages/theme/modules/checkout/composables/useMakeOrder/index.ts
index 6fd7dc88a..687cc0596 100644
--- a/packages/theme/modules/checkout/composables/useMakeOrder/index.ts
+++ b/packages/theme/modules/checkout/composables/useMakeOrder/index.ts
@@ -5,7 +5,11 @@ import useCart from '~/modules/checkout/composables/useCart';
import type { PlaceOrderOutput } from '~/modules/GraphQL/types';
import type { UseMakeOrderErrors, UseMakeOrderInterface } from './useMakeOrder';
-/** The `useMakeOrder()` composable allows making an order. */
+/**
+ * Allows making an order from a cart.
+ *
+ * See the {@link UseMakeOrderInterface} for a list of methods and values available in this composable.
+ */
export function useMakeOrder(): UseMakeOrderInterface {
const loading = ref(false);
const error = ref({ make: null });
diff --git a/packages/theme/modules/checkout/composables/useMakeOrder/useMakeOrder.ts b/packages/theme/modules/checkout/composables/useMakeOrder/useMakeOrder.ts
index afe3dc9a2..32ff0ddd0 100644
--- a/packages/theme/modules/checkout/composables/useMakeOrder/useMakeOrder.ts
+++ b/packages/theme/modules/checkout/composables/useMakeOrder/useMakeOrder.ts
@@ -10,7 +10,9 @@ export interface UseMakeOrderErrors {
make: Error | null;
}
-/** The interface provided by {@link useMakeOrder} composable. */
+/**
+ * Data and methods returned from the {@link useRelatedProducts} composable.
+ */
export interface UseMakeOrderInterface {
/** Makes an order with current cart. */
make(): Promise;
diff --git a/packages/theme/modules/checkout/composables/usePaymentProvider/commands/getAvailablePaymentMethodsCommand.ts b/packages/theme/modules/checkout/composables/usePaymentProvider/commands/getAvailablePaymentMethodsCommand.ts
index 5ef6e89f0..db6714b37 100644
--- a/packages/theme/modules/checkout/composables/usePaymentProvider/commands/getAvailablePaymentMethodsCommand.ts
+++ b/packages/theme/modules/checkout/composables/usePaymentProvider/commands/getAvailablePaymentMethodsCommand.ts
@@ -1,10 +1,8 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { AvailablePaymentMethod } from '~/modules/GraphQL/types';
-type Context = ReturnType;
-
export const getAvailablePaymentMethodsCommand = {
- execute: async (context: Context, cartId: string): Promise => {
+ execute: async (context: UseContextReturn, cartId: string): Promise => {
const { data } = await context.app.$vsf.$magento.api.getAvailablePaymentMethods({ cartId });
return data?.cart?.available_payment_methods ?? [];
diff --git a/packages/theme/modules/checkout/composables/usePaymentProvider/commands/setPaymentMethodOnCartCommand.ts b/packages/theme/modules/checkout/composables/usePaymentProvider/commands/setPaymentMethodOnCartCommand.ts
index 190b27d36..09fa90323 100644
--- a/packages/theme/modules/checkout/composables/usePaymentProvider/commands/setPaymentMethodOnCartCommand.ts
+++ b/packages/theme/modules/checkout/composables/usePaymentProvider/commands/setPaymentMethodOnCartCommand.ts
@@ -1,11 +1,9 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { AvailablePaymentMethod } from '~/modules/GraphQL/types';
import type { PaymentMethodParams } from '../usePaymentProvider';
-type Context = ReturnType;
-
export const setPaymentMethodOnCartCommand = {
- execute: async (context: Context, params: PaymentMethodParams): Promise => {
+ execute: async (context: UseContextReturn, params: PaymentMethodParams): Promise => {
const { data } = await context.app.$vsf.$magento.api.setPaymentMethodOnCart(params);
return data?.setPaymentMethodOnCart?.cart.available_payment_methods ?? [];
diff --git a/packages/theme/modules/checkout/composables/usePaymentProvider/index.ts b/packages/theme/modules/checkout/composables/usePaymentProvider/index.ts
index 10db9b893..f554dc82a 100644
--- a/packages/theme/modules/checkout/composables/usePaymentProvider/index.ts
+++ b/packages/theme/modules/checkout/composables/usePaymentProvider/index.ts
@@ -12,8 +12,10 @@ import type {
} from './usePaymentProvider';
/**
- * The `usePaymentProvider()` composable allows loading the available payment
+ * Allows loading the available payment
* methods for current cart, and selecting (saving) one of them.
+ *
+ * See the {@link UsePaymentProviderInterface} for a list of methods and values available in this composable.
*/
export function usePaymentProvider(): UsePaymentProviderInterface {
const context = useContext();
diff --git a/packages/theme/modules/checkout/composables/usePaymentProvider/usePaymentProvider.ts b/packages/theme/modules/checkout/composables/usePaymentProvider/usePaymentProvider.ts
index 31b4ecb72..6dd972098 100644
--- a/packages/theme/modules/checkout/composables/usePaymentProvider/usePaymentProvider.ts
+++ b/packages/theme/modules/checkout/composables/usePaymentProvider/usePaymentProvider.ts
@@ -24,7 +24,9 @@ export type UsePaymentProviderSaveParams = ComposableFunctionArgs<{
paymentMethod: PaymentMethodInput;
}>;
-/** The interface provided by {@link usePaymentProvider} composable. */
+/**
+ * Data and methods returned from the {@link usePaymentProvider} composable.
+ */
export interface UsePaymentProviderInterface {
/** Indicates whether any of the composable methods is in progress. */
loading: Readonly[>;
diff --git a/packages/theme/modules/checkout/composables/useShipping/index.ts b/packages/theme/modules/checkout/composables/useShipping/index.ts
index 4fd1a5984..372940a56 100644
--- a/packages/theme/modules/checkout/composables/useShipping/index.ts
+++ b/packages/theme/modules/checkout/composables/useShipping/index.ts
@@ -10,9 +10,11 @@ import type {
} from './useShipping';
/**
- * The `useShipping()` composable allows loading the shipping information for
+ * Allows loading the shipping information for
* the current cart and saving (selecting) other shipping information for the
* same cart.
+ *
+ * See the {@link UseShippingInterface} for a list of methods and values available in this composable.
*/
export function useShipping(): UseShippingInterface {
const loading = ref(false);
@@ -20,9 +22,9 @@ export function useShipping(): UseShippingInterface {
const { cart, load: loadCart } = useCart();
const { app } = useContext();
- const load = async (params: UseShippingLoadParams = {}): Promise] => {
+ const load = async (params: UseShippingLoadParams = {}): Promise => {
Logger.debug('useShipping.load');
- let shippingInfo = null;
+ let shippingInfo : ShippingCartAddress | null = null;
try {
loading.value = true;
@@ -32,21 +34,18 @@ export function useShipping(): UseShippingInterface {
[shippingInfo] = cart.value.shipping_addresses;
error.value.load = null;
-
- return shippingInfo;
} catch (err) {
error.value.load = err;
Logger.error('useShipping/load', err);
} finally {
loading.value = false;
}
-
- return {};
+ return shippingInfo;
};
- const save = async ({ shippingDetails }: UseShippingSaveParams): Promise => {
+ const save = async ({ shippingDetails }: UseShippingSaveParams): Promise => {
Logger.debug('useShipping.save');
- let shippingInfo = null;
+ let shippingInfo : ShippingCartAddress | null = null;
try {
loading.value = true;
@@ -86,8 +85,6 @@ export function useShipping(): UseShippingInterface {
[shippingInfo] = data.setShippingAddressesOnCart.cart.shipping_addresses;
error.value.save = null;
-
- return shippingInfo;
} catch (err) {
error.value.save = err;
Logger.error('useShipping/save', err);
@@ -95,7 +92,7 @@ export function useShipping(): UseShippingInterface {
loading.value = false;
}
- return {};
+ return shippingInfo;
};
return {
diff --git a/packages/theme/modules/checkout/composables/useShipping/useShipping.ts b/packages/theme/modules/checkout/composables/useShipping/useShipping.ts
index 724410901..bea11db58 100644
--- a/packages/theme/modules/checkout/composables/useShipping/useShipping.ts
+++ b/packages/theme/modules/checkout/composables/useShipping/useShipping.ts
@@ -19,11 +19,12 @@ export type UseShippingLoadParams = ComposableFunctionArgs<{}>;
/** The params received by {@link useShipping}'s `save` method. */
export type UseShippingSaveParams = ComposableFunctionArgs<{
- params: any;
shippingDetails: any;
}>;
-/** The interface provided by {@link useShipping} composable. */
+/**
+ * Data and methods returned from the {@link useShipping} composable.
+ */
export interface UseShippingInterface {
/**
* Contains errors from any of the composable methods.
@@ -36,8 +37,8 @@ export interface UseShippingInterface {
loading: Readonly[>;
/** Loads the shipping information for current cart. */
- load(params?: UseShippingLoadParams): Promise];
+ load(params?: UseShippingLoadParams): Promise
/** Save new shipping information for current cart. */
- save(params: UseShippingSaveParams): Promise;
+ save(params: UseShippingSaveParams): Promise;
}
diff --git a/packages/theme/modules/checkout/composables/useShippingProvider/commands/setShippingMethodsOnCartCommand.ts b/packages/theme/modules/checkout/composables/useShippingProvider/commands/setShippingMethodsOnCartCommand.ts
index 24768512b..c4c652707 100644
--- a/packages/theme/modules/checkout/composables/useShippingProvider/commands/setShippingMethodsOnCartCommand.ts
+++ b/packages/theme/modules/checkout/composables/useShippingProvider/commands/setShippingMethodsOnCartCommand.ts
@@ -1,10 +1,8 @@
-import type { useContext } from '@nuxtjs/composition-api';
+import { UseContextReturn } from '~/types/core';
import type { SetShippingMethodsOnCartInput, Cart } from '~/modules/GraphQL/types';
-type Context = ReturnType;
-
export const setShippingMethodsOnCartCommand = {
- execute: async (context: Context, shippingMethodParams: SetShippingMethodsOnCartInput): Promise => {
+ execute: async (context: UseContextReturn, shippingMethodParams: SetShippingMethodsOnCartInput): Promise => {
const { data } = await context.app.$vsf.$magento.api.setShippingMethodsOnCart(shippingMethodParams);
// TODO: Find out why 'Cart' doesn't match the type of the response data.
diff --git a/packages/theme/modules/checkout/composables/useShippingProvider/index.ts b/packages/theme/modules/checkout/composables/useShippingProvider/index.ts
index a455fbe5c..628113a00 100644
--- a/packages/theme/modules/checkout/composables/useShippingProvider/index.ts
+++ b/packages/theme/modules/checkout/composables/useShippingProvider/index.ts
@@ -11,7 +11,7 @@ import type {
} from './useShippingProvider';
/**
- * The `useShippingProvider` composable allows loading the shipping provider
+ * Allows loading the shipping provider
* for the current cart and saving (selecting) other shipping provider for the
* same cart.
*
diff --git a/packages/theme/modules/checkout/composables/useShippingProvider/useShippingProvider.ts b/packages/theme/modules/checkout/composables/useShippingProvider/useShippingProvider.ts
index 7c9e53b0e..0e187973b 100644
--- a/packages/theme/modules/checkout/composables/useShippingProvider/useShippingProvider.ts
+++ b/packages/theme/modules/checkout/composables/useShippingProvider/useShippingProvider.ts
@@ -1,5 +1,5 @@
import type { Ref } from '@nuxtjs/composition-api';
-import type { SelectedShippingMethod } from '~/modules/GraphQL/types';
+import type { SelectedShippingMethod, ShippingMethodInput } from '~/modules/GraphQL/types';
import type { ComposableFunctionArgs } from '~/composables/types';
/**
@@ -26,12 +26,11 @@ export type UseShippingProviderLoadParams = ComposableFunctionArgs<{}>;
* The params object accepted by the `save` method in the {@link useShippingProvider|useShippingProvider()} composable
*/
export type UseShippingProviderSaveParams = ComposableFunctionArgs<{
- // TODO: Define this type.
- shippingMethod: any;
+ shippingMethod: ShippingMethodInput;
}>;
/**
- * The refs and methods returned by the {@link useShippingProvider|useShippingProvider()} composable
+ * Data and methods returned from the {@link useShippingProvider|useShippingProvider()} composable
*/
export interface UseShippingProviderInterface {
/**
diff --git a/packages/theme/modules/checkout/getters/cartGetters.ts b/packages/theme/modules/checkout/getters/cartGetters.ts
index f960ad45c..0b0302736 100644
--- a/packages/theme/modules/checkout/getters/cartGetters.ts
+++ b/packages/theme/modules/checkout/getters/cartGetters.ts
@@ -1,13 +1,9 @@
-import {
- AgnosticPrice,
- AgnosticTotals,
- AgnosticAttribute,
- AgnosticCoupon,
- AgnosticDiscount,
+import type {
+ Totals,
} from '~/composables/types';
-
+import type { Price } from '~/modules/catalog/types';
+import type { ProductAttribute, Product } from '~/modules/catalog/product/types';
import { PaymentMethodInterface } from '~/modules/checkout/types';
-
import {
CartItemInterface,
Cart,
@@ -16,10 +12,7 @@ import {
SelectedShippingMethod,
ConfigurableCartItem,
} from '~/modules/GraphQL/types';
-import type { Product } from '~/modules/catalog/product/types';
-
-import { CartGetters as CartGettersBase } from '~/getters/types';
-
+import { CartGetters as CartGettersBase, CartDiscount, Coupon } from '~/getters/types';
import { getName, getSlug as getSlugGetter, getProductThumbnailImage } from '~/modules/catalog/product/getters/productGetters';
export const getItems = (cart: Cart): CartItemInterface[] => {
@@ -35,7 +28,7 @@ export const getSlug = (product: CartItemInterface): string => getSlugGetter(pro
export const getItemImage = (product: CartItemInterface): string => getProductThumbnailImage(product.product as Product);
-export const getItemPrice = (product: CartItemInterface): AgnosticPrice => {
+export const getItemPrice = (product: CartItemInterface): Price => {
if (!product || !product.prices) {
return {
regular: 0,
@@ -56,8 +49,6 @@ export const getItemPrice = (product: CartItemInterface): AgnosticPrice => {
special: specialPrice || 0,
// @ts-ignore
credit: Math.round(specialPrice / 10),
- // @TODO: Who set this installment value?
- installment: Math.round((specialPrice * 1.1046) / 10),
discountPercentage: 100 - Math.round((specialPrice / regularPrice) * 100),
total: product.prices?.row_total?.value,
};
@@ -70,7 +61,7 @@ export const getItemQty = (product: CartItemInterface): number => product.quanti
export const getItemAttributes = (
{ product }: CartItemInterface & { product: Product },
_filterByAttributeName?: Array,
-): Record => {
+): Record => {
const attributes = {};
if (!product || !product.configurable_options) {
@@ -89,7 +80,7 @@ export const getItemAttributes = (
obj[value.value_index] = value.label;
return obj;
}),
- } as AgnosticAttribute;
+ } as ProductAttribute;
}
return attributes;
};
@@ -98,15 +89,15 @@ export const getItemSku = (product: CartItemInterface): string => product?.produ
const calculateDiscounts = (discounts: Discount[]): number => discounts.reduce((a, b) => Number.parseFloat(`${a}`) + Number.parseFloat(`${b.amount.value}`), 0);
-export const getTotals = (cart: Cart): AgnosticTotals => {
- if (!cart || !cart.prices) return {} as AgnosticTotals;
+export const getTotals = (cart: Cart): Totals => {
+ if (!cart || !cart.prices) return {} as Totals;
const subtotal = cart.prices.subtotal_including_tax.value;
return {
total: cart.prices.grand_total.value,
subtotal: cart.prices.subtotal_including_tax.value,
special: (cart.prices.discounts) ? subtotal - calculateDiscounts(cart.prices.discounts) : subtotal,
- } as AgnosticTotals;
+ } as Totals;
};
export const getShippingPrice = (cart: Cart): number => {
@@ -140,25 +131,24 @@ export const getTotalItems = (cart: Cart): number => {
export const getConfiguredVariant = (product: ConfigurableCartItem): ProductInterface | null => product?.configured_variant || null;
-// eslint-disable-next-line import/no-named-as-default-member
-export const getFormattedPrice = (price: number) => getFormattedPrice(price);
-
-export const getCoupons = (cart: Cart): AgnosticCoupon[] => (Array.isArray(cart?.applied_coupons) ? cart.applied_coupons.map((c) => ({
+export const getCoupons = (cart: Cart): Coupon[] => (Array.isArray(cart?.applied_coupons) ? cart.applied_coupons.map((c) => ({
id: c.code,
name: c.code,
value: 0,
code: c.code,
-} as AgnosticCoupon)) : []);
+} as Coupon)) : []);
-export const getDiscounts = (cart: Cart): AgnosticDiscount[] => (Array.isArray(cart?.prices?.discounts) ? cart.prices.discounts.map((d) => ({
+export const getDiscounts = (cart: Cart): CartDiscount[] => (Array.isArray(cart?.prices?.discounts) ? cart.prices.discounts.map((d) => ({
id: d.label,
name: d.label,
description: '',
value: d.amount.value,
code: d.label,
-} as AgnosticDiscount)) : []);
+} as CartDiscount)) : []);
+
+export const getDiscountAmount = (cart: Cart): number => calculateDiscounts(cart?.prices?.discounts ?? []);
-export const getAppliedCoupon = (cart: Cart): AgnosticCoupon | null => (Array.isArray(cart?.applied_coupons) && cart?.applied_coupons.length > 0 ? {
+export const getAppliedCoupon = (cart: Cart): Coupon | null => (Array.isArray(cart?.applied_coupons) && cart?.applied_coupons.length > 0 ? {
id: cart.applied_coupons[0].code,
name: cart.applied_coupons[0].code,
value: 0,
@@ -176,7 +166,7 @@ export const getAvailablePaymentMethods = (cart: Cart): PaymentMethodInterface[]
export const getStockStatus = (product: CartItemInterface): string => product.product.stock_status;
export interface CartGetters extends CartGettersBase {
- getAppliedCoupon(cart: Cart): AgnosticCoupon | null;
+ getAppliedCoupon(cart: Cart): Coupon | null;
getAvailablePaymentMethods(cart: Cart): PaymentMethodInterface[];
getSelectedShippingMethod(cart: Cart): SelectedShippingMethod | null;
productHasSpecialPrice(product: CartItemInterface): boolean;
@@ -189,7 +179,6 @@ const cartGetters: CartGetters = {
getAvailablePaymentMethods,
getCoupons,
getDiscounts,
- getFormattedPrice,
getItemAttributes,
getItemImage,
getItemName,
@@ -202,6 +191,7 @@ const cartGetters: CartGetters = {
getShippingPrice,
getTotalItems,
getTotals,
+ getDiscountAmount,
productHasSpecialPrice,
getStockStatus,
getConfiguredVariant,
diff --git a/packages/theme/modules/checkout/getters/orderGetters.ts b/packages/theme/modules/checkout/getters/orderGetters.ts
index e965e582f..45b5bf3b5 100644
--- a/packages/theme/modules/checkout/getters/orderGetters.ts
+++ b/packages/theme/modules/checkout/getters/orderGetters.ts
@@ -1,4 +1,4 @@
-import type { AgnosticPagination } from '~/composables/types';
+import type { Pagination } from '~/composables/types';
import type { CustomerOrders, CustomerOrder, OrderItemInterface } from '~/modules/GraphQL/types';
export const getDate = (order: CustomerOrder): string => new Date(order?.order_date?.replace(/ /g, 'T')).toLocaleDateString();
@@ -7,7 +7,7 @@ export const getPrice = (order: CustomerOrder): number => order?.total?.base_gra
export const getItemPrice = (item: OrderItemInterface): number => item?.product_sale_price?.value ?? 0;
-const getPagination = (orders: CustomerOrders): AgnosticPagination => ({
+const getPagination = (orders: CustomerOrders): Pagination => ({
currentPage: orders?.page_info?.current_page || 1,
totalPages: orders?.page_info?.total_pages || 1,
totalItems: orders?.total_count || 0,
diff --git a/packages/theme/modules/checkout/index.ts b/packages/theme/modules/checkout/index.ts
index c091eed38..89220faf2 100644
--- a/packages/theme/modules/checkout/index.ts
+++ b/packages/theme/modules/checkout/index.ts
@@ -1,11 +1,12 @@
-import { Module } from '@nuxt/types';
import path from 'node:path';
import url from 'node:url';
+import type { Module } from '@nuxt/types';
+import type { NuxtRouteConfig } from '@nuxt/types/config/router';
const nuxtModule : Module = function checkoutModule() {
const moduleDir = path.dirname(url.fileURLToPath(import.meta.url));
- this.extendRoutes((routes) => {
+ this.extendRoutes((routes: NuxtRouteConfig[]) => {
routes.unshift(
{
name: 'checkout',
diff --git a/packages/theme/modules/checkout/pages/Checkout.vue b/packages/theme/modules/checkout/pages/Checkout.vue
index 23475cd63..5b14e5baf 100644
--- a/packages/theme/modules/checkout/pages/Checkout.vue
+++ b/packages/theme/modules/checkout/pages/Checkout.vue
@@ -95,8 +95,6 @@ export default defineComponent({
if (products.value.length === 0 && currentStep.value !== 'thank-you') {
await router.push(app.localePath('/'));
}
-
- return null;
});
return {
diff --git a/packages/theme/modules/checkout/pages/Checkout/Billing.vue b/packages/theme/modules/checkout/pages/Checkout/Billing.vue
index 9629108fa..98d362670 100644
--- a/packages/theme/modules/checkout/pages/Checkout/Billing.vue
+++ b/packages/theme/modules/checkout/pages/Checkout/Billing.vue
@@ -298,12 +298,16 @@ import { useUserAddress } from '~/modules/customer/composables/useUserAddress';
import UserAddressDetails from '~/components/UserAddressDetails.vue';
import {
addressFromApiToForm,
+ CheckoutAddressForm,
formatAddressReturnToData,
+ getInitialCheckoutAddressForm,
} from '~/helpers/checkout/address';
import { mergeItem } from '~/helpers/asyncLocalStorage';
import { isPreviousStepValid } from '~/helpers/checkout/steps';
-import type { ShippingCartAddress, Customer } from '~/modules/GraphQL/types';
+import type {
+ ShippingCartAddress, BillingCartAddress, Country, Customer, CustomerAddress,
+} from '~/modules/GraphQL/types';
const NOT_SELECTED_ADDRESS = '';
@@ -336,9 +340,9 @@ export default defineComponent({
setup() {
const router = useRouter();
const { app } = useContext();
- const shippingDetails = ref({});
- const billingAddress = ref({});
- const userBilling = ref({});
+ const shippingDetails = ref(null);
+ const billingAddress = ref(null);
+ const userBilling = ref(null);
const {
save, load: loadBilling, loading,
@@ -355,19 +359,23 @@ export default defineComponent({
search: searchCountry,
} = useCountrySearch();
- const countries = ref([]);
- const country = ref(null);
+ const countries = ref([]);
+ const country = ref(null);
const { isAuthenticated } = useUser();
- let oldBilling = null;
+ let oldBilling : CheckoutAddressForm | null = null;
const sameAsShipping = ref(false);
- const billingDetails = ref(addressFromApiToForm(billingAddress.value));
+ const billingDetails = ref(
+ billingAddress.value
+ ? addressFromApiToForm(billingAddress.value)
+ : getInitialCheckoutAddressForm(),
+ );
const currentAddressId = ref(NOT_SELECTED_ADDRESS);
const setAsDefault = ref(false);
const isFormSubmitted = ref(false);
const canAddNewAddress = ref(true);
const isBillingDetailsStepCompleted = ref(false);
- const addresses = computed(() => userBillingGetters.getAddresses(userBilling.value) ?? []);
+ const addresses = computed(() => (userBilling.value ? userBillingGetters.getAddresses(userBilling.value) : []));
const canMoveForward = computed(() => !loading.value && billingDetails.value && Object.keys(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
@@ -383,11 +391,9 @@ export default defineComponent({
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const countriesList = computed(() => addressGetter.countriesList(countries.value));
-
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const regionInformation = computed(() => addressGetter.regionList(country.value));
- const handleAddressSubmit = (reset) => async () => {
+ const handleAddressSubmit = (reset: () => void) => async () => {
const addressId = currentAddressId.value;
const billingDetailsData = {
billingDetails: {
@@ -438,19 +444,19 @@ export default defineComponent({
const handleAddNewAddressBtnClick = () => {
currentAddressId.value = NOT_SELECTED_ADDRESS;
- billingDetails.value = {};
+ billingDetails.value = getInitialCheckoutAddressForm();
canAddNewAddress.value = true;
isBillingDetailsStepCompleted.value = false;
};
- const handleSetCurrentAddress = (addr) => {
+ const handleSetCurrentAddress = (addr: CustomerAddress) => {
billingDetails.value = { ...addressFromApiToForm(addr) };
- currentAddressId.value = addr?.id;
+ currentAddressId.value = String(addr?.id);
canAddNewAddress.value = false;
isBillingDetailsStepCompleted.value = false;
};
- const changeBillingDetails = (field, value) => {
+ const changeBillingDetails = (field: string, value: unknown) => {
billingDetails.value = {
...billingDetails.value,
[field]: value,
@@ -469,13 +475,13 @@ export default defineComponent({
}
};
- const changeCountry = async (id) => {
+ const changeCountry = async (id: string) => {
changeBillingDetails('country_code', id);
country.value = await searchCountry({ id });
};
watch(billingAddress, (addr) => {
- billingDetails.value = addressFromApiToForm(addr || {});
+ billingDetails.value = addr ? addressFromApiToForm(addr) : getInitialCheckoutAddressForm();
});
onMounted(async () => {
@@ -495,9 +501,7 @@ export default defineComponent({
if (!(userBilling.value as Customer)?.addresses && isAuthenticated.value) {
userBilling.value = await loadUserBilling();
}
- const billingAddresses = userBillingGetters.getAddresses(
- userBilling.value,
- );
+ const billingAddresses = userBilling.value ? userBillingGetters.getAddresses(userBilling.value) : [];
if (!billingAddresses || billingAddresses.length === 0) {
return;
diff --git a/packages/theme/modules/checkout/pages/Checkout/Payment.vue b/packages/theme/modules/checkout/pages/Checkout/Payment.vue
index d1bc81337..fe7da4295 100644
--- a/packages/theme/modules/checkout/pages/Checkout/Payment.vue
+++ b/packages/theme/modules/checkout/pages/Checkout/Payment.vue
@@ -185,6 +185,7 @@ import useCart from '~/modules/checkout/composables/useCart';
import getShippingMethodPrice from '~/helpers/checkout/getShippingMethodPrice';
import { removeItem } from '~/helpers/asyncLocalStorage';
import { isPreviousStepValid } from '~/helpers/checkout/steps';
+import type { BundleCartItem, ConfigurableCartItem } from '~/modules/GraphQL/types';
export default defineComponent({
name: 'ReviewOrderAndPayment',
@@ -208,8 +209,8 @@ export default defineComponent({
const router = useRouter();
const isPaymentReady = ref(false);
const terms = ref(false);
- const getAttributes = (product) => product.configurable_options || [];
- const getBundles = (product) => product.bundle_options?.map((b) => b.values).flat() || [];
+ const getAttributes = (product: ConfigurableCartItem) => product.configurable_options || [];
+ const getBundles = (product: BundleCartItem) => product.bundle_options?.map((b) => b.values).flat() || [];
onMounted(async () => {
const validStep = await isPreviousStepValid('billing');
diff --git a/packages/theme/modules/checkout/pages/Checkout/Shipping.vue b/packages/theme/modules/checkout/pages/Checkout/Shipping.vue
index 13a4e077f..8ee844561 100644
--- a/packages/theme/modules/checkout/pages/Checkout/Shipping.vue
+++ b/packages/theme/modules/checkout/pages/Checkout/Shipping.vue
@@ -277,11 +277,13 @@ import addressGetter from '~/modules/customer/getters/addressGetter';
import {
useCountrySearch,
} from '~/composables';
-import type { Country, AvailableShippingMethod, ShippingCartAddress } from '~/modules/GraphQL/types';
+import type {
+ Country, AvailableShippingMethod, ShippingCartAddress, CustomerAddress, Customer,
+} from '~/modules/GraphQL/types';
import useShipping from '~/modules/checkout/composables/useShipping';
import useUser from '~/modules/customer/composables/useUser';
import useUserAddress from '~/modules/customer/composables/useUserAddress';
-import { addressFromApiToForm } from '~/helpers/checkout/address';
+import { addressFromApiToForm, CheckoutAddressForm, getInitialCheckoutAddressForm } from '~/helpers/checkout/address';
import { mergeItem } from '~/helpers/asyncLocalStorage';
import { isPreviousStepValid } from '~/helpers/checkout/steps';
@@ -315,8 +317,8 @@ export default defineComponent({
setup() {
const router = useRouter();
const { app } = useContext();
- const address = ref({});
- const userShipping = ref({});
+ const address = ref(null);
+ const userShipping = ref(null);
const {
load: loadShipping,
save: saveShipping,
@@ -334,7 +336,7 @@ export default defineComponent({
const countries = ref([]);
const country = ref(null);
const { isAuthenticated } = useUser();
- const shippingDetails = ref(addressFromApiToForm(address.value) || {});
+ const shippingDetails = ref(address.value ? addressFromApiToForm(address.value) : getInitialCheckoutAddressForm());
const shippingMethods = ref([]);
const currentAddressId = ref(NOT_SELECTED_ADDRESS);
@@ -346,7 +348,6 @@ export default defineComponent({
const addresses = computed(() => userShippingGetters.getAddresses(userShipping.value));
const canMoveForward = computed(() => !isShippingLoading.value && shippingDetails.value && Object.keys(
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
shippingDetails.value,
).length > 0);
@@ -356,22 +357,21 @@ export default defineComponent({
}
return addresses.value.length > 0;
});
- // @ts-ignore
+
const countriesList = computed(() => addressGetter.countriesList(countries.value));
const regionInformation = computed(() => addressGetter.regionList(country.value));
- const handleAddressSubmit = (reset) => async () => {
+ const handleAddressSubmit = (reset: () => void) => async () => {
const addressId = currentAddressId.value;
const shippingDetailsData = {
...shippingDetails.value,
customerAddressId: addressId,
};
await mergeItem('checkout', { shipping: shippingDetailsData });
- // @TODO remove expect-error when https://github.com/vuestorefront/vue-storefront/issues/5967 is applied
- // @ts-expect-error
- const shippingInfo : ShippingCartAddress = await saveShipping({ shippingDetails: shippingDetailsData });
- shippingMethods.value = shippingInfo.available_shipping_methods;
+
+ const shippingInfo = await saveShipping({ shippingDetails: shippingDetailsData });
+ shippingMethods.value = shippingInfo?.available_shipping_methods ?? [];
if (addressId !== NOT_SELECTED_ADDRESS && setAsDefault.value) {
const [chosenAddress] = userShippingGetters.getAddresses(
@@ -390,19 +390,19 @@ export default defineComponent({
const handleAddNewAddressBtnClick = () => {
currentAddressId.value = NOT_SELECTED_ADDRESS;
- shippingDetails.value = {};
+ shippingDetails.value = getInitialCheckoutAddressForm();
canAddNewAddress.value = true;
isShippingDetailsStepCompleted.value = false;
};
- const handleSetCurrentAddress = (addr) => {
+ const handleSetCurrentAddress = (addr: CustomerAddress) => {
shippingDetails.value = { ...addressFromApiToForm(addr) };
- currentAddressId.value = addr?.id;
+ currentAddressId.value = String(addr?.id);
canAddNewAddress.value = false;
isShippingDetailsStepCompleted.value = false;
};
- const changeShippingDetails = (field, value) => {
+ const changeShippingDetails = (field: string, value: unknown) => {
shippingDetails.value = {
...shippingDetails.value,
[field]: value,
@@ -415,19 +415,19 @@ export default defineComponent({
const defaultAddress = userShippingGetters.getAddresses(
userShipping.value,
{ default_shipping: true },
- );
+ ) as [CustomerAddress] | [];
if (defaultAddress && defaultAddress.length > 0) {
handleSetCurrentAddress(defaultAddress[0]);
}
};
- const changeCountry = async (id) => {
+ const changeCountry = async (id: string) => {
changeShippingDetails('country_code', id);
country.value = await searchCountry({ id });
};
watch(address, (addr) => {
- shippingDetails.value = addressFromApiToForm(addr || {});
+ shippingDetails.value = addr ? addressFromApiToForm(addr) : getInitialCheckoutAddressForm();
});
onMounted(async () => {
const validStep = await isPreviousStepValid('user-account');
@@ -456,9 +456,7 @@ export default defineComponent({
return;
}
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const hasEmptyShippingDetails = !shippingDetails.value
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|| Object.keys(shippingDetails.value).length === 0;
if (hasEmptyShippingDetails) {
selectDefaultAddress();
diff --git a/packages/theme/modules/checkout/pages/Checkout/UserAccount.vue b/packages/theme/modules/checkout/pages/Checkout/UserAccount.vue
index eda2b7533..0ebc0d889 100644
--- a/packages/theme/modules/checkout/pages/Checkout/UserAccount.vue
+++ b/packages/theme/modules/checkout/pages/Checkout/UserAccount.vue
@@ -236,7 +236,7 @@ export default defineComponent({
is_subscribed: false,
});
- const handleFormSubmit = (reset) => async () => {
+ const handleFormSubmit = (reset: () => void) => async () => {
if (isRecaptchaEnabled.value) {
$recaptcha.init();
}
@@ -263,7 +263,7 @@ export default defineComponent({
await login({
user: {
- username: form.value.email,
+ email: form.value.email,
password: form.value.password,
...recaptchaParams,
},
diff --git a/packages/theme/modules/customer/components/LoginModal/LoginModal.vue b/packages/theme/modules/customer/components/LoginModal/LoginModal.vue
new file mode 100644
index 000000000..1a5801f8d
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/LoginModal.vue
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+
+ onLoginFormSubmit(form)"
+ @change-form="currentlyDisplayedForm = $event"
+ >
+
+ {{ userError.login && userError.login.message }}
+
+
+ onRegisterFormSubmit(form)"
+ @change-form="currentlyDisplayedForm = $event"
+ >
+
+ {{ userError.register && userError.register.message }}
+
+
+ onForgotPasswordFormSubmit(form)"
+ >
+
+ {{ forgotPasswordError.request && forgotPasswordError.request.message }}
+
+
+
+
+ {{ forgotPasswordForm.username }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/modules/customer/components/LoginModal/__tests__/LoginModal.spec.ts b/packages/theme/modules/customer/components/LoginModal/__tests__/LoginModal.spec.ts
new file mode 100644
index 000000000..fe855589d
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/__tests__/LoginModal.spec.ts
@@ -0,0 +1,173 @@
+/* eslint-disable no-underscore-dangle */
+import { render, waitFor } from '@testing-library/vue';
+import { ref } from '@nuxtjs/composition-api';
+import { createLocalVue } from '@vue/test-utils';
+import { PiniaVuePlugin } from 'pinia';
+import { createTestingPinia } from '@pinia/testing';
+import { useUser } from '~/modules/customer/composables/useUser';
+import { useForgotPassword } from '~/modules/customer/composables/useForgotPassword';
+import { HTMLElementWithVue } from '~/types/vueTestingLibrary';
+import LoginModal from '../LoginModal.vue';
+import {
+ ForgotPasswordFormFields, FormName, LoginFormFields, RegisterFormFields,
+} from '../forms/types';
+
+const localVue = createLocalVue();
+localVue.use(PiniaVuePlugin);
+
+jest.mock('~/composables/useUiState', () => ({ useUiState: () => ({ isLoginModalOpen: true }) }));
+jest.mock('~/modules/customer/composables/useUser', () => ({ useUser: jest.fn() }));
+const useUserMock = {
+ login: jest.fn(),
+ register: jest.fn(),
+ loading: ref(false),
+ error: ref({ login: null }),
+};
+(useUser as jest.Mock).mockReturnValue(useUserMock);
+
+jest.mock('~/modules/customer/composables/useForgotPassword', () => ({ useForgotPassword: jest.fn() }));
+const useForgotPasswordMock = {
+ request: jest.fn(),
+ error: { value: { request: null, setNew: null } },
+ loading: ref(false),
+};
+(useForgotPassword as jest.Mock).mockReturnValue(useForgotPasswordMock);
+
+describe('LoginModal', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ it('renders login form initially', () => {
+ const { getByTestId } = render(LoginModal, {
+ props: { isLoginModalOpen: true },
+ localVue,
+ pinia: createTestingPinia(),
+ });
+ getByTestId('login-form');
+ });
+
+ it('sends login form', async () => {
+ const { getByTestId } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ });
+ const loginForm = getByTestId('login-form');
+ const form : LoginFormFields = {
+ email: 'john@smith.com',
+ password: 'P@ssw0rd!',
+ };
+ await waitFor(() => {
+ (loginForm as HTMLElementWithVue).__vue__.$emit('submit', form);
+ });
+
+ expect(useUserMock.login).toHaveBeenCalled();
+ });
+
+ it('displays error message if login fails', async () => {
+ const useUserMockWithError = {
+ login: jest.fn(),
+ register: jest.fn(),
+ loading: false,
+ error: { login: { message: 'error:login' } },
+ };
+ (useUser as jest.Mock).mockReturnValueOnce(useUserMockWithError);
+ const { getByText } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ });
+ await waitFor(() => {
+ getByText('error:login');
+ });
+ });
+
+ it('sends register form', async () => {
+ const { getByTestId } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ });
+
+ await waitFor(() => {
+ (getByTestId('login-form') as HTMLElementWithVue).__vue__.$emit('change-form', 'register' as FormName);
+ });
+ const registerForm = getByTestId('register-form') as HTMLElementWithVue;
+ const form : RegisterFormFields = {
+ email: 'john@smith.com',
+ password: 'P@ssw0rd!',
+ firstName: 'John',
+ lastName: 'Smith',
+ isWillToCreateAccountConfirmed: true,
+ shouldSubscribeToNewsletter: false,
+ };
+ registerForm.__vue__.$emit('submit', form);
+
+ await waitFor(() => {
+ expect(useUserMock.register).toHaveBeenCalled();
+ });
+ });
+
+ it('displays error message if register fails', async () => {
+ const useUserMockWithError = {
+ login: jest.fn(),
+ register: jest.fn(),
+ loading: false,
+ error: { register: { message: 'error:register' } },
+ };
+ (useUser as jest.Mock).mockReturnValueOnce(useUserMockWithError);
+ const { getByText, getByTestId } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ });
+
+ await waitFor(() => {
+ (getByTestId('login-form') as HTMLElementWithVue).__vue__.$emit('change-form', 'register' as FormName);
+ });
+ await waitFor(() => {
+ getByText('error:register');
+ });
+ });
+
+ it('sends forgot password form and shows thank you page', async () => {
+ const { getByTestId, getByText } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ stubs: ['i18n'],
+ });
+
+ await waitFor(() => {
+ (getByTestId('login-form') as HTMLElementWithVue).__vue__.$emit('change-form', 'forgotPassword' as FormName);
+ });
+ const forgotPasswordForm = getByTestId('forgot-password-form') as HTMLElementWithVue;
+ const form : ForgotPasswordFormFields = {
+ username: 'john@smith.com',
+ };
+
+ forgotPasswordForm.__vue__.$emit('submit', form);
+
+ await waitFor(() => {
+ getByTestId('forgot-password-thank-you');
+ getByText('john@smith.com');
+ expect(useForgotPasswordMock.request).toHaveBeenCalled();
+ });
+ });
+
+ it('displays error message if forgot password from send fails', async () => {
+ const useForgotPasswordMockWithError = {
+ request: jest.fn(),
+ error: { request: { message: 'error:forgotpassword' }, setNew: null },
+ register: jest.fn(),
+ loading: false,
+ };
+ (useForgotPassword as jest.Mock).mockReturnValueOnce(useForgotPasswordMockWithError);
+ const { getByText, getByTestId } = render(LoginModal, {
+ localVue,
+ pinia: createTestingPinia(),
+ });
+
+ await waitFor(() => {
+ (getByTestId('login-form') as HTMLElementWithVue).__vue__.$emit('change-form', 'forgotPassword' as FormName);
+ });
+ await waitFor(() => {
+ getByText('error:forgotpassword');
+ });
+ });
+});
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordForm.vue b/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordForm.vue
new file mode 100644
index 000000000..8ec58844c
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordForm.vue
@@ -0,0 +1,97 @@
+
+
+
{{ $t('Forgot Password') }}
+
+
+
+
+
+
+
+
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordThankYou.vue b/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordThankYou.vue
new file mode 100644
index 000000000..b5efc6c65
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/ForgotPasswordThankYou.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
{{ $t('Thank You Inbox') }}
+
+
+
+
+
+
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/LoginForm.vue b/packages/theme/modules/customer/components/LoginModal/forms/LoginForm.vue
new file mode 100644
index 000000000..91df4c0f0
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/LoginForm.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+ {{ $t('Forgotten password?') }}
+
+
+
+
+ {{ $t('No account') }}
+
+
+ {{ $t('Register today') }}
+
+
+
+
+
+
+
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/RegisterForm.vue b/packages/theme/modules/customer/components/LoginModal/forms/RegisterForm.vue
new file mode 100644
index 000000000..cd0720af3
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/RegisterForm.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/__tests__/ForgotPasswordForm.spec.ts b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/ForgotPasswordForm.spec.ts
new file mode 100644
index 000000000..b51443e5d
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/ForgotPasswordForm.spec.ts
@@ -0,0 +1,29 @@
+import { render } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+
+import ForgotPasswordFragment from '../ForgotPasswordForm.vue';
+import { ForgotPasswordFormFields } from '../types';
+
+describe('ForgotPasswordForm', () => {
+ it('emits submit if validation passes', async () => {
+ const form : ForgotPasswordFormFields = { username: 'john@smith.com' };
+ const { emitted, getByTestId } = render(ForgotPasswordFragment, { props: { form } });
+ await userEvent.click(getByTestId('login-forgot-password-form-submit'));
+ const [submitEventPayload] = emitted().submit;
+ expect(submitEventPayload[0]).toStrictEqual(form);
+ });
+
+ it('requires non-empty email', async () => {
+ const form : ForgotPasswordFormFields = { username: '' };
+ const { getByText, getByTestId } = render(ForgotPasswordFragment, { props: { form } });
+ await userEvent.click(getByTestId('login-forgot-password-form-submit'));
+ getByText('This field is required');
+ });
+
+ it('requires valid e-mail address', async () => {
+ const form : ForgotPasswordFormFields = { username: 'not_an_email' };
+ const { getByTestId, getByText } = render(ForgotPasswordFragment, { props: { form } });
+ await userEvent.click(getByTestId('login-forgot-password-form-submit'));
+ getByText('Invalid email');
+ });
+});
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/__tests__/LoginForm.spec.ts b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/LoginForm.spec.ts
new file mode 100644
index 000000000..47b7bffca
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/LoginForm.spec.ts
@@ -0,0 +1,66 @@
+import { render } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+import LoginForm from '../LoginForm.vue';
+import { LoginFormFields } from '../types';
+
+describe('LoginForm', () => {
+ it('emits submit if validation passes', async () => {
+ const form : LoginFormFields = {
+ email: 'john@smith.com',
+ password: '123123',
+ };
+ const { emitted, getByTestId } = render(LoginForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ const [submitEventPayload] = emitted().submit;
+ expect(submitEventPayload[0]).toStrictEqual(form);
+ });
+
+ it('requires non-empty email', async () => {
+ const form : LoginFormFields = {
+ email: '',
+ password: '123123',
+ };
+ const { getByTestId, getByText } = render(LoginForm, { props: { form } });
+ const submitButton = getByTestId('login-form-submit');
+ await userEvent.click(submitButton);
+ getByText('This field is required');
+ });
+
+ it('requires valid e-mail adddress', async () => {
+ const form : LoginFormFields = {
+ email: 'not_an_email',
+ password: '123123',
+ };
+ const { getByTestId, getByText } = render(LoginForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ getByText('Invalid email');
+ });
+
+ it('requires non-empty password', async () => {
+ const form : LoginFormFields = {
+ email: 'john@smith.com',
+ password: '',
+ };
+ const { getByTestId, getByText } = render(LoginForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ getByText('This field is required');
+ });
+
+ it('displays recaptcha', async () => {
+ const form : LoginFormFields = {
+ email: 'john@smith.com',
+ password: '123123',
+ };
+ const { getByTestId } = render(LoginForm, {
+ props: {
+ form,
+ showRecaptcha: true,
+ },
+ stubs: {
+ recaptcha: { template: 'recaptcha
' },
+ },
+ });
+ await userEvent.click(getByTestId('login-form-submit'));
+ getByTestId('recaptcha');
+ });
+});
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/__tests__/RegisterForm.spec.ts b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/RegisterForm.spec.ts
new file mode 100644
index 000000000..b96588ea8
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/__tests__/RegisterForm.spec.ts
@@ -0,0 +1,51 @@
+import { render } from '@testing-library/vue';
+import userEvent from '@testing-library/user-event';
+
+import RegisterForm from '../RegisterForm.vue';
+import { RegisterFormFields } from '../types';
+
+describe('RegisterForm', () => {
+ it('requires fields', async () => {
+ const form : RegisterFormFields = {
+ email: '',
+ firstName: '',
+ lastName: '',
+ password: '',
+ shouldSubscribeToNewsletter: false,
+ isWillToCreateAccountConfirmed: true,
+ };
+ const { emitted, getByTestId, getAllByText } = render(RegisterForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ expect(emitted().submit).not.toBeDefined();
+ expect(getAllByText('This field is required')).toHaveLength(4);
+ });
+ it('requires correct format of fields', async () => {
+ const form : RegisterFormFields = {
+ email: 'not_an_email',
+ firstName: 'John',
+ lastName: 'Smith',
+ password: '1',
+ shouldSubscribeToNewsletter: false,
+ isWillToCreateAccountConfirmed: true,
+ };
+ const { emitted, getByTestId, getByText } = render(RegisterForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ expect(emitted().submit).not.toBeDefined();
+ getByText(/The password must be at least/);
+ getByText('Invalid email');
+ });
+ it('emits submit if validation passes', async () => {
+ const form : RegisterFormFields = {
+ email: 'john@smith.com',
+ firstName: 'John',
+ lastName: 'Smith',
+ password: 'P@ssw0rd!',
+ shouldSubscribeToNewsletter: false,
+ isWillToCreateAccountConfirmed: true,
+ };
+ const { emitted, getByTestId } = render(RegisterForm, { props: { form } });
+ await userEvent.click(getByTestId('login-form-submit'));
+ const [submitEventPayload] = emitted().submit;
+ expect(submitEventPayload[0]).toStrictEqual(form);
+ });
+});
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/styles.scss b/packages/theme/modules/customer/components/LoginModal/forms/styles.scss
new file mode 100644
index 000000000..cc767032b
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/styles.scss
@@ -0,0 +1,19 @@
+.form {
+ margin-top: var(--spacer-sm);
+
+ &__element {
+ margin: 0 0 var(--spacer-xl) 0;
+ }
+}
+
+.action {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: var(--spacer-xl) 0 var(--spacer-xl) 0;
+ font: var(--font-weight--light) var(--font-size--base) / 1.6 var(--font-family--secondary);
+
+ & > * {
+ margin: 0 0 0 var(--spacer-xs);
+ }
+}
diff --git a/packages/theme/modules/customer/components/LoginModal/forms/types.ts b/packages/theme/modules/customer/components/LoginModal/forms/types.ts
new file mode 100644
index 000000000..1f08311b8
--- /dev/null
+++ b/packages/theme/modules/customer/components/LoginModal/forms/types.ts
@@ -0,0 +1,17 @@
+export type LoginFormFields = {
+ email: string,
+ password: string,
+};
+export type RegisterFormFields = {
+ email: string,
+ firstName: string,
+ lastName: string,
+ password: string,
+ shouldSubscribeToNewsletter: boolean,
+ isWillToCreateAccountConfirmed: boolean,
+};
+export type ForgotPasswordFormFields = {
+ username: string,
+};
+
+export type FormName = 'login' | 'register' | 'forgotPassword' | 'forgotPasswordThankYou';
diff --git a/packages/theme/modules/customer/composables/useAddresses/index.ts b/packages/theme/modules/customer/composables/useAddresses/index.ts
index e39afae56..080c201b8 100644
--- a/packages/theme/modules/customer/composables/useAddresses/index.ts
+++ b/packages/theme/modules/customer/composables/useAddresses/index.ts
@@ -8,7 +8,7 @@ import type { UseAddressesInterface, UseAddressesParamsInput, UseAddressesErrors
/**
* @public
*
- * The `useAddresses` composable allows loading and manipulating addresses of the current user. These
+ * Allows loading and manipulating addresses of the current user. These
* addresses can be used for both billing and shipping.
*
* See the {@link UseAddressesInterface} for a list of methods and values available in this composable.
diff --git a/packages/theme/modules/customer/composables/useAddresses/useAddresses.ts b/packages/theme/modules/customer/composables/useAddresses/useAddresses.ts
index 3b017ff11..c6efc56dc 100644
--- a/packages/theme/modules/customer/composables/useAddresses/useAddresses.ts
+++ b/packages/theme/modules/customer/composables/useAddresses/useAddresses.ts
@@ -35,7 +35,7 @@ export interface UseAddressesParamsInput {
}
/**
- * The refs and methods returned by the {@link useAddresses|useAddresses()} composable
+ * Data and methods returned from the {@link useAddresses|useAddresses()} composable
*/
export interface UseAddressesInterface {
/**
diff --git a/packages/theme/modules/customer/composables/useForgotPassword/index.ts b/packages/theme/modules/customer/composables/useForgotPassword/index.ts
index 522d7db38..f8d0c30fa 100644
--- a/packages/theme/modules/customer/composables/useForgotPassword/index.ts
+++ b/packages/theme/modules/customer/composables/useForgotPassword/index.ts
@@ -15,9 +15,9 @@ import {
} from '~/modules/customer/composables/useForgotPassword/useForgotPassword';
/**
- * The `useForgotPassword` composable alows to request a password reset email and to set a new password to a user
+ * Allows to request a password reset email and to set a new password to a user.
*
- * Se the {@link UseForgotPasswordInterface} page for more information
+ * Se the {@link UseForgotPasswordInterface} for a list of methods and values available in this composable.
*/
export function useForgotPassword(): UseForgotPasswordInterface {
const { app } = useContext();
@@ -42,6 +42,7 @@ export function useForgotPassword(): UseForgotPasswordInterface {
const { data } = await app.context.$vsf.$magento.api.requestPasswordResetEmail({ email: resetPasswordParams.email, recaptchaToken: resetPasswordParams.recaptchaToken });
Logger.debug('[Result]:', { data });
error.value.request = data;
+ result.value.resetPasswordResult = data?.requestPasswordResetEmail ?? false;
} catch (err) {
error.value.request = err;
Logger.error('useForgotPassword/request', err);
@@ -63,8 +64,7 @@ export function useForgotPassword(): UseForgotPasswordInterface {
});
Logger.debug('[Result]:', { data });
-
- result.value = data;
+ result.value.setNewPasswordResult = data?.resetPassword ?? false;
error.value.setNew = null;
} catch (err) {
error.value.setNew = err;
diff --git a/packages/theme/modules/customer/composables/useForgotPassword/useForgotPassword.ts b/packages/theme/modules/customer/composables/useForgotPassword/useForgotPassword.ts
index 7364a1db4..f89833cfd 100644
--- a/packages/theme/modules/customer/composables/useForgotPassword/useForgotPassword.ts
+++ b/packages/theme/modules/customer/composables/useForgotPassword/useForgotPassword.ts
@@ -3,8 +3,8 @@ import { ComposableFunctionArgs } from '~/composables/types';
/** Represents the result of a password change or reset operation */
export interface UseForgotPasswordResults {
- resetPasswordResult: any, // TODO: Add types
- setNewPasswordResult: any // TODO: Add types
+ resetPasswordResult: boolean,
+ setNewPasswordResult: boolean
}
/** Errors returned by the {@link useForgotPassword} composable */
@@ -28,7 +28,7 @@ export interface UseForgotPasswordSetNewParams {
}
/**
- * Represents the data and methods returned by the {@link useForgotPassword} composable
+ * Data and methods returned from the {@link useForgotPassword} composable
*/
export interface UseForgotPasswordInterface {
/** Returns the result of the reset operation */
diff --git a/packages/theme/modules/customer/composables/useGuestUser/commands/attachToCartCommand.ts b/packages/theme/modules/customer/composables/useGuestUser/commands/attachToCartCommand.ts
index 3813475ab..bcd7ad705 100644
--- a/packages/theme/modules/customer/composables/useGuestUser/commands/attachToCartCommand.ts
+++ b/packages/theme/modules/customer/composables/useGuestUser/commands/attachToCartCommand.ts
@@ -1,8 +1,9 @@
+import { UseContextReturn } from '~/types/core';
import { Logger } from '~/helpers/logger';
import { SetGuestEmailOnCartInput } from '~/modules/GraphQL/types';
export const attachToCartCommand = {
- execute: async (context, params): Promise => {
+ execute: async (context: UseContextReturn, params): Promise => {
Logger.debug('[Magento]: Attach guest cart to user');
const emailOnCartInput: SetGuestEmailOnCartInput = {
@@ -10,7 +11,7 @@ export const attachToCartCommand = {
cart_id: params?.cart?.value?.id,
};
- await context.$magento.api.setGuestEmailOnCart({
+ await context.app.$vsf.$magento.api.setGuestEmailOnCart({
...emailOnCartInput,
});
},
diff --git a/packages/theme/modules/customer/composables/useGuestUser/index.ts b/packages/theme/modules/customer/composables/useGuestUser/index.ts
index a91c58170..b6f9a7641 100644
--- a/packages/theme/modules/customer/composables/useGuestUser/index.ts
+++ b/packages/theme/modules/customer/composables/useGuestUser/index.ts
@@ -5,15 +5,14 @@ import { AttachToCartParams, UseGuestUserInterface, UseGuestUserErrors } from '~
import { attachToCartCommand } from '~/modules/customer/composables/useGuestUser/commands/attachToCartCommand';
/**
- * The `useGuestUser` composable allows to attach a guest cart to a user
+ * Allows to attach a guest cart to a user.
*
- * See {@link UseGuestUserInterface} page for more information
+ * See the {@link UseGuestUserInterface} for a list of methods and values available in this composable.
*/
export function useGuestUser(): UseGuestUserInterface {
const loading = ref(false);
const error = ref({ attachToCart: null });
- const { app } = useContext();
- const context = app.$vsf;
+ const context = useContext();
const attachToCart = async (params: ComposableFunctionArgs) => {
Logger.debug('useGuestUserFactory.attachToCart', { params });
diff --git a/packages/theme/modules/customer/composables/useGuestUser/useGuestUser.ts b/packages/theme/modules/customer/composables/useGuestUser/useGuestUser.ts
index da7808a21..df73ec692 100644
--- a/packages/theme/modules/customer/composables/useGuestUser/useGuestUser.ts
+++ b/packages/theme/modules/customer/composables/useGuestUser/useGuestUser.ts
@@ -1,5 +1,5 @@
import { ComputedRef, DeepReadonly, Ref } from '@nuxtjs/composition-api';
-import { PlatformApi, Composable, ComposableFunctionArgs } from '~/composables/types';
+import { ComposableFunctionArgs } from '~/composables/types';
import { Cart } from '~/modules/GraphQL/types';
/** Errors returned by the {@link useGuestUser} composable */
@@ -14,8 +14,10 @@ export interface AttachToCartParams {
[x: string]: any;
}
-/** Represents the data and methods returned by the {@link useGuestUser} composable */
-export interface UseGuestUserInterface extends Composable {
+/**
+ * Data and methods returned from the {@link useGuestUser} composable
+ */
+export interface UseGuestUserInterface {
/** Attaches guest cart to user */
attachToCart(params: ComposableFunctionArgs): Promise;
/** Indicates the loading state for `attachToCart` */
diff --git a/packages/theme/modules/customer/composables/useUser/index.ts b/packages/theme/modules/customer/composables/useUser/index.ts
index 702da0e41..0da71e8d9 100644
--- a/packages/theme/modules/customer/composables/useUser/index.ts
+++ b/packages/theme/modules/customer/composables/useUser/index.ts
@@ -13,6 +13,7 @@ import { generateUserData } from '~/modules/customer/helpers/generateUserData';
import { Customer } from '~/modules/GraphQL/types';
import type {
UseUserInterface,
+ UseUserErrors,
UseUserLoadParams,
UseUserLoginParams,
UseUserLogoutParams,
@@ -22,16 +23,16 @@ import type {
} from './useUser';
/**
- * The `useUser()` composable allows loading and manipulating data of the current user.
+ * Allows loading and manipulating data of the current user.
*
- * See the {@link UseUserInterface} page for more information.
+ * See the {@link UseUserInterface} for a list of methods and values available in this composable.
*/
export function useUser(): UseUserInterface {
const customerStore = useCustomerStore();
const { app } = useContext();
const { setCart } = useCart();
const loading: Ref = ref(false);
- const errorsFactory = () => ({
+ const errorsFactory = () : UseUserErrors => ({
updateUser: null,
register: null,
login: null,
@@ -255,7 +256,7 @@ export function useUser(): UseUserInterface {
// return factoryParams.logIn(context, { username: email, password, recaptchaToken: newRecaptchaToken });
// }
error.value.register = null;
- await login({ user: { username: email, password }, customQuery: {} });
+ await login({ user: { email, password }, customQuery: {} });
} catch (err) {
error.value.register = err;
Logger.error('useUser/register', err);
diff --git a/packages/theme/modules/customer/composables/useUser/useUser.ts b/packages/theme/modules/customer/composables/useUser/useUser.ts
index b2cdc36ca..b026afba2 100644
--- a/packages/theme/modules/customer/composables/useUser/useUser.ts
+++ b/packages/theme/modules/customer/composables/useUser/useUser.ts
@@ -1,6 +1,8 @@
import type { Ref, ComputedRef } from '@nuxtjs/composition-api';
import type { ComposableFunctionArgs } from '~/composables/types';
-import type { Customer, ChangeCustomerPasswordMutation } from '~/modules/GraphQL/types';
+import type {
+ Customer, ChangeCustomerPasswordMutation, CustomerCreateInput, GenerateCustomerTokenMutationVariables,
+} from '~/modules/GraphQL/types';
/**
* Errors that occured in the `useUser` composable
@@ -25,14 +27,14 @@ export type UseUserUpdateUserParams = ComposableFunctionArgs<{
* Parameters accepted by the `register` method in the `useUser` composable
*/
export type UseUserRegisterParams = ComposableFunctionArgs<{
- user: any; // TODO: Neither `Customer`, nor `CustomerCreateInput` match expected data
+ user: CustomerCreateInput;
}>;
/**
* Parameters accepted by the `login` method in the `useUser` composable
*/
export type UseUserLoginParams = ComposableFunctionArgs<{
- user: any; // TODO: Neither `Customer`, nor `CustomerCreateInput` match expected data
+ user: GenerateCustomerTokenMutationVariables & { recaptchaToken?: string }
}>;
/**
@@ -54,7 +56,7 @@ export type UseUserChangePasswordParams = ComposableFunctionArgs<{
export type UseUserLoadParams = ComposableFunctionArgs<{}>;
/**
- * Represents the data returned from and functions available in the `useUser()` composable.
+ * Data and methods returned from the {@link useUser|useUser()} composable
*/
export interface UseUserInterface {
/**
diff --git a/packages/theme/modules/customer/composables/useUserAddress/commands/createCustomerAddressCommand.ts b/packages/theme/modules/customer/composables/useUserAddress/commands/createCustomerAddressCommand.ts
index 8ec6b2326..dd74df2af 100644
--- a/packages/theme/modules/customer/composables/useUserAddress/commands/createCustomerAddressCommand.ts
+++ b/packages/theme/modules/customer/composables/useUserAddress/commands/createCustomerAddressCommand.ts
@@ -1,8 +1,9 @@
+import { UseContextReturn } from '~/types/core';
import { CustomerAddressInput } from '~/modules/GraphQL/types';
export const createCustomerAddressCommand = {
- execute: async (context, params: CustomerAddressInput) => {
- const { data } = await context.$vsf.$magento.api.createCustomerAddress(params);
+ execute: async (context: UseContextReturn, params: CustomerAddressInput) => {
+ const { data } = await context.app.$vsf.$magento.api.createCustomerAddress(params);
return data?.createCustomerAddress ?? {};
},
diff --git a/packages/theme/modules/customer/composables/useUserAddress/commands/deleteCustomerAddressCommand.ts b/packages/theme/modules/customer/composables/useUserAddress/commands/deleteCustomerAddressCommand.ts
index 3e8d56faa..b9d3b6ec4 100644
--- a/packages/theme/modules/customer/composables/useUserAddress/commands/deleteCustomerAddressCommand.ts
+++ b/packages/theme/modules/customer/composables/useUserAddress/commands/deleteCustomerAddressCommand.ts
@@ -1,8 +1,9 @@
+import { UseContextReturn } from '~/types/core';
import { CustomerAddress } from '~/modules/GraphQL/types';
export const deleteCustomerAddressCommand = {
- execute: async (context, address: CustomerAddress) => {
- const { data } = await context.$vsf.$magento.api.deleteCustomerAddress(address.id);
+ execute: async (context: UseContextReturn, address: CustomerAddress) => {
+ const { data } = await context.app.$vsf.$magento.api.deleteCustomerAddress(address.id);
return data?.deleteCustomerAddress ?? {};
},
diff --git a/packages/theme/modules/customer/composables/useUserAddress/commands/updateCustomerAddressCommand.ts b/packages/theme/modules/customer/composables/useUserAddress/commands/updateCustomerAddressCommand.ts
index 185c9f63e..c0b900755 100644
--- a/packages/theme/modules/customer/composables/useUserAddress/commands/updateCustomerAddressCommand.ts
+++ b/packages/theme/modules/customer/composables/useUserAddress/commands/updateCustomerAddressCommand.ts
@@ -1,11 +1,12 @@
+import { UseContextReturn } from '~/types/core';
import { CustomerAddressInput } from '~/modules/GraphQL/types';
export const updateCustomerAddressCommand = {
- execute: async (context, params: {
+ execute: async (context: UseContextReturn, params: {
addressId: number;
input: CustomerAddressInput;
}) => {
- const { data } = await context.$vsf.$magento.api.updateCustomerAddress(params);
+ const { data } = await context.app.$vsf.$magento.api.updateCustomerAddress(params);
return data?.updateCustomerAddress ?? {};
},
diff --git a/packages/theme/modules/customer/composables/useUserAddress/index.ts b/packages/theme/modules/customer/composables/useUserAddress/index.ts
index 512a461f4..d981aa8a6 100644
--- a/packages/theme/modules/customer/composables/useUserAddress/index.ts
+++ b/packages/theme/modules/customer/composables/useUserAddress/index.ts
@@ -17,7 +17,7 @@ import type {
} from './useUserAddress';
/**
- * The `useUserAddress()` composable allows loading and manipulating addresses of the current user.
+ * Allows loading and manipulating addresses of the current user.
*
* See the {@link UseUserAddressInterface} for a list of methods and values available in this composable.
*/
diff --git a/packages/theme/modules/customer/composables/useUserAddress/useUserAddress.ts b/packages/theme/modules/customer/composables/useUserAddress/useUserAddress.ts
index 4e5770fe3..fa2db7d23 100644
--- a/packages/theme/modules/customer/composables/useUserAddress/useUserAddress.ts
+++ b/packages/theme/modules/customer/composables/useUserAddress/useUserAddress.ts
@@ -55,7 +55,7 @@ export type UseUserAddressSetDefaultAddressParams = ComposableFunctionArgs<{
}>;
/**
- * The refs and methods returned by the {@link useUserAddress|useUserAddress()} composable
+ * Data and methods returned from the {@link useUserAddress|useUserAddress()} composable
*/
export interface UseUserAddressInterface {
/**
diff --git a/packages/theme/modules/customer/composables/useUserOrder/index.ts b/packages/theme/modules/customer/composables/useUserOrder/index.ts
index 9b4c0cee6..6f18eb0c7 100644
--- a/packages/theme/modules/customer/composables/useUserOrder/index.ts
+++ b/packages/theme/modules/customer/composables/useUserOrder/index.ts
@@ -7,9 +7,9 @@ import type {
} from './useUserOrder';
/**
- * The `useUserOrder()` composable allows fetching customer orders.
+ * Allows fetching customer orders.
*
- * See the {@link UseUserOrderInterface} page for more information.
+ * See the {@link UseUserOrderInterface} for a list of methods and values available in this composable.
*/
export function useUserOrder(): UseUserOrderInterface {
const { app } = useContext();
diff --git a/packages/theme/modules/customer/composables/useUserOrder/useUserOrder.ts b/packages/theme/modules/customer/composables/useUserOrder/useUserOrder.ts
index 6fd7d3fd9..f1b0a926b 100644
--- a/packages/theme/modules/customer/composables/useUserOrder/useUserOrder.ts
+++ b/packages/theme/modules/customer/composables/useUserOrder/useUserOrder.ts
@@ -19,7 +19,7 @@ export interface UseUserOrderErrors {
export type UseUserOrderSearchParams = ComposableFunctionArgs;
/**
- * Represents the data returned from and functions available in the `useUserOrder()` composable
+ * Data and methods returned from the {@link useUserOrder|useUserOrder()} composable
*/
export interface UseUserOrderInterface {
/**
diff --git a/packages/theme/modules/customer/enums/OrderStatusEnum.ts b/packages/theme/modules/customer/enums/OrderStatusEnum.ts
new file mode 100644
index 000000000..d0dfcccab
--- /dev/null
+++ b/packages/theme/modules/customer/enums/OrderStatusEnum.ts
@@ -0,0 +1,11 @@
+export enum OrderStatusEnum {
+ PROCESSING = 'processing',
+ PENDING_PAYMENT = 'pending_payment',
+ ON_HOLD = 'holded',
+ OPEN = 'open',
+ PENDING = 'pending',
+ CONFIRMED = 'confirmed',
+ SHIPPED = 'shipped',
+ COMPLETE = 'complete',
+ CANCELED = 'canceled',
+}
diff --git a/packages/theme/modules/customer/getters/types.d.ts b/packages/theme/modules/customer/getters/types.d.ts
index b8bb627f0..218f5bb5d 100644
--- a/packages/theme/modules/customer/getters/types.d.ts
+++ b/packages/theme/modules/customer/getters/types.d.ts
@@ -15,24 +15,25 @@ export interface AddressGetter {
}[];
}
-export interface UserBillingGetters {
+export interface UserBillingGetters {
getAddresses: (billing: USER_BILLING, criteria?: Record) => USER_BILLING_ITEM[];
getDefault: (billing: USER_BILLING) => USER_BILLING_ITEM;
getTotal: (billing: USER_BILLING) => number;
getPostCode: (address: USER_BILLING_ITEM) => string;
getStreetName: (address: USER_BILLING_ITEM) => string;
- getStreetNumber: (address: USER_BILLING_ITEM) => string | number;
getCity: (address: USER_BILLING_ITEM) => string;
getFirstName: (address: USER_BILLING_ITEM) => string;
getLastName: (address: USER_BILLING_ITEM) => string;
getCountry: (address: USER_BILLING_ITEM) => string;
- getPhone: (address: USER_BILLING_ITEM) => string;
- getEmail: (address: USER_BILLING_ITEM) => string;
+ getPhone: (address: TRANSFORMED_USER_BILLING_ITEM) => string;
+ getEmail: (address: TRANSFORMED_USER_BILLING_ITEM) => string;
getProvince: (address: USER_BILLING_ITEM) => string;
getCompanyName: (address: USER_BILLING_ITEM) => string;
getTaxNumber: (address: USER_BILLING_ITEM) => string;
- getId: (address: USER_BILLING_ITEM) => string;
+ getId: (address: USER_BILLING_ITEM) => string | number;
getApartmentNumber: (address: USER_BILLING_ITEM) => string | number;
+ getNeighborhood: (address: TRANSFORMED_USER_BILLING_ITEM) => string
+ getAddressExtra: (address: TRANSFORMED_USER_BILLING_ITEM) => string
isDefault: (address: USER_BILLING_ITEM) => boolean;
}
@@ -40,6 +41,7 @@ export interface UserGetters {
getFirstName: (customer: USER) => string;
getLastName: (customer: USER) => string;
getFullName: (customer: USER) => string;
+
getEmailAddress: (customer: USER) => string;
[getterName: string]: (element: any, options?: any) => unknown;
}
@@ -49,22 +51,20 @@ export interface ForgotPasswordGetters {
isPasswordChanged: (result: FORGOT_PASSWORD_RESULT) => boolean
}
-export interface UserShippingGetters {
+export interface UserShippingGetters {
getAddresses: (shipping: USER_SHIPPING, criteria?: Record) => USER_SHIPPING_ITEM[];
getDefault: (shipping: USER_SHIPPING) => USER_SHIPPING_ITEM;
getTotal: (shipping: USER_SHIPPING) => number;
getPostCode: (address: USER_SHIPPING_ITEM) => string;
getStreetName: (address: USER_SHIPPING_ITEM) => string;
- getStreetNumber: (address: USER_SHIPPING_ITEM) => string | number;
getCity: (address: USER_SHIPPING_ITEM) => string;
getFirstName: (address: USER_SHIPPING_ITEM) => string;
getLastName: (address: USER_SHIPPING_ITEM) => string;
getCountry: (address: USER_SHIPPING_ITEM) => string;
- getPhone: (address: USER_SHIPPING_ITEM) => string;
- getEmail: (address: USER_SHIPPING_ITEM) => string;
+ getPhone: (address: TRANSFORMED_USER_SHIPPING_ITEM) => string;
+ getEmail: (address: TRANSFORMED_USER_SHIPPING_ITEM) => string;
getProvince: (address: USER_SHIPPING_ITEM) => string;
getCompanyName: (address: USER_SHIPPING_ITEM) => string;
- getTaxNumber: (address: USER_SHIPPING_ITEM) => string;
getId: (address: USER_SHIPPING_ITEM) => string | number;
getApartmentNumber: (address: USER_SHIPPING_ITEM) => string | number;
isDefault: (address: USER_SHIPPING_ITEM) => boolean;
diff --git a/packages/theme/modules/customer/getters/userBillingGetters.ts b/packages/theme/modules/customer/getters/userBillingGetters.ts
index 73d4f3e3e..53cf3caf9 100644
--- a/packages/theme/modules/customer/getters/userBillingGetters.ts
+++ b/packages/theme/modules/customer/getters/userBillingGetters.ts
@@ -1,25 +1,20 @@
-import { UserBillingGetters as BaseGetters } from '~/modules/customer/getters/types';
+import { Customer, CustomerAddress } from '~/modules/GraphQL/types';
+import { UserBillingGetters } from '~/modules/customer/getters/types';
+import { TransformedCustomerAddress } from '../composables/types';
-interface UserBillingGetters extends BaseGetters{
- getNeighborhood: (address: any) => string
- getAddressExtra: (address: any) => string
-}
-
-const userBillingGetters: UserBillingGetters = {
+const userBillingGetters: UserBillingGetters = {
getAddresses: (billing, criteria?: Record) => {
if (!criteria || Object.keys(criteria).length === 0) {
return billing.addresses;
}
const entries = Object.entries(criteria);
- return billing.addresses.filter((address) => entries.every(([key, value]) => address[key] === value));
+ return billing.addresses.filter((address) => entries.every(([key, value]: [keyof CustomerAddress, unknown]) => address[key] === value));
},
- getDefault: (billing) => billing.addresses.find(({ isDefault }) => isDefault),
+ getDefault: (billing) => billing.addresses.find(({ default_billing }) => default_billing),
getTotal: (billing) => billing.addresses.length,
-
getPostCode: (address) => address?.postcode || '',
getStreetName: (address) => (Array.isArray(address?.street) ? address?.street[0] : ''),
- getStreetNumber: (address) => address?.streetNumber || '',
getCity: (address) => address?.city || '',
getFirstName: (address) => address?.firstname || '',
getLastName: (address) => address?.lastname || '',
@@ -30,7 +25,6 @@ const userBillingGetters: UserBillingGetters = {
getCompanyName: (address) => address?.company || '',
getNeighborhood: (address) => (Array.isArray(address?.street) ? address?.street[2] : address?.neighborhood),
getAddressExtra: (address) => (Array.isArray(address?.street) ? address?.street[3] : address?.extra),
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
getTaxNumber: (address) => address.vat_id || '',
getId: (address) => address?.id || '',
getApartmentNumber: (address) => (Array.isArray(address?.street) ? address?.street[1] : ''),
diff --git a/packages/theme/modules/customer/getters/userShippingGetters.ts b/packages/theme/modules/customer/getters/userShippingGetters.ts
index 97b9d16f8..f926ddf27 100644
--- a/packages/theme/modules/customer/getters/userShippingGetters.ts
+++ b/packages/theme/modules/customer/getters/userShippingGetters.ts
@@ -1,24 +1,25 @@
import { UserShippingGetters } from '~/modules/customer/getters/types';
+import { Customer, CustomerAddress } from '~/modules/GraphQL/types';
import { Logger } from '~/helpers/logger';
+import { TransformedCustomerAddress } from '../composables/types';
-const userShippingGetters: UserShippingGetters = {
+const userShippingGetters: UserShippingGetters = {
getAddresses: (shipping, criteria?: Record) => {
Logger.debug(shipping);
- if (!shipping || !shipping.addresses) return [] as Record;
+ if (!shipping || !shipping.addresses) return [] as CustomerAddress[];
if (!criteria || Object.keys(criteria).length === 0) {
return shipping.addresses;
}
const entries = Object.entries(criteria);
- return shipping.addresses.filter((address) => entries.every(([key, value]) => address[key] === value));
+ return shipping.addresses.filter((address) => entries.every(([key, value]: [ keyof CustomerAddress, unknown ]) => address[key] === value));
},
- getDefault: (shipping) => shipping.addresses.find(({ isDefault }) => isDefault),
+ getDefault: (shipping) => shipping.addresses.find(({ default_shipping }) => default_shipping),
getTotal: (shipping) => shipping.addresses.length,
-
getPostCode: (address) => address?.postcode || '',
getStreetName: (address) => (Array.isArray(address?.street) ? address?.street[0] : ''),
- getStreetNumber: (address) => address?.streetNumber || '',
+ getApartmentNumber: (address) => (Array.isArray(address?.street) ? address?.street[1] : ''),
getCity: (address) => address?.city || '',
getFirstName: (address) => address?.firstname || '',
getLastName: (address) => address?.lastname || '',
@@ -27,10 +28,7 @@ const userShippingGetters: UserShippingGetters = {
getEmail: (address) => address?.email || '',
getProvince: (address) => (address?.region?.region_code || address?.region?.region) || '',
getCompanyName: (address) => address?.company || '',
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- getTaxNumber: (address) => '',
getId: (address) => address?.id || '',
- getApartmentNumber: (address) => (Array.isArray(address?.street) ? address?.street[1] : ''),
isDefault: (address) => address?.default_shipping || false,
};
diff --git a/packages/theme/modules/customer/helpers/generateUserData.ts b/packages/theme/modules/customer/helpers/generateUserData.ts
index 13c0cf88a..73c293711 100644
--- a/packages/theme/modules/customer/helpers/generateUserData.ts
+++ b/packages/theme/modules/customer/helpers/generateUserData.ts
@@ -1,6 +1,6 @@
import type { CustomerCreateInput } from '~/modules/GraphQL/types';
-export const generateUserData = (userData): CustomerCreateInput => {
+export const generateUserData = (userData: any): CustomerCreateInput => {
const baseData = {
email: userData.email,
firstname: userData.firstName || userData.firstname,
diff --git a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue
index 7d7acc6a5..a7df3bbed 100644
--- a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue
+++ b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressEdit.vue
@@ -46,19 +46,19 @@ export default defineComponent({
const router = useRouter();
const context = useContext();
- const addressesComposable = useAddresses();
+ const { load: loadAddress, update: updateAddress } = useAddresses();
const address = ref(null);
const numericAddressId = Number.parseInt(props.addressId, 10); // because in below find(), CustomerAddress['id'] is numeric
useFetch(async () => {
- const addressesData = await addressesComposable.load(); // TODO don't fetch all addresses just to pluck one address
+ const addressesData = await loadAddress();
address.value = userAddressesGetters
.getAddresses(addressesData)
.find(({ id }) => id === numericAddressId);
});
const update = async ({ form } : SubmitEventPayload) => {
- await addressesComposable.update({ address: { ...form, id: numericAddressId } });
+ await updateAddress({ address: { ...form, id: numericAddressId } });
await router.push(context.localeRoute({ name: 'customer-addresses-details' }));
};
diff --git a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue
index 21bce9e30..ee7d70911 100644
--- a/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue
+++ b/packages/theme/modules/customer/pages/MyAccount/AddressesDetails/AddressesDetails.vue
@@ -124,7 +124,7 @@ export default defineComponent({
const context = useContext();
const router = useRouter();
- const userAddresses = ref([]);
+ const userAddresses = ref([]);
const { load, remove } = useAddresses();
const { fetch } = useFetch(async () => {
const addressesData = await load();
diff --git a/packages/theme/modules/customer/pages/MyAccount/MyWishlist.vue b/packages/theme/modules/customer/pages/MyAccount/MyWishlist.vue
index 0d0b340e6..2c7b5129b 100644
--- a/packages/theme/modules/customer/pages/MyAccount/MyWishlist.vue
+++ b/packages/theme/modules/customer/pages/MyAccount/MyWishlist.vue
@@ -39,7 +39,7 @@
:nuxt-img-config="{
fit: 'cover',
}"
- :is-added-to-cart="isInCart({ product: product.product })"
+ :is-added-to-cart="isInCart(product.product)"
:link="
localePath(
`/p/${productGetters.getProductSku(
diff --git a/packages/theme/modules/customer/pages/MyAccount/OrderHistory/OrderHistory.vue b/packages/theme/modules/customer/pages/MyAccount/OrderHistory/OrderHistory.vue
index acfe12e7e..77231be68 100644
--- a/packages/theme/modules/customer/pages/MyAccount/OrderHistory/OrderHistory.vue
+++ b/packages/theme/modules/customer/pages/MyAccount/OrderHistory/OrderHistory.vue
@@ -111,11 +111,11 @@ import {
ref, computed, defineComponent, useRoute, useFetch,
} from '@nuxtjs/composition-api';
import LazyHydrate from 'vue-lazy-hydration';
-import { AgnosticOrderStatus } from '~/composables/types';
import orderGetters from '~/modules/checkout/getters/orderGetters';
import { useUiHelpers } from '~/composables';
import { useUserOrder } from '~/modules/customer/composables/useUserOrder';
import type { CustomerOrders, CustomerOrder } from '~/modules/GraphQL/types';
+import { OrderStatusEnum } from '~/modules/customer/enums/OrderStatusEnum';
export default defineComponent({
name: 'OrderHistory',
@@ -158,9 +158,9 @@ export default defineComponent({
const getStatusTextClass = (order: CustomerOrder) => {
switch (order.status) {
- case AgnosticOrderStatus.Open:
+ case OrderStatusEnum.OPEN:
return 'text-warning';
- case AgnosticOrderStatus.Complete:
+ case OrderStatusEnum.COMPLETE:
return 'text-success';
default:
return '';
diff --git a/packages/theme/modules/magento/defaultConfig.ts b/packages/theme/modules/magento/defaultConfig.ts
index c73080c9e..8a30f32b0 100644
--- a/packages/theme/modules/magento/defaultConfig.ts
+++ b/packages/theme/modules/magento/defaultConfig.ts
@@ -8,7 +8,7 @@ export const defaultConfig = {
storeCookieName: 'vsf-store',
messageCookieName: 'vsf-message',
},
- locale: undefined,
- country: undefined,
- currency: undefined,
+ locale: undefined as undefined,
+ country: undefined as undefined,
+ currency: undefined as undefined,
} as const;
diff --git a/packages/theme/modules/magento/helpers/index.ts b/packages/theme/modules/magento/helpers/index.ts
index d629284c2..361ee4c0c 100644
--- a/packages/theme/modules/magento/helpers/index.ts
+++ b/packages/theme/modules/magento/helpers/index.ts
@@ -1,6 +1,7 @@
+import { NuxtAppOptions } from '@nuxt/types';
import { defaultConfig } from '~/modules/magento/defaultConfig';
-export const getLocaleSettings = (app, moduleOptions, additionalProperties) => {
+export const getLocaleSettings = (app: NuxtAppOptions, moduleOptions: Record, additionalProperties: any) => {
const localeSettings = moduleOptions.cookies
? {
currency: additionalProperties.state.getCurrency(),
@@ -16,7 +17,8 @@ export const getLocaleSettings = (app, moduleOptions, additionalProperties) => {
};
};
-export const mapConfigToSetupObject = ({ app, moduleOptions, additionalProperties = {} }) => ({
+export const mapConfigToSetupObject = ({ app, moduleOptions, additionalProperties = {} } :
+{ app: NuxtAppOptions, moduleOptions: Record, additionalProperties: Record }) => ({
...defaultConfig,
...moduleOptions,
...additionalProperties,
diff --git a/packages/theme/modules/magento/plugin.ts b/packages/theme/modules/magento/plugin.ts
index c8916d726..65c15ecfe 100644
--- a/packages/theme/modules/magento/plugin.ts
+++ b/packages/theme/modules/magento/plugin.ts
@@ -7,7 +7,7 @@ import { defaultConfig } from '~/modules/magento/defaultConfig';
const moduleOptions = JSON.parse('<%= JSON.stringify(options) %>');
export default integrationPlugin((plugin) => {
- const getCookieName = (property: string) : string => moduleOptions.cookies?.[property] ?? defaultConfig.cookies[property];
+ const getCookieName = (property: keyof typeof defaultConfig['cookies']) : string => moduleOptions.cookies?.[property] ?? defaultConfig.cookies[property];
const cookieNames = {
cart: getCookieName('cartCookieName'),
diff --git a/packages/theme/modules/wishlist/components/WishlistSidebar.vue b/packages/theme/modules/wishlist/components/WishlistSidebar.vue
index d618719a1..dc355ed73 100644
--- a/packages/theme/modules/wishlist/components/WishlistSidebar.vue
+++ b/packages/theme/modules/wishlist/components/WishlistSidebar.vue
@@ -146,8 +146,9 @@ import {
} from '@nuxtjs/composition-api';
import productGetters from '~/modules/catalog/product/getters/productGetters';
import {
- useUiState, useImage, AgnosticPrice,
+ useUiState, useImage,
} from '~/composables';
+import type { Price } from '~/modules/catalog/types';
import { useWishlist } from '~/modules/wishlist/composables/useWishlist';
import { useUser } from '~/modules/customer/composables/useUser';
import { useWishlistStore } from '~/modules/wishlist/store/wishlistStore';
@@ -155,7 +156,7 @@ import EmptyWishlist from '~/modules/wishlist/components/EmptyWishlist.vue';
import SvgImage from '~/components/General/SvgImage.vue';
-import type { WishlistItemInterface } from '~/modules/GraphQL/types';
+import type { WishlistItemInterface, ConfigurableProduct, BundleProduct } from '~/modules/GraphQL/types';
export default defineComponent({
name: 'WishlistSidebar',
@@ -185,7 +186,7 @@ export default defineComponent({
() => wishlistStore.wishlist?.items_v2?.items ?? [],
);
- const getItemPrice = (product: WishlistItemInterface): AgnosticPrice => {
+ const getItemPrice = (product: WishlistItemInterface): Price => {
let regular = 0;
let special = null;
@@ -217,8 +218,8 @@ export default defineComponent({
() => wishlistStore.wishlist?.items_count ?? 0,
);
- const getAttributes = (product) => product?.product?.configurable_options || [];
- const getBundles = (product) => product?.product?.items?.map((b) => b.title).flat() || [];
+ const getAttributes = (product: WishlistItemInterface) => (product?.product as ConfigurableProduct)?.configurable_options || [];
+ const getBundles = (product: WishlistItemInterface) => (product?.product as BundleProduct)?.items?.map((b) => b.title).flat() || [];
const getItemLink = (item: WishlistItemInterface) => localeRoute({
path: `/p/${item.product.sku}${productGetters.getSlug(
item.product,
diff --git a/packages/theme/modules/wishlist/composables/useWishlist/index.ts b/packages/theme/modules/wishlist/composables/useWishlist/index.ts
index 4f1d86026..dca78d16e 100644
--- a/packages/theme/modules/wishlist/composables/useWishlist/index.ts
+++ b/packages/theme/modules/wishlist/composables/useWishlist/index.ts
@@ -15,17 +15,16 @@ import type {
import { useUiNotification } from '~/composables';
/**
- * The `useWishlist()` composable allows loading and manipulating wishlist of the current user.
+ * Allows loading and manipulating wishlist of the current user.
*
- * See the {@link UseWishlistInterface} page for more information.
+ * See the {@link UseWishlistInterface} for a list of methods and values available in this composable.
*/
export function useWishlist(): UseWishlistInterface {
const wishlistStore = useWishlistStore();
const { app } = useContext();
const { send: sendNotification } = useUiNotification();
const loading = ref(false);
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- const calculateWishlistTotal = (wishlists) => wishlists.reduce((prev, next) => (prev?.items_count ?? 0) + (next?.items_count ?? 0), 0);
+ const calculateWishlistTotal = (wishlists: Wishlist[]) => wishlists.reduce((acc, current) => acc + (current?.items_count ?? 0), 0);
const error = ref({
addItem: null,
removeItem: null,
@@ -35,21 +34,23 @@ export function useWishlist(): UseWishlistInterface {
afterAddingWishlistItemToCart: null,
});
- // eslint-disable-next-line consistent-return
const load = async (params?: UseWishlistLoadParams) => {
Logger.debug('useWishlist/load');
try {
loading.value = true;
Logger.debug('[Magento Storefront]: useWishlist.load params->', params);
- const apiState = app.context.$vsf.$magento.config.state;
+ const apiState = app.$vsf.$magento.config.state;
if (apiState.getCustomerToken()) {
- const { data } = await app.context.$vsf.$magento.api.wishlist(params?.searchParams, params?.customQuery);
+ const { data } = await app.$vsf.$magento.api.wishlist(params?.searchParams, params?.customQuery);
Logger.debug('[Result]:', { data });
const loadedWishlist = data?.customer?.wishlists ?? [];
- wishlistStore.wishlist = loadedWishlist[0] ?? {};
+ if (loadedWishlist[0]) {
+ // @ts-expect-error M2-579
+ [wishlistStore.wishlist] = loadedWishlist;
+ }
}
error.value.load = null;
@@ -109,7 +110,7 @@ export function useWishlist(): UseWishlistInterface {
const loadItemsCount = async (): Promise => {
Logger.debug('useWishlist/wishlistItemsCount');
const apiState = app.context.$vsf.$magento.config.state;
- let itemsCount = null;
+ let itemsCount : number | null = null;
try {
loading.value = true;
@@ -118,7 +119,7 @@ export function useWishlist(): UseWishlistInterface {
const { data } = await app.context.$vsf.$magento.api.wishlistItemsCount();
Logger.debug('[Result]:', { data });
- const loadedWishlist = data?.customer?.wishlists ?? [];
+ const loadedWishlist : Wishlist[] = data?.customer?.wishlists ?? [];
itemsCount = calculateWishlistTotal(loadedWishlist);
wishlistStore.$patch((state) => {
state.wishlist.items_count = itemsCount;
@@ -152,14 +153,11 @@ export function useWishlist(): UseWishlistInterface {
const itemOnWishlist = findItemOnWishlist(wishlistStore.wishlist, product);
- // todo: legacy code, should be double-checked and probably removed
if (itemOnWishlist) {
- return await removeItem({
- product,
- });
+ return;
}
- if (!app.$vsf.$magento.config.state.getCustomerToken()) { // TODO: replace by value from pinia store after sueCart composable will be refactored
+ if (!app.$vsf.$magento.config.state.getCustomerToken()) {
Logger.error('Need to be authenticated to add a product to wishlist');
}
@@ -224,7 +222,6 @@ export function useWishlist(): UseWishlistInterface {
break;
}
default:
- // todo implement other options
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
Logger.error(`Product Type ${product.__typename} not supported in add to wishlist yet`);
@@ -296,6 +293,10 @@ export function useWishlist(): UseWishlistInterface {
}
};
+ const addOrRemoveItem = async ({ product, customQuery }: UseWishlistAddItemParams) => {
+ await (isInWishlist({ product }) ? removeItem({ product, customQuery }) : addItem({ product, customQuery }));
+ };
+
return {
loadItemsCount,
isInWishlist,
@@ -305,6 +306,7 @@ export function useWishlist(): UseWishlistInterface {
clear,
setWishlist,
afterAddingWishlistItemToCart,
+ addOrRemoveItem,
loading: readonly(loading),
error: readonly(error),
};
diff --git a/packages/theme/modules/wishlist/composables/useWishlist/useWishlist.ts b/packages/theme/modules/wishlist/composables/useWishlist/useWishlist.ts
index 82b4dfdd0..c4529f4c3 100644
--- a/packages/theme/modules/wishlist/composables/useWishlist/useWishlist.ts
+++ b/packages/theme/modules/wishlist/composables/useWishlist/useWishlist.ts
@@ -22,9 +22,7 @@ export interface UseWishlistErrors {
/**
* Parameters accepted by the `loadItemsCount` method in the `useWishlist` composable
*/
-export type UseWishlistLoadItemsCountParams = ComposableFunctionArgs<{
- // TODO: Add type
-}>;
+export type UseWishlistLoadItemsCountParams = ComposableFunctionArgs<{}>;
/**
* Parameters accepted by the `isInWishlist` method in the `useWishlist` composable
@@ -71,13 +69,13 @@ export type UseWishlistAfterAddingWishlistItemToCartParams = ComposableFunctionA
}>;
/**
- * Represents the data returned from and functions available in the `useWishlist()` composable.
+ * Data and methods returned from the {@link useWishlist|useWishlist()} composable
*/
export interface UseWishlistInterface {
/**
* Returns a total number of items added to the wishlist of the current user
*/
- loadItemsCount(params?: UseWishlistLoadItemsCountParams): Promise;
+ loadItemsCount(): Promise;
/**
* Checks if given product is in the wishlist of the current user
@@ -114,6 +112,11 @@ export interface UseWishlistInterface {
*/
afterAddingWishlistItemToCart(params: UseWishlistAfterAddingWishlistItemToCartParams): void;
+ /**
+ * Adds item to the wishlist if is not already added, otherwise remove it from the wishlist
+ */
+ addOrRemoveItem(params: UseWishlistAddItemParams): Promise;
+
/**
* Indicates whether any of the methods is in progress
*/
diff --git a/packages/theme/modules/wishlist/getters/wishlistGetters.ts b/packages/theme/modules/wishlist/getters/wishlistGetters.ts
index 6bdd7d791..a9b9eb0f8 100644
--- a/packages/theme/modules/wishlist/getters/wishlistGetters.ts
+++ b/packages/theme/modules/wishlist/getters/wishlistGetters.ts
@@ -1,22 +1,17 @@
-/* istanbul ignore file */
-import {
- AgnosticPrice,
- AgnosticTotals, AgnosticPagination,
-} from '~/composables/types';
+import type { Totals, Pagination } from '~/composables/types';
+import type { Price } from '~/modules/catalog/types';
+import type { WishlistGetters as BaseWishlistGetters } from '~/getters/types';
+import type {
+ Wishlist, ProductInterface, WishlistItemInterface,
+} from '~/modules/GraphQL/types';
-import { WishlistGetters as BaseWishlistGetters } from '~/getters/types';
-import { Wishlist, WishlistQuery } from '~/modules/GraphQL/types';
+export const getItems = (wishlist: Wishlist): WishlistItemInterface[] => wishlist.items_v2.items;
-export type WishlistProduct = WishlistQuery['customer']['wishlists'][0]['items_v2']['items'][0] & { variant: any };
+export const getItemName = (product: WishlistItemInterface): string => product?.product?.name || '';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getItems = (wishlist): WishlistProduct[] => wishlist.items_v2.items;
+export const getItemImage = (product: WishlistItemInterface): string => product?.product?.thumbnail.url || '';
-export const getItemName = (product: WishlistProduct): string => product?.product?.name || '';
-
-export const getItemImage = (product: WishlistProduct): string => product?.product?.thumbnail.url || '';
-
-export const getItemPrice = (product: WishlistProduct): AgnosticPrice => {
+export const getItemPrice = (product: WishlistItemInterface): Price => {
let regular = 0;
let special = null;
@@ -35,44 +30,32 @@ export const getItemPrice = (product: WishlistProduct): AgnosticPrice => {
};
};
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getItemQty = (product: WishlistProduct): number => product.quantity;
+export const getItemQty = (product: WishlistItemInterface): number => product.quantity;
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getItemAttributes = (product: WishlistProduct, filterByAttributeName?: string[]) => ({ '': '' });
+export const getItemAttributes = (_product: WishlistItemInterface, _filterByAttributeName?: string[]) => ({ '': '' });
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getItemSku = (product: WishlistProduct): string => product?.product?.sku || '';
+export const getItemSku = (product: WishlistItemInterface): string => product?.product?.sku || '';
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-// @ts-ignore
-export const getTotals = (wishlist): AgnosticTotals => {
+export const getTotals = (wishlist: Wishlist[] | Wishlist): Totals => {
if (Array.isArray(wishlist)) {
return wishlist[0]?.items_v2?.items.reduce((acc, curr) => ({
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands,@typescript-eslint/no-unsafe-argument
total: acc.total + getItemPrice(curr).special,
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands,@typescript-eslint/no-unsafe-argument
subtotal: acc.subtotal + getItemPrice(curr).regular,
}), ({ total: 0, subtotal: 0 }));
}
return wishlist?.items_v2?.items.reduce((acc, curr) => ({
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/restrict-plus-operands
total: acc.total + getItemPrice(curr).special,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/restrict-plus-operands
subtotal: acc.subtotal + getItemPrice(curr).regular,
}), ({ total: 0, subtotal: 0 }));
};
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getShippingPrice = (wishlist: Wishlist): number => 0;
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getTotalItems = (wishlist: Wishlist): number => (Array.isArray(wishlist) ? wishlist[0]?.items_count : (wishlist?.items_count || 0));
+export const getShippingPrice = (_wishlist: Wishlist): number => 0;
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export const getFormattedPrice = (price: number): string => '';
+export const getTotalItems = (wishlist: Wishlist | Wishlist[]): number => (Array.isArray(wishlist)
+ ? wishlist[0]?.items_count
+ : (wishlist?.items_count || 0));
-const getPagination = (wishlistData: Wishlist): AgnosticPagination => ({
+const getPagination = (wishlistData: Wishlist): Pagination => ({
currentPage: wishlistData?.items_v2?.page_info?.current_page || 1,
totalPages: wishlistData?.items_v2?.page_info?.total_pages || 1,
totalItems: wishlistData?.items_count || 0,
@@ -80,23 +63,26 @@ const getPagination = (wishlistData: Wishlist): AgnosticPagination => ({
pageOptions: [10, 50, 100],
});
-const getProducts = (wishlistData: Wishlist[] | Wishlist): {
- product: WishlistProduct;
- quantity: number;
- added_at: string;
-}[] => {
+type MappedProduct = {
+ product: ProductInterface,
+ quantity: number,
+ added_at: string,
+ id: string,
+};
+
+const getProducts = (wishlistData: Wishlist[] | Wishlist): MappedProduct[] => {
if (!wishlistData || (Array.isArray(wishlistData) && wishlistData.length === 0)) {
return [];
}
- const reducer = (acc, curr) => [...acc, ...curr?.items_v2?.items.map((item) => ({
+ const reducer = (acc: MappedProduct[], curr: Wishlist) : MappedProduct[] => [...acc, ...curr?.items_v2?.items.map((item) => ({
product: item.product,
quantity: item.quantity,
added_at: item.added_at,
id: item.id,
})) ?? []];
- const mapper = (item) => ({
+ const mapper = (item: WishlistItemInterface): MappedProduct => ({
product: item.product,
quantity: item.quantity,
added_at: item.added_at,
@@ -104,21 +90,22 @@ const getProducts = (wishlistData: Wishlist[] | Wishlist): {
});
return Array.isArray(wishlistData)
- ? wishlistData.reduce((acc, curr) => reducer(acc, curr), [])
+ ? wishlistData.reduce((acc, curr) => reducer(acc, curr), [] as MappedProduct[])
: wishlistData?.items_v2?.items.map((e) => mapper(e));
};
-export interface WishlistGetters extends BaseWishlistGetters {
+export interface WishlistGetters extends BaseWishlistGetters {
getShippingPrice(wishlist: Wishlist): number;
- getItemQty(product: WishlistProduct): number;
+ getItemQty(product: WishlistItemInterface): number;
- getPagination(wishlistData): AgnosticPagination;
+ getPagination(wishlistData: Wishlist | Wishlist[]): Pagination;
- getProducts(wishlistData): {
- product: WishlistProduct;
+ getProducts(wishlistData: Wishlist | Wishlist[]): {
+ product: ProductInterface;
quantity: number;
added_at: string;
+ id: string;
}[];
}
@@ -133,7 +120,6 @@ const wishlistGetters: WishlistGetters = {
getItemAttributes,
getItemSku,
getTotalItems,
- getFormattedPrice,
getPagination,
getProducts,
};
diff --git a/packages/theme/nuxt.config.js b/packages/theme/nuxt.config.js
index 58efe69ef..82445ea4a 100755
--- a/packages/theme/nuxt.config.js
+++ b/packages/theme/nuxt.config.js
@@ -215,7 +215,6 @@ export default () => {
'~/plugins/i18n',
'~/plugins/fcPlugin',
'~/plugins/dompurify',
- '~/plugins/graphqlClient',
'~/plugins/storeConfigPlugin',
],
serverMiddleware: [
diff --git a/packages/theme/package.json b/packages/theme/package.json
index 0409d8430..afb30e6fb 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,6 +1,6 @@
{
"name": "@vue-storefront/magento-theme",
- "version": "1.0.0-rc.8",
+ "version": "1.0.0-rc.9",
"private": true,
"license": "MIT",
"homepage": "https://github.com/vuestorefront/magento2",
@@ -36,16 +36,17 @@
"@pinia/nuxt": "^0.1.8",
"@storefront-ui/vue": "^0.13.0",
"@vue-storefront/cache": "~2.5.6",
- "@vue-storefront/magento-api": "^1.0.0-rc.8",
+ "@vue-storefront/magento-api": "^1.0.0-rc.9",
"@vue-storefront/middleware": "~2.5.6",
"@vue-storefront/nuxt": "~2.5.6",
"@vue-storefront/redis-cache": "^1.0.1",
"axios": "^0.26.1",
"body-parser": "1.19.2",
+ "body-scroll-lock": "^3.0.1",
"cookie-universal-nuxt": "^2.1.5",
"deepdash": "^5.3.9",
"express": "4.17.3",
- "graphql-request": "4.1.0",
+ "graphql-tag": "^2.12.6",
"is-https": "^4.0.0",
"isomorphic-dompurify": "^0.18.0",
"lodash.debounce": "^4.0.8",
@@ -66,9 +67,12 @@
"@nuxt/types": "latest",
"@nuxt/typescript-build": "^2.1.0",
"@pinia/testing": "^0.0.11",
- "@testing-library/jest-dom": "^5.16.1",
- "@testing-library/user-event": "^14.1.1",
- "@testing-library/vue": "^5.8.2",
+ "@testing-library/jest-dom": "^5.16.4",
+ "@testing-library/user-event": "^14.2.0",
+ "@testing-library/vue": "^5.8.3",
+ "@types/lodash.debounce": "^4.0.6",
+ "@types/lodash.merge": "^4.6.7",
+ "@types/lodash.unescape": "^4.0.7",
"@vue/test-utils": "^1.3.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^27.4.6",
@@ -91,7 +95,7 @@
"mochawesome-merge": "^4.2.1",
"mochawesome-report-generator": "^6.0.1",
"npm-check-updates": "^12.1.0",
- "ts-jest": "^27.1.3",
+ "ts-jest": "^27.1.5",
"ts-loader": "^8.1.0",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
diff --git a/packages/theme/plugins/dompurify.ts b/packages/theme/plugins/dompurify.ts
index d04e72b7d..3ed6dcf18 100644
--- a/packages/theme/plugins/dompurify.ts
+++ b/packages/theme/plugins/dompurify.ts
@@ -1,5 +1,6 @@
import _unescape from 'lodash.unescape';
import DOMPurify from 'isomorphic-dompurify';
+import type { Plugin } from '@nuxt/types';
declare module 'vue/types/vue' {
interface Vue {
@@ -7,6 +8,8 @@ declare module 'vue/types/vue' {
}
}
-export default (_, inject) => {
+const plugin : Plugin = (_, inject) => {
inject('dompurify', (html: string): string => _unescape(DOMPurify.sanitize(html)));
};
+
+export default plugin;
diff --git a/packages/theme/plugins/graphqlClient.ts b/packages/theme/plugins/graphqlClient.ts
deleted file mode 100644
index 6db59263c..000000000
--- a/packages/theme/plugins/graphqlClient.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { GraphQLClient } from 'graphql-request';
-import type { Plugin, Context as NuxtContext } from '@nuxt/types';
-
-/**
- * Workaround for the fact that graphql-request does not support dynamic headers
- * If you passed new GraphQLClient(baseUrl, {headers: { Authorization: getCustomerToken() }})
- * the Authorization header would never be updated (even if you deleted the cookie in the browser)
- *
- * Under the hood, graphql-request spreads the headers passed to it (`...headers`) before each request
- * This proxy - when ...spread - intelligently returns only those object keys (which later become request headers)
- * whose corresponding cookie value exists
- *
- * @example
- * ```ts
- * const headers = getHeaderProxy(ctx);
- * setCustomerToken('abcd');
- * console.log(headers) // { Authorization: 'abcd' } (notice it's `headers`, not `headers()`)
- * // 'store' and 'Content-Currency' keys missing because their cookies aren't set'
- * ```
- * */
-const getHeaderProxy = (ctx: NuxtContext) => {
- const { getCustomerToken, getStore, getCurrency } = ctx.app.$vsf.$magento.config.state;
- const proxyHandler : ProxyHandler = {
- get(_target, key, _receiver) {
- return {
- Authorization: `Bearer ${getCustomerToken()}`,
- store: getStore(),
- 'Content-Currency': getCurrency(),
- }[key] ?? undefined;
- },
- ownKeys() {
- // Important to get fresh values on each read of ownKeys
- const customerToken = getCustomerToken();
- const store = getStore();
- const currency = getCurrency();
- /*
- * Add key to object only if it'll have a value
- * We don't want to send 'Authorization: undefined' in Network requests - so the key *needs* to be absent
- */
- return [
- ...(customerToken ? ['Authorization'] : []),
- ...(store ? ['store'] : []),
- ...(currency ? ['Content-Currency'] : []),
- ];
- },
- getOwnPropertyDescriptor(target, key) {
- return {
- value: this.get(target, key, null),
- enumerable: true,
- configurable: true,
- };
- },
- };
-
- return new Proxy({}, proxyHandler);
-};
-
-type GraphQlClients = Record<'query' | 'mutation', GraphQLClient>;
-
-const plugin : Plugin = (ctx, inject) => {
- const graphqlEndpoint = process.env.VSF_MAGENTO_GRAPHQL_URL;
-
- const headers = getHeaderProxy(ctx);
-
- const graphqlClients : GraphQlClients = {
- query: new GraphQLClient(graphqlEndpoint, { method: 'GET', headers }),
- mutation: new GraphQLClient(graphqlEndpoint, { method: 'POST', headers }),
- };
-
- inject('graphql', graphqlClients);
-};
-
-export default plugin;
-
-declare module '@nuxt/types' {
- interface NuxtAppOptions {
- $graphql: GraphQlClients;
- }
-
- interface Context {
- $graphql: GraphQlClients;
- }
-}
diff --git a/packages/theme/plugins/query/StoreConfig.gql.ts b/packages/theme/plugins/query/StoreConfig.gql.ts
index 55a2df573..1d36bb7bd 100644
--- a/packages/theme/plugins/query/StoreConfig.gql.ts
+++ b/packages/theme/plugins/query/StoreConfig.gql.ts
@@ -1,4 +1,4 @@
-import { gql } from 'graphql-request';
+import gql from 'graphql-tag';
/** GraphQL Query that fetches store configuration from the API */
export const StoreConfigQuery = gql`
diff --git a/packages/theme/plugins/storeConfigPlugin.ts b/packages/theme/plugins/storeConfigPlugin.ts
index 1f971f03f..5ad475c00 100644
--- a/packages/theme/plugins/storeConfigPlugin.ts
+++ b/packages/theme/plugins/storeConfigPlugin.ts
@@ -2,12 +2,14 @@ import { PiniaPluginContext } from 'pinia';
import { Plugin } from '@nuxt/types';
import { ref, set } from '@nuxtjs/composition-api';
import StoreConfigGql from '~/plugins/query/StoreConfig.gql';
+import type { StoreConfig } from '~/modules/GraphQL/types';
+
+const storeConfigPlugin: Plugin = async ({ $pinia, app }) => {
+ const { data }: { data: { storeConfig?: StoreConfig } } = await app.$vsf.$magento.api.customQuery({ query: StoreConfigGql });
-const storeConfigPlugin: Plugin = async ({ $pinia, $graphql }) => {
- const configData = await $graphql.query.request(StoreConfigGql);
$pinia.use(({ store }: PiniaPluginContext) => {
if (store.$id !== 'magentoConfig') return;
- const storeConfig = ref(configData.storeConfig);
+ const storeConfig = ref(data?.storeConfig ?? {});
// eslint-disable-next-line no-prototype-builtins
if (!store.$state.hasOwnProperty('storeConfig')) {
diff --git a/packages/theme/test-utils.js b/packages/theme/test-utils.js
index ec2811d0c..0c332c135 100644
--- a/packages/theme/test-utils.js
+++ b/packages/theme/test-utils.js
@@ -14,6 +14,9 @@ const customRender = (component, options = {}, callback = null) => render(compon
$fc,
$dompurify,
localePath,
+ $i18n: {
+ t: jest.fn((value) => value),
+ },
$nuxt: {
context: {
app: {
diff --git a/packages/theme/test-utils/mocks/categoryFiltersMock.ts b/packages/theme/test-utils/mocks/categoryFiltersMock.ts
new file mode 100644
index 000000000..06e49c893
--- /dev/null
+++ b/packages/theme/test-utils/mocks/categoryFiltersMock.ts
@@ -0,0 +1,44 @@
+import type { Aggregation } from '~/modules/GraphQL/types';
+
+export const categoryFiltersData: Aggregation[] = [
+ {
+ label: 'Pattern',
+ count: 2,
+ attribute_code: 'pattern',
+ position: 0,
+ options: [
+ {
+ count: 11,
+ label: 'Solid',
+ value: '196',
+ },
+ {
+ count: 1,
+ label: '194',
+ value: '194',
+ },
+ ],
+ },
+ {
+ label: 'Category',
+ count: 7,
+ attribute_code: 'category_id',
+ position: 1,
+ options: [
+ {
+ count: 12,
+ label: 'Women',
+ value: '20',
+ },
+ {
+ count: 12,
+ label: 'Tops',
+ value: '21',
+ },
+ ],
+ },
+];
+
+export const categoryFiltersDataWithOneOption: Aggregation = Object.assign(categoryFiltersData[0], {
+ options: [categoryFiltersData[0].options[0]],
+});
diff --git a/packages/theme/test-utils/mocks/categoryNavbarMock.ts b/packages/theme/test-utils/mocks/categoryNavbarMock.ts
index 46d058e10..95b2cf990 100644
--- a/packages/theme/test-utils/mocks/categoryNavbarMock.ts
+++ b/packages/theme/test-utils/mocks/categoryNavbarMock.ts
@@ -1,6 +1,6 @@
-import { AgnosticPagination } from '~/composables/types';
+import { Pagination } from '~/composables/types';
-export const paginationData: AgnosticPagination = {
+export const paginationData: Pagination = {
currentPage: 1,
totalPages: 2,
totalItems: 11,
diff --git a/packages/theme/test-utils/mocks/categoryTreeDataMock.ts b/packages/theme/test-utils/mocks/categoryTreeDataMock.ts
index 1f62fe1d6..15e250ef2 100644
--- a/packages/theme/test-utils/mocks/categoryTreeDataMock.ts
+++ b/packages/theme/test-utils/mocks/categoryTreeDataMock.ts
@@ -324,4 +324,543 @@ export const categoryTreeData: CategoryTree[] = [
},
];
+export const defaultCategoryTreeData = [
+ {
+ is_anchor: 1,
+ name: 'Women',
+ position: 2,
+ product_count: 75,
+ uid: 'MjA=',
+ url_path: 'women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Tops',
+ position: 1,
+ product_count: 50,
+ uid: 'MjE=',
+ url_path: 'women/tops-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Jackets',
+ position: 1,
+ product_count: 12,
+ uid: 'MjM=',
+ url_path: 'women/tops-women/jackets-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Hoodies & Sweatshirts',
+ position: 2,
+ product_count: 12,
+ uid: 'MjQ=',
+ url_path: 'women/tops-women/hoodies-and-sweatshirts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tees',
+ position: 3,
+ product_count: 12,
+ uid: 'MjU=',
+ url_path: 'women/tops-women/tees-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Bras & Tanks',
+ position: 4,
+ product_count: 14,
+ uid: 'MjY=',
+ url_path: 'women/tops-women/tanks-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ {
+ is_anchor: 1,
+ name: 'Bottoms',
+ position: 2,
+ product_count: 25,
+ uid: 'MjI=',
+ url_path: 'women/bottoms-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Pants',
+ position: 1,
+ product_count: 13,
+ uid: 'Mjc=',
+ url_path: 'women/bottoms-women/pants-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Shorts',
+ position: 2,
+ product_count: 12,
+ uid: 'Mjg=',
+ url_path: 'women/bottoms-women/shorts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ is_anchor: 0,
+ name: 'Men',
+ position: 3,
+ product_count: 0,
+ uid: 'MTE=',
+ url_path: 'men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Tops',
+ position: 1,
+ product_count: 48,
+ uid: 'MTI=',
+ url_path: 'men/tops-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Jackets',
+ position: 1,
+ product_count: 11,
+ uid: 'MTQ=',
+ url_path: 'men/tops-men/jackets-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Hoodies & Sweatshirts',
+ position: 2,
+ product_count: 13,
+ uid: 'MTU=',
+ url_path: 'men/tops-men/hoodies-and-sweatshirts-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tees',
+ position: 3,
+ product_count: 12,
+ uid: 'MTY=',
+ url_path: 'men/tops-men/tees-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tanks',
+ position: 4,
+ product_count: 12,
+ uid: 'MTc=',
+ url_path: 'men/tops-men/tanks-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ {
+ is_anchor: 1,
+ name: 'Bottoms',
+ position: 2,
+ product_count: 24,
+ uid: 'MTM=',
+ url_path: 'men/bottoms-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Pants',
+ position: 1,
+ product_count: 12,
+ uid: 'MTg=',
+ url_path: 'men/bottoms-men/pants-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Shorts',
+ position: 2,
+ product_count: 12,
+ uid: 'MTk=',
+ url_path: 'men/bottoms-men/shorts-men',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ is_anchor: 0,
+ name: 'Gear',
+ position: 4,
+ product_count: 34,
+ uid: 'Mw==',
+ url_path: 'gear',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Bags',
+ position: 1,
+ product_count: 14,
+ uid: 'NA==',
+ url_path: 'gear/bags',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [],
+ },
+ {
+ is_anchor: 1,
+ name: 'Fitness Equipment',
+ position: 2,
+ product_count: 11,
+ uid: 'NQ==',
+ url_path: 'gear/fitness-equipment',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [],
+ },
+ {
+ is_anchor: 1,
+ name: 'Watches',
+ position: 3,
+ product_count: 9,
+ uid: 'Ng==',
+ url_path: 'gear/watches',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [],
+ },
+ ],
+ },
+ {
+ is_anchor: 0,
+ name: 'Training',
+ position: 5,
+ product_count: 6,
+ uid: 'OQ==',
+ url_path: 'training',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+];
+
+export const womanCategoryTreeData = {
+ currentItems: {
+ value: [
+ {
+ is_anchor: 1,
+ name: 'Tops',
+ position: 1,
+ product_count: 50,
+ uid: 'MjE=',
+ url_path: 'women/tops-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Jackets',
+ position: 1,
+ product_count: 12,
+ uid: 'MjM=',
+ url_path: 'women/tops-women/jackets-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Hoodies & Sweatshirts',
+ position: 2,
+ product_count: 12,
+ uid: 'MjQ=',
+ url_path: 'women/tops-women/hoodies-and-sweatshirts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tees',
+ position: 3,
+ product_count: 12,
+ uid: 'MjU=',
+ url_path: 'women/tops-women/tees-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Bras & Tanks',
+ position: 4,
+ product_count: 14,
+ uid: 'MjY=',
+ url_path: 'women/tops-women/tanks-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ {
+ is_anchor: 1,
+ name: 'Bottoms',
+ position: 2,
+ product_count: 25,
+ uid: 'MjI=',
+ url_path: 'women/bottoms-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Pants',
+ position: 1,
+ product_count: 13,
+ uid: 'Mjc=',
+ url_path: 'women/bottoms-women/pants-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Shorts',
+ position: 2,
+ product_count: 12,
+ uid: 'Mjg=',
+ url_path: 'women/bottoms-women/shorts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ ],
+ },
+ current: {
+ value: {
+ is_anchor: 1,
+ name: 'Women',
+ position: 2,
+ product_count: 75,
+ uid: 'MjA=',
+ url_path: 'women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Tops',
+ position: 1,
+ product_count: 50,
+ uid: 'MjE=',
+ url_path: 'women/tops-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Jackets',
+ position: 1,
+ product_count: 12,
+ uid: 'MjM=',
+ url_path: 'women/tops-women/jackets-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Hoodies & Sweatshirts',
+ position: 2,
+ product_count: 12,
+ uid: 'MjQ=',
+ url_path: 'women/tops-women/hoodies-and-sweatshirts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tees',
+ position: 3,
+ product_count: 12,
+ uid: 'MjU=',
+ url_path: 'women/tops-women/tees-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Bras & Tanks',
+ position: 4,
+ product_count: 14,
+ uid: 'MjY=',
+ url_path: 'women/tops-women/tanks-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ {
+ is_anchor: 1,
+ name: 'Bottoms',
+ position: 2,
+ product_count: 25,
+ uid: 'MjI=',
+ url_path: 'women/bottoms-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Pants',
+ position: 1,
+ product_count: 13,
+ uid: 'Mjc=',
+ url_path: 'women/bottoms-women/pants-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Shorts',
+ position: 2,
+ product_count: 12,
+ uid: 'Mjg=',
+ url_path: 'women/bottoms-women/shorts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ ],
+ },
+ },
+ history: {
+ value: [
+ {
+ is_anchor: 1,
+ name: 'Women',
+ position: 2,
+ product_count: 75,
+ uid: 'MjA=',
+ url_path: 'women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Tops',
+ position: 1,
+ product_count: 50,
+ uid: 'MjE=',
+ url_path: 'women/tops-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Jackets',
+ position: 1,
+ product_count: 12,
+ uid: 'MjM=',
+ url_path: 'women/tops-women/jackets-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Hoodies & Sweatshirts',
+ position: 2,
+ product_count: 12,
+ uid: 'MjQ=',
+ url_path: 'women/tops-women/hoodies-and-sweatshirts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Tees',
+ position: 3,
+ product_count: 12,
+ uid: 'MjU=',
+ url_path: 'women/tops-women/tees-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Bras & Tanks',
+ position: 4,
+ product_count: 14,
+ uid: 'MjY=',
+ url_path: 'women/tops-women/tanks-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ {
+ is_anchor: 1,
+ name: 'Bottoms',
+ position: 2,
+ product_count: 25,
+ uid: 'MjI=',
+ url_path: 'women/bottoms-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ children: [
+ {
+ is_anchor: 1,
+ name: 'Pants',
+ position: 1,
+ product_count: 13,
+ uid: 'Mjc=',
+ url_path: 'women/bottoms-women/pants-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ {
+ is_anchor: 1,
+ name: 'Shorts',
+ position: 2,
+ product_count: 12,
+ uid: 'Mjg=',
+ url_path: 'women/bottoms-women/shorts-women',
+ url_suffix: '.html',
+ include_in_menu: 1,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+};
+
export default categoryTreeData;
diff --git a/packages/theme/test-utils/mocks/injectMock.ts b/packages/theme/test-utils/mocks/injectMock.ts
new file mode 100644
index 000000000..bf676be12
--- /dev/null
+++ b/packages/theme/test-utils/mocks/injectMock.ts
@@ -0,0 +1,6 @@
+export const injectMock = (extend = {}) => ({
+ isFilterSelected: jest.fn(
+ (id: string, optVal: string): boolean => id === optVal,
+ ),
+ ...extend,
+});
diff --git a/packages/theme/test-utils/mocks/removableFiltersMock.ts b/packages/theme/test-utils/mocks/removableFiltersMock.ts
new file mode 100644
index 000000000..813aa16af
--- /dev/null
+++ b/packages/theme/test-utils/mocks/removableFiltersMock.ts
@@ -0,0 +1,27 @@
+export type RemovableFilter = {
+ [key: string]: string;
+};
+
+export const removableFiltersData: RemovableFilter[] = [
+ {
+ id: 'new',
+ name: 'New',
+ label: '1',
+ value: '1',
+ type: 'yes_no',
+ },
+ {
+ id: 'climate',
+ name: 'Climate',
+ label: 'All-Weather',
+ value: '201',
+ type: 'checkbox',
+ },
+ {
+ id: 'color',
+ name: 'Color',
+ label: 'Black',
+ value: '49',
+ type: 'swatch_color',
+ },
+];
diff --git a/packages/theme/test-utils/mocks/useCart.js b/packages/theme/test-utils/mocks/useCart.js
index 9e1d7d878..f0d7c62ad 100644
--- a/packages/theme/test-utils/mocks/useCart.js
+++ b/packages/theme/test-utils/mocks/useCart.js
@@ -1,3 +1,16 @@
+import { ref } from '@nuxtjs/composition-api';
+
+const createError = () => ref({
+ addItem: null,
+ removeItem: null,
+ updateItemQty: null,
+ load: null,
+ clear: null,
+ applyCoupon: null,
+ removeCoupon: null,
+ loadTotalQty: null,
+});
+
export const useEmptyCartMock = (cartData = {}) => ({
load: jest.fn(),
loading: false,
@@ -7,6 +20,7 @@ export const useEmptyCartMock = (cartData = {}) => ({
cart: {
value: {},
},
+ error: createError(),
...cartData,
});
@@ -16,8 +30,26 @@ export const useCartMock = (cartData = {}) => ({
removeItem: jest.fn(),
updateItemQty: jest.fn(),
loadTotalQty: jest.fn(() => 2),
+ error: createError(),
cart: {
value: {
+ prices: {
+ __typename: 'CartPrices',
+ subtotal_excluding_tax: {
+ __typename: 'Money',
+ value: 107,
+ },
+ subtotal_including_tax: {
+ __typename: 'Money',
+ value: 107,
+ },
+ applied_taxes: [],
+ discounts: null,
+ grand_total: {
+ __typename: 'Money',
+ value: 107,
+ },
+ },
items: [
{
__typename: 'Product1',
@@ -78,6 +110,7 @@ export const useCartMock = (cartData = {}) => ({
__typename: 'CategoryTree',
uid: 'MTM=',
name: 'Bottoms',
+
url_suffix: '.html',
url_path: 'men/bottoms-men',
breadcrumbs: [
@@ -291,7 +324,83 @@ export const useCartMock = (cartData = {}) => ({
},
],
},
- ],
+ {
+ uid: 'MjYwNw==',
+ product: {
+ 0: {},
+ uid: 'NDY=',
+ __typename: 'BundleProduct',
+ sku: '24-WG080',
+ name: 'Sprite Yoga Companion Kit',
+ stock_status: 'IN_STOCK',
+ only_x_left_in_stock: null,
+ rating_summary: 0,
+ thumbnail: {
+ url: 'https://magento2demo.frodigo.com/media/catalog/product/cache/6008460c710ac4e87d1a4c53dc478d67/l/u/luma-yoga-kit-2.jpg', position: null, disabled: null, label: 'Sprite Yoga Companion Kit', __typename: 'ProductImage',
+ },
+ url_key: 'sprite-yoga-companion-kit',
+ url_rewrites: [{ url: 'sprite-yoga-companion-kit.html', __typename: 'UrlRewrite' }, { url: 'gear/sprite-yoga-companion-kit.html', __typename: 'UrlRewrite' }, { url: 'gear/fitness-equipment/sprite-yoga-companion-kit.html', __typename: 'UrlRewrite' }],
+ price_range: { maximum_price: { final_price: { currency: 'USD', value: 77, __typename: 'Money' }, regular_price: { currency: 'USD', value: 77, __typename: 'Money' }, __typename: 'ProductPrice' }, minimum_price: { final_price: { currency: 'USD', value: 61, __typename: 'Money' }, regular_price: { currency: 'USD', value: 61, __typename: 'Money' }, __typename: 'ProductPrice' }, __typename: 'PriceRange' },
+ categories: [{
+ uid: 'Mw==', name: 'Gear', url_suffix: '.html', url_path: 'gear', breadcrumbs: null, __typename: 'CategoryTree',
+ }, {
+ uid: 'NQ==', name: 'Fitness Equipment', url_suffix: '.html', url_path: 'gear/fitness-equipment', breadcrumbs: [{ category_name: 'Gear', category_url_path: 'gear', __typename: 'Breadcrumb' }], __typename: 'CategoryTree',
+ }],
+ review_count: 0,
+ reviews: { items: [], __typename: 'ProductReviews' },
+ original_sku: '24-WG080',
+ },
+ prices: {
+ __typename: 'CartItemPrices',
+ row_total: {
+ value: 70,
+ __typename: 'Money',
+ },
+ row_total_including_tax: {
+ value: 70,
+ __typename: 'Money',
+ },
+ total_item_discount: {
+ value: 0,
+ __typename: 'Money',
+ },
+ },
+ quantity: 1,
+ bundle_options: [{
+ uid: 'YnVuZGxlLzE=',
+ label: 'Sprite Stasis Ball',
+ type: 'radio',
+ values: [{
+ id: 3, label: 'Sprite Stasis Ball 75 cm', price: 32, quantity: 1, __typename: 'SelectedBundleOptionValue',
+ }],
+ __typename: 'SelectedBundleOption',
+ }, {
+ uid: 'YnVuZGxlLzI=',
+ label: 'Sprite Foam Yoga Brick',
+ type: 'radio',
+ values: [{
+ id: 4, label: 'Sprite Foam Yoga Brick', price: 5, quantity: 1, __typename: 'SelectedBundleOptionValue',
+ }],
+ __typename: 'SelectedBundleOption',
+ }, {
+ uid: 'YnVuZGxlLzM=',
+ label: 'Sprite Yoga Strap',
+ type: 'radio',
+ values: [{
+ id: 5, label: 'Sprite Yoga Strap 6 foot', price: 14, quantity: 1, __typename: 'SelectedBundleOptionValue',
+ }],
+ __typename: 'SelectedBundleOption',
+ }, {
+ uid: 'YnVuZGxlLzQ=',
+ label: 'Sprite Foam Roller',
+ type: 'radio',
+ values: [{
+ id: 8, label: 'Sprite Foam Roller', price: 19, quantity: 1, __typename: 'SelectedBundleOptionValue',
+ }],
+ __typename: 'SelectedBundleOption',
+ }],
+ __typename: 'BundleCartItem',
+ }],
total_quantity: 2,
},
},
diff --git a/packages/theme/test-utils/mocks/useCategoryStore.ts b/packages/theme/test-utils/mocks/useCategoryStore.ts
new file mode 100644
index 000000000..b8938ea8c
--- /dev/null
+++ b/packages/theme/test-utils/mocks/useCategoryStore.ts
@@ -0,0 +1,6 @@
+export const useCategoryStoreMock = (extend = {}) => ({
+ categories: [],
+ ...extend,
+});
+
+export default useCategoryStoreMock;
diff --git a/packages/theme/test-utils/mocks/useContext.ts b/packages/theme/test-utils/mocks/useContext.ts
new file mode 100644
index 000000000..0e380282a
--- /dev/null
+++ b/packages/theme/test-utils/mocks/useContext.ts
@@ -0,0 +1,9 @@
+export const useContextMock = (extend = {}) => ({
+ app: {
+ localePath: jest.fn((val) => val),
+ },
+ localePath: jest.fn((val) => val),
+ ...extend,
+});
+
+export default useContextMock;
diff --git a/packages/theme/test-utils/mocks/useRoute.ts b/packages/theme/test-utils/mocks/useRoute.ts
new file mode 100644
index 000000000..0d26bba3a
--- /dev/null
+++ b/packages/theme/test-utils/mocks/useRoute.ts
@@ -0,0 +1,8 @@
+export const useRouteMock = (extend = {}) => ({
+ value: {
+ fullPath: '/default/c/gear.html',
+ },
+ ...extend,
+});
+
+export default useRouteMock;
diff --git a/packages/theme/test-utils/mocks/useRouter.ts b/packages/theme/test-utils/mocks/useRouter.ts
new file mode 100644
index 000000000..6b2172710
--- /dev/null
+++ b/packages/theme/test-utils/mocks/useRouter.ts
@@ -0,0 +1,6 @@
+export const useRouterMock = (extend = {}) => ({
+ push: jest.fn((val) => val),
+ ...extend,
+});
+
+export default useRouterMock;
diff --git a/packages/theme/test-utils/mocks/useTraverseCategoryMock.ts b/packages/theme/test-utils/mocks/useTraverseCategoryMock.ts
index 3bac772b5..ef61ad902 100644
--- a/packages/theme/test-utils/mocks/useTraverseCategoryMock.ts
+++ b/packages/theme/test-utils/mocks/useTraverseCategoryMock.ts
@@ -185,7 +185,7 @@ export const categoryAncestorsThirdLevelMock = [
} as CategoryTree,
];
-export const useTraverseCategoryMock = (categoryAncestors = [], extend = []) => ({
+export const useTraverseCategoryMock = (categoryAncestors : CategoryTree[] = [], extend : any[] = []) => ({
loadCategoryTree: jest.fn(),
isCategoryTreeLoaded: ref(true),
categoryAncestors: ref(categoryAncestors),
diff --git a/packages/theme/test-utils/mocks/useUiHelpersMock.ts b/packages/theme/test-utils/mocks/useUiHelpersMock.ts
index 35b177541..0b880a87d 100644
--- a/packages/theme/test-utils/mocks/useUiHelpersMock.ts
+++ b/packages/theme/test-utils/mocks/useUiHelpersMock.ts
@@ -1,6 +1,8 @@
export const useUiHelpersMock = (extend = {}) => ({
- getCatLink: jest.fn((val) => val),
changeSorting: jest.fn(),
+ clearFilters: jest.fn(),
+ getCatLink: jest.fn((val) => val),
+ getFacetsFromURL: jest.fn(() => ({ filters: {} })),
...extend,
});
diff --git a/packages/theme/test-utils/mocks/useUiState.js b/packages/theme/test-utils/mocks/useUiState.ts
similarity index 89%
rename from packages/theme/test-utils/mocks/useUiState.js
rename to packages/theme/test-utils/mocks/useUiState.ts
index d3786802e..d9a35a982 100644
--- a/packages/theme/test-utils/mocks/useUiState.js
+++ b/packages/theme/test-utils/mocks/useUiState.ts
@@ -4,6 +4,7 @@ export const useUiStateMock = (extend = {}) => ({
changeToCategoryListView: jest.fn(),
toggleCartSidebar: jest.fn(),
toggleFilterSidebar: jest.fn(),
+ toggleMobileMenu: jest.fn(),
...extend,
});
diff --git a/packages/theme/tsconfig.json b/packages/theme/tsconfig.json
index cf92fe1a6..8c78d3749 100644
--- a/packages/theme/tsconfig.json
+++ b/packages/theme/tsconfig.json
@@ -32,7 +32,7 @@
"@nuxt/image",
"jest"
],
- "typeRoots": ["../../node_modules/@types"],
+ "typeRoots": ["../../node_modules/@types", "./node_modules/@types"],
"resolveJsonModule": true,
"rootDir": "./",
"declarationDir": "./lib",
diff --git a/packages/theme/types/componentTemplateRef.ts b/packages/theme/types/componentTemplateRef.ts
new file mode 100644
index 000000000..9a6158a36
--- /dev/null
+++ b/packages/theme/types/componentTemplateRef.ts
@@ -0,0 +1,13 @@
+import type { ComponentInstance } from '@nuxtjs/composition-api';
+
+/**
+ * Template refs can also be applied to components, not only HTML elements,
+ * so this type uses Vue's ComponentInstance interface rather than HTMLElement.
+ *
+ * ComponentInstance["$el"] is `Element` by default. Element doesn't have `.focus()``,
+ * which would cause TypeScript errors if you use `myTemplateRef.$el.focus()`.
+ * For that reason, it's extended to the more specific `HTMLElement`` here
+ *
+ * * @template TElement The root element of the component. Can be used to pass a more specific element, like HTMLInputElement
+ */
+export type ComponentTemplateRef = ComponentInstance & { $el: TElement };
diff --git a/packages/theme/types/core.ts b/packages/theme/types/core.ts
index 093c60a83..77c24db80 100644
--- a/packages/theme/types/core.ts
+++ b/packages/theme/types/core.ts
@@ -1,863 +1,9 @@
-/* istanbul ignore file */
+import { useContext } from '@nuxtjs/composition-api';
-import type { Ref } from '@nuxtjs/composition-api';
-import type { Request, Response } from 'express';
-
-/**
- * Default name of the cookie storing active localization code
- */
-export const VSF_LOCALE_COOKIE = 'vsf-locale';
-
-/**
- * Default name of the cookie storing active currency code
- */
-export const VSF_CURRENCY_COOKIE = 'vsf-currency';
-
-/**
- * Default name of the cookie storing active country code
- */
-export const VSF_COUNTRY_COOKIE = 'vsf-country';
-
-/**
- * Default name of the cookie storing active store code
- */
-export const VSF_STORE_COOKIE = 'vsf-store';
-
-/**
- * Default name of the cookie storing active channel code
- */
-export const VSF_CHANNEL_COOKIE = 'vsf-channel';
-
-export type ComputedProperty = Readonly[>>;
+export type UseContextReturn = ReturnType];
export type CustomQuery = Record;
-export type ComposableFunctionArgs = T & { customQuery?: CustomQuery };
-
-export interface UseProductErrors {
- search: Error;
-}
-
-export interface UseSearchErrors {
- search: Error;
-}
-
-export interface IntegrationContext {
- client: CLIENT;
- config: CONFIG;
- api: API;
- [x: string]: any;
-}
-
-export interface Context {
- [x: string]: IntegrationContext | any;
-}
-
-export type PlatformApi = {
- [functionName: string]: (context: Context, ...args: any[]) => Promise
-};
-
-export type ContextedPlatformApi = {
- [P in keyof T]: T[P] extends (context: Context, ...arg: infer X) => Promise
- ? (...arg: X) => Promise
- : never
-};
-
-export interface Composable {
- api?: ContextedPlatformApi
-}
-
-export interface UseProduct<
- PRODUCTS,
- PRODUCT_SEARCH_PARAMS,
- API extends PlatformApi = any,
-> extends Composable {
- products: ComputedProperty;
- loading: ComputedProperty;
- error: ComputedProperty;
- search(params: ComposableFunctionArgs): Promise;
- [x: string]: any;
-}
-
-export interface UseForgotPasswordErrors {
- request: Error;
- setNew: Error;
-}
-
-export interface UseForgotPassword {
- result: ComputedProperty;
- loading: ComputedProperty;
- error: ComputedProperty;
- setNew(params: ComposableFunctionArgs<{ tokenValue: string, newPassword: string }>): Promise;
- request(params: ComposableFunctionArgs<{ email: string }>): Promise;
-}
-
-export interface UseSearch {
- result: ComputedProperty;
- loading: ComputedProperty;
- error: ComputedProperty;
- search(params: ComposableFunctionArgs): Promise;
-}
-
-export interface UseUserRegisterParams {
- email: string;
- password: string;
- firstName?: string;
- lastName?: string;
- [x: string]: any;
-}
-
-export interface UseUserLoginParams {
- username: string;
- password: string;
- [x: string]: any;
-}
-export interface UseUserErrors {
- updateUser: Error;
- register: Error;
- login: Error;
- logout: Error;
- changePassword: Error;
- load: Error;
-}
-
-export interface UseUser
-<
- USER,
- UPDATE_USER_PARAMS,
- API extends PlatformApi = any,
-> extends Composable {
- user: ComputedProperty;
- setUser: (user: USER) => void;
- updateUser: (params: { user: UPDATE_USER_PARAMS; customQuery?: CustomQuery }) => Promise;
- register: (params: { user: UseUserRegisterParams; customQuery?: CustomQuery }) => Promise;
- login: (params: { user: UseUserLoginParams; customQuery?: CustomQuery }) => Promise;
- logout: (params?: { customQuery: CustomQuery }) => Promise;
- changePassword: (params: { current: string; new: string, customQuery?: CustomQuery }) => Promise;
- load: (params?: { customQuery: CustomQuery }) => Promise;
- isAuthenticated: Ref;
- loading: ComputedProperty;
- error: ComputedProperty;
-}
-
-export interface UseUserOrderSearchParams {
- id?: any;
- page?: number;
- perPage?: number;
- [x: string]: any;
-}
-export interface UseUserOrderErrors {
- search: Error;
-}
-export interface UseUserOrder<
- ORDERS,
- ORDER_SEARCH_PARAMS,
- API extends PlatformApi = any,
-> extends Composable {
- orders: ComputedProperty;
- search(params: ComposableFunctionArgs): Promise;
- loading: ComputedProperty;
- error: ComputedProperty;
-}
-
-export interface UseUserAddress extends Composable {
- addresses: ComputedProperty;
- totalAddresses: ComputedProperty;
- addAddress: (address: ADDRESS) => Promise;
- deleteAddress: (address: ADDRESS) => Promise;
- updateAddress: (address: ADDRESS) => Promise;
- searchAddresses: (params?: { [x: string]: any }) => Promise;
- loading: ComputedProperty;
-}
-export interface UseUserShippingErrors {
- addAddress: Error;
- deleteAddress: Error;
- updateAddress: Error;
- load: Error;
- setDefaultAddress: Error;
-}
-export interface UseUserShipping<
- USER_SHIPPING,
- USER_SHIPPING_ITEM,
- API extends PlatformApi = any,
-> extends Composable {
- shipping: ComputedProperty;
- addAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise;
- deleteAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise;
- updateAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise;
- load: () => Promise;
- setDefaultAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise;
- loading: ComputedProperty;
- error: ComputedProperty;
-}
-
-export interface UserShippingGetters {
- getAddresses: (shipping: USER_SHIPPING, criteria?: Record) => USER_SHIPPING_ITEM[];
- getDefault: (shipping: USER_SHIPPING) => USER_SHIPPING_ITEM;
- getTotal: (shipping: USER_SHIPPING) => number;
- getPostCode: (address: USER_SHIPPING_ITEM) => string;
- getStreetName: (address: USER_SHIPPING_ITEM) => string;
- getStreetNumber: (address: USER_SHIPPING_ITEM) => string | number;
- getCity: (address: USER_SHIPPING_ITEM) => string;
- getFirstName: (address: USER_SHIPPING_ITEM) => string;
- getLastName: (address: USER_SHIPPING_ITEM) => string;
- getCountry: (address: USER_SHIPPING_ITEM) => string;
- getPhone: (address: USER_SHIPPING_ITEM) => string;
- getEmail: (address: USER_SHIPPING_ITEM) => string;
- getProvince: (address: USER_SHIPPING_ITEM) => string;
- getCompanyName: (address: USER_SHIPPING_ITEM) => string;
- getTaxNumber: (address: USER_SHIPPING_ITEM) => string;
- getId: (address: USER_SHIPPING_ITEM) => string | number;
- getApartmentNumber: (address: USER_SHIPPING_ITEM) => string | number;
- isDefault: (address: USER_SHIPPING_ITEM) => boolean;
-}
-
-export interface UseUserBillingErrors {
- addAddress: Error;
- deleteAddress: Error;
- updateAddress: Error;
- load: Error;
- setDefaultAddress: Error;
-}
-export interface UseUserBilling<
- USER_BILLING,
- USER_BILLING_ITEM,
- API extends PlatformApi = any,
-> extends Composable {
- billing: ComputedProperty;
- addAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise;
- deleteAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise;
- updateAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise;
- load: () => Promise;
- setDefaultAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise;
- loading: ComputedProperty;
- error: ComputedProperty;
-}
-
-export interface UserBillingGetters {
- getAddresses: (billing: USER_BILLING, criteria?: Record) => USER_BILLING_ITEM[];
- getDefault: (billing: USER_BILLING) => USER_BILLING_ITEM;
- getTotal: (billing: USER_BILLING) => number;
- getPostCode: (address: USER_BILLING_ITEM) => string;
- getStreetName: (address: USER_BILLING_ITEM) => string;
- getStreetNumber: (address: USER_BILLING_ITEM) => string | number;
- getCity: (address: USER_BILLING_ITEM) => string;
- getFirstName: (address: USER_BILLING_ITEM) => string;
- getLastName: (address: USER_BILLING_ITEM) => string;
- getCountry: (address: USER_BILLING_ITEM) => string;
- getPhone: (address: USER_BILLING_ITEM) => string;
- getEmail: (address: USER_BILLING_ITEM) => string;
- getProvince: (address: USER_BILLING_ITEM) => string;
- getCompanyName: (address: USER_BILLING_ITEM) => string;
- getTaxNumber: (address: USER_BILLING_ITEM) => string;
- getId: (address: USER_BILLING_ITEM) => string;
- getApartmentNumber: (address: USER_BILLING_ITEM) => string | number;
- isDefault: (address: USER_BILLING_ITEM) => boolean;
-}
-
-export interface UseCategoryErrors {
- search: Error;
-}
-export interface UseCategory<
- CATEGORY,
- CATEGORY_SEARCH_PARAMS,
- API extends PlatformApi = any,
-> extends Composable {
- categories: ComputedProperty;
- search(params: ComposableFunctionArgs): Promise;
- loading: ComputedProperty;
- error: ComputedProperty;
-}
-
-export interface UseCartErrors {
- addItem: Error;
- removeItem: Error;
- updateItemQty: Error;
- load: Error;
- clear: Error;
- applyCoupon: Error;
- removeCoupon: Error;
-}
-export interface UseCart
-<
- CART,
- CART_ITEM,
- PRODUCT,
- API extends PlatformApi = any,
-> extends Composable {
- cart: ComputedProperty;
- setCart(cart: CART): void;
- addItem(params: { product: PRODUCT; quantity: number; customQuery?: CustomQuery }): Promise;
- isInCart: ({ product: PRODUCT }) => boolean;
- removeItem(params: { product: CART_ITEM; customQuery?: CustomQuery }): Promise;
- updateItemQty(params: { product: CART_ITEM; quantity?: number; customQuery?: CustomQuery }): Promise;
- clear(): Promise;
- applyCoupon(params: { couponCode: string; customQuery?: CustomQuery }): Promise;
- removeCoupon(params: { couponCode: string; customQuery?: CustomQuery }): Promise;
- load(): Promise;
- load(params: { customQuery?: CustomQuery }): Promise;
- error: ComputedProperty;
- loading: ComputedProperty;
-}
-export interface UseWishlistErrors {
- addItem: Error;
- removeItem: Error;
- load: Error;
- clear: Error;
-}
-export interface UseWishlist
-<
- WISHLIST,
- WISHLIST_ITEM,
- PRODUCT,
- API extends PlatformApi = any,
-> extends Composable {
- wishlist: ComputedProperty;
- loading: ComputedProperty;
- addItem(params: { product: PRODUCT; customQuery?: CustomQuery }): Promise;
- removeItem(params: { product: WISHLIST_ITEM; customQuery?: CustomQuery }): Promise;
- load(): Promise;
- load(params: { customQuery?: CustomQuery }): Promise;
- clear(): Promise;
- setWishlist: (wishlist: WISHLIST) => void;
- isInWishlist({ product: PRODUCT }): boolean;
- error: ComputedProperty;
-}
-
-export interface UseCompare {
- compare: ComputedProperty;
- addToCompare: (product: PRODUCT) => Promise;
- removeFromCompare: (product: PRODUCT) => Promise;
- clearCompare: () => Promise;
- loading: ComputedProperty;
-}
-
-export interface UseMakeOrderErrors {
- make: Error;
-}
-
-export interface UseMakeOrder extends Composable {
- order: Ref;
- make(params: { customQuery?: CustomQuery }): Promise;
- error: ComputedProperty;
- loading: ComputedProperty;
-}
-
-export interface UseCheckout
-<
- PAYMENT_METHODS,
- SHIPPING_METHODS,
- PERSONAL_DETAILS,
- SHIPPING_DETAILS,
- BILLING_DETAILS,
- CHOOSEN_PAYMENT_METHOD,
- CHOOSEN_SHIPPING_METHOD,
- PLACE_ORDER,
- API extends PlatformApi = any,
-> extends Composable {
- paymentMethods: Ref;
- shippingMethods: Ref;
- personalDetails: PERSONAL_DETAILS;
- shippingDetails: SHIPPING_DETAILS;
- billingDetails: BILLING_DETAILS;
- chosenPaymentMethod: CHOOSEN_PAYMENT_METHOD;
- chosenShippingMethod: CHOOSEN_SHIPPING_METHOD;
- placeOrder: PLACE_ORDER;
- loading: ComputedProperty;
-}
-export interface UseReviewErrors {
- search: Error;
- addReview: Error;
-}
-export interface UseReview<
- REVIEW,
- REVIEWS_SEARCH_PARAMS,
- REVIEW_ADD_PARAMS,
- API extends PlatformApi = any,
-> extends Composable {
- search(params: ComposableFunctionArgs): Promise;
- addReview(params: ComposableFunctionArgs): Promise;
- error: ComputedProperty;
- reviews: ComputedProperty