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.
-[![All Contributors](https://img.shields.io/badge/all_contributors-15-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#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: + +Anatomy of the useUser composable + +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 ` @@ -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 @@