@scayle/storefront-product-listing 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,9 +70,3 @@ Learn more about [SCAYLE’s architecture](https://scayle.dev/en/getting-started
70
70
  [npm-downloads-href]: https://npmjs.com/package/@scayle/storefront-product-listing
71
71
  [license-src]: https://img.shields.io/npm/l/@scayle/storefront-product-listing.svg?style=flat&colorA=18181B&colorB=28CF8D
72
72
  [license-href]: https://npmjs.com/package/@scayle/storefront-product-listing
73
-
74
- ## License
75
-
76
- Licensed under the [MIT License](https://opensource.org/license/mit/)
77
-
78
- ---
@@ -1,14 +1,26 @@
1
+ import { type ComputedRef } from 'vue';
1
2
  import type { ProductSearchQuery } from '@scayle/storefront-nuxt';
3
+ import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
4
+ export type AppliedAttributeValues = Record<string, (string | number)[]>;
5
+ export type AppliedBooleanValues = Record<string, boolean>;
2
6
  export interface AppliedFiltersOptions {
3
7
  filtersPrefix: string;
4
8
  }
9
+ export interface UseAppliedFiltersReturn {
10
+ appliedFilter: ComputedRef<ProductSearchQuery>;
11
+ appliedFiltersCount: ComputedRef<number>;
12
+ appliedBooleanValues: ComputedRef<AppliedBooleanValues>;
13
+ appliedAttributeValues: ComputedRef<AppliedAttributeValues>;
14
+ areFiltersApplied: ComputedRef<boolean>;
15
+ }
5
16
  /**
6
17
  * Composable to manage applied filters based on route query parameters.
7
18
  *
8
- * @param {object} options - Configuration options for applied filters.
19
+ * @param {RouteLocationNormalizedLoadedGeneric} [route] - Route object to handle the queries.
20
+ * @param {AppliedFiltersOptions} options - Configuration options for applied filters.
9
21
  * @param {string} options.filtersPrefix - The prefix used to identify filter-related query parameters.
10
22
  *
11
- * @returns {object} - An object containing the following properties:
23
+ * @returns {UseAppliedFiltersReturn} - An object containing reactive data and computed properties for managing applied filters:
12
24
  *
13
25
  * - `appliedFilter` {ComputedRef<ProductSearchQuery>}: A computed property returning a `ProductSearchQuery` object based on the route's query parameters.
14
26
  * - `attributes`: An array of attribute filters, where each filter has:
@@ -21,17 +33,8 @@ export interface AppliedFiltersOptions {
21
33
  * - `term`: (Optional) A search term filter as a string.
22
34
  *
23
35
  * - `appliedFiltersCount` {ComputedRef<number>}: A computed property that returns the total count of applied filters. Counts minPrice/maxPrice as one and counts each attribute filter separately.
24
- *
25
- * - `appliedAttributeValues` {ComputedRef<Record<string, Array<number>>>}: A computed property that returns a record mapping attribute keys to arrays of numbers representing selected values for each attribute.
26
- *
27
- * - `appliedBooleanValues` {ComputedRef<Record<string, boolean>>}: A computed property that returns a record mapping attribute keys to their boolean values for boolean filters.
28
- *
36
+ * - `appliedAttributeValues` {ComputedRef<AppliedAttributeValues>}: A computed property that returns a record mapping attribute keys to arrays of numbers representing selected values for each attribute.
37
+ * - `appliedBooleanValues` {ComputedRef<appliedBooleanValues>}: A computed property that returns a record mapping attribute keys to their boolean values for boolean filters.
29
38
  * - `areFiltersApplied` {ComputedRef<boolean>}: A computed property that returns `true` if any filters are currently applied, otherwise `false`.
30
39
  */
31
- export declare function useAppliedFilters({ filtersPrefix }?: AppliedFiltersOptions): {
32
- appliedFilter: import("vue").ComputedRef<ProductSearchQuery>;
33
- appliedFiltersCount: import("vue").ComputedRef<number>;
34
- appliedAttributeValues: import("vue").ComputedRef<Record<string, (string | number)[]>>;
35
- appliedBooleanValues: import("vue").ComputedRef<Record<string, boolean>>;
36
- areFiltersApplied: import("vue").ComputedRef<boolean>;
37
- };
40
+ export declare function useAppliedFilters(route: RouteLocationNormalizedLoadedGeneric, { filtersPrefix }?: AppliedFiltersOptions): UseAppliedFiltersReturn;
@@ -1,8 +1,6 @@
1
1
  import { computed } from "vue";
2
- import { useRoute } from "#app/composables/router";
3
2
  import { parseFilterDataFromRoute } from "../utils/filters.js";
4
- export function useAppliedFilters({ filtersPrefix } = { filtersPrefix: "filters" }) {
5
- const route = useRoute();
3
+ export function useAppliedFilters(route, { filtersPrefix } = { filtersPrefix: "filters" }) {
6
4
  const appliedFilter = computed(() => {
7
5
  return parseFilterDataFromRoute(route, filtersPrefix);
8
6
  });
@@ -14,24 +12,25 @@ export function useAppliedFilters({ filtersPrefix } = { filtersPrefix: "filters"
14
12
  }
15
13
  return count;
16
14
  });
17
- const appliedAttributeValues = computed(() => {
18
- const values = {};
19
- appliedFilter.value?.attributes?.forEach((attribute) => {
20
- if (attribute.type !== "attributes" || !attribute.key) {
21
- return;
22
- }
23
- values[attribute.key] = attribute.values;
24
- });
25
- return values;
26
- });
15
+ const appliedAttributeValues = computed(
16
+ () => {
17
+ const attributes = appliedFilter.value?.attributes ?? [];
18
+ return attributes.reduce((values, attribute) => {
19
+ if (attribute.type === "attributes" && attribute.key) {
20
+ values[attribute.key] = attribute.values;
21
+ }
22
+ return values;
23
+ }, {});
24
+ }
25
+ );
27
26
  const appliedBooleanValues = computed(() => {
28
- const values = {};
29
- appliedFilter.value?.attributes?.forEach((attribute) => {
27
+ const attributes = appliedFilter.value?.attributes ?? [];
28
+ return attributes.reduce((values, attribute) => {
30
29
  if (attribute.type === "boolean") {
31
30
  values[attribute.key] = attribute.value;
32
31
  }
33
- });
34
- return values;
32
+ return values;
33
+ }, {});
35
34
  });
36
35
  const areFiltersApplied = computed(() => {
37
36
  return appliedFiltersCount.value > 0;
@@ -1,12 +1,12 @@
1
1
  import { useFilters } from '@scayle/storefront-nuxt/composables';
2
2
  import { type ComputedRef, type MaybeRefOrGetter } from 'vue';
3
- import type { LocationQuery } from 'vue-router';
3
+ import type { LocationQuery, RouteLocationNormalizedLoadedGeneric } from 'vue-router';
4
4
  import type { FilterItemWithValues } from '../../types/filter.js';
5
5
  export type ProductListFilterOptions = Partial<{
6
6
  immediate: boolean;
7
7
  keyPrefix: string;
8
8
  }>;
9
- type UseProductListFilterReturn = Pick<Awaited<ReturnType<typeof useFilters>>, 'error' | 'status' | 'fetch' | 'fetching'> & {
9
+ export type UseProductListFilterReturn = Pick<Awaited<ReturnType<typeof useFilters>>, 'error' | 'status' | 'fetch' | 'fetching'> & {
10
10
  availableFilters: ComputedRef<FilterItemWithValues[]>;
11
11
  clearedPriceQuery: ComputedRef<LocationQuery | undefined>;
12
12
  unfilteredCount: ComputedRef<number>;
@@ -14,12 +14,13 @@ type UseProductListFilterReturn = Pick<Awaited<ReturnType<typeof useFilters>>, '
14
14
  /**
15
15
  * Composable to manage filters based on current category ID and route query parameters.
16
16
  *
17
+ * @param {RouteLocationNormalizedLoadedGeneric} [route] - Route object to handle the queries.
17
18
  * @param {number} [currentCategoryId] - current/active category ID
18
- * @param {ProductListingFilterOptions} options - Configuration options for filters.
19
+ * @param {ProductListFilterOptions} options - Configuration options for filters.
19
20
  * @param {string} options.immediate - Will enable "immediate" flag via "useFilters" composable in it.
20
21
  * @param {string} options.keyPrefix - The prefix used to construct "key" via "useFilters" payload. Default is "base".
21
22
  *
22
- * @returns {UseProductListFilterReturn} - An object containing the following properties:
23
+ * @returns {UseProductListFilterReturn} - An object containing reactive data, computed properties and async RPC call for managing filters:
23
24
  * - `fetch`: call for fetching the filters.
24
25
  * - `error`: returns "useFilters" error.
25
26
  * - `status`: returns "useFilters" status.
@@ -28,5 +29,4 @@ type UseProductListFilterReturn = Pick<Awaited<ReturnType<typeof useFilters>>, '
28
29
  * - `unfilteredCount` {ComputedRef<number>}: returns unfiltered filters count.
29
30
  * - `clearedPriceQuery` {ComputedRef<LocationQuery | undefined>}: constructs an empty query object.
30
31
  */
31
- export declare function useProductListFilter(currentCategoryId?: MaybeRefOrGetter<number | undefined>, options?: ProductListFilterOptions): UseProductListFilterReturn & Promise<UseProductListFilterReturn>;
32
- export {};
32
+ export declare function useProductListFilter(route: RouteLocationNormalizedLoadedGeneric, currentCategoryId?: MaybeRefOrGetter<number | undefined>, options?: ProductListFilterOptions): UseProductListFilterReturn & Promise<UseProductListFilterReturn>;
@@ -2,12 +2,10 @@ import { extendPromise } from "@scayle/storefront-nuxt";
2
2
  import { useFilters } from "@scayle/storefront-nuxt/composables";
3
3
  import { computed, toValue } from "vue";
4
4
  import { getClearedPriceQuery } from "../utils/filters.js";
5
- import { useRoute } from "#app/composables/router";
6
5
  import { useAppliedFilters } from "./useAppliedFilters.js";
7
- export function useProductListFilter(currentCategoryId, options = {}) {
8
- const route = useRoute();
6
+ export function useProductListFilter(route, currentCategoryId, options = {}) {
9
7
  const { immediate = true, keyPrefix = "base" } = options;
10
- const { appliedFilter } = useAppliedFilters();
8
+ const { appliedFilter } = useAppliedFilters(route);
11
9
  const filterData = useFilters({
12
10
  params: computed(() => ({
13
11
  categoryId: toValue(currentCategoryId),
@@ -1,8 +1,15 @@
1
- import { APISortOption, APISortOrder, type ProductSortConfig } from '@scayle/storefront-nuxt';
1
+ import { type ProductSortConfig } from '@scayle/storefront-nuxt';
2
+ import { type ComputedRef } from 'vue';
3
+ import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
2
4
  type SelectedSort = ProductSortConfig & {
3
5
  key: string;
4
6
  label: string;
5
7
  };
8
+ export interface UseProductListSortReturn {
9
+ selectedSort: ComputedRef<SelectedSort | undefined>;
10
+ sortLinks: ComputedRef<SelectedSort[]>;
11
+ isDefaultSortSelected: ComputedRef<boolean>;
12
+ }
6
13
  export interface ProductListSortOptions {
7
14
  sortingOptions?: SelectedSort[];
8
15
  defaultSortingKey?: string;
@@ -10,7 +17,8 @@ export interface ProductListSortOptions {
10
17
  /**
11
18
  * Custom composable to handle product list sorting.
12
19
  *
13
- * @param {ProductListOptions} [options] - Options for configuring the sorting behavior.
20
+ * @param {RouteLocationNormalizedLoadedGeneric} [route] - Route object to handle the queries and path.
21
+ * @param {ProductListSortOptions} [options] - Options for configuring the sorting behavior.
14
22
  * @param {SelectedSort[]} [options.sortingOptions] - Array of available sorting options.
15
23
  * @param {string} [options.defaultSortingKey] - Default sorting key to be applied if no specific sort is selected.
16
24
  * @returns - computed properties and methods related to sorting:
@@ -18,23 +26,5 @@ export interface ProductListSortOptions {
18
26
  * - {ComputedRef<SelectedSort[]>} sortLinks - Links with updated query parameters for each sorting option.
19
27
  * - {ComputedRef<boolean>} isDefaultSortSelected - Boolean indicating if the default sort is selected.
20
28
  */
21
- export declare function useProductListSort({ sortingOptions, defaultSortingKey }?: ProductListSortOptions): {
22
- selectedSort: import("vue").ComputedRef<SelectedSort | undefined>;
23
- sortLinks: import("vue").ComputedRef<{
24
- to: {
25
- path: string;
26
- query: {
27
- sort: string;
28
- };
29
- };
30
- by?: APISortOption;
31
- direction?: APISortOrder;
32
- score?: "category_scores" | "brand_scores";
33
- channel?: string;
34
- sortingKey?: string | string[];
35
- key: string;
36
- label: string;
37
- }[]>;
38
- isDefaultSortSelected: import("vue").ComputedRef<boolean>;
39
- };
29
+ export declare function useProductListSort(route: RouteLocationNormalizedLoadedGeneric, { sortingOptions, defaultSortingKey }?: ProductListSortOptions): UseProductListSortReturn;
40
30
  export {};
@@ -3,8 +3,7 @@ import {
3
3
  APISortOrder
4
4
  } from "@scayle/storefront-nuxt";
5
5
  import { computed } from "vue";
6
- import { useRoute } from "#app/composables/router";
7
- export function useProductListSort({ sortingOptions, defaultSortingKey } = {
6
+ export function useProductListSort(route, { sortingOptions, defaultSortingKey } = {
8
7
  sortingOptions: [
9
8
  {
10
9
  key: "top_seller",
@@ -39,7 +38,6 @@ export function useProductListSort({ sortingOptions, defaultSortingKey } = {
39
38
  ],
40
39
  defaultSortingKey: "top_seller"
41
40
  }) {
42
- const route = useRoute();
43
41
  const selectedSort = computed(() => {
44
42
  const sort = route.query.sort && sortingOptions?.find((option) => option.key === route.query.sort);
45
43
  if (!sort) {
@@ -1,9 +1,10 @@
1
1
  import { type ComputedRef, type Ref } from 'vue';
2
2
  import type { BreadcrumbItem, Category } from '@scayle/storefront-nuxt';
3
3
  import type { BreadcrumbList, WithContext } from 'schema-dts';
4
+ import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
4
5
  type CanonicalLink = Record<'rel' | 'key' | 'href', string>;
5
6
  type UrlParams = Record<'baseUrl' | 'fullPath', string>;
6
- interface UseProductListingSeoData {
7
+ export interface UseProductListingSeoDataReturn {
7
8
  title: ComputedRef<string>;
8
9
  activeCategoryName: ComputedRef<string | undefined>;
9
10
  robots: ComputedRef<string>;
@@ -15,6 +16,7 @@ interface UseProductListingSeoData {
15
16
  *
16
17
  * @param {Ref<Category>} category - Reactive category
17
18
  * @param {BreadcrumbItem[]} breadcrumbs - Breadcrumb items.
19
+ * @param {RouteLocationNormalizedLoadedGeneric} [route] - route object to handle queries.
18
20
  * @param {UrlParams} URL params - URL data for constructing the canonical link.
19
21
  *
20
22
  * @returns {UseProductListingSeoData} An object containing reactive data and computed properties for managing SEO data.
@@ -25,5 +27,5 @@ interface UseProductListingSeoData {
25
27
  * @property {ComputedRef<CanonicalLink[]>} canonicalLink - A computed canonical link.
26
28
  * @property {ComputedRef<WithContext<BreadcrumbList> | []>} categoryBreadcrumbSchema - A computed breadcrumb schema.
27
29
  */
28
- export declare function useProductListingSeoData(category: Ref<Category | undefined | null>, breadcrumbs: BreadcrumbItem[], { baseUrl, fullPath }: UrlParams): UseProductListingSeoData;
30
+ export declare function useProductListingSeoData(category: Ref<Category | undefined | null>, breadcrumbs: BreadcrumbItem[], route: RouteLocationNormalizedLoadedGeneric, { baseUrl, fullPath }: UrlParams): UseProductListingSeoDataReturn;
29
31
  export {};
@@ -5,9 +5,9 @@ import {
5
5
  } from "@scayle/storefront-nuxt";
6
6
  import { useAppliedFilters } from "./useAppliedFilters.js";
7
7
  import { useProductListSort } from "./useProductListSort.js";
8
- export function useProductListingSeoData(category, breadcrumbs, { baseUrl, fullPath }) {
9
- const { areFiltersApplied } = useAppliedFilters();
10
- const { isDefaultSortSelected } = useProductListSort();
8
+ export function useProductListingSeoData(category, breadcrumbs, route, { baseUrl, fullPath }) {
9
+ const { areFiltersApplied } = useAppliedFilters(route);
10
+ const { isDefaultSortSelected } = useProductListSort(route);
11
11
  const robots = computed(() => {
12
12
  return !areFiltersApplied.value && isDefaultSortSelected.value ? "index,follow" : "noindex,follow";
13
13
  });
@@ -1,18 +1,31 @@
1
1
  import { type FetchProductsByCategoryParams, type Product, type ProductWith } from '@scayle/storefront-nuxt';
2
- import type { LocationQuery } from 'vue-router';
3
2
  import { type MaybeRefOrGetter, type ComputedRef } from 'vue';
4
3
  import { useProducts } from '@scayle/storefront-nuxt/composables';
5
4
  import type { Pagination } from '@scayle/storefront-api';
6
- type UseProductsByCategoryReturn = Pick<Awaited<ReturnType<typeof useProducts>>, 'error' | 'fetching'> & {
5
+ import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router';
6
+ export type UseProductsByCategoryReturn = Pick<Awaited<ReturnType<typeof useProducts>>, 'error' | 'fetching'> & {
7
7
  products: ComputedRef<Product[]>;
8
8
  pagination: ComputedRef<Pagination | undefined>;
9
9
  totalProductsCount: ComputedRef<number>;
10
10
  paginationOffset: ComputedRef<number>;
11
11
  };
12
+ export interface ProductsByCategoryOptions {
13
+ params: Omit<Partial<FetchProductsByCategoryParams>, 'category' | 'categoryId'>;
14
+ fetchingOptions: Partial<{
15
+ lazy: boolean;
16
+ immediate: boolean;
17
+ }>;
18
+ productsPerPage: number;
19
+ fetchProductsCacheTtl: number;
20
+ cacheKeyPrefix: string;
21
+ fetchingKey?: string;
22
+ withParams?: ProductWith;
23
+ }
12
24
  /**
13
25
  * Composable to fetch and manage products by category with configurable options.
14
26
  *
15
27
  * @param {MaybeRefOrGetter<number>} categoryId - The ID of the category to fetch products for. Accepts a ref, computed, or static number.
28
+ * @param {RouteLocationNormalizedLoadedGeneric} [route] - Route object to handle the queries.
16
29
  * @param {ProductsByCategoryOptions} options - Configuration options for fetching products.
17
30
  * @param {Partial<FetchProductsByCategoryParams>} options.params - Parameters to control product fetch behavior, excluding `category` and `categoryId`.
18
31
  * @param {Partial<{ lazy: boolean; immediate: boolean }>} options.fetchingOptions - Options for fetch timing, such as lazy loading or immediate fetching.
@@ -30,18 +43,4 @@ type UseProductsByCategoryReturn = Pick<Awaited<ReturnType<typeof useProducts>>,
30
43
  * @property {Ref<boolean>} fetching - A reactive boolean indicating if products are currently being fetched.
31
44
  * @property {Ref<Error | null>} error - A reactive error object, null if no error occurred.
32
45
  */
33
- export interface ProductsByCategoryOptions {
34
- params: Omit<Partial<FetchProductsByCategoryParams>, 'category' | 'categoryId'>;
35
- fetchingOptions: Partial<{
36
- lazy: boolean;
37
- immediate: boolean;
38
- }>;
39
- productsPerPage: number;
40
- fetchProductsCacheTtl: number;
41
- cacheKeyPrefix: string;
42
- fetchingKey?: string;
43
- withParams?: ProductWith;
44
- query: LocationQuery;
45
- }
46
- export declare function useProductsByCategory(categoryId: MaybeRefOrGetter<number>, { params, withParams, cacheKeyPrefix, fetchingKey, fetchingOptions, fetchProductsCacheTtl, productsPerPage, query, }?: Partial<ProductsByCategoryOptions>): UseProductsByCategoryReturn & Promise<UseProductsByCategoryReturn>;
47
- export {};
46
+ export declare function useProductsByCategory(categoryId: MaybeRefOrGetter<number>, route: RouteLocationNormalizedLoadedGeneric, { params, withParams, cacheKeyPrefix, fetchingKey, fetchingOptions, fetchProductsCacheTtl, productsPerPage, }?: Partial<ProductsByCategoryOptions>): UseProductsByCategoryReturn & Promise<UseProductsByCategoryReturn>;
@@ -8,15 +8,14 @@ import { useAppliedFilters } from "./useAppliedFilters.js";
8
8
  const PRODUCTS_PER_PAGE = 24;
9
9
  const DEFAULT_CACHE_TTL = 1e3;
10
10
  const DEFAULT_CACHE_KEY_PREFIX = "PLP";
11
- export function useProductsByCategory(categoryId, {
11
+ export function useProductsByCategory(categoryId, route, {
12
12
  params,
13
13
  withParams,
14
14
  cacheKeyPrefix,
15
15
  fetchingKey,
16
16
  fetchingOptions,
17
17
  fetchProductsCacheTtl,
18
- productsPerPage,
19
- query
18
+ productsPerPage
20
19
  } = {
21
20
  params: {
22
21
  includeSoldOut: false,
@@ -28,13 +27,13 @@ export function useProductsByCategory(categoryId, {
28
27
  fetchProductsCacheTtl: DEFAULT_CACHE_TTL,
29
28
  cacheKeyPrefix: DEFAULT_CACHE_KEY_PREFIX
30
29
  }) {
31
- const { selectedSort } = useProductListSort();
32
- const { appliedFilter } = useAppliedFilters();
30
+ const { selectedSort } = useProductListSort(route);
31
+ const { appliedFilter } = useAppliedFilters(route);
33
32
  const productsData = useProducts({
34
33
  params: () => ({
35
- ...query?.page && { page: +query.page },
34
+ ...route?.query.page && { page: +route?.query.page },
36
35
  sort: selectedSort.value,
37
- perPage: query?.products_per_page ? parseInt(query.products_per_page, 10) : productsPerPage,
36
+ perPage: route?.query.products_per_page ? parseInt(route?.query.products_per_page, 10) : productsPerPage,
38
37
  with: withParams,
39
38
  categoryId: toValue(categoryId),
40
39
  includeSoldOut: params?.includeSoldOut,
@@ -51,11 +50,15 @@ export function useProductsByCategory(categoryId, {
51
50
  key: fetchingKey || `${toValue(categoryId)}-products`,
52
51
  options: fetchingOptions
53
52
  });
54
- const products = computed(() => productsData.data.value?.products ?? []);
55
- const pagination = computed(() => productsData.data.value?.pagination);
56
- const totalProductsCount = computed(
57
- () => productsData.data.value?.pagination.total ?? 0
58
- );
53
+ const products = computed(() => {
54
+ return productsData.data.value?.products ?? [];
55
+ });
56
+ const pagination = computed(() => {
57
+ return productsData.data.value?.pagination;
58
+ });
59
+ const totalProductsCount = computed(() => {
60
+ return productsData.data.value?.pagination.total ?? 0;
61
+ });
59
62
  const paginationOffset = computed(() => {
60
63
  const page = pagination.value?.page ?? 1;
61
64
  const perPage = pagination.value?.perPage ?? productsPerPage ?? PRODUCTS_PER_PAGE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-product-listing",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Collection of essential composables and utilities to work with product listing",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",