From 0e40878a806582c53f0349d048bcc351544aea8b Mon Sep 17 00:00:00 2001 From: Patryk Tomczyk <13100280+patzick@users.noreply.github.com> Date: Mon, 5 Oct 2020 13:13:31 +0200 Subject: [PATCH] refactor(listing): useListing composable with criteria refactor (#1086) * refactor(listing): useListing composable with criteria refactory --- api/composables.api.md | 99 +- api/helpers.api.md | 13 + api/shopware-6-client.api.md | 15 +- docs/landing/getting-started/upgrade.md | 10 + ...omposables.applicationvuecontext._route.md | 14 + .../api/composables.applicationvuecontext.md | 2 + ...composables.applicationvuecontext.route.md | 14 + .../composables.createlistingcomposable.md | 32 + .../api/composables.getapplicationcontext.md | 3 +- ...mposables.iuselisting.changecurrentpage.md | 14 + ...s.iuselisting.changecurrentsortingorder.md | 14 + ...osables.iuselisting.getavailablefilters.md | 14 + ...mposables.iuselisting.getcurrentfilters.md | 14 + ...mposables.iuselisting.getcurrentlisting.md | 14 + .../composables.iuselisting.getcurrentpage.md | 14 + ...bles.iuselisting.getcurrentsortingorder.md | 14 + .../composables.iuselisting.getelements.md | 14 + ...mposables.iuselisting.getinitiallisting.md | 14 + .../api/composables.iuselisting.getlimit.md | 14 + ...omposables.iuselisting.getsortingorders.md | 17 + .../api/composables.iuselisting.gettotal.md | 14 + ...posables.iuselisting.gettotalpagescount.md | 14 + .../api/composables.iuselisting.initsearch.md | 14 + .../api/composables.iuselisting.loading.md | 14 + .../composables.iuselisting.loadingmore.md | 14 + .../api/composables.iuselisting.loadmore.md | 14 + .../resources/api/composables.iuselisting.md | 41 + .../api/composables.iuselisting.search.md | 16 + ...mposables.iuselisting.setinitiallisting.md | 14 + ...bles.iuseproductquicksearch.getproducts.md | 14 + ...osables.iuseproductquicksearch.gettotal.md | 14 + ...posables.iuseproductquicksearch.loading.md | 14 + ...osables.iuseproductquicksearch.loadmore.md | 14 + .../api/composables.iuseproductquicksearch.md | 27 + ...mposables.iuseproductquicksearch.search.md | 14 + ...ables.iuseproductquicksearch.searchterm.md | 14 + .../resources/api/composables.listingkey.md | 15 + docs/landing/resources/api/composables.md | 14 + .../api/composables.usecategoryfilters.md | 19 + .../resources/api/composables.usedefaults.md | 4 +- .../resources/api/composables.uselisting.md | 15 + .../composables.useproductlisting.error.md | 14 + .../composables.useproductlisting.loading.md | 14 + .../api/composables.useproductlisting.md | 19 + .../api/composables.useproductquicksearch.md | 15 + .../api/helpers.getlistingfilters.md | 26 + .../api/helpers.listingfilter.code.md | 14 + .../api/helpers.listingfilter.label.md | 14 + .../resources/api/helpers.listingfilter.md | 23 + docs/landing/resources/api/helpers.md | 2 + .../shopware-6-client.getcategoryproducts.md | 16 + .../resources/api/shopware-6-client.md | 4 +- ...md => shopware-6-client.searchproducts.md} | 15 +- ...opware-6-client.searchsuggestedproducts.md | 28 + .../interfaces/search/SearchCriteria.ts | 1 + .../__tests__/createListingComposable.spec.ts | 887 +++++++++ .../__tests__/useCategoryFilters.spec.ts | 9 + packages/composables/__tests__/useCms.spec.ts | 113 +- .../composables/__tests__/useDefaults.spec.ts | 22 + .../composables/__tests__/useListing.spec.ts | 87 + .../__tests__/useProductListing.spec.ts | 9 + .../__tests__/useProductQuickSearch.spec.ts | 97 + .../__tests__/useProductSearch.spec.ts | 8 + packages/composables/package.json | 3 + packages/composables/src/appContext.ts | 3 + .../src/factories/createListingComposable.ts | 249 +++ .../src/hooks/useCategoryFilters/index.ts | 9 +- .../composables/src/hooks/useCms/index.ts | 38 +- .../src/hooks/useProductListing.ts | 13 +- .../composables/src/hooks/useProductSearch.ts | 8 + packages/composables/src/index.ts | 3 + .../src/internalHelpers/defaultApiParams.json | 236 ++- .../src/internalHelpers/searchCriteria.ts | 2 +- packages/composables/src/logic/useDefaults.ts | 11 +- packages/composables/src/logic/useListing.ts | 50 + .../src/logic/useProductQuickSearch.ts | 70 + packages/default-theme/assets/scss/main.scss | 1 + .../cms/blocks/CmsBlockProductListing.vue | 43 + packages/default-theme/cms/cmsMap.json | 2 +- .../CmsElementCategorySidebarFilter.vue | 325 +--- .../cms/elements/CmsElementProductListing.vue | 310 +--- .../components/SwProductDetails.vue | 2 +- .../components/SwProductListing.vue | 114 +- .../components/SwProductListingFilters.vue | 209 ++- .../default-theme/components/SwSearchBar.vue | 51 +- .../components/listing/NoFilterFound.vue | 34 + .../listing/SwProductListingFilter.vue | 38 +- .../components/listing/types/color.vue | 61 + .../components/listing/types/content.vue | 12 + .../components/listing/types/entity.vue | 36 +- .../components/listing/types/fabric.vue | 12 + .../components/listing/types/length.vue | 12 + .../components/listing/types/manufacturer.vue | 12 + .../components/listing/types/price.vue | 12 + .../components/listing/types/range.vue | 94 +- .../components/listing/types/rating.vue | 8 +- .../listing/types/shipping-free.vue | 21 +- .../components/listing/types/size.vue | 12 + .../components/listing/types/textile.vue | 12 + .../components/listing/types/tone.vue | 12 + .../components/listing/types/width.vue | 12 + packages/default-theme/layouts/default.vue | 2 +- packages/default-theme/locales/de-DE.json | 7 + packages/default-theme/locales/en-GB.json | 7 + packages/default-theme/pages/_lang/_.vue | 7 +- packages/default-theme/pages/_lang/search.vue | 130 +- packages/default-theme/store/index.js | 14 + .../listing/getListingFilters.spec.ts | 145 ++ .../helpers/src/listing/getListingFilters.ts | 41 + packages/helpers/src/listing/index.ts | 1 + .../src/product/getProductThumbnailUrl.ts | 1 - .../services/PageService/getCmsPage.spec.ts | 45 + .../getCategoryProducts.spec.ts | 47 + .../SearchService/getSuggestedResults.spec.ts | 12 + .../SearchService/searchProducts.spec.ts | 50 + .../searchSuggestedProducts.spec.ts | 50 + .../src/services/pageService.ts | 22 +- .../src/services/productService.ts | 23 +- .../src/services/searchService.ts | 45 +- scripts/build.js | 5 +- yarn.lock | 1585 +++++++++-------- 121 files changed, 4538 insertions(+), 1982 deletions(-) create mode 100644 docs/landing/resources/api/composables.applicationvuecontext._route.md create mode 100644 docs/landing/resources/api/composables.applicationvuecontext.route.md create mode 100644 docs/landing/resources/api/composables.createlistingcomposable.md create mode 100644 docs/landing/resources/api/composables.iuselisting.changecurrentpage.md create mode 100644 docs/landing/resources/api/composables.iuselisting.changecurrentsortingorder.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getavailablefilters.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getcurrentfilters.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getcurrentlisting.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getcurrentpage.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getcurrentsortingorder.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getelements.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getinitiallisting.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getlimit.md create mode 100644 docs/landing/resources/api/composables.iuselisting.getsortingorders.md create mode 100644 docs/landing/resources/api/composables.iuselisting.gettotal.md create mode 100644 docs/landing/resources/api/composables.iuselisting.gettotalpagescount.md create mode 100644 docs/landing/resources/api/composables.iuselisting.initsearch.md create mode 100644 docs/landing/resources/api/composables.iuselisting.loading.md create mode 100644 docs/landing/resources/api/composables.iuselisting.loadingmore.md create mode 100644 docs/landing/resources/api/composables.iuselisting.loadmore.md create mode 100644 docs/landing/resources/api/composables.iuselisting.md create mode 100644 docs/landing/resources/api/composables.iuselisting.search.md create mode 100644 docs/landing/resources/api/composables.iuselisting.setinitiallisting.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.getproducts.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.gettotal.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.loading.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.loadmore.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.search.md create mode 100644 docs/landing/resources/api/composables.iuseproductquicksearch.searchterm.md create mode 100644 docs/landing/resources/api/composables.listingkey.md create mode 100644 docs/landing/resources/api/composables.usecategoryfilters.md create mode 100644 docs/landing/resources/api/composables.uselisting.md create mode 100644 docs/landing/resources/api/composables.useproductlisting.error.md create mode 100644 docs/landing/resources/api/composables.useproductlisting.loading.md create mode 100644 docs/landing/resources/api/composables.useproductlisting.md create mode 100644 docs/landing/resources/api/composables.useproductquicksearch.md create mode 100644 docs/landing/resources/api/helpers.getlistingfilters.md create mode 100644 docs/landing/resources/api/helpers.listingfilter.code.md create mode 100644 docs/landing/resources/api/helpers.listingfilter.label.md create mode 100644 docs/landing/resources/api/helpers.listingfilter.md create mode 100644 docs/landing/resources/api/shopware-6-client.getcategoryproducts.md rename docs/landing/resources/api/{shopware-6-client.getsuggestedresults.md => shopware-6-client.searchproducts.md} (57%) create mode 100644 docs/landing/resources/api/shopware-6-client.searchsuggestedproducts.md create mode 100644 packages/composables/__tests__/createListingComposable.spec.ts create mode 100644 packages/composables/__tests__/useListing.spec.ts create mode 100644 packages/composables/__tests__/useProductQuickSearch.spec.ts create mode 100644 packages/composables/src/factories/createListingComposable.ts create mode 100644 packages/composables/src/logic/useListing.ts create mode 100644 packages/composables/src/logic/useProductQuickSearch.ts create mode 100644 packages/default-theme/cms/blocks/CmsBlockProductListing.vue create mode 100644 packages/default-theme/components/listing/NoFilterFound.vue create mode 100644 packages/default-theme/components/listing/types/color.vue create mode 100644 packages/default-theme/components/listing/types/content.vue create mode 100644 packages/default-theme/components/listing/types/fabric.vue create mode 100644 packages/default-theme/components/listing/types/length.vue create mode 100644 packages/default-theme/components/listing/types/manufacturer.vue create mode 100644 packages/default-theme/components/listing/types/price.vue create mode 100644 packages/default-theme/components/listing/types/size.vue create mode 100644 packages/default-theme/components/listing/types/textile.vue create mode 100644 packages/default-theme/components/listing/types/tone.vue create mode 100644 packages/default-theme/components/listing/types/width.vue create mode 100644 packages/helpers/__tests__/listing/getListingFilters.spec.ts create mode 100644 packages/helpers/src/listing/getListingFilters.ts create mode 100644 packages/shopware-6-client/__tests__/services/PageService/getCmsPage.spec.ts create mode 100644 packages/shopware-6-client/__tests__/services/ProductService/getCategoryProducts.spec.ts create mode 100644 packages/shopware-6-client/__tests__/services/SearchService/searchProducts.spec.ts create mode 100644 packages/shopware-6-client/__tests__/services/SearchService/searchSuggestedProducts.spec.ts diff --git a/api/composables.api.md b/api/composables.api.md index 033e2d642..1520f5a5a 100644 --- a/api/composables.api.md +++ b/api/composables.api.md @@ -22,7 +22,9 @@ import { CustomerUpdateProfileParam } from '@shopware-pwa/shopware-6-client'; import { EqualsFilter } from '@shopware-pwa/commons/interfaces/search/SearchFilter'; import { GuestOrderParams } from '@shopware-pwa/commons/interfaces/request/GuestOrderParams'; import { Includes } from '@shopware-pwa/commons/interfaces/search/SearchCriteria'; +import { IUseListing as IUseListing_2 } from '@shopware-pwa/composables'; import { LineItem } from '@shopware-pwa/commons/interfaces/models/checkout/cart/line-item/LineItem'; +import { ListingFilter } from '@shopware-pwa/helpers'; import { NavigationElement } from '@shopware-pwa/commons/interfaces/models/content/navigation/Navigation'; import { Order } from '@shopware-pwa/commons/interfaces/models/checkout/order/Order'; import { PaymentMethod } from '@shopware-pwa/commons/interfaces/models/checkout/payment/PaymentMethod'; @@ -35,6 +37,7 @@ import { SessionContext } from '@shopware-pwa/commons/interfaces/response/Sessio import { ShippingAddress } from '@shopware-pwa/commons/interfaces/request/GuestOrderParams'; import { ShippingMethod } from '@shopware-pwa/commons/interfaces/models/checkout/shipping/ShippingMethod'; import { ShopwareApiInstance } from '@shopware-pwa/shopware-6-client'; +import { ShopwareSearchParams } from '@shopware-pwa/commons/interfaces/search/SearchCriteria'; import { Sort } from '@shopware-pwa/commons/interfaces/search/SearchCriteria'; import { VueConstructor } from 'vue'; @@ -47,6 +50,8 @@ export interface ApplicationVueContext extends VueConstructor { // (undocumented) $interceptors?: any; // (undocumented) + $route?: any; + // (undocumented) $router?: any; // (undocumented) $shopwareApiInstance?: ShopwareApiInstance; @@ -61,6 +66,8 @@ export interface ApplicationVueContext extends VueConstructor { // (undocumented) interceptors?: any; // (undocumented) + route?: any; + // (undocumented) router?: any; // (undocumented) shopwareApiInstance?: ShopwareApiInstance; @@ -97,6 +104,14 @@ export function createCheckoutStep({ stepNumber, stepFields, stepDataUpdated, }: stepDataUpdated: (updatedData: CheckoutStepFields, guestOrderParams: Ref>>) => Partial; }): (rootContext: ApplicationVueContext) => CreateCheckoutStep; +// @beta +export function createListingComposable({ rootContext, searchMethod, searchDefaults, listingKey, }: { + rootContext: ApplicationVueContext_2; + searchMethod: (searchParams: Partial) => Promise; + searchDefaults: ShopwareSearchParams; + listingKey: string; +}): IUseListing; + // @beta (undocumented) export interface CurrentPagination { // (undocumented) @@ -112,6 +127,7 @@ export function getApplicationContext(rootContext: ApplicationVueContext, key?: apiInstance: ShopwareApiInstance | undefined; vuexStore: any; router: any; + route: any; i18n: any; cookies: any; shopwareDefaults: any; @@ -214,6 +230,53 @@ export interface IUseIntercept { intercept: (broadcastKey: string, method: Function) => void; } +// @beta +export interface IUseListing { + // (undocumented) + changeCurrentPage: (pageNumber?: number | string) => Promise; + // (undocumented) + changeCurrentSortingOrder: (order: string | string[]) => Promise; + // (undocumented) + getAvailableFilters: ComputedRef; + // (undocumented) + getCurrentFilters: ComputedRef; + // (undocumented) + getCurrentListing: ComputedRef; + // (undocumented) + getCurrentPage: ComputedRef; + // (undocumented) + getCurrentSortingOrder: ComputedRef; + // (undocumented) + getElements: ComputedRef; + // (undocumented) + getInitialListing: ComputedRef; + // (undocumented) + getLimit: ComputedRef; + // (undocumented) + getSortingOrders: ComputedRef<{ + key: string; + label: string; + }>; + // (undocumented) + getTotal: ComputedRef; + // (undocumented) + getTotalPagesCount: ComputedRef; + // (undocumented) + initSearch: (criteria: Partial) => Promise; + // (undocumented) + loading: ComputedRef; + // (undocumented) + loadingMore: ComputedRef; + // (undocumented) + loadMore: () => Promise; + // (undocumented) + search: (criteria: Partial, options?: { + preventRouteChange?: boolean; + }) => Promise; + // (undocumented) + setInitialListing: (initialListing: Partial) => void; +} + // @beta export interface IUseNavigation { // (undocumented) @@ -226,6 +289,22 @@ export interface IUseNavigation { routes: Ref>; } +// @beta (undocumented) +export interface IUseProductQuickSearch { + // (undocumented) + getProducts: ComputedRef; + // (undocumented) + getTotal: ComputedRef; + // (undocumented) + loading: ComputedRef; + // (undocumented) + loadMore: () => Promise; + // (undocumented) + search: (additionalCriteria?: Partial) => Promise; + // (undocumented) + searchTerm: Ref; +} + // @beta export interface IUseSessionContext { // (undocumented) @@ -305,6 +384,9 @@ export interface IUseUser { user: Ref; } +// @beta (undocumented) +export type listingKey = "productSearchListing" | "categoryListing"; + // @beta (undocumented) interface Notification_2 { // (undocumented) @@ -326,7 +408,7 @@ export const useAddToCart: (rootContext: ApplicationVueContext, product: Product // @beta export const useCart: (rootContext: ApplicationVueContext) => IUseCart; -// @alpha (undocumented) +// @beta @deprecated (undocumented) export const useCategoryFilters: (rootContext: ApplicationVueContext) => any; // @beta @@ -373,11 +455,15 @@ export const useCurrency: (rootContext: ApplicationVueContext) => UseCurrency; export const useDefaults: (rootContext: ApplicationVueContext, defaultsKey: string) => { getIncludesConfig: () => Includes; getAssociationsConfig: () => Association[]; + getDefaults: () => ShopwareSearchParams; }; // @beta export const useIntercept: (rootContext: ApplicationVueContext_2) => IUseIntercept; +// @beta (undocumented) +export const useListing: (rootContext: ApplicationVueContext_2, listingKey?: listingKey) => IUseListing_2; + // @beta export const useNavigation: (rootContext: ApplicationVueContext) => IUseNavigation; @@ -409,7 +495,7 @@ export interface UseProduct { // @alpha (undocumented) export const useProduct: (rootContext: ApplicationVueContext, loadedProduct?: any) => UseProduct; -// @alpha (undocumented) +// @beta @deprecated (undocumented) export interface UseProductListing { // (undocumented) [x: string]: any; @@ -419,10 +505,13 @@ export interface UseProductListing { loading: Ref; } -// @alpha (undocumented) +// @beta @deprecated (undocumented) export const useProductListing: (rootContext: ApplicationVueContext, initialListing?: ProductListingResult | undefined) => UseProductListing; -// @alpha (undocumented) +// @beta (undocumented) +export const useProductQuickSearch: (rootContext: ApplicationVueContext_2) => IUseProductQuickSearch; + +// @alpha @deprecated (undocumented) export interface UseProductSearch { // (undocumented) availableFilters: Readonly>; @@ -456,7 +545,7 @@ export interface UseProductSearch { toggleFilter: (filter: EqualsFilter | RangeFilter, forceSave: boolean) => void; } -// @alpha (undocumented) +// @alpha @deprecated (undocumented) export const useProductSearch: (rootContext: ApplicationVueContext) => UseProductSearch; // @alpha (undocumented) diff --git a/api/helpers.api.md b/api/helpers.api.md index bdc558c65..2126765e9 100644 --- a/api/helpers.api.md +++ b/api/helpers.api.md @@ -100,6 +100,9 @@ export const getFilterSearchCriteria: (selectedFilters: any) => any[]; // @beta (undocumented) export function getListingAvailableFilters(aggregations: Aggregations | undefined | null): UiCategoryFilter[]; +// @beta (undocumented) +export function getListingFilters(aggregations: Aggregations | undefined | null): ListingFilter[]; + // @alpha export function getMessagesFromErrorsArray(errors: ShopwareError[]): string[]; @@ -192,6 +195,16 @@ export function isProductSimple({ product, }?: { product?: Product; }): boolean; +// @beta (undocumented) +export interface ListingFilter { + // (undocumented) + [key: string]: any; + // (undocumented) + code: string; + // (undocumented) + label: string; +} + // @beta export function loadScript(src: string): Promise; diff --git a/api/shopware-6-client.api.md b/api/shopware-6-client.api.md index 4ed4a34d3..df63ed828 100644 --- a/api/shopware-6-client.api.md +++ b/api/shopware-6-client.api.md @@ -27,6 +27,7 @@ import { SearchCriteria } from '@shopware-pwa/commons/interfaces/search/SearchCr import { SearchResult } from '@shopware-pwa/commons/interfaces/response/SearchResult'; import { SessionContext } from '@shopware-pwa/commons/interfaces/response/SessionContext'; import { ShippingMethod } from '@shopware-pwa/commons/interfaces/models/checkout/shipping/ShippingMethod'; +import { ShopwareSearchParams } from '@shopware-pwa/commons/interfaces/search/SearchCriteria'; import { StoreNavigationElement } from '@shopware-pwa/commons/interfaces/models/content/navigation/Navigation'; // @beta @deprecated @@ -175,9 +176,15 @@ export function getCategories(searchCriteria?: SearchCriteria, contextInstance?: // @alpha (undocumented) export function getCategory(categoryId: string, contextInstance?: ShopwareApiInstance): Promise; +// @beta +export const getCategoryProducts: (categoryId: string, criteria?: ShopwareSearchParams | undefined, contextInstance?: ShopwareApiInstance) => Promise; + // @alpha export const getCategoryProductsListing: (categoryId: string, searchCriteria?: SearchCriteria | undefined, contextInstance?: ShopwareApiInstance) => Promise; +// @alpha (undocumented) +export function getCmsPage(path: string, criteria?: ShopwareSearchParams, contextInstance?: ShopwareApiInstance): Promise>; + // @beta export function getCustomer(contextInstance?: ShopwareApiInstance): Promise; @@ -265,7 +272,7 @@ export function getStoreOrderPaymentUrl(orderId: string, contextInstance?: Shopw apiAlias: string; }>; -// @beta (undocumented) +// @alpha @deprecated (undocumented) export function getSuggestedResults(term: string, searchCriteria?: SearchCriteria, contextInstance?: ShopwareApiInstance): Promise; // @alpha (undocumented) @@ -373,6 +380,12 @@ export function removeCartItem(itemId: string, contextInstance?: ShopwareApiInst // @alpha export function resetPassword(params: CustomerResetPasswordParam, contextInstance?: ShopwareApiInstance): Promise; +// @beta +export function searchProducts(criteria?: ShopwareSearchParams, contextInstance?: ShopwareApiInstance): Promise; + +// @beta +export function searchSuggestedProducts(criteria?: ShopwareSearchParams, contextInstance?: ShopwareApiInstance): Promise; + // @beta (undocumented) export function sendContactForm(params: ContactFormData, contextInstance?: ShopwareApiInstance): Promise; diff --git a/docs/landing/getting-started/upgrade.md b/docs/landing/getting-started/upgrade.md index 4d5e20a89..7fd627a35 100644 --- a/docs/landing/getting-started/upgrade.md +++ b/docs/landing/getting-started/upgrade.md @@ -16,6 +16,16 @@ We want Shopware PWA to be in sync with the latest endpoints of Shopware, to be **MIGRATION STEP**: we simplified project upgrade process. Now you can remove `@shopware-pwa/*` dependencies repm your `package.json` file and leave only `@shopware-pwa/nuxt-module`. Thanks to this you change version only in a single place. +**FEATURE**: new composable `useListing` has been created. It uses `createListingComposable` factory, which you can use as well is `useListing` won't be enough for your needs. It allows you to have listing for all types of listings from shopware, like products, orders etc. It supports SSR and returns composable with a common listing interface. + +**DEPRECATION**: composable `useCategoryFilters` is now deprecated - use `useListing` instead + +**DEPRECATION**: composable `useProductListing` is now deprecated - use `useListing` instead + +**DEPRECATION**: composable `useProductSearch` is now deprecated - use `useProductQuickSearch` instead + +**DEPRECATION**: API client method `getSuggestedResults` is now deprecated - use `searchSuggestedProducts` instead + ## Migrate version 0.3.x to 0.4.x All changes are documented in our [Changelog](https://github.com/DivanteLtd/shopware-pwa/blob/master/CHANGELOG.md) diff --git a/docs/landing/resources/api/composables.applicationvuecontext._route.md b/docs/landing/resources/api/composables.applicationvuecontext._route.md new file mode 100644 index 000000000..f02b6e99a --- /dev/null +++ b/docs/landing/resources/api/composables.applicationvuecontext._route.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [ApplicationVueContext](./composables.applicationvuecontext.md) > [$route](./composables.applicationvuecontext._route.md) + +## ApplicationVueContext.$route property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +$route?: any; +``` diff --git a/docs/landing/resources/api/composables.applicationvuecontext.md b/docs/landing/resources/api/composables.applicationvuecontext.md index fb43518fd..443fb9ead 100644 --- a/docs/landing/resources/api/composables.applicationvuecontext.md +++ b/docs/landing/resources/api/composables.applicationvuecontext.md @@ -23,6 +23,7 @@ export interface ApplicationVueContext extends VueConstructor | [$cookies](./composables.applicationvuecontext._cookies.md) | any | (BETA) | | [$i18n](./composables.applicationvuecontext._i18n.md) | any | (BETA) | | [$interceptors](./composables.applicationvuecontext._interceptors.md) | any | (BETA) | +| [$route](./composables.applicationvuecontext._route.md) | any | (BETA) | | [$router](./composables.applicationvuecontext._router.md) | any | (BETA) | | [$shopwareApiInstance](./composables.applicationvuecontext._shopwareapiinstance.md) | ShopwareApiInstance | (BETA) | | [$shopwareDefaults](./composables.applicationvuecontext._shopwaredefaults.md) | any | (BETA) | @@ -30,6 +31,7 @@ export interface ApplicationVueContext extends VueConstructor | [cookies](./composables.applicationvuecontext.cookies.md) | any | (BETA) | | [i18n](./composables.applicationvuecontext.i18n.md) | any | (BETA) | | [interceptors](./composables.applicationvuecontext.interceptors.md) | any | (BETA) | +| [route](./composables.applicationvuecontext.route.md) | any | (BETA) | | [router](./composables.applicationvuecontext.router.md) | any | (BETA) | | [shopwareApiInstance](./composables.applicationvuecontext.shopwareapiinstance.md) | ShopwareApiInstance | (BETA) | | [shopwareDefaults](./composables.applicationvuecontext.shopwaredefaults.md) | any | (BETA) | diff --git a/docs/landing/resources/api/composables.applicationvuecontext.route.md b/docs/landing/resources/api/composables.applicationvuecontext.route.md new file mode 100644 index 000000000..67c77e0ec --- /dev/null +++ b/docs/landing/resources/api/composables.applicationvuecontext.route.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [ApplicationVueContext](./composables.applicationvuecontext.md) > [route](./composables.applicationvuecontext.route.md) + +## ApplicationVueContext.route property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +route?: any; +``` diff --git a/docs/landing/resources/api/composables.createlistingcomposable.md b/docs/landing/resources/api/composables.createlistingcomposable.md new file mode 100644 index 000000000..a45cfc8eb --- /dev/null +++ b/docs/landing/resources/api/composables.createlistingcomposable.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [createListingComposable](./composables.createlistingcomposable.md) + +## createListingComposable() function + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Factory to create your own listing. By default you can use useListing composable, which provides you predefined listings for category(cms) listing and product search listing. Using factory you can provide our own compatible search method and use it for example for creating listing of orders in my account. + +Signature: + +```typescript +export declare function createListingComposable({ rootContext, searchMethod, searchDefaults, listingKey, }: { + rootContext: ApplicationVueContext; + searchMethod: (searchParams: Partial) => Promise; + searchDefaults: ShopwareSearchParams; + listingKey: string; +}): IUseListing; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { rootContext, searchMethod, searchDefaults, listingKey, } | { rootContext: [ApplicationVueContext](./composables.applicationvuecontext.md); searchMethod: (searchParams: Partial<ShopwareSearchParams>) => Promise<ProductListingResult>; searchDefaults: ShopwareSearchParams; listingKey: string; } | | + +Returns: + +[IUseListing](./composables.iuselisting.md)<ELEMENTS\_TYPE> + diff --git a/docs/landing/resources/api/composables.getapplicationcontext.md b/docs/landing/resources/api/composables.getapplicationcontext.md index 32f7a177c..8815ab05f 100644 --- a/docs/landing/resources/api/composables.getapplicationcontext.md +++ b/docs/landing/resources/api/composables.getapplicationcontext.md @@ -15,6 +15,7 @@ export declare function getApplicationContext(rootContext: ApplicationVueContext apiInstance: ShopwareApiInstance | undefined; vuexStore: any; router: any; + route: any; i18n: any; cookies: any; shopwareDefaults: any; @@ -32,5 +33,5 @@ export declare function getApplicationContext(rootContext: ApplicationVueContext Returns: -{ apiInstance: ShopwareApiInstance \| undefined; vuexStore: any; router: any; i18n: any; cookies: any; shopwareDefaults: any; interceptors: any; contextName: string; } +{ apiInstance: ShopwareApiInstance \| undefined; vuexStore: any; router: any; route: any; i18n: any; cookies: any; shopwareDefaults: any; interceptors: any; contextName: string; } diff --git a/docs/landing/resources/api/composables.iuselisting.changecurrentpage.md b/docs/landing/resources/api/composables.iuselisting.changecurrentpage.md new file mode 100644 index 000000000..7b4982b37 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.changecurrentpage.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [changeCurrentPage](./composables.iuselisting.changecurrentpage.md) + +## IUseListing.changeCurrentPage property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +changeCurrentPage: (pageNumber?: number | string) => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.changecurrentsortingorder.md b/docs/landing/resources/api/composables.iuselisting.changecurrentsortingorder.md new file mode 100644 index 000000000..6e54fcedc --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.changecurrentsortingorder.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [changeCurrentSortingOrder](./composables.iuselisting.changecurrentsortingorder.md) + +## IUseListing.changeCurrentSortingOrder property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +changeCurrentSortingOrder: (order: string | string[]) => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getavailablefilters.md b/docs/landing/resources/api/composables.iuselisting.getavailablefilters.md new file mode 100644 index 000000000..890f3a4ed --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getavailablefilters.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getAvailableFilters](./composables.iuselisting.getavailablefilters.md) + +## IUseListing.getAvailableFilters property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getAvailableFilters: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getcurrentfilters.md b/docs/landing/resources/api/composables.iuselisting.getcurrentfilters.md new file mode 100644 index 000000000..461d82d91 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getcurrentfilters.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getCurrentFilters](./composables.iuselisting.getcurrentfilters.md) + +## IUseListing.getCurrentFilters property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getCurrentFilters: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getcurrentlisting.md b/docs/landing/resources/api/composables.iuselisting.getcurrentlisting.md new file mode 100644 index 000000000..5ba5db22b --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getcurrentlisting.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getCurrentListing](./composables.iuselisting.getcurrentlisting.md) + +## IUseListing.getCurrentListing property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getCurrentListing: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getcurrentpage.md b/docs/landing/resources/api/composables.iuselisting.getcurrentpage.md new file mode 100644 index 000000000..f05f915d0 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getcurrentpage.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getCurrentPage](./composables.iuselisting.getcurrentpage.md) + +## IUseListing.getCurrentPage property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getCurrentPage: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getcurrentsortingorder.md b/docs/landing/resources/api/composables.iuselisting.getcurrentsortingorder.md new file mode 100644 index 000000000..e11cf1eb2 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getcurrentsortingorder.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getCurrentSortingOrder](./composables.iuselisting.getcurrentsortingorder.md) + +## IUseListing.getCurrentSortingOrder property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getCurrentSortingOrder: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getelements.md b/docs/landing/resources/api/composables.iuselisting.getelements.md new file mode 100644 index 000000000..bd99469c4 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getelements.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getElements](./composables.iuselisting.getelements.md) + +## IUseListing.getElements property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getElements: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getinitiallisting.md b/docs/landing/resources/api/composables.iuselisting.getinitiallisting.md new file mode 100644 index 000000000..48b77d1df --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getinitiallisting.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getInitialListing](./composables.iuselisting.getinitiallisting.md) + +## IUseListing.getInitialListing property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getInitialListing: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getlimit.md b/docs/landing/resources/api/composables.iuselisting.getlimit.md new file mode 100644 index 000000000..f815c7ff8 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getlimit.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getLimit](./composables.iuselisting.getlimit.md) + +## IUseListing.getLimit property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getLimit: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.getsortingorders.md b/docs/landing/resources/api/composables.iuselisting.getsortingorders.md new file mode 100644 index 000000000..f4541a471 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.getsortingorders.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getSortingOrders](./composables.iuselisting.getsortingorders.md) + +## IUseListing.getSortingOrders property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getSortingOrders: ComputedRef<{ + key: string; + label: string; + }>; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.gettotal.md b/docs/landing/resources/api/composables.iuselisting.gettotal.md new file mode 100644 index 000000000..2cd0d4433 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.gettotal.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getTotal](./composables.iuselisting.gettotal.md) + +## IUseListing.getTotal property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getTotal: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.gettotalpagescount.md b/docs/landing/resources/api/composables.iuselisting.gettotalpagescount.md new file mode 100644 index 000000000..4e1da2dab --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.gettotalpagescount.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [getTotalPagesCount](./composables.iuselisting.gettotalpagescount.md) + +## IUseListing.getTotalPagesCount property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getTotalPagesCount: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.initsearch.md b/docs/landing/resources/api/composables.iuselisting.initsearch.md new file mode 100644 index 000000000..7eb86630a --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.initsearch.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [initSearch](./composables.iuselisting.initsearch.md) + +## IUseListing.initSearch property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +initSearch: (criteria: Partial) => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.loading.md b/docs/landing/resources/api/composables.iuselisting.loading.md new file mode 100644 index 000000000..e51825c36 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.loading.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [loading](./composables.iuselisting.loading.md) + +## IUseListing.loading property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loading: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.loadingmore.md b/docs/landing/resources/api/composables.iuselisting.loadingmore.md new file mode 100644 index 000000000..ef2505f84 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.loadingmore.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [loadingMore](./composables.iuselisting.loadingmore.md) + +## IUseListing.loadingMore property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loadingMore: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.loadmore.md b/docs/landing/resources/api/composables.iuselisting.loadmore.md new file mode 100644 index 000000000..79c1d0a14 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.loadmore.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [loadMore](./composables.iuselisting.loadmore.md) + +## IUseListing.loadMore property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loadMore: () => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.md b/docs/landing/resources/api/composables.iuselisting.md new file mode 100644 index 000000000..243a325a9 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.md @@ -0,0 +1,41 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) + +## IUseListing interface + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Listing interface, can be used to display category products, search products or any other Shopware search interface (ex. orders with pagination) + +Signature: + +```typescript +export interface IUseListing +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [changeCurrentPage](./composables.iuselisting.changecurrentpage.md) | (pageNumber?: number \| string) => Promise<void> | (BETA) | +| [changeCurrentSortingOrder](./composables.iuselisting.changecurrentsortingorder.md) | (order: string \| string\[\]) => Promise<void> | (BETA) | +| [getAvailableFilters](./composables.iuselisting.getavailablefilters.md) | ComputedRef<ListingFilter\[\]> | (BETA) | +| [getCurrentFilters](./composables.iuselisting.getcurrentfilters.md) | ComputedRef<any> | (BETA) | +| [getCurrentListing](./composables.iuselisting.getcurrentlisting.md) | ComputedRef<ProductListingResult> | (BETA) | +| [getCurrentPage](./composables.iuselisting.getcurrentpage.md) | ComputedRef<string \| number> | (BETA) | +| [getCurrentSortingOrder](./composables.iuselisting.getcurrentsortingorder.md) | ComputedRef<string> | (BETA) | +| [getElements](./composables.iuselisting.getelements.md) | ComputedRef<ELEMENTS\_TYPE\[\]> | (BETA) | +| [getInitialListing](./composables.iuselisting.getinitiallisting.md) | ComputedRef<ProductListingResult> | (BETA) | +| [getLimit](./composables.iuselisting.getlimit.md) | ComputedRef<number> | (BETA) | +| [getSortingOrders](./composables.iuselisting.getsortingorders.md) | ComputedRef<{ key: string; label: string; }> | (BETA) | +| [getTotal](./composables.iuselisting.gettotal.md) | ComputedRef<number> | (BETA) | +| [getTotalPagesCount](./composables.iuselisting.gettotalpagescount.md) | ComputedRef<number> | (BETA) | +| [initSearch](./composables.iuselisting.initsearch.md) | (criteria: Partial<ShopwareSearchParams>) => Promise<void> | (BETA) | +| [loading](./composables.iuselisting.loading.md) | ComputedRef<boolean> | (BETA) | +| [loadingMore](./composables.iuselisting.loadingmore.md) | ComputedRef<boolean> | (BETA) | +| [loadMore](./composables.iuselisting.loadmore.md) | () => Promise<void> | (BETA) | +| [search](./composables.iuselisting.search.md) | (criteria: Partial<ShopwareSearchParams>, options?: { preventRouteChange?: boolean; }) => Promise<void> | (BETA) | +| [setInitialListing](./composables.iuselisting.setinitiallisting.md) | (initialListing: Partial<ProductListingResult>) => void | (BETA) | + diff --git a/docs/landing/resources/api/composables.iuselisting.search.md b/docs/landing/resources/api/composables.iuselisting.search.md new file mode 100644 index 000000000..033a81145 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.search.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [search](./composables.iuselisting.search.md) + +## IUseListing.search property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +search: (criteria: Partial, options?: { + preventRouteChange?: boolean; + }) => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuselisting.setinitiallisting.md b/docs/landing/resources/api/composables.iuselisting.setinitiallisting.md new file mode 100644 index 000000000..9ce200c55 --- /dev/null +++ b/docs/landing/resources/api/composables.iuselisting.setinitiallisting.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseListing](./composables.iuselisting.md) > [setInitialListing](./composables.iuselisting.setinitiallisting.md) + +## IUseListing.setInitialListing property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +setInitialListing: (initialListing: Partial) => void; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.getproducts.md b/docs/landing/resources/api/composables.iuseproductquicksearch.getproducts.md new file mode 100644 index 000000000..8250bdc74 --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.getproducts.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [getProducts](./composables.iuseproductquicksearch.getproducts.md) + +## IUseProductQuickSearch.getProducts property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getProducts: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.gettotal.md b/docs/landing/resources/api/composables.iuseproductquicksearch.gettotal.md new file mode 100644 index 000000000..acfc530b9 --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.gettotal.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [getTotal](./composables.iuseproductquicksearch.gettotal.md) + +## IUseProductQuickSearch.getTotal property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +getTotal: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.loading.md b/docs/landing/resources/api/composables.iuseproductquicksearch.loading.md new file mode 100644 index 000000000..1ea320daa --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.loading.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [loading](./composables.iuseproductquicksearch.loading.md) + +## IUseProductQuickSearch.loading property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loading: ComputedRef; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.loadmore.md b/docs/landing/resources/api/composables.iuseproductquicksearch.loadmore.md new file mode 100644 index 000000000..c4b7e0f22 --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.loadmore.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [loadMore](./composables.iuseproductquicksearch.loadmore.md) + +## IUseProductQuickSearch.loadMore property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loadMore: () => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.md b/docs/landing/resources/api/composables.iuseproductquicksearch.md new file mode 100644 index 000000000..f352b62ae --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) + +## IUseProductQuickSearch interface + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export interface IUseProductQuickSearch +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getProducts](./composables.iuseproductquicksearch.getproducts.md) | ComputedRef<Product\[\]> | (BETA) | +| [getTotal](./composables.iuseproductquicksearch.gettotal.md) | ComputedRef<number> | (BETA) | +| [loading](./composables.iuseproductquicksearch.loading.md) | ComputedRef<boolean> | (BETA) | +| [loadMore](./composables.iuseproductquicksearch.loadmore.md) | () => Promise<void> | (BETA) | +| [search](./composables.iuseproductquicksearch.search.md) | (additionalCriteria?: Partial<ShopwareSearchParams>) => Promise<void> | (BETA) | +| [searchTerm](./composables.iuseproductquicksearch.searchterm.md) | Ref<string> | (BETA) | + diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.search.md b/docs/landing/resources/api/composables.iuseproductquicksearch.search.md new file mode 100644 index 000000000..cb9574d9b --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.search.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [search](./composables.iuseproductquicksearch.search.md) + +## IUseProductQuickSearch.search property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +search: (additionalCriteria?: Partial) => Promise; +``` diff --git a/docs/landing/resources/api/composables.iuseproductquicksearch.searchterm.md b/docs/landing/resources/api/composables.iuseproductquicksearch.searchterm.md new file mode 100644 index 000000000..ee9542983 --- /dev/null +++ b/docs/landing/resources/api/composables.iuseproductquicksearch.searchterm.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) > [searchTerm](./composables.iuseproductquicksearch.searchterm.md) + +## IUseProductQuickSearch.searchTerm property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +searchTerm: Ref; +``` diff --git a/docs/landing/resources/api/composables.listingkey.md b/docs/landing/resources/api/composables.listingkey.md new file mode 100644 index 000000000..f45d0d980 --- /dev/null +++ b/docs/landing/resources/api/composables.listingkey.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [listingKey](./composables.listingkey.md) + +## listingKey type + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export declare type listingKey = "productSearchListing" | "categoryListing"; +``` diff --git a/docs/landing/resources/api/composables.md b/docs/landing/resources/api/composables.md index b4fae03d2..627fb3118 100644 --- a/docs/landing/resources/api/composables.md +++ b/docs/landing/resources/api/composables.md @@ -8,6 +8,7 @@ | Function | Description | | --- | --- | +| [createListingComposable({ rootContext, searchMethod, searchDefaults, listingKey, })](./composables.createlistingcomposable.md) | (BETA) Factory to create your own listing. By default you can use useListing composable, which provides you predefined listings for category(cms) listing and product search listing. Using factory you can provide our own compatible search method and use it for example for creating listing of orders in my account. | | [getApplicationContext(rootContext, key)](./composables.getapplicationcontext.md) | (BETA) | | [getDefaultApiParams()](./composables.getdefaultapiparams.md) | (BETA) Returns default system API params | @@ -21,10 +22,13 @@ | [IUseCart](./composables.iusecart.md) | (BETA) interface for [useCart](./composables.usecart.md) composable | | [IUseCheckout](./composables.iusecheckout.md) | (BETA) interface for [useCheckout](./composables.usecheckout.md) composable | | [IUseIntercept](./composables.iuseintercept.md) | (BETA) interface for [useIntercept](./composables.useintercept.md) composable | +| [IUseListing](./composables.iuselisting.md) | (BETA) Listing interface, can be used to display category products, search products or any other Shopware search interface (ex. orders with pagination) | | [IUseNavigation](./composables.iusenavigation.md) | (BETA) interface for [useNavigation](./composables.usenavigation.md) composable | +| [IUseProductQuickSearch](./composables.iuseproductquicksearch.md) | (BETA) | | [IUseSessionContext](./composables.iusesessioncontext.md) | (BETA) interface for [useSessionContext](./composables.usesessioncontext.md) composable | | [IUseUser](./composables.iuseuser.md) | (BETA) interface for [useUser](./composables.useuser.md) composable | | [Notification\_2](./composables.notification_2.md) | (BETA) | +| [UseProductListing](./composables.useproductlisting.md) | (BETA) | ## Variables @@ -33,12 +37,22 @@ | [INTERCEPTOR\_KEYS](./composables.interceptor_keys.md) | (BETA) Keys used accross composables with the description of incommint parameters. | | [useAddToCart](./composables.useaddtocart.md) | (BETA) Add product to cart. Options - [IUseAddToCart](./composables.iuseaddtocart.md) | | [useCart](./composables.usecart.md) | (BETA) Composable for cart management. Options - [IUseCart](./composables.iusecart.md) | +| [useCategoryFilters](./composables.usecategoryfilters.md) | (BETA) | | [useCheckout](./composables.usecheckout.md) | (BETA) Composable for Checkout management. Options - [IUseCheckout](./composables.iusecheckout.md) | | [useDefaults](./composables.usedefaults.md) | (BETA) Returns default config depending on config key. It is used in composables, so defaultsKey is in most cases composable name (ex. useDefaults(rootContext, "useCms")) | | [useIntercept](./composables.useintercept.md) | (BETA) Allows to broadcast and intercept events across application. | +| [useListing](./composables.uselisting.md) | (BETA) | | [useNavigation](./composables.usenavigation.md) | (BETA) Composable for navigation. Options - [IUseNavigation](./composables.iusenavigation.md) | | [useNotifications](./composables.usenotifications.md) | (BETA) | +| [useProductListing](./composables.useproductlisting.md) | (BETA) | +| [useProductQuickSearch](./composables.useproductquicksearch.md) | (BETA) | | [useSessionContext](./composables.usesessioncontext.md) | (BETA) Composable for session management. Options - [IUseSessionContext](./composables.iusesessioncontext.md) | | [useUIState](./composables.useuistate.md) | (BETA) Simple state management for UI purposes. | | [useUser](./composables.useuser.md) | (BETA) Composable for user management. Options - [IUseUser](./composables.iuseuser.md) | +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [listingKey](./composables.listingkey.md) | (BETA) | + diff --git a/docs/landing/resources/api/composables.usecategoryfilters.md b/docs/landing/resources/api/composables.usecategoryfilters.md new file mode 100644 index 000000000..5d2472ef7 --- /dev/null +++ b/docs/landing/resources/api/composables.usecategoryfilters.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [useCategoryFilters](./composables.usecategoryfilters.md) + +## useCategoryFilters variable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +> Warning: This API is now obsolete. +> +> please see useListing instead +> + +Signature: + +```typescript +useCategoryFilters: (rootContext: ApplicationVueContext) => any +``` diff --git a/docs/landing/resources/api/composables.usedefaults.md b/docs/landing/resources/api/composables.usedefaults.md index a2421e568..eec0e884d 100644 --- a/docs/landing/resources/api/composables.usedefaults.md +++ b/docs/landing/resources/api/composables.usedefaults.md @@ -15,12 +15,13 @@ Returns default config depending on config key. It is used in composables, so de useDefaults: (rootContext: ApplicationVueContext, defaultsKey: string) => { getIncludesConfig: () => Includes; getAssociationsConfig: () => Association[]; + getDefaults: () => ShopwareSearchParams; } ``` ## Remarks -To extend defaults you need to add configuration to `shopware-pwa.config.js` file. Let's say we want to have a product manufacturer and media associations on CMS pages. We need to add to configuration file: +To extend defaults you need to add configuration to `shopware-pwa.config.js` file. Let's say we want to have a product manufacturer, media associations and listing limit on CMS pages. We need to add to configuration file: ```js // inside shopware-pwa.config.js @@ -29,6 +30,7 @@ module.exports = { // ... other settings apiDefaults: { useCms: { + limit: 8, includes: { product: ["manufacturer"] }, diff --git a/docs/landing/resources/api/composables.uselisting.md b/docs/landing/resources/api/composables.uselisting.md new file mode 100644 index 000000000..40088c13b --- /dev/null +++ b/docs/landing/resources/api/composables.uselisting.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [useListing](./composables.uselisting.md) + +## useListing variable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +useListing: (rootContext: ApplicationVueContext, listingKey?: listingKey) => IUseListing +``` diff --git a/docs/landing/resources/api/composables.useproductlisting.error.md b/docs/landing/resources/api/composables.useproductlisting.error.md new file mode 100644 index 000000000..017ffcb1d --- /dev/null +++ b/docs/landing/resources/api/composables.useproductlisting.error.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [UseProductListing](./composables.useproductlisting.md) > [error](./composables.useproductlisting.error.md) + +## UseProductListing.error property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +error: Ref; +``` diff --git a/docs/landing/resources/api/composables.useproductlisting.loading.md b/docs/landing/resources/api/composables.useproductlisting.loading.md new file mode 100644 index 000000000..3110e0082 --- /dev/null +++ b/docs/landing/resources/api/composables.useproductlisting.loading.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [UseProductListing](./composables.useproductlisting.md) > [loading](./composables.useproductlisting.loading.md) + +## UseProductListing.loading property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +loading: Ref; +``` diff --git a/docs/landing/resources/api/composables.useproductlisting.md b/docs/landing/resources/api/composables.useproductlisting.md new file mode 100644 index 000000000..3b513e5db --- /dev/null +++ b/docs/landing/resources/api/composables.useproductlisting.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [useProductListing](./composables.useproductlisting.md) + +## useProductListing variable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +> Warning: This API is now obsolete. +> +> please see useListing instead +> + +Signature: + +```typescript +useProductListing: (rootContext: ApplicationVueContext, initialListing?: ProductListingResult | undefined) => UseProductListing +``` diff --git a/docs/landing/resources/api/composables.useproductquicksearch.md b/docs/landing/resources/api/composables.useproductquicksearch.md new file mode 100644 index 000000000..c6c3989be --- /dev/null +++ b/docs/landing/resources/api/composables.useproductquicksearch.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [useProductQuickSearch](./composables.useproductquicksearch.md) + +## useProductQuickSearch variable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +useProductQuickSearch: (rootContext: ApplicationVueContext) => IUseProductQuickSearch +``` diff --git a/docs/landing/resources/api/helpers.getlistingfilters.md b/docs/landing/resources/api/helpers.getlistingfilters.md new file mode 100644 index 000000000..3ad8437e4 --- /dev/null +++ b/docs/landing/resources/api/helpers.getlistingfilters.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [@shopware-pwa/helpers](./helpers.md) > [getListingFilters](./helpers.getlistingfilters.md) + +## getListingFilters() function + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export declare function getListingFilters(aggregations: Aggregations | undefined | null): ListingFilter[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| aggregations | Aggregations \| undefined \| null | | + +Returns: + +[ListingFilter](./helpers.listingfilter.md)\[\] + diff --git a/docs/landing/resources/api/helpers.listingfilter.code.md b/docs/landing/resources/api/helpers.listingfilter.code.md new file mode 100644 index 000000000..df875f8b3 --- /dev/null +++ b/docs/landing/resources/api/helpers.listingfilter.code.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/helpers](./helpers.md) > [ListingFilter](./helpers.listingfilter.md) > [code](./helpers.listingfilter.code.md) + +## ListingFilter.code property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +code: string; +``` diff --git a/docs/landing/resources/api/helpers.listingfilter.label.md b/docs/landing/resources/api/helpers.listingfilter.label.md new file mode 100644 index 000000000..660433803 --- /dev/null +++ b/docs/landing/resources/api/helpers.listingfilter.label.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/helpers](./helpers.md) > [ListingFilter](./helpers.listingfilter.md) > [label](./helpers.listingfilter.label.md) + +## ListingFilter.label property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +label: string; +``` diff --git a/docs/landing/resources/api/helpers.listingfilter.md b/docs/landing/resources/api/helpers.listingfilter.md new file mode 100644 index 000000000..f0467f0d9 --- /dev/null +++ b/docs/landing/resources/api/helpers.listingfilter.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [@shopware-pwa/helpers](./helpers.md) > [ListingFilter](./helpers.listingfilter.md) + +## ListingFilter interface + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + + +Signature: + +```typescript +export interface ListingFilter +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [code](./helpers.listingfilter.code.md) | string | (BETA) | +| [label](./helpers.listingfilter.label.md) | string | (BETA) | + diff --git a/docs/landing/resources/api/helpers.md b/docs/landing/resources/api/helpers.md index 12e505e1c..644a72132 100644 --- a/docs/landing/resources/api/helpers.md +++ b/docs/landing/resources/api/helpers.md @@ -15,6 +15,7 @@ | Function | Description | | --- | --- | | [getListingAvailableFilters(aggregations)](./helpers.getlistingavailablefilters.md) | (BETA) | +| [getListingFilters(aggregations)](./helpers.getlistingfilters.md) | (BETA) | | [getProductRegularPrice(product)](./helpers.getproductregularprice.md) | (BETA) Get the price for 1 unit of a product | | [getProductThumbnailUrl(product)](./helpers.getproductthumbnailurl.md) | (BETA) get the thumbnail image URL with the smallest width | | [getProductTierPrices(product)](./helpers.getproducttierprices.md) | (BETA) Get the prices depending on quantity added to cart. Tier prices can be set in Advanced pricing tab in Product view (admin panel) | @@ -26,6 +27,7 @@ | Interface | Description | | --- | --- | +| [ListingFilter](./helpers.listingfilter.md) | (BETA) | | [StoreNavigationRoute](./helpers.storenavigationroute.md) | (BETA) | | [TierPrice](./helpers.tierprice.md) | (BETA) | | [UiCategoryFilter](./helpers.uicategoryfilter.md) | (BETA) | diff --git a/docs/landing/resources/api/shopware-6-client.getcategoryproducts.md b/docs/landing/resources/api/shopware-6-client.getcategoryproducts.md new file mode 100644 index 000000000..ed39ab609 --- /dev/null +++ b/docs/landing/resources/api/shopware-6-client.getcategoryproducts.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [@shopware-pwa/shopware-6-client](./shopware-6-client.md) > [getCategoryProducts](./shopware-6-client.getcategoryproducts.md) + +## getCategoryProducts variable + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Get default amount of products and listing configuration for given category + +Signature: + +```typescript +getCategoryProducts: (categoryId: string, criteria?: ShopwareSearchParams | undefined, contextInstance?: ShopwareApiInstance) => Promise +``` diff --git a/docs/landing/resources/api/shopware-6-client.md b/docs/landing/resources/api/shopware-6-client.md index 0fd5a5ef3..5d2a69b86 100644 --- a/docs/landing/resources/api/shopware-6-client.md +++ b/docs/landing/resources/api/shopware-6-client.md @@ -21,7 +21,6 @@ | [getResults(term, searchCriteria, contextInstance)](./shopware-6-client.getresults.md) | (BETA) | | [getSearchResults(term, searchCriteria, contextInstance)](./shopware-6-client.getsearchresults.md) | (BETA) | | [getStoreNavigation({ requestActiveId, requestRootId, depth, buildTree, searchCriteria, }, contextInstance)](./shopware-6-client.getstorenavigation.md) | (BETA) | -| [getSuggestedResults(term, searchCriteria, contextInstance)](./shopware-6-client.getsuggestedresults.md) | (BETA) | | [invokeGet({ address }, contextInstance)](./shopware-6-client.invokeget.md) | (BETA) Invoke custom GET request to shopware API. Mostly for plugins usage. You can skip domain and pass only endpoint ex. /api/my/endpoint | | [invokePost({ address, payload, }, contextInstance)](./shopware-6-client.invokepost.md) | (BETA) Invoke custom POST request to shopware API. Mostly for plugins usage. You can skip domain and pass only endpoint ex. /api/my/endpoint | | [login({ username, password }, contextInstance)](./shopware-6-client.login.md) | (BETA) Login user to shopware instance. | @@ -29,6 +28,8 @@ | [newsletterSubscribe(params, contextInstance)](./shopware-6-client.newslettersubscribe.md) | (BETA) | | [newsletterUnsubscribe({ email, }, contextInstance)](./shopware-6-client.newsletterunsubscribe.md) | (BETA) | | [removeCartItem(itemId, contextInstance)](./shopware-6-client.removecartitem.md) | (BETA) Deletes the cart line item by id.This method may be used for deleting "product" type item lines as well as "promotion" type item lines. | +| [searchProducts(criteria, contextInstance)](./shopware-6-client.searchproducts.md) | (BETA) Search for products based on criteria. From: Shopware 6.4 | +| [searchSuggestedProducts(criteria, contextInstance)](./shopware-6-client.searchsuggestedproducts.md) | (BETA) Search for suggested products based on criteria. From: Shopware 6.4 | | [sendContactForm(params, contextInstance)](./shopware-6-client.sendcontactform.md) | (BETA) | ## Interfaces @@ -49,6 +50,7 @@ | Variable | Description | | --- | --- | | [config](./shopware-6-client.config.md) | (BETA) | +| [getCategoryProducts](./shopware-6-client.getcategoryproducts.md) | (BETA) Get default amount of products and listing configuration for given category | | [onConfigChange](./shopware-6-client.onconfigchange.md) | (BETA) | | [setup](./shopware-6-client.setup.md) | (BETA) Setup configuration. Merge default values with provided in param. This method will override existing config. For config update invoke \*\*update\*\* method. | | [update](./shopware-6-client.update.md) | (BETA) Update current configuration. This will change only provided values. | diff --git a/docs/landing/resources/api/shopware-6-client.getsuggestedresults.md b/docs/landing/resources/api/shopware-6-client.searchproducts.md similarity index 57% rename from docs/landing/resources/api/shopware-6-client.getsuggestedresults.md rename to docs/landing/resources/api/shopware-6-client.searchproducts.md index 4e0205d49..36b85dd64 100644 --- a/docs/landing/resources/api/shopware-6-client.getsuggestedresults.md +++ b/docs/landing/resources/api/shopware-6-client.searchproducts.md @@ -1,31 +1,28 @@ -[Home](./index.md) > [@shopware-pwa/shopware-6-client](./shopware-6-client.md) > [getSuggestedResults](./shopware-6-client.getsuggestedresults.md) +[Home](./index.md) > [@shopware-pwa/shopware-6-client](./shopware-6-client.md) > [searchProducts](./shopware-6-client.searchproducts.md) -## getSuggestedResults() function +## searchProducts() function > This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. > +Search for products based on criteria. From: Shopware 6.4 + Signature: ```typescript -export declare function getSuggestedResults(term: string, searchCriteria?: SearchCriteria, contextInstance?: ShopwareApiInstance): Promise; +export declare function searchProducts(criteria?: ShopwareSearchParams, contextInstance?: ShopwareApiInstance): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| term | string | | -| searchCriteria | SearchCriteria | | +| criteria | ShopwareSearchParams | | | contextInstance | [ShopwareApiInstance](./shopware-6-client.shopwareapiinstance.md) | | Returns: Promise<ProductListingResult> -## Exceptions - -ClientApiError - diff --git a/docs/landing/resources/api/shopware-6-client.searchsuggestedproducts.md b/docs/landing/resources/api/shopware-6-client.searchsuggestedproducts.md new file mode 100644 index 000000000..638c66d82 --- /dev/null +++ b/docs/landing/resources/api/shopware-6-client.searchsuggestedproducts.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [@shopware-pwa/shopware-6-client](./shopware-6-client.md) > [searchSuggestedProducts](./shopware-6-client.searchsuggestedproducts.md) + +## searchSuggestedProducts() function + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Search for suggested products based on criteria. From: Shopware 6.4 + +Signature: + +```typescript +export declare function searchSuggestedProducts(criteria?: ShopwareSearchParams, contextInstance?: ShopwareApiInstance): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| criteria | ShopwareSearchParams | | +| contextInstance | [ShopwareApiInstance](./shopware-6-client.shopwareapiinstance.md) | | + +Returns: + +Promise<ProductListingResult> + diff --git a/packages/commons/interfaces/search/SearchCriteria.ts b/packages/commons/interfaces/search/SearchCriteria.ts index 5ae428ac0..c083fe99f 100644 --- a/packages/commons/interfaces/search/SearchCriteria.ts +++ b/packages/commons/interfaces/search/SearchCriteria.ts @@ -80,4 +80,5 @@ export interface ShopwareSearchParams { properties?: string | undefined | never[]; manufacturer?: string | undefined | never[]; includes?: Includes; + query?: string; } diff --git a/packages/composables/__tests__/createListingComposable.spec.ts b/packages/composables/__tests__/createListingComposable.spec.ts new file mode 100644 index 000000000..c56115ce2 --- /dev/null +++ b/packages/composables/__tests__/createListingComposable.spec.ts @@ -0,0 +1,887 @@ +import Vue from "vue"; +import VueCompositionApi from "@vue/composition-api"; +Vue.use(VueCompositionApi); + +import { createListingComposable } from "../src/factories/createListingComposable"; + +import * as Composables from "@shopware-pwa/composables"; +jest.mock("@shopware-pwa/composables"); +const mockedComposables = Composables as jest.Mocked; + +describe("Composables - createListingComposable", () => { + const rootContextMock = jest.fn(); + const searchMethodMock = jest.fn(); + + const vuexStoreMock = { + commit: jest.fn(), + getters: { + getAppliedListings: {}, + getInitialListings: {}, + }, + }; + let routerReplaceValue: any = null; + let routerReplaceCatch: any = null; + const routerMock = { + replace: (param: any) => { + routerReplaceValue = param; + return { + catch: (method: any) => (routerReplaceCatch = method), + }; + }, + currentRoute: { + query: {}, + }, + }; + + beforeEach(() => { + jest.resetAllMocks(); + vuexStoreMock.getters.getInitialListings = {}; + vuexStoreMock.getters.getAppliedListings = {}; + routerReplaceValue = null; + routerMock.currentRoute.query = {}; + routerReplaceCatch = null; + mockedComposables.getApplicationContext.mockImplementation(() => { + return { + vuexStore: vuexStoreMock, + router: routerMock, + } as any; + }); + }); + + it("should return composable with all values", () => { + const resultComposable = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: { limit: 7 }, + searchMethod: searchMethodMock, + }); + expect(resultComposable).toEqual({ + getInitialListing: expect.anything(), + setInitialListing: expect.anything(), + initSearch: expect.anything(), + search: expect.anything(), + getCurrentListing: expect.anything(), + getElements: expect.anything(), + getSortingOrders: expect.anything(), + getCurrentSortingOrder: expect.anything(), + changeCurrentSortingOrder: expect.anything(), + getCurrentPage: expect.anything(), + changeCurrentPage: expect.anything(), + getTotal: expect.anything(), + getTotalPagesCount: expect.anything(), + getLimit: expect.anything(), + getAvailableFilters: expect.anything(), + getCurrentFilters: expect.anything(), + loading: expect.anything(), + loadMore: expect.anything(), + loadingMore: expect.anything(), + }); + }); + + describe("getInitialListing", () => { + it("should return empty initialListing if key not found", () => { + vuexStoreMock.getters.getInitialListings = { + someOtherKey: { limit: 15 }, + }; + const { getInitialListing } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getInitialListing.value).toEqual({}); + }); + it("should return initialListing from store", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { limit: 15 }, + }; + const { getInitialListing } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getInitialListing.value).toEqual({ limit: 15 }); + }); + }); + + describe("setInitialListing", () => { + it("should invoke store commit on invocation for initial and applied listing", () => { + const { setInitialListing } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + setInitialListing({ page: 5 }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_INITIAL_LISTING", { + listingKey: "testKey", + initialListing: { page: 5 }, + }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_APPLIED_LISTING", { + listingKey: "testKey", + appliedListing: null, + }); + }); + }); + + describe("getCurrentListing", () => { + it("should return applied listing if exist", () => { + vuexStoreMock.getters.getAppliedListings = { + testKey: { limit: 15 }, + }; + const { getCurrentListing } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentListing.value).toEqual({ + limit: 15, + }); + }); + + it("should return initial listing if there is no applied one", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { limit: 17 }, + }; + const { getCurrentListing } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentListing.value).toEqual({ + limit: 17, + }); + }); + }); + + describe("getElements", () => { + it("should return an empty array when there is no currentListing", () => { + const { getElements } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getElements.value).toEqual([]); + }); + + it("should return elements from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { elements: [1, 2, 3] }, + }; + const { getElements } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getElements.value).toEqual([1, 2, 3]); + }); + }); + + describe("getTotal", () => { + it("should return 0 when there is no currentListing", () => { + const { getTotal } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getTotal.value).toEqual(0); + }); + + it("should return total from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { total: 55 }, + }; + const { getTotal } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getTotal.value).toEqual(55); + }); + }); + + describe("getLimit", () => { + it("should return 10 when there is no configuration set", () => { + const { getLimit } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getLimit.value).toEqual(10); + }); + + it("should return limit from search defaults when there is no listing", () => { + const { getLimit } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: { + limit: 4, + }, + searchMethod: searchMethodMock, + }); + expect(getLimit.value).toEqual(4); + }); + + it("should return limit from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { limit: 2 }, + }; + const { getLimit } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getLimit.value).toEqual(2); + }); + }); + + describe("getTotalPagesCount", () => { + it("should return 0 when there is no currentListing", () => { + const { getTotalPagesCount } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getTotalPagesCount.value).toEqual(0); + }); + + it("should return ceiled pages count from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { total: 55 }, + }; + const { getTotalPagesCount } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getTotalPagesCount.value).toEqual(6); + }); + }); + + describe("getSortingOrders", () => { + it("should return empty object when there is no currentListing", () => { + const { getSortingOrders } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getSortingOrders.value).toEqual([]); + }); + + it("should return availableSortings from currentListing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { availableSortings: [1, 2] }, + }; + const { getSortingOrders } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getSortingOrders.value).toEqual([1, 2]); + }); + + it("should return olsSortings for shopware 6.3 configuration", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { sortings: { key1: 1, key2: 2 } }, + }; + const { getSortingOrders } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getSortingOrders.value).toEqual([1, 2]); + }); + }); + + describe("getCurrentSortingOrder", () => { + it("should return null when there is no currentListing", () => { + const { getCurrentSortingOrder } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentSortingOrder.value).toBeUndefined(); + }); + + it("should return sorting order from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { sorting: "name-asc" }, + }; + const { getCurrentSortingOrder } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentSortingOrder.value).toEqual("name-asc"); + }); + }); + + describe("changeCurrentSortingOrder", () => { + it("should invoke search with changed order", async () => { + const { changeCurrentSortingOrder } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await changeCurrentSortingOrder("my-new-order"); + expect(searchMethodMock).toBeCalledWith({ order: "my-new-order" }); + expect(routerReplaceValue).toEqual({ query: { order: "my-new-order" } }); + }); + + it("should include router filters and override order value", async () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + order: "some-old-order", + }; + const { changeCurrentSortingOrder } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await changeCurrentSortingOrder("my-new-order"); + expect(searchMethodMock).toBeCalledWith({ + order: "my-new-order", + manufacturer: "nike", + }); + }); + }); + + describe("getCurrentPage", () => { + it("should return 1 when there is no currentListing", () => { + const { getCurrentPage } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentPage.value).toEqual(1); + }); + + it("should return page number from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { page: 4 }, + }; + const { getCurrentPage } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentPage.value).toEqual(4); + }); + }); + + describe("changeCurrentPage", () => { + it("should invoke search with changed page number", async () => { + const { changeCurrentPage } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await changeCurrentPage(6); + expect(searchMethodMock).toBeCalledWith({ p: 6 }); + expect(routerReplaceValue).toEqual({ query: { p: 6 } }); + }); + + it("should include router filters and override order value", async () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + p: 3, + }; + const { changeCurrentPage } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await changeCurrentPage(7); + expect(searchMethodMock).toBeCalledWith({ + p: 7, + manufacturer: "nike", + }); + }); + + it("should invoke search with first page if argument not provided", async () => { + const { changeCurrentPage } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await changeCurrentPage(); + expect(searchMethodMock).toBeCalledWith({ p: 1 }); + expect(routerReplaceValue).toEqual({ query: { p: 1 } }); + }); + }); + + describe("getAvailableFilters", () => { + it("should return an empty array when there is no currentListing", () => { + const { getAvailableFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getAvailableFilters.value).toEqual([]); + }); + + it("should return elements from current listing", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { + aggregations: { + myAggregation: { + someValue: 1, + }, + }, + }, + }; + const { getAvailableFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getAvailableFilters.value).toEqual([ + { + code: "myAggregation", + label: "myAggregation", + someValue: 1, + }, + ]); + }); + }); + + describe("getCurrentFilters", () => { + it("should return an empty object when there is no filters", () => { + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({}); + }); + + it("should return combined filters from current listing and router query", () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { currentFilters: { limit: 6 } }, + }; + routerMock.currentRoute.query = { + manufacturer: "nike", + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + limit: 6, + manufacturer: "nike", + }); + }); + + it("should filter out empty values", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + property: false, + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + }); + }); + + it("should filter out navigationId", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + navigationId: "qwe", + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + }); + }); + + it("should handle priceFilter", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + price: { + min: 33, + max: 34, + }, + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + "min-price": 33, + "max-price": 34, + }); + }); + + it("should handle priceFilter with only min value", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + price: { + min: 33, + }, + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + "min-price": 33, + }); + }); + + it("should handle priceFilter with only max value", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + price: { + max: 33, + }, + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + "max-price": 33, + }); + }); + + it("should filter out page number", () => { + routerMock.currentRoute.query = { + manufacturer: "nike", + p: "3", + }; + const { getCurrentFilters } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(getCurrentFilters.value).toEqual({ + manufacturer: "nike", + }); + }); + }); + + describe("initSearch", () => { + it("should invoke search method", async () => { + const { initSearch } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await initSearch({ limit: 7 }); + expect(searchMethodMock).toBeCalledWith({ limit: 7 }); + }); + + it("should invoke setInitialListing after search", async () => { + searchMethodMock.mockResolvedValueOnce({ limit: 77 }); + const { initSearch } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await initSearch({ limit: 7 }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_INITIAL_LISTING", { + initialListing: { limit: 77 }, + listingKey: "testKey", + }); + }); + + it("should show loading on searching", (resolve) => { + const { initSearch, loading } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loading.value).toEqual(false); + initSearch({ limit: 5 }).then(() => { + expect(loading.value).toEqual(false); + resolve(); + }); + expect(loading.value).toEqual(true); + }); + + it("should stop loading after throwing error", async () => { + searchMethodMock.mockRejectedValueOnce(new Error("something's wrong")); + const { initSearch, loading } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loading.value).toEqual(false); + await expect(initSearch({ limit: 5 })).rejects.toThrow( + "something's wrong" + ); + expect(loading.value).toEqual(false); + }); + }); + + describe("search", () => { + it("should invoke search method", async () => { + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }); + expect(searchMethodMock).toBeCalledWith({ limit: 7 }); + }); + + it("should set applied listing after search", async () => { + searchMethodMock.mockResolvedValueOnce({ limit: 77 }); + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_APPLIED_LISTING", { + appliedListing: { limit: 77 }, + listingKey: "testKey", + }); + }); + + it("should silently fail if router throws error", async () => { + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }); + expect(routerReplaceCatch).toBeTruthy(); + routerReplaceCatch(); + expect(searchMethodMock).toBeCalledWith({ limit: 7 }); + }); + + it("should by default change route with passed criteria", async () => { + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }); + expect(routerReplaceValue).toEqual({ query: { limit: 7 } }); + }); + + it("should change route when search options doesn't contains preventRouteChange flag", async () => { + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }, {}); + expect(routerReplaceValue).toEqual({ query: { limit: 7 } }); + }); + + it("should not change route when search options contains preventRouteChange flag", async () => { + const { search } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await search({ limit: 7 }, { preventRouteChange: true }); + expect(routerReplaceValue).toBeNull(); + }); + + it("should show loading on searching", (resolve) => { + const { search, loading } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loading.value).toEqual(false); + search({ limit: 5 }).then(() => { + expect(loading.value).toEqual(false); + resolve(); + }); + expect(loading.value).toEqual(true); + }); + + it("should stop loading after throwing error", async () => { + searchMethodMock.mockRejectedValueOnce(new Error("something's wrong")); + const { search, loading } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loading.value).toEqual(false); + await expect(search({ limit: 5 })).rejects.toThrow("something's wrong"); + expect(loading.value).toEqual(false); + }); + }); + + describe("loadMore", () => { + it("should invoke search method", async () => { + searchMethodMock.mockResolvedValueOnce({ + page: 2, + elements: [{ id: 1, name: "shoe" }], + }); + const { loadMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await loadMore(); + expect(searchMethodMock).toBeCalledWith({ p: 2 }); + }); + + it("should set applied listing after loadMore", async () => { + searchMethodMock.mockResolvedValueOnce({ + page: 2, + elements: [{ id: 1, name: "shoe" }], + }); + const { loadMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await loadMore(); + expect(vuexStoreMock.commit).toBeCalledWith("SET_APPLIED_LISTING", { + appliedListing: { page: 2, elements: [{ id: 1, name: "shoe" }] }, + listingKey: "testKey", + }); + }); + + it("should show loading on searching", (resolve) => { + searchMethodMock.mockResolvedValueOnce({ + page: 2, + elements: [{ id: 1, name: "shoe" }], + }); + const { loadMore, loadingMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loadingMore.value).toEqual(false); + loadMore().then(() => { + expect(loadingMore.value).toEqual(false); + resolve(); + }); + expect(loadingMore.value).toEqual(true); + }); + + it("should stop loading after throwing error", async () => { + searchMethodMock.mockRejectedValueOnce(new Error("something's wrong")); + const { loadMore, loadingMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + expect(loadingMore.value).toEqual(false); + await expect(loadMore()).rejects.toThrow("something's wrong"); + expect(loadingMore.value).toEqual(false); + }); + + it("should merge loadMore result elements and override page number", async () => { + vuexStoreMock.getters.getInitialListings = { + testKey: { page: 5, elements: [{ id: "333", name: "bag" }] }, + }; + searchMethodMock.mockResolvedValueOnce({ + page: 7, + elements: [{ id: 1, name: "shoe" }], + }); + const { loadMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await loadMore(); + expect(searchMethodMock).toBeCalledWith({ p: 6 }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_APPLIED_LISTING", { + appliedListing: { + page: 7, + elements: [ + { id: "333", name: "bag" }, + { id: 1, name: "shoe" }, + ], + }, + listingKey: "testKey", + }); + }); + + it("should merge loadMore result elements", async () => { + searchMethodMock.mockResolvedValueOnce({ + page: 7, + elements: [{ id: 1, name: "shoe" }], + }); + const { loadMore } = createListingComposable({ + rootContext: rootContextMock as any, + listingKey: "testKey", + searchDefaults: null as any, + searchMethod: searchMethodMock, + }); + await loadMore(); + expect(searchMethodMock).toBeCalledWith({ p: 2 }); + expect(vuexStoreMock.commit).toBeCalledWith("SET_APPLIED_LISTING", { + appliedListing: { + page: 7, + elements: [{ id: 1, name: "shoe" }], + }, + listingKey: "testKey", + }); + }); + }); +}); diff --git a/packages/composables/__tests__/useCategoryFilters.spec.ts b/packages/composables/__tests__/useCategoryFilters.spec.ts index bc8c1ab48..53cc5c6ac 100644 --- a/packages/composables/__tests__/useCategoryFilters.spec.ts +++ b/packages/composables/__tests__/useCategoryFilters.spec.ts @@ -12,6 +12,8 @@ import { getDefaultApiParams, } from "@shopware-pwa/composables"; +const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); + describe("Composables - useCategoryFilters", () => { const statePage: Ref = ref(null); const rootContextMock: any = { @@ -29,6 +31,13 @@ describe("Composables - useCategoryFilters", () => { statePage.value = null; }); + it("should display deprecation info on invocation", () => { + useCategoryFilters(rootContextMock); + expect(consoleWarnSpy).toBeCalledWith( + '[DEPRECATED][@shopware-pwa/composables][useCategoryFilters] This method has been deprecated. Use "useListing" instead.' + ); + }); + describe("computed", () => { describe("availableFilters", () => { it("should return empty array if there is no page loaded", () => { diff --git a/packages/composables/__tests__/useCms.spec.ts b/packages/composables/__tests__/useCms.spec.ts index 161811d11..a3fba7767 100644 --- a/packages/composables/__tests__/useCms.spec.ts +++ b/packages/composables/__tests__/useCms.spec.ts @@ -38,7 +38,7 @@ describe("Composables - useCms", () => { listingConfiguration: {}, apiAlias: "pwa_page_result", }; - mockedGetPage.getPage.mockResolvedValueOnce(response); + mockedGetPage.getCmsPage.mockResolvedValueOnce(response); expect(page.value).toEqual(null); await search(); expect(page.value).toBeTruthy(); @@ -49,7 +49,7 @@ describe("Composables - useCms", () => { it("should have failed on bad url settings", async () => { const { search, page, error } = useCms(rootContextMock); - mockedGetPage.getPage.mockRejectedValueOnce({ + mockedGetPage.getCmsPage.mockRejectedValueOnce({ message: "Something went wrong...", }); expect(page.value).toEqual(null); @@ -61,7 +61,7 @@ describe("Composables - useCms", () => { it("should performs search request with no or empty configuration for SearchCriteria", async () => { const { search, page, error } = useCms(rootContextMock); - mockedGetPage.getPage.mockRejectedValueOnce({ + mockedGetPage.getCmsPage.mockRejectedValueOnce({ message: "Something went wrong...", }); expect(page.value).toEqual(null); @@ -75,96 +75,85 @@ describe("Composables - useCms", () => { describe("search", () => { it("should performs search default pagination limit if not provided", async () => { const { search, page } = useCms(rootContextMock); - mockedGetPage.getPage.mockResolvedValueOnce({} as any); - expect(page.value).toEqual(null); - await search(""); - expect(mockedGetPage.getPage).toBeCalledWith( - "", - { - configuration: { - associations: getDefaultApiParams()?.["useCms"]?.associations, - includes: getDefaultApiParams()?.["useCms"]?.includes, - }, - pagination: { limit: 10 }, - }, - rootContextMock.$shopwareApiInstance + let invocationCriteria: any = null; + mockedGetPage.getCmsPage.mockImplementationOnce( + async (path: string, searchCriteria, apiInstance): Promise => { + invocationCriteria = searchCriteria; + return {}; + } ); - }); - - it("should perform search default pagination limit if wrong value", async () => { - const { search, page } = useCms(rootContextMock); - mockedGetPage.getPage.mockResolvedValueOnce({} as any); expect(page.value).toEqual(null); - await search("", { pagination: "null" }); - expect(mockedGetPage.getPage).toBeCalledWith( + await search(""); + expect(mockedGetPage.getCmsPage).toBeCalledWith( "", - { - configuration: { - associations: getDefaultApiParams()?.["useCms"]?.associations, - includes: getDefaultApiParams()?.["useCms"]?.includes, - }, - pagination: { limit: 10 }, - }, + expect.any(Object), rootContextMock.$shopwareApiInstance ); + expect(invocationCriteria?.limit).toEqual(10); }); it("should performs search with pagination if provided", async () => { const { search, page } = useCms(rootContextMock); - mockedGetPage.getPage.mockResolvedValueOnce({} as any); + let invocationCriteria: any = null; + mockedGetPage.getCmsPage.mockImplementationOnce( + async (path: string, searchCriteria, apiInstance): Promise => { + invocationCriteria = searchCriteria; + return {}; + } + ); expect(page.value).toEqual(null); - await search("", { pagination: { limit: 50 } }); - expect(mockedGetPage.getPage).toBeCalledWith( + await search("", { limit: 50 }); + expect(mockedGetPage.getCmsPage).toBeCalledWith( "", - { - configuration: { - associations: getDefaultApiParams()?.["useCms"]?.associations, - includes: getDefaultApiParams()?.["useCms"]?.includes, - }, - pagination: { limit: 50 }, - }, + expect.any(Object), rootContextMock.$shopwareApiInstance ); + expect(invocationCriteria?.limit).toEqual(50); }); it("should provide default includes if not provided, but configuration exist", async () => { const { search, page } = useCms(rootContextMock); - mockedGetPage.getPage.mockResolvedValueOnce({} as any); + let invocationCriteria: any = null; + mockedGetPage.getCmsPage.mockImplementationOnce( + async (path: string, searchCriteria, apiInstance): Promise => { + invocationCriteria = searchCriteria; + return {}; + } + ); expect(page.value).toEqual(null); await search("", { configuration: {}, }); - expect(mockedGetPage.getPage).toBeCalledWith( + expect(mockedGetPage.getCmsPage).toBeCalledWith( "", - { - configuration: { - associations: getDefaultApiParams()?.["useCms"]?.associations, - includes: getDefaultApiParams()?.["useCms"]?.includes, - }, - pagination: { limit: 10 }, - }, + expect.any(Object), rootContextMock.$shopwareApiInstance ); + expect(invocationCriteria?.includes).not.toBeFalsy(); }); it("should invoke search with custom includes", async () => { + // rootContextMock.$shopwareDefaults = {}; const { search, page } = useCms(rootContextMock); - mockedGetPage.getPage.mockResolvedValueOnce({} as any); + let invocationCriteria: any = null; + mockedGetPage.getCmsPage.mockImplementationOnce( + async (path: string, searchCriteria, apiInstance): Promise => { + invocationCriteria = searchCriteria; + return {}; + } + ); expect(page.value).toEqual(null); await search("", { - configuration: { includes: { product: ["name"] } }, + includes: { product: ["someCustomField"] }, }); - expect(mockedGetPage.getPage).toBeCalledWith( + expect(mockedGetPage.getCmsPage).toBeCalledWith( "", - { - configuration: { - associations: getDefaultApiParams()?.["useCms"]?.associations, - includes: { product: ["name"] }, - }, - pagination: { limit: 10 }, - }, + expect.any(Object), rootContextMock.$shopwareApiInstance ); + expect(invocationCriteria?.includes?.product).toContain( + "someCustomField" + ); }); }); }); @@ -179,7 +168,7 @@ describe("Composables - useCms", () => { listingConfiguration: {}, apiAlias: "pwa_page_result", }; - mockedGetPage.getPage.mockResolvedValueOnce(response); + mockedGetPage.getCmsPage.mockResolvedValueOnce(response); expect(categoryId.value).toBeNull(); await search(); expect(categoryId.value).toEqual("3f637f17cd9f4891a2d7625d19fb37c9"); @@ -187,7 +176,7 @@ describe("Composables - useCms", () => { describe("computed", () => { describe("getBreadcrumbsObject", () => { - it("should contain empty array when there aren't any available countries", async () => { + it("should return page breadcrumbs after search", async () => { const { getBreadcrumbsObject, search } = useCms(rootContextMock); const response: shopwareClient.PageResolverResult = { breadcrumb: { @@ -202,7 +191,7 @@ describe("Composables - useCms", () => { listingConfiguration: {}, apiAlias: "pwa_page_result", }; - mockedGetPage.getPage.mockResolvedValueOnce(response); + mockedGetPage.getCmsPage.mockResolvedValueOnce(response); expect(getBreadcrumbsObject.value).toEqual([]); await search(); expect(getBreadcrumbsObject.value).toEqual({ diff --git a/packages/composables/__tests__/useDefaults.spec.ts b/packages/composables/__tests__/useDefaults.spec.ts index c94946da0..0387a317c 100644 --- a/packages/composables/__tests__/useDefaults.spec.ts +++ b/packages/composables/__tests__/useDefaults.spec.ts @@ -102,4 +102,26 @@ describe("Composables - useDefaults", () => { ); }); }); + + describe("getDefaults", () => { + it("should return defaults for productListing", () => { + const { getDefaults } = useDefaults(rootContextMock, "useProductListing"); + expect(getDefaults()).toEqual( + getDefaultApiParams()?.["useProductListing"] + ); + }); + + it("should return empty object for unknown defauts", () => { + const { getDefaults } = useDefaults(rootContextMock, "someUnknownKey"); + expect(getDefaults()).toEqual({}); + }); + + it("should warn when accessing for non existing defaults", () => { + const { getDefaults } = useDefaults(rootContextMock, "someUnknownKey"); + expect(getDefaults()).toEqual({}); + expect(consoleWarnSpy).toBeCalledWith( + "[WARNING][@shopware-pwa/composables][useDefaults]: there is no defaults configuration for key: someUnknownKey" + ); + }); + }); }); diff --git a/packages/composables/__tests__/useListing.spec.ts b/packages/composables/__tests__/useListing.spec.ts new file mode 100644 index 000000000..a90fceb04 --- /dev/null +++ b/packages/composables/__tests__/useListing.spec.ts @@ -0,0 +1,87 @@ +import Vue from "vue"; +import VueCompositionApi, { ref } from "@vue/composition-api"; +Vue.use(VueCompositionApi); + +import { useListing } from "../src/logic/useListing"; + +import * as Composables from "@shopware-pwa/composables"; +jest.mock("@shopware-pwa/composables"); +const mockedComposables = Composables as jest.Mocked; +import * as ApiClient from "@shopware-pwa/shopware-6-client"; +jest.mock("@shopware-pwa/shopware-6-client"); +const mockedApiClient = ApiClient as jest.Mocked; + +describe("Composables - useListing", () => { + const rootContextMock: any = { + $shopwareApiInstance: jest.fn(), + }; + + let returnedSearchMethod: any = null; + const apiInstanceMock = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + returnedSearchMethod = null; + mockedComposables.createListingComposable = jest + .fn() + .mockImplementation(({ searchMethod }) => { + returnedSearchMethod = searchMethod; + }); + const getDefaultsMock = jest.fn().mockImplementation(() => { + return { limit: 5 }; + }); + + mockedComposables.useDefaults.mockImplementation(() => { + return { + getDefaults: getDefaultsMock, + } as any; + }); + mockedComposables.getApplicationContext.mockImplementation(() => { + return { + apiInstance: apiInstanceMock, + } as any; + }); + mockedComposables.useCms.mockImplementation(() => { + return { + categoryId: ref("321"), + } as any; + }); + }); + + it("should use categoryListing by default", () => { + useListing(rootContextMock, undefined as any); + expect(mockedComposables.createListingComposable).toBeCalledWith({ + listingKey: "categoryListing", + rootContext: rootContextMock, + searchDefaults: { + limit: 5, + }, + searchMethod: expect.any(Function), + }); + }); + + it("should invoke proper search method for categoryListing", async () => { + useListing(rootContextMock, "categoryListing"); + expect(mockedComposables.createListingComposable).toBeCalled(); + expect(returnedSearchMethod).toBeTruthy(); + await returnedSearchMethod({ limit: 8 }); + expect(mockedApiClient.searchProducts).not.toBeCalled(); + expect(mockedApiClient.getCategoryProducts).toBeCalledWith( + "321", + { limit: 8 }, + apiInstanceMock + ); + }); + + it("should invoke proper search method for productSearchListing", async () => { + useListing(rootContextMock, "productSearchListing"); + expect(mockedComposables.createListingComposable).toBeCalled(); + expect(returnedSearchMethod).toBeTruthy(); + await returnedSearchMethod({ limit: 8 }); + expect(mockedApiClient.getCategoryProducts).not.toBeCalled(); + expect(mockedApiClient.searchProducts).toBeCalledWith( + { limit: 8 }, + apiInstanceMock + ); + }); +}); diff --git a/packages/composables/__tests__/useProductListing.spec.ts b/packages/composables/__tests__/useProductListing.spec.ts index cc845978e..ff64f43f6 100644 --- a/packages/composables/__tests__/useProductListing.spec.ts +++ b/packages/composables/__tests__/useProductListing.spec.ts @@ -19,6 +19,7 @@ import { } from "@shopware-pwa/commons/interfaces/search/SearchFilter"; const mockedApiClient = shopwareClient as jest.Mocked; +const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); describe("Composables - useProductListing", () => { const statePage: Ref = ref(null); @@ -37,6 +38,14 @@ describe("Composables - useProductListing", () => { jest.resetAllMocks(); statePage.value = null; }); + + it("should display deprecation info on invocation", () => { + useProductListing(rootContextMock); + expect(consoleWarnSpy).toBeCalledWith( + '[DEPRECATED][@shopware-pwa/composables][useCategoryFilters] This method has been deprecated. Use "useListing" instead.' + ); + }); + describe("no reference to the products collection", () => { it("should have no value if search wasn't performed", async () => { const { products } = useProductListing(rootContextMock); diff --git a/packages/composables/__tests__/useProductQuickSearch.spec.ts b/packages/composables/__tests__/useProductQuickSearch.spec.ts new file mode 100644 index 000000000..74ccea122 --- /dev/null +++ b/packages/composables/__tests__/useProductQuickSearch.spec.ts @@ -0,0 +1,97 @@ +import Vue from "vue"; +import VueCompositionApi, { ref } from "@vue/composition-api"; +Vue.use(VueCompositionApi); + +import { useProductQuickSearch } from "../src/logic/useProductQuickSearch"; + +import * as Composables from "@shopware-pwa/composables"; +jest.mock("@shopware-pwa/composables"); +const mockedComposables = Composables as jest.Mocked; +import * as ApiClient from "@shopware-pwa/shopware-6-client"; +jest.mock("@shopware-pwa/shopware-6-client"); +const mockedApiClient = ApiClient as jest.Mocked; + +describe("Composables - useProductQuickSearch", () => { + const rootContextMock: any = { + $shopwareApiInstance: jest.fn(), + }; + + let returnedSearchMethod: any = null; + const apiInstanceMock = jest.fn(); + const factorySearchMethodMock = jest.fn(); + const loadingMockRef = ref(false); + + beforeEach(() => { + jest.resetAllMocks(); + returnedSearchMethod = null; + loadingMockRef.value = false; + mockedComposables.createListingComposable = jest + .fn() + .mockImplementation(({ searchMethod }) => { + returnedSearchMethod = searchMethod; + return { + loading: loadingMockRef, + search: factorySearchMethodMock, + }; + }); + const getDefaultsMock = jest.fn().mockImplementation(() => { + return { limit: 5 }; + }); + + mockedComposables.useDefaults.mockImplementation(() => { + return { + getDefaults: getDefaultsMock, + } as any; + }); + mockedComposables.getApplicationContext.mockImplementation(() => { + return { + apiInstance: apiInstanceMock, + } as any; + }); + // mockedComposables.useCms.mockImplementation(() => { + // return { + // categoryId: ref("321"), + // } as any; + // }); + }); + + it("should have searchTerm ref", () => { + const { searchTerm } = useProductQuickSearch(rootContextMock); + expect(searchTerm.value).toEqual(""); + searchTerm.value = "new search value"; + expect(searchTerm.value).toEqual("new search value"); + }); + + it("should use listingKey - productQuickSearch", () => { + useProductQuickSearch(rootContextMock); + expect(mockedComposables.createListingComposable).toBeCalledWith({ + listingKey: "productQuickSearch", + rootContext: rootContextMock, + searchDefaults: { + limit: 5, + }, + searchMethod: expect.any(Function), + }); + }); + + it("should invoke API search method", async () => { + useProductQuickSearch(rootContextMock); + expect(mockedComposables.createListingComposable).toBeCalled(); + expect(returnedSearchMethod).toBeTruthy(); + await returnedSearchMethod({ limit: 8, query: "search term" }); + expect(mockedApiClient.searchSuggestedProducts).toBeCalledWith( + { limit: 8, query: "search term" }, + apiInstanceMock + ); + }); + + it("should invoke search method inside createListingComposable with preventing route change", async () => { + const { searchTerm, search } = useProductQuickSearch(rootContextMock); + searchTerm.value = "someTerm"; + await search(); + expect(factorySearchMethodMock).toBeCalledWith( + { query: "someTerm" }, + { preventRouteChange: true } + ); + }); +}); diff --git a/packages/composables/__tests__/useProductSearch.spec.ts b/packages/composables/__tests__/useProductSearch.spec.ts index 497c1fc82..932d72113 100644 --- a/packages/composables/__tests__/useProductSearch.spec.ts +++ b/packages/composables/__tests__/useProductSearch.spec.ts @@ -6,6 +6,7 @@ jest.mock("@shopware-pwa/shopware-6-client"); import * as shopwareClient from "@shopware-pwa/shopware-6-client"; import { SearchFilterType } from "@shopware-pwa/commons/interfaces/search/SearchFilter"; const mockedApi = shopwareClient as jest.Mocked; +const consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(() => {}); describe("Composables - useProductSearch", () => { const rootContextMock: any = { @@ -16,6 +17,13 @@ describe("Composables - useProductSearch", () => { jest.resetAllMocks(); }); + it("should display deprecation info on invocation", () => { + useProductSearch(rootContextMock); + expect(consoleWarnSpy).toBeCalledWith( + '[DEPRECATED][@shopware-pwa/composables][useProductSearch] This method has been deprecated. Use "useProductQuickSearch" instead.' + ); + }); + describe("initial values", () => { it("should have no listing result if search wasn't performed", async () => { const { searchResult } = useProductSearch(rootContextMock); diff --git a/packages/composables/package.json b/packages/composables/package.json index 95d206cde..8a3d7952c 100644 --- a/packages/composables/package.json +++ b/packages/composables/package.json @@ -32,5 +32,8 @@ }, "publishConfig": { "access": "public" + }, + "devDependencies": { + "lodash": "^4.17.20" } } diff --git a/packages/composables/src/appContext.ts b/packages/composables/src/appContext.ts index f989773c4..b3130c936 100644 --- a/packages/composables/src/appContext.ts +++ b/packages/composables/src/appContext.ts @@ -12,8 +12,10 @@ export interface ApplicationVueContext extends VueConstructor { shopwareApiInstance?: ShopwareApiInstance; $store?: any; // Vuex Store store?: any; // Vuex Store + $route?: any; // Vue router $router?: any; // Vue router router?: any; // Vue router + route?: any; // Vue router $i18n?: any; // Vue i18n plugin i18n?: any; // Vue i18n plugin $cookies?: any; // cookie-universal @@ -54,6 +56,7 @@ export function getApplicationContext( apiInstance: context?.$shopwareApiInstance || context?.shopwareApiInstance, vuexStore: context?.$store || context?.store, router: context?.$router || context?.router, + route: context?.$route || context?.route, i18n: context?.$i18n || context?.i18n, cookies: context?.$cookies || context?.cookies, shopwareDefaults: context?.$shopwareDefaults || context?.shopwareDefaults, diff --git a/packages/composables/src/factories/createListingComposable.ts b/packages/composables/src/factories/createListingComposable.ts new file mode 100644 index 000000000..daf962082 --- /dev/null +++ b/packages/composables/src/factories/createListingComposable.ts @@ -0,0 +1,249 @@ +import { getListingFilters, ListingFilter } from "@shopware-pwa/helpers"; + +import { + ApplicationVueContext, + getApplicationContext, +} from "@shopware-pwa/composables"; +import { computed, ComputedRef, ref } from "@vue/composition-api"; +import merge from "lodash/merge"; +import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; +import { ProductListingResult } from "@shopware-pwa/commons/interfaces/response/ProductListingResult"; + +/** + * Listing interface, can be used to display category products, search products or any other Shopware search interface (ex. orders with pagination) + * + * @beta + */ +export interface IUseListing { + getInitialListing: ComputedRef; + setInitialListing: (initialListing: Partial) => void; + initSearch: (criteria: Partial) => Promise; + search: ( + criteria: Partial, + options?: { + preventRouteChange?: boolean; + } + ) => Promise; + loadMore: () => Promise; + getCurrentListing: ComputedRef; + getElements: ComputedRef; + getSortingOrders: ComputedRef<{ key: string; label: string }>; + getCurrentSortingOrder: ComputedRef; + changeCurrentSortingOrder: (order: string | string[]) => Promise; + getCurrentPage: ComputedRef; + changeCurrentPage: (pageNumber?: number | string) => Promise; + getTotal: ComputedRef; + getTotalPagesCount: ComputedRef; + getLimit: ComputedRef; + getAvailableFilters: ComputedRef; + getCurrentFilters: ComputedRef; + loading: ComputedRef; + loadingMore: ComputedRef; +} + +/** + * Factory to create your own listing. By default you can use useListing composable, which provides you predefined listings for category(cms) listing and product search listing. + * Using factory you can provide our own compatible search method and use it for example for creating listing of orders in my account. + * + * @beta + */ +export function createListingComposable({ + rootContext, + searchMethod, + searchDefaults, + listingKey, +}: { + rootContext: ApplicationVueContext; + searchMethod: ( + searchParams: Partial + ) => Promise; + searchDefaults: ShopwareSearchParams; + listingKey: string; +}): IUseListing { + const { vuexStore, router } = getApplicationContext( + rootContext, + "createListingComposable" + ); + + const loading = ref(false); + const loadingMore = ref(false); + + const getInitialListing = computed( + () => vuexStore.getters.getInitialListings[listingKey] || {} + ); + const setInitialListing = (initialListing: ProductListingResult) => { + vuexStore.commit("SET_INITIAL_LISTING", { listingKey, initialListing }); + appliedListing.value = null; + }; + + // for internal usage, actual listing is computed from applied and initial listing + const appliedListing = computed({ + get: () => vuexStore.getters.getAppliedListings[listingKey], + set: (appliedListing) => { + vuexStore.commit("SET_APPLIED_LISTING", { listingKey, appliedListing }); + }, + }); + + const initSearch = async ( + criteria: Partial + ): Promise => { + loading.value = true; + try { + const searchCriteria = merge({}, searchDefaults, criteria); + + const result = await searchMethod(searchCriteria); + + setInitialListing(result); + } catch (e) { + throw e; + } finally { + loading.value = false; + } + }; + + const search = async ( + criteria: Partial, + options?: { + preventRouteChange?: boolean; + } + ): Promise => { + loading.value = true; + const changeRoute = options?.preventRouteChange !== true; + try { + // replace URL query params with currently selected criteria + changeRoute && + router + .replace({ + query: { + ...criteria, + }, + }) + .catch(() => {}); + + // prepare full criteria using defaults and currently selected criteria + const searchCriteria = merge({}, searchDefaults, criteria); + const result = await searchMethod(searchCriteria); + appliedListing.value = result; + } catch (e) { + throw e; + } finally { + loading.value = false; + } + }; + + const loadMore = async (): Promise => { + loadingMore.value = true; + try { + const query = { + ...router.currentRoute.query, + p: getCurrentPage.value + 1, + }; + + const searchCriteria = merge({}, searchDefaults, query); + const result = await searchMethod(searchCriteria); + appliedListing.value = { + ...getCurrentListing.value, + page: result.page, + elements: [ + ...(getCurrentListing.value.elements || []), + ...result.elements, + ], + }; + } catch (e) { + throw e; + } finally { + loadingMore.value = false; + } + }; + + const getCurrentListing = computed(() => { + return appliedListing.value || getInitialListing.value; + }); + + const getElements = computed(() => { + return getCurrentListing.value.elements || []; + }); + const getTotal = computed(() => { + return getCurrentListing.value.total || 0; + }); + const getLimit = computed(() => { + return getCurrentListing.value.limit || searchDefaults?.limit || 10; + }); + + const getTotalPagesCount = computed(() => + Math.ceil(getTotal.value / getLimit.value) + ); + + const getSortingOrders = computed(() => { + const oldSortings = Object.values(getCurrentListing.value.sortings || {}); // before Shopware 6.4 + return getCurrentListing.value.availableSortings || oldSortings; + }); + + const getCurrentSortingOrder = computed( + () => getCurrentListing.value.sorting + ); + const changeCurrentSortingOrder = async (order: string | string[]) => { + const query = { + ...router.currentRoute.query, + order, + }; + await search(query); + }; + + const getCurrentPage = computed(() => getCurrentListing.value.page || 1); + const changeCurrentPage = async (pageNumber: number | string) => { + const query = { + ...router.currentRoute.query, + p: pageNumber || 1, + }; + await search(query); + }; + + const getAvailableFilters = computed(() => { + return getListingFilters(getCurrentListing.value.aggregations); + }); + + const getCurrentFilters = computed(() => { + const currentFiltersResult: any = {}; + const currentFilters = { + ...getCurrentListing.value.currentFilters, + ...router.currentRoute.query, + }; + Object.keys(currentFilters).forEach((objectKey) => { + if (!currentFilters[objectKey]) return; + if (objectKey === "navigationId") return; + if (objectKey === "price") { + if (currentFilters[objectKey].min) + currentFiltersResult["min-price"] = currentFilters[objectKey].min; + if (currentFilters[objectKey].max) + currentFiltersResult["max-price"] = currentFilters[objectKey].max; + return; + } + if (objectKey === "p") return; + currentFiltersResult[objectKey] = currentFilters[objectKey]; + }); + return currentFiltersResult; + }); + + return { + getInitialListing, + setInitialListing, + initSearch, + search, + getCurrentListing, + getElements, + getSortingOrders, + getCurrentSortingOrder, + changeCurrentSortingOrder, + getCurrentPage, + changeCurrentPage, + getTotal, + getTotalPagesCount, + getLimit, + getAvailableFilters, + getCurrentFilters, + loading, + loadMore, + loadingMore, + }; +} diff --git a/packages/composables/src/hooks/useCategoryFilters/index.ts b/packages/composables/src/hooks/useCategoryFilters/index.ts index d4f7a5780..205efb910 100644 --- a/packages/composables/src/hooks/useCategoryFilters/index.ts +++ b/packages/composables/src/hooks/useCategoryFilters/index.ts @@ -8,11 +8,18 @@ import { SwSorting, } from "@shopware-pwa/helpers"; import { ApplicationVueContext } from "../../appContext"; +import { deprecationWarning } from "@shopware-pwa/commons"; /** - * @alpha + * @beta + * @deprecated please see useListing instead */ export const useCategoryFilters = (rootContext: ApplicationVueContext): any => { + deprecationWarning({ + methodName: "useCategoryFilters", + newMethodName: "useListing", + packageName: "composables", + }); getApplicationContext(rootContext, "useCategoryFilters"); const { page } = useCms(rootContext); diff --git a/packages/composables/src/hooks/useCms/index.ts b/packages/composables/src/hooks/useCms/index.ts index 391199a14..5412c7f00 100644 --- a/packages/composables/src/hooks/useCms/index.ts +++ b/packages/composables/src/hooks/useCms/index.ts @@ -1,11 +1,11 @@ import { ref, Ref, computed } from "@vue/composition-api"; -import { getPage } from "@shopware-pwa/shopware-6-client"; +import { getCmsPage } from "@shopware-pwa/shopware-6-client"; import { SearchCriteria } from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; import { parseUrlQuery } from "@shopware-pwa/helpers"; import { ClientApiError } from "@shopware-pwa/commons/interfaces/errors/ApiError"; -import { getApplicationContext } from "@shopware-pwa/composables"; +import { getApplicationContext, useDefaults } from "@shopware-pwa/composables"; import { ApplicationVueContext } from "../../appContext"; -import { useDefaults } from "../../logic/useDefaults"; +import merge from "lodash/merge"; /** * @alpha @@ -16,10 +16,7 @@ export const useCms = (rootContext: ApplicationVueContext): any => { "useCms" ); - const { getAssociationsConfig, getIncludesConfig } = useDefaults( - rootContext, - "useCms" - ); + const { getDefaults } = useDefaults(rootContext, "useCms"); const error: Ref = ref(null); const loading: Ref = ref(false); const page = computed(() => { @@ -36,32 +33,11 @@ export const useCms = (rootContext: ApplicationVueContext): any => { const search = async (path: string, query?: any) => { loading.value = true; - const searchCriteria: SearchCriteria = parseUrlQuery(query); - // Temp Maciej solution for associations - if (!searchCriteria.configuration) searchCriteria.configuration = {}; - // Temp solution for consistant page size - // @TODO: https://github.com/DivanteLtd/shopware-pwa/issues/739 - if (!searchCriteria.pagination || searchCriteria.pagination === "null") { - searchCriteria.pagination = {}; - } - - if (!searchCriteria.pagination.limit) { - searchCriteria.pagination.limit = 10; - } - - if (!searchCriteria.configuration.associations) - searchCriteria.configuration.associations = []; - - const associations = getAssociationsConfig(); - searchCriteria.configuration.associations.push(...associations); - - if (!searchCriteria.configuration.includes) { - // performance enhancement - searchCriteria.configuration.includes = getIncludesConfig(); - } + const criteria: SearchCriteria = parseUrlQuery(query); + const searchCriteria = merge({}, getDefaults(), criteria); try { - const result = await getPage(path, searchCriteria, apiInstance); + const result = await getCmsPage(path, searchCriteria, apiInstance); vuexStore.commit("SET_PAGE", result); } catch (e) { const err: ClientApiError = e; diff --git a/packages/composables/src/hooks/useProductListing.ts b/packages/composables/src/hooks/useProductListing.ts index 864c8f040..b6e40d67d 100644 --- a/packages/composables/src/hooks/useProductListing.ts +++ b/packages/composables/src/hooks/useProductListing.ts @@ -30,8 +30,11 @@ import { toggleEntityFilter, toggleFilter as toggleGenericFilter, } from "../internalHelpers/searchCriteria"; +import { deprecationWarning } from "@shopware-pwa/commons"; + /** - * @alpha + * @beta + * @deprecated please see useListing instead */ export interface UseProductListing { loading: Ref; @@ -60,12 +63,18 @@ const selectedCriteria = Vue.observable({ } as any); /** - * @alpha + * @beta + * @deprecated please see useListing instead */ export const useProductListing = ( rootContext: ApplicationVueContext, initialListing?: ProductListingResult ): UseProductListing => { + deprecationWarning({ + methodName: "useProductListing", + newMethodName: "useListing", + packageName: "composables", + }); const { apiInstance } = getApplicationContext( rootContext, "useProductListing" diff --git a/packages/composables/src/hooks/useProductSearch.ts b/packages/composables/src/hooks/useProductSearch.ts index a47a36c6f..f786a8878 100644 --- a/packages/composables/src/hooks/useProductSearch.ts +++ b/packages/composables/src/hooks/useProductSearch.ts @@ -30,6 +30,7 @@ import { toggleEntityFilter, toggleFilter as toggleGenericFilter, } from "../internalHelpers/searchCriteria"; +import { deprecationWarning } from "@shopware-pwa/commons"; /** * @beta @@ -42,6 +43,7 @@ export interface CurrentPagination { /** * @alpha + * @deprecated please see useProductQuickSearch instead */ export interface UseProductSearch { loadingSearch: Readonly>; @@ -67,6 +69,7 @@ export interface UseProductSearch { /** * @alpha + * @deprecated please see useProductQuickSearch instead */ export const useProductSearch = ( rootContext: ApplicationVueContext @@ -75,6 +78,11 @@ export const useProductSearch = ( rootContext, "useProductSearch" ); + deprecationWarning({ + methodName: "useProductSearch", + newMethodName: "useProductQuickSearch", + packageName: "composables", + }); const loadingSearch: Ref = ref(false); const loadingSuggestions: Ref = ref(false); diff --git a/packages/composables/src/index.ts b/packages/composables/src/index.ts index 750322c14..b68b4a6de 100644 --- a/packages/composables/src/index.ts +++ b/packages/composables/src/index.ts @@ -18,4 +18,7 @@ export * from "./logic/useDefaults"; export * from "./logic/useNotifications"; export * from "./logic/useIntercept"; export * from "./getDefaultApiParams"; +export * from "./logic/useListing"; +export * from "./logic/useProductQuickSearch"; +export * from "./factories/createListingComposable"; export { getApplicationContext, ApplicationVueContext } from "./appContext"; diff --git a/packages/composables/src/internalHelpers/defaultApiParams.json b/packages/composables/src/internalHelpers/defaultApiParams.json index 50dd55027..3e985970c 100644 --- a/packages/composables/src/internalHelpers/defaultApiParams.json +++ b/packages/composables/src/internalHelpers/defaultApiParams.json @@ -1,5 +1,6 @@ { "useCms": { + "limit": 10, "associations": [ { "name": "manufacturer" @@ -32,91 +33,85 @@ ] }, { - "name": "options", - "associations": [ - { - "name": "group" - } - ] - }], + "name": "options", + "associations": [ + { + "name": "group" + } + ] + } + ], "includes": { - "cms_page_slot": [ - "id", - "type", - "slot", - "blockId", - "config", - "data", - "backgroundMediaMode", - "backgroundMedia" + "cms_page_slot": [ + "id", + "type", + "slot", + "blockId", + "config", + "data", + "backgroundMediaMode", + "backgroundMedia" ], "cms_page_block": [ - "slots", - "type", - "id", - "backgroundColor", - "backgroundMedia", - "sectionPosition", - "backgroundColor" + "slots", + "type", + "id", + "backgroundColor", + "backgroundMedia", + "sectionPosition" ], "cms_page_section": [ - "id", - "backgroundMedia", - "blocks", - "type", - "sizingMode" - ], - "cms_page": [ - "id", - "name", - "sections", - "type", - "config" + "id", + "backgroundMedia", + "blocks", + "type", + "sizingMode" ], + "cms_page": ["id", "name", "sections", "type", "config"], "product": [ - "media", - "productReviews", - "children", - "name", - "ratingAverage", - "calculatedPrice", - "calculatedPrices", - "cover", - "parentId", - "id", - "translated", - "options", - "properties", - "manufacturer" - ], - "product_media": [ - "media" - ], - "media": [ - "thumbnails", - "width", - "height", - "url" - ], - "calculated_price": [ - "unitPrice", - "quantity" - ], - "product_group_option": [ - "name", - "id", - "group", - "translated" - ], - "product_group": [ - "id", - "name", - "options", - "translated" + "media", + "productReviews", + "children", + "name", + "ratingAverage", + "calculatedPrice", + "calculatedPrices", + "cover", + "parentId", + "id", + "translated", + "options", + "properties", + "manufacturer" + ], + "product_media": ["media"], + "media": ["thumbnails", "width", "height", "url"], + "calculated_price": ["unitPrice", "quantity"], + "product_group_option": ["name", "id", "group", "translated"], + "product_group": ["id", "name", "options", "translated"], + "product_listing": [ + "sorting", + "currentFilters", + "elements", + "page", + "limit", + "sortings", + "availableSortings", + "total", + "aggregations" + ], + "property_group": ["id", "translated", "options"], + "property_group_option": [ + "name", + "translated", + "id", + "colorHexCode", + "media" ] } }, "useProductListing": { + "limit": 10, "includes": { "product": [ "name", @@ -128,30 +123,46 @@ "translated", "options" ], - "product_media": [ - "media" - ], - "media": [ - "thumbnails", - "width", - "height", - "url" - ], - "calculated_price": [ - "unitPrice", - "quantity" - ], - "product_group_option": [ - "name", - "id", - "group", - "translated" + "product_media": ["media"], + "media": ["thumbnails", "width", "height", "url"], + "calculated_price": ["unitPrice", "quantity"], + "product_group_option": ["name", "id", "group", "translated"], + "product_group": ["id", "name", "options", "translated"], + "property_group": ["id", "translated", "options"], + "property_group_option": [ + "name", + "translated", + "id", + "colorHexCode", + "media" + ] + } + }, + "useListing": { + "limit": 10, + "includes": { + "product": [ + "name", + "ratingAverage", + "calculatedPrice", + "calculatedPrices", + "cover", + "id", + "translated", + "options" ], - "product_group": [ - "id", - "name", - "options", - "translated" + "product_media": ["media"], + "media": ["thumbnails", "width", "height", "url"], + "calculated_price": ["unitPrice", "quantity"], + "product_group_option": ["name", "id", "group", "translated"], + "product_group": ["id", "name", "options", "translated"], + "property_group": ["id", "translated", "options"], + "property_group_option": [ + "name", + "translated", + "id", + "colorHexCode", + "media" ] } }, @@ -192,7 +203,7 @@ "name": "options", "associations": [ { - "name": "group" + "name": "group" } ] } @@ -217,28 +228,11 @@ "seoUrls", "optionIds" ], - "product_media": [ - "media" - ], - "media": [ - "url" - ], - "calculated_price": [ - "unitPrice", - "quantity" - ], - "product_group_option": [ - "name", - "id", - "group", - "translated" - ], - "product_group": [ - "id", - "name", - "options", - "translated" - ] + "product_media": ["media"], + "media": ["url"], + "calculated_price": ["unitPrice", "quantity"], + "product_group_option": ["name", "id", "group", "translated"], + "product_group": ["id", "name", "options", "translated"] } } -} \ No newline at end of file +} diff --git a/packages/composables/src/internalHelpers/searchCriteria.ts b/packages/composables/src/internalHelpers/searchCriteria.ts index 07d4316f7..a9498ea66 100644 --- a/packages/composables/src/internalHelpers/searchCriteria.ts +++ b/packages/composables/src/internalHelpers/searchCriteria.ts @@ -27,7 +27,7 @@ export function appendSearchCriteriaToUrl( manufacturer: manufacturer, properties: properties, }; - const combinedURL = stringify(query, { + const combinedURL = stringify(query as any, { arrayFormat: "separator", arrayFormatSeparator: "|", skipNull: true, diff --git a/packages/composables/src/logic/useDefaults.ts b/packages/composables/src/logic/useDefaults.ts index 3cc316e6c..834234a24 100644 --- a/packages/composables/src/logic/useDefaults.ts +++ b/packages/composables/src/logic/useDefaults.ts @@ -1,5 +1,8 @@ import { ApplicationVueContext, getApplicationContext } from "../appContext"; -import { Includes } from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; +import { + Includes, + ShopwareSearchParams, +} from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; import { Association } from "@shopware-pwa/commons/interfaces/search/Association"; import { warning } from "@shopware-pwa/commons"; @@ -9,7 +12,7 @@ import { warning } from "@shopware-pwa/commons"; * * @remarks * To extend defaults you need to add configuration to `shopware-pwa.config.js` file. - * Let's say we want to have a product manufacturer and media associations on CMS pages. We need to add to configuration file: + * Let's say we want to have a product manufacturer, media associations and listing limit on CMS pages. We need to add to configuration file: * ```js * // inside shopware-pwa.config.js * @@ -17,6 +20,7 @@ import { warning } from "@shopware-pwa/commons"; * // ... other settings * apiDefaults: { * useCms: { + * limit: 8, * includes: { * product: ["manufacturer"] * }, @@ -37,6 +41,7 @@ export const useDefaults = ( ): { getIncludesConfig: () => Includes; getAssociationsConfig: () => Association[]; + getDefaults: () => ShopwareSearchParams; } => { const { shopwareDefaults } = getApplicationContext( rootContext, @@ -63,9 +68,11 @@ export const useDefaults = ( const getIncludesConfig = () => getDefaultsFor(defaultsKey)?.includes || {}; const getAssociationsConfig = () => getDefaultsFor(defaultsKey)?.associations || []; + const getDefaults = () => getDefaultsFor(defaultsKey) || {}; return { getIncludesConfig, getAssociationsConfig, + getDefaults, }; }; diff --git a/packages/composables/src/logic/useListing.ts b/packages/composables/src/logic/useListing.ts new file mode 100644 index 000000000..610e480d3 --- /dev/null +++ b/packages/composables/src/logic/useListing.ts @@ -0,0 +1,50 @@ +import { + getCategoryProducts, + searchProducts, +} from "@shopware-pwa/shopware-6-client"; + +import { + useCms, + ApplicationVueContext, + getApplicationContext, + useDefaults, + createListingComposable, + IUseListing, +} from "@shopware-pwa/composables"; +import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; +import { Product } from "@shopware-pwa/commons/interfaces/models/content/product/Product"; + +/** + * @beta + */ +export type listingKey = "productSearchListing" | "categoryListing"; + +/** + * @beta + */ +export const useListing = ( + rootContext: ApplicationVueContext, + listingKey: listingKey = "categoryListing" +): IUseListing => { + const { getDefaults } = useDefaults(rootContext, "useListing"); + const { apiInstance } = getApplicationContext(rootContext, "useListing"); + + let searchMethod; + if (listingKey === "productSearchListing") { + searchMethod = async (searchCriteria: Partial) => { + return searchProducts(searchCriteria, apiInstance); + }; + } else { + const { categoryId } = useCms(rootContext); + searchMethod = async (searchCriteria: Partial) => { + return getCategoryProducts(categoryId.value, searchCriteria, apiInstance); + }; + } + + return createListingComposable({ + rootContext, + listingKey, + searchMethod, + searchDefaults: getDefaults(), + }); +}; diff --git a/packages/composables/src/logic/useProductQuickSearch.ts b/packages/composables/src/logic/useProductQuickSearch.ts new file mode 100644 index 000000000..886f37a06 --- /dev/null +++ b/packages/composables/src/logic/useProductQuickSearch.ts @@ -0,0 +1,70 @@ +import { searchSuggestedProducts } from "@shopware-pwa/shopware-6-client"; + +import { + ApplicationVueContext, + getApplicationContext, + useDefaults, + createListingComposable, +} from "@shopware-pwa/composables"; +import { ShopwareSearchParams } from "@shopware-pwa/commons/interfaces/search/SearchCriteria"; +import { Product } from "@shopware-pwa/commons/interfaces/models/content/product/Product"; +import { ComputedRef, Ref, ref } from "@vue/composition-api"; + +/** + * @beta + */ +export interface IUseProductQuickSearch { + searchTerm: Ref; + loading: ComputedRef; + search: (additionalCriteria?: Partial) => Promise; + loadMore: () => Promise; + getProducts: ComputedRef; + getTotal: ComputedRef; +} + +/** + * @beta + */ +export const useProductQuickSearch = ( + rootContext: ApplicationVueContext +): IUseProductQuickSearch => { + const { getDefaults } = useDefaults(rootContext, "useProductQuickSearch"); + const { apiInstance } = getApplicationContext(rootContext, "useListing"); + + const searchTerm = ref(""); + + const listingKey = "productQuickSearch"; + const searchMethod = async ( + searchCriteria: Partial + ) => { + return searchSuggestedProducts(searchCriteria, apiInstance); + }; + + const listingComposable = createListingComposable({ + rootContext, + listingKey, + searchMethod, + searchDefaults: getDefaults(), + }); + + const search = async ( + additionalCriteria: Partial = {} + ) => { + const searchCriteria = { + query: searchTerm.value, + ...additionalCriteria, + }; + return listingComposable.search(searchCriteria, { + preventRouteChange: true, + }); + }; + + return { + searchTerm, + loading: listingComposable.loading, + search, + loadMore: listingComposable.loadMore, + getProducts: listingComposable.getElements, + getTotal: listingComposable.getTotal, + }; +}; diff --git a/packages/default-theme/assets/scss/main.scss b/packages/default-theme/assets/scss/main.scss index 8f85816d0..ac05609f4 100644 --- a/packages/default-theme/assets/scss/main.scss +++ b/packages/default-theme/assets/scss/main.scss @@ -7,6 +7,7 @@ html { overflow-x: hidden; height: 100vh; overflow: auto; + scroll-behavior: smooth; } a { diff --git a/packages/default-theme/cms/blocks/CmsBlockProductListing.vue b/packages/default-theme/cms/blocks/CmsBlockProductListing.vue new file mode 100644 index 000000000..69a8d6c3f --- /dev/null +++ b/packages/default-theme/cms/blocks/CmsBlockProductListing.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/packages/default-theme/cms/cmsMap.json b/packages/default-theme/cms/cmsMap.json index adfb75250..cec9aaa8b 100644 --- a/packages/default-theme/cms/cmsMap.json +++ b/packages/default-theme/cms/cmsMap.json @@ -6,7 +6,7 @@ "blocks": { "text-on-image": "CmsBlockTextOnImage", "sidebar-filter": "CmsBlockDefault", - "product-listing": "CmsBlockDefault", + "product-listing": "CmsBlockProductListing", "image-text": "CmsBlockImageText", "image": "CmsBlockDefault", "image-cover": "CmsBlockImageCover", diff --git a/packages/default-theme/cms/elements/CmsElementCategorySidebarFilter.vue b/packages/default-theme/cms/elements/CmsElementCategorySidebarFilter.vue index f44aba51f..5ea63744b 100644 --- a/packages/default-theme/cms/elements/CmsElementCategorySidebarFilter.vue +++ b/packages/default-theme/cms/elements/CmsElementCategorySidebarFilter.vue @@ -1,123 +1,14 @@ - - diff --git a/packages/default-theme/cms/elements/CmsElementProductListing.vue b/packages/default-theme/cms/elements/CmsElementProductListing.vue index 578f68937..f4772c2a9 100644 --- a/packages/default-theme/cms/elements/CmsElementProductListing.vue +++ b/packages/default-theme/cms/elements/CmsElementProductListing.vue @@ -1,96 +1,17 @@ - - diff --git a/packages/default-theme/components/SwProductDetails.vue b/packages/default-theme/components/SwProductDetails.vue index 75f546afc..082a09475 100644 --- a/packages/default-theme/components/SwProductDetails.vue +++ b/packages/default-theme/components/SwProductDetails.vue @@ -191,7 +191,7 @@ export default { }, mounted() { - this.product.options.forEach((option) => { + this.product.options?.forEach((option) => { this.selected = Object.assign({}, this.selected, { [option.group.name]: option.id, }) diff --git a/packages/default-theme/components/SwProductListing.vue b/packages/default-theme/components/SwProductListing.vue index aabbd54bb..7bceb7535 100644 --- a/packages/default-theme/components/SwProductListing.vue +++ b/packages/default-theme/components/SwProductListing.vue @@ -1,7 +1,7 @@