diff --git a/packages/composables/src/composables/useReview/index.ts b/packages/composables/src/composables/useReview/index.ts
index 2ee3abe5d..f020125b0 100644
--- a/packages/composables/src/composables/useReview/index.ts
+++ b/packages/composables/src/composables/useReview/index.ts
@@ -1,4 +1,8 @@
/* istanbul ignore file */
+
+/**
+ * @deprecated since version 1.0.0
+ */
import {
ComposableFunctionArgs,
Context,
diff --git a/packages/composables/src/factories/useReviewFactory.ts b/packages/composables/src/factories/useReviewFactory.ts
index 09d6310eb..bf4896af5 100644
--- a/packages/composables/src/factories/useReviewFactory.ts
+++ b/packages/composables/src/factories/useReviewFactory.ts
@@ -1,3 +1,6 @@
+/**
+ * @deprecated since version 1.0.0
+ */
import { Ref, computed } from '@vue/composition-api';
import {
ComposableFunctionArgs,
diff --git a/packages/theme/components/ProductAddReviewForm.vue b/packages/theme/components/ProductAddReviewForm.vue
index 393ac2ba1..4d8156747 100644
--- a/packages/theme/components/ProductAddReviewForm.vue
+++ b/packages/theme/components/ProductAddReviewForm.vue
@@ -116,14 +116,13 @@ import {
useRoute,
useContext,
} from '@nuxtjs/composition-api';
-import { useReview } from '@vue-storefront/magento';
import { extend, ValidationObserver, ValidationProvider } from 'vee-validate';
import { min, oneOf, required } from 'vee-validate/dist/rules';
import {
SfInput, SfButton, SfSelect, SfTextarea,
} from '@storefront-ui/vue';
import { reviewGetters, userGetters } from '~/getters';
-import { useUser } from '~/composables';
+import { useUser, useReview } from '~/composables';
extend('required', {
...required,
@@ -168,16 +167,16 @@ export default defineComponent({
typeof $recaptcha !== 'undefined' && $config.isRecaptcha,
);
const {
- loading, loadReviewMetadata, metadata, error,
- } = useReview(
- `productReviews-${id}`,
- );
+ loading, loadReviewMetadata, error,
+ } = useReview();
const { isAuthenticated, user } = useUser();
const reviewSent = ref(false);
const form = ref(BASE_FORM(id));
+ const metadata = ref([]);
+
const ratingMetadata = computed(() => reviewGetters.getReviewMetadata([...metadata.value]));
const formSubmitValue = computed(() => {
@@ -234,7 +233,7 @@ export default defineComponent({
};
onBeforeMount(async () => {
- await loadReviewMetadata();
+ metadata.value = await loadReviewMetadata();
});
return {
diff --git a/packages/theme/components/__tests__/ProductAddReviewForm.spec.js b/packages/theme/components/__tests__/ProductAddReviewForm.spec.js
index 9269b2ea2..ed057e20b 100644
--- a/packages/theme/components/__tests__/ProductAddReviewForm.spec.js
+++ b/packages/theme/components/__tests__/ProductAddReviewForm.spec.js
@@ -2,13 +2,13 @@
import { waitFor } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { useRoute } from '@nuxtjs/composition-api';
-import { useUser, useReview } from '@vue-storefront/magento';
import {
render,
useUserMock,
useReviewMock,
} from '~/test-utils';
+import { useReview, useUser } from '~/composables';
import ProductAddReviewForm from '../ProductAddReviewForm';
jest.mock('@vue-storefront/magento', () => {
@@ -16,7 +16,6 @@ jest.mock('@vue-storefront/magento', () => {
return {
...originalModule,
useUser: jest.fn(),
- useReview: jest.fn(),
};
});
@@ -30,6 +29,14 @@ jest.mock('@nuxtjs/composition-api', () => {
};
});
+jest.mock('~/composables/useReview', () => {
+ const originalModule = jest.requireActual('~/composables/useReview');
+ return {
+ ...originalModule,
+ useReview: jest.fn(),
+ };
+});
+
describe.skip('', () => {
it('Form fields are rendered and validated', async () => {
useUser.mockReturnValue(useUserMock());
diff --git a/packages/theme/composables/context.d.ts b/packages/theme/composables/context.d.ts
new file mode 100644
index 000000000..fa267d60a
--- /dev/null
+++ b/packages/theme/composables/context.d.ts
@@ -0,0 +1,8 @@
+import { ApiClientMethods, IntegrationContext } from '@vue-storefront/core';
+import { ClientInstance, Config, MagentoApiMethods } from '@vue-storefront/magento-api';
+
+declare module '@vue-storefront/core' {
+ export interface Context {
+ $magento: IntegrationContext>;
+ }
+}
diff --git a/packages/theme/composables/index.ts b/packages/theme/composables/index.ts
index 90bb0f03a..57859973e 100644
--- a/packages/theme/composables/index.ts
+++ b/packages/theme/composables/index.ts
@@ -16,6 +16,7 @@ export { default as useCart } from './useCart';
export { default as useContent } from './useContent';
export { default as useCategorySearch } from './useCategorySearch';
export { default as useProduct } from './useProduct';
+export { default as useReview } from './useReview';
export { default as useShipping } from './useShipping';
export { default as useShippingProvider } from './useShippingProvider';
export { default as useRelatedProducts } from './useRelatedProducts';
diff --git a/packages/theme/composables/useCart/useCart.d.ts b/packages/theme/composables/useCart/useCart.d.ts
index 920cae894..d0e45d708 100644
--- a/packages/theme/composables/useCart/useCart.d.ts
+++ b/packages/theme/composables/useCart/useCart.d.ts
@@ -17,7 +17,7 @@ export interface UseCartInterface {
clear: (params: ComposableFunctionArgs<{ realCart?: boolean; }>) => Promise;
applyCoupon: (params: ComposableFunctionArgs<{ couponCode: string; }>) => Promise;
removeCoupon: (params: ComposableFunctionArgs<{}>) => Promise;
- isInCart: (context: Context, params: { currentCart: CART; product: PRODUCT }) => boolean;
+ isInCart: (params: { currentCart: CART; product: PRODUCT }) => boolean;
setCart: (newCart: CART) => void;
cart: ComputedRef;
loading: Ref;
diff --git a/packages/theme/composables/useProduct/commands/getProductDetailsCommand.ts b/packages/theme/composables/useProduct/commands/getProductDetailsCommand.ts
index b46fcaf1b..e5a35ffd1 100644
--- a/packages/theme/composables/useProduct/commands/getProductDetailsCommand.ts
+++ b/packages/theme/composables/useProduct/commands/getProductDetailsCommand.ts
@@ -10,6 +10,6 @@ export const getProductDetailsCommand = {
...searchParams,
} as GetProductSearchParams, customQuery);
- return result.data?.products ?? [];
+ return result.data?.products;
},
};
diff --git a/packages/theme/composables/useProduct/commands/getProductListCommand.ts b/packages/theme/composables/useProduct/commands/getProductListCommand.ts
index 9873677b9..287c6b2d6 100644
--- a/packages/theme/composables/useProduct/commands/getProductListCommand.ts
+++ b/packages/theme/composables/useProduct/commands/getProductListCommand.ts
@@ -8,6 +8,6 @@ export const getProductListCommand = {
.api
.products(searchParams as GetProductSearchParams, customQuery);
- return result.data?.products ?? [];
+ return result.data?.products;
},
};
diff --git a/packages/theme/composables/useProduct/index.ts b/packages/theme/composables/useProduct/index.ts
index 3e990d3a9..5cc4883be 100644
--- a/packages/theme/composables/useProduct/index.ts
+++ b/packages/theme/composables/useProduct/index.ts
@@ -5,6 +5,7 @@ import {
import { ref, useContext } from '@nuxtjs/composition-api';
import { getProductListCommand } from '~/composables/useProduct/commands/getProductListCommand';
import { getProductDetailsCommand } from '~/composables/useProduct/commands/getProductDetailsCommand';
+import { ProductsListQuery } from '~/modules/GraphQL/types';
export const useProduct = (id: string) => {
const loading = ref(false);
@@ -17,7 +18,7 @@ export const useProduct = (id: string) => {
const getProductList = async (searchParams) => {
Logger.debug(`useProduct/${id}/getProductList`, searchParams);
- let products = [];
+ let products : ProductsListQuery['products'] = null;
try {
loading.value = true;
@@ -35,7 +36,7 @@ export const useProduct = (id: string) => {
const getProductDetails = async (searchParams) => {
Logger.debug(`useProduct/${id}/getProductDetails`, searchParams);
- let products = [];
+ let products : ProductsListQuery['products'] = null;
try {
loading.value = true;
diff --git a/packages/theme/composables/useReview/commands/addReviewCommand.ts b/packages/theme/composables/useReview/commands/addReviewCommand.ts
new file mode 100644
index 000000000..0d3912a98
--- /dev/null
+++ b/packages/theme/composables/useReview/commands/addReviewCommand.ts
@@ -0,0 +1,20 @@
+import { Context, Logger } from '@vue-storefront/core';
+import {CreateProductReviewInput } from '~/modules/GraphQL/types';
+import { ComposableFunctionArgs } from '~/composables/types';
+
+export const addReviewCommand = {
+ execute: async (context: Context, params: ComposableFunctionArgs) => {
+ Logger.debug('[Magento] add review params input:', JSON.stringify(params, null, 2));
+
+ const {
+ customQuery,
+ ...input
+ } = params;
+
+ const { data } = await context.$magento.api.createProductReview(input);
+
+ Logger.debug('[Result]:', { data });
+
+ return data?.createProductReview?.review ?? {};
+ },
+};
diff --git a/packages/theme/composables/useReview/commands/loadCustomerReviewsCommand.ts b/packages/theme/composables/useReview/commands/loadCustomerReviewsCommand.ts
new file mode 100644
index 000000000..d8ea60761
--- /dev/null
+++ b/packages/theme/composables/useReview/commands/loadCustomerReviewsCommand.ts
@@ -0,0 +1,14 @@
+import { ComposableFunctionArgs, Context, Logger } from '@vue-storefront/core';
+import { CustomerProductReviewParams } from '@vue-storefront/magento-api';
+
+export const loadCustomerReviewsCommand = {
+ execute: async (context: Context, params?: ComposableFunctionArgs) => {
+ Logger.debug('[Magento] load customer review based on:', { params });
+
+ const { data } = await context.$magento.api.customerProductReview(params);
+
+ Logger.debug('[Result]:', { data });
+
+ return data?.customer ?? {};
+ },
+};
diff --git a/packages/theme/composables/useReview/commands/loadReviewMetadataCommand.ts b/packages/theme/composables/useReview/commands/loadReviewMetadataCommand.ts
new file mode 100644
index 000000000..63d77ac35
--- /dev/null
+++ b/packages/theme/composables/useReview/commands/loadReviewMetadataCommand.ts
@@ -0,0 +1,13 @@
+import { Context, Logger } from '@vue-storefront/core';
+
+export const loadReviewMetadataCommand = {
+ execute: async (context: Context) => {
+ Logger.debug('[Magento] load review metadata');
+
+ const { data } = await context.$magento.api.productReviewRatingsMetadata();
+
+ Logger.debug('[Result]:', { data });
+
+ return data?.productReviewRatingsMetadata?.items ?? [];
+ },
+};
diff --git a/packages/theme/composables/useReview/commands/searchReviewsCommand.ts b/packages/theme/composables/useReview/commands/searchReviewsCommand.ts
new file mode 100644
index 000000000..e6a20bf71
--- /dev/null
+++ b/packages/theme/composables/useReview/commands/searchReviewsCommand.ts
@@ -0,0 +1,20 @@
+import { Context, Logger } from '@vue-storefront/core';
+import { ComposableFunctionArgs } from '~/composables/types';
+import { GetProductSearchParams } from '~/composables/useProduct/useProduct';
+
+export const searchReviewsCommand = {
+ execute: async (context: Context, params?: ComposableFunctionArgs) => {
+ Logger.debug('[Magento] search review params input:', JSON.stringify(params, null, 2));
+
+ const {
+ customQuery,
+ ...input
+ } = params;
+
+ const { data } = await context.$magento.api.productReview(input as GetProductSearchParams);
+
+ Logger.debug('[Result]:', { data });
+
+ return data?.products?.items ?? [];
+ },
+};
diff --git a/packages/theme/composables/useReview/index.ts b/packages/theme/composables/useReview/index.ts
new file mode 100644
index 000000000..d0135f761
--- /dev/null
+++ b/packages/theme/composables/useReview/index.ts
@@ -0,0 +1,93 @@
+/* eslint-disable consistent-return */
+import { ref, useContext } from '@nuxtjs/composition-api';
+import { ComposableFunctionArgs, Context, Logger } from '@vue-storefront/core';
+import { CreateProductReviewInput } from '~/modules/GraphQL/types';
+import { UseReviewErrors } from './useReview';
+import { addReviewCommand } from './commands/addReviewCommand';
+import { loadCustomerReviewsCommand } from './commands/loadCustomerReviewsCommand';
+import { loadReviewMetadataCommand } from './commands/loadReviewMetadataCommand';
+import { searchReviewsCommand } from './commands/searchReviewsCommand';
+import { GetProductSearchParams } from '../useProduct/useProduct';
+
+export const useReview = () => {
+ const loading = ref(false);
+ const error = ref({
+ search: null,
+ addReview: null,
+ loadReviewMetadata: null,
+ loadCustomerReviews: null,
+ });
+
+ const { app } = useContext();
+ const context = app.$vsf as Context;
+
+ const search = async (searchParams: ComposableFunctionArgs) => {
+ Logger.debug('useReview/search', searchParams);
+
+ try {
+ loading.value = true;
+ error.value.search = null;
+ return await searchReviewsCommand.execute(context, searchParams);
+ } catch (err) {
+ error.value.search = err;
+ Logger.error('useReview/search', err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ const loadCustomerReviews = async () => {
+ Logger.debug('useReview/loadCustomerReviews');
+
+ try {
+ loading.value = true;
+ error.value.loadCustomerReviews = null;
+ return await loadCustomerReviewsCommand.execute(context);
+ } catch (err) {
+ error.value.loadCustomerReviews = err;
+ Logger.error('useReview/loadCustomerReviews', err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ const loadReviewMetadata = async () => {
+ Logger.debug('useReview/loadReviewMetadata');
+
+ try {
+ loading.value = true;
+ error.value.loadReviewMetadata = null;
+ return await loadReviewMetadataCommand.execute(context);
+ } catch (err) {
+ error.value.loadReviewMetadata = err;
+ Logger.error('useReview/loadReviewMetadata', err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ const addReview = async (params: ComposableFunctionArgs) => {
+ Logger.debug('useReview/addReview', params);
+ try {
+ loading.value = true;
+ error.value.addReview = null;
+ return await addReviewCommand.execute(context, params);
+ } catch (err) {
+ error.value.addReview = err;
+ Logger.error('useReview/addReview', err);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ return {
+ search,
+ addReview,
+ loadReviewMetadata,
+ loadCustomerReviews,
+ loading,
+ error,
+ };
+};
+
+export default useReview;
diff --git a/packages/theme/composables/useReview/useReview.d.ts b/packages/theme/composables/useReview/useReview.d.ts
new file mode 100644
index 000000000..57f19c248
--- /dev/null
+++ b/packages/theme/composables/useReview/useReview.d.ts
@@ -0,0 +1,6 @@
+export interface UseReviewErrors {
+ search: Error;
+ addReview: Error;
+ loadReviewMetadata: Error;
+ loadCustomerReviews: Error;
+}
diff --git a/packages/theme/pages/MyAccount/MyReviews.vue b/packages/theme/pages/MyAccount/MyReviews.vue
index 09810e736..5c8b0b6e5 100644
--- a/packages/theme/pages/MyAccount/MyReviews.vue
+++ b/packages/theme/pages/MyAccount/MyReviews.vue
@@ -55,9 +55,11 @@
import {
SfTabs, SfLoader, SfReview, SfRating,
} from '@storefront-ui/vue';
-import { useReview } from '@vue-storefront/magento';
import { reviewGetters } from '~/getters';
-import { computed, defineComponent, onMounted } from '@nuxtjs/composition-api';
+import {
+ computed, defineComponent, onMounted, ref,
+} from '@nuxtjs/composition-api';
+import { useReview } from '~/composables';
export default defineComponent({
name: 'MyReviews',
@@ -68,14 +70,13 @@ export default defineComponent({
SfRating,
},
setup() {
- const { reviews, loading, loadCustomerReviews } = useReview(
- 'productReviews-my-reviews',
- );
+ const { loading, loadCustomerReviews } = useReview();
+ const reviews = ref([]);
const userReviews = computed(() => reviewGetters.getItems(reviews.value));
onMounted(async () => {
- await loadCustomerReviews();
+ reviews.value = await loadCustomerReviews();
});
return {
diff --git a/packages/theme/pages/Product.vue b/packages/theme/pages/Product.vue
index 6ced7da86..bde1ca7d8 100644
--- a/packages/theme/pages/Product.vue
+++ b/packages/theme/pages/Product.vue
@@ -270,7 +270,6 @@ import {
SfSelect,
SfTabs,
} from '@storefront-ui/vue';
-import { useReview } from '@vue-storefront/magento';
import {
ref,
computed,
@@ -282,7 +281,7 @@ import {
import { useCache, CacheTagPrefix } from '@vue-storefront/cache';
import { productGetters, reviewGetters } from '~/getters';
import {
- useProduct, useCart, useWishlist, useUser,
+ useProduct, useCart, useWishlist, useUser, useReview,
} from '~/composables';
import { productData } from '~/helpers/product/productData';
import cacheControl from '~/helpers/cacheControl';
@@ -341,11 +340,11 @@ export default defineComponent({
const { getProductDetails, loading: productLoading } = useProduct();
const { addItem, loading } = useCart();
const {
- reviews: productReviews,
search: searchReviews,
loading: reviewsLoading,
addReview,
- } = useReview(`productReviews-${id}`);
+ } = useReview();
+ const productReviews = ref([]);
const { isAuthenticated } = useUser();
const { addItem: addItemToWishlist, isInWishlist } = useWishlist();
const { error: nuxtError, app } = useContext();
@@ -482,7 +481,7 @@ export default defineComponent({
if (product?.value?.length === 0) nuxtError({ statusCode: 404 });
- await searchReviews(baseSearchQuery);
+ productReviews.value = await searchReviews(baseSearchQuery);
const tags = [
{