@shopgate/pwa-common-commerce 7.33.3 → 7.34.0-beta.2

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.
Files changed (110) hide show
  1. package/category/constants/Portals.js +10 -0
  2. package/filter/actions/helpers/buildRequestFilters.d.ts +9 -0
  3. package/filter/actions/helpers/buildRequestFilters.d.ts.map +1 -0
  4. package/filter/constants/Pipelines.d.ts +2 -0
  5. package/filter/constants/Pipelines.d.ts.map +1 -0
  6. package/filter/selectors/index.d.ts +42 -0
  7. package/filter/selectors/index.d.ts.map +1 -0
  8. package/package.json +3 -3
  9. package/product/constants/Pipelines.d.ts +13 -0
  10. package/product/constants/Pipelines.d.ts.map +1 -0
  11. package/product/constants/Portals.js +14 -0
  12. package/product/constants/index.d.ts +67 -0
  13. package/product/constants/index.d.ts.map +1 -0
  14. package/product/helpers/index.d.ts +5 -0
  15. package/product/helpers/index.d.ts.map +1 -0
  16. package/product/selectors/product.d.ts +513 -0
  17. package/product/selectors/product.d.ts.map +1 -0
  18. package/product/subscriptions/index.js +9 -2
  19. package/reviews/action-creators/errorProductReviews.d.ts +19 -0
  20. package/reviews/action-creators/errorProductReviews.d.ts.map +1 -0
  21. package/reviews/action-creators/errorProductReviews.js +5 -4
  22. package/reviews/action-creators/errorReviews.d.ts +19 -0
  23. package/reviews/action-creators/errorReviews.d.ts.map +1 -0
  24. package/reviews/action-creators/errorReviews.js +5 -4
  25. package/reviews/action-creators/receiveProductReviews.d.ts +25 -0
  26. package/reviews/action-creators/receiveProductReviews.d.ts.map +1 -0
  27. package/reviews/action-creators/receiveProductReviews.js +10 -7
  28. package/reviews/action-creators/receiveReviewVote.d.ts +18 -0
  29. package/reviews/action-creators/receiveReviewVote.d.ts.map +1 -0
  30. package/reviews/action-creators/receiveReviewVote.js +15 -0
  31. package/reviews/action-creators/receiveReviews.d.ts +29 -0
  32. package/reviews/action-creators/receiveReviews.d.ts.map +1 -0
  33. package/reviews/action-creators/receiveReviews.js +13 -8
  34. package/reviews/action-creators/requestProductReviews.d.ts +21 -0
  35. package/reviews/action-creators/requestProductReviews.d.ts.map +1 -0
  36. package/reviews/action-creators/requestProductReviews.js +6 -5
  37. package/reviews/action-creators/requestReviews.d.ts +19 -0
  38. package/reviews/action-creators/requestReviews.d.ts.map +1 -0
  39. package/reviews/action-creators/requestReviews.js +5 -4
  40. package/reviews/action-creators/reviewSettings.d.ts +31 -0
  41. package/reviews/action-creators/reviewSettings.d.ts.map +1 -0
  42. package/reviews/action-creators/reviewSettings.js +28 -0
  43. package/reviews/actions/fetchProductReviewSettings.d.ts +5 -0
  44. package/reviews/actions/fetchProductReviewSettings.d.ts.map +1 -0
  45. package/reviews/actions/fetchProductReviewSettings.js +25 -0
  46. package/reviews/actions/fetchProductReviews.d.ts +17 -0
  47. package/reviews/actions/fetchProductReviews.d.ts.map +1 -0
  48. package/reviews/actions/fetchProductReviews.js +17 -9
  49. package/reviews/actions/fetchReviews.d.ts +22 -0
  50. package/reviews/actions/fetchReviews.d.ts.map +1 -0
  51. package/reviews/actions/fetchReviews.js +48 -14
  52. package/reviews/actions/submitReviewVote.d.ts +15 -0
  53. package/reviews/actions/submitReviewVote.d.ts.map +1 -0
  54. package/reviews/actions/submitReviewVote.js +46 -0
  55. package/reviews/constants/Pipelines.d.ts +7 -0
  56. package/reviews/constants/Pipelines.d.ts.map +1 -0
  57. package/reviews/constants/Pipelines.js +3 -1
  58. package/reviews/constants/index.d.ts +47 -0
  59. package/reviews/constants/index.d.ts.map +1 -0
  60. package/reviews/constants/index.js +43 -1
  61. package/reviews/helpers/filters.d.ts +16 -0
  62. package/reviews/helpers/filters.d.ts.map +1 -0
  63. package/reviews/helpers/filters.js +36 -0
  64. package/reviews/helpers/summary.d.ts +8 -0
  65. package/reviews/helpers/summary.d.ts.map +1 -0
  66. package/reviews/helpers/summary.js +55 -0
  67. package/reviews/index.js +1 -0
  68. package/reviews/reducers/index.js +5 -1
  69. package/reviews/reducers/ownReviewVotes.d.ts +13 -0
  70. package/reviews/reducers/ownReviewVotes.d.ts.map +1 -0
  71. package/reviews/reducers/ownReviewVotes.js +23 -0
  72. package/reviews/reducers/reviewSettings.d.ts +13 -0
  73. package/reviews/reducers/reviewSettings.d.ts.map +1 -0
  74. package/reviews/reducers/reviewSettings.js +33 -0
  75. package/reviews/reducers/reviewSummariesByProductId.d.ts +18 -0
  76. package/reviews/reducers/reviewSummariesByProductId.d.ts.map +1 -0
  77. package/reviews/reducers/reviewSummariesByProductId.js +27 -0
  78. package/reviews/reducers/reviewsByHash.d.ts +25 -0
  79. package/reviews/reducers/reviewsByHash.d.ts.map +1 -0
  80. package/reviews/reducers/reviewsByHash.js +70 -27
  81. package/reviews/reducers/reviewsById.d.ts +20 -0
  82. package/reviews/reducers/reviewsById.d.ts.map +1 -0
  83. package/reviews/reducers/reviewsById.js +23 -7
  84. package/reviews/reducers/reviewsByProductId.d.ts +23 -0
  85. package/reviews/reducers/reviewsByProductId.d.ts.map +1 -0
  86. package/reviews/reducers/reviewsByProductId.js +38 -23
  87. package/reviews/selectors/index.d.ts +278 -0
  88. package/reviews/selectors/index.d.ts.map +1 -0
  89. package/reviews/selectors/index.js +167 -56
  90. package/reviews/selectors/ownReviewVotes.d.ts +9 -0
  91. package/reviews/selectors/ownReviewVotes.d.ts.map +1 -0
  92. package/reviews/selectors/ownReviewVotes.js +7 -0
  93. package/reviews/selectors/reviewSettings.d.ts +63 -0
  94. package/reviews/selectors/reviewSettings.d.ts.map +1 -0
  95. package/reviews/selectors/reviewSettings.js +40 -0
  96. package/reviews/selectors/reviewSummary.d.ts +18 -0
  97. package/reviews/selectors/reviewSummary.d.ts.map +1 -0
  98. package/reviews/selectors/reviewSummary.js +45 -0
  99. package/reviews/streams/index.js +19 -1
  100. package/reviews/subscriptions/index.js +14 -1
  101. package/reviews/types/reviewSettings.d.ts +31 -0
  102. package/reviews/types/reviewSettings.d.ts.map +1 -0
  103. package/reviews/types/reviewSettings.js +1 -0
  104. package/reviews/types/reviewSummary.d.ts +16 -0
  105. package/reviews/types/reviewSummary.d.ts.map +1 -0
  106. package/reviews/types/reviewSummary.js +1 -0
  107. package/reviews/types/reviews.d.ts +190 -0
  108. package/reviews/types/reviews.d.ts.map +1 -0
  109. package/reviews/types/reviews.js +1 -0
  110. package/search/constants/Portals.js +10 -1
@@ -1,33 +1,42 @@
1
1
  import { createSelector } from 'reselect';
2
+ import { SORT_DATE_DESC } from '@shopgate/pwa-common/constants/DisplayOptions';
2
3
  import { generateResultHash } from '@shopgate/pwa-common/helpers/redux';
3
4
  import { isUserLoggedIn } from '@shopgate/pwa-common/selectors/user';
4
- import { REVIEW_PREVIEW_COUNT } from "../constants";
5
+ import { getBaseProductId as getBaseProductIdSelector } from '@shopgate/engage/product/selectors/product';
5
6
  import * as pipelines from "../constants/Pipelines";
6
- import { getBaseProductId } from "../../product/selectors/product";
7
+ import { areReviewFiltersEqual } from "../helpers/filters";
8
+ import { isReviewCursorPagination } from "./reviewSettings";
9
+ export * from "./reviewSettings";
10
+ export * from "./reviewSummary";
11
+ export * from "./ownReviewVotes";
12
+ const NO_FILTERS = {};
13
+ const getBaseProductId = getBaseProductIdSelector;
7
14
 
8
15
  /**
9
- * @param {Object} state The global state.
10
- * @return {Object}
16
+ * Selects the reviews slice.
17
+ * @param state The global state.
18
+ * @returns The reviews slice.
11
19
  */
12
20
  const getReviewsState = state => state.reviews;
13
21
 
14
22
  /**
15
- * @param {Object} state The global state.
16
- * @return {Object}
23
+ * Selects the review previews stored by product id.
24
+ * @param state The global state.
25
+ * @returns The review previews stored by product id.
17
26
  */
18
27
  const getProductReviewsExcerptState = state => state.reviews.reviewsByProductId;
19
28
 
20
29
  /**
21
30
  * Select the product reviews state.
22
- * @param {Object} state The current application state.
23
- * @return {Object} The product reviews state.
31
+ * @param state The current application state.
32
+ * @returns The product reviews state.
24
33
  */
25
34
  const getReviewsByHash = createSelector(getReviewsState, state => state.reviewsByHash);
26
35
 
27
36
  /**
28
37
  * Retrieves the fetching state for the current product's reviews.
29
- * @param {Object} state The current application state.
30
- * @return {Object|null} The reviews for a product.
38
+ * @param state The current application state.
39
+ * @returns The reviews for a product.
31
40
  */
32
41
  const getCollectionForCurrentBaseProduct = createSelector(getBaseProductId, getReviewsByHash, (productId, reviews) => {
33
42
  const hash = generateResultHash({
@@ -42,26 +51,26 @@ const getCollectionForCurrentBaseProduct = createSelector(getBaseProductId, getR
42
51
 
43
52
  /**
44
53
  * Select the product reviews state
45
- * @param {Object} state The current application state.
46
- * @return {Object} The product reviews state.
54
+ * @param state The current application state.
55
+ * @returns The product reviews state.
47
56
  */
48
57
  const getReviewsByProductId = createSelector(getReviewsState, state => state.reviewsByProductId);
49
58
 
50
59
  /**
51
60
  * Retrieves the reviews collection which contains all reviews data.
52
- * @param {Object} state The current application state.
53
- * @return {Object} The reviews collection stored as reviewId => review pairs.
61
+ * @param state The current application state.
62
+ * @returns The reviews collection stored as reviewId => review pairs.
54
63
  */
55
64
  export const getReviews = createSelector(getReviewsState, state => state.reviewsById || {});
56
65
 
57
66
  /**
58
67
  * Retrieves the number of reviews for a product
59
- * @param {Object} state The current application state.
60
- * @return {number} The total review count for a product
68
+ * @param state The current application state.
69
+ * @returns The total review count for a product
61
70
  */
62
71
  export const getProductReviewCount = createSelector(getBaseProductId, getReviewsByProductId, (productId, reviewsState) => {
63
72
  const collection = reviewsState[productId];
64
- if (!collection || !collection.totalReviewCount) {
73
+ if (!collection || typeof collection.totalReviewCount !== 'number') {
65
74
  return null;
66
75
  }
67
76
  return collection.totalReviewCount;
@@ -69,19 +78,19 @@ export const getProductReviewCount = createSelector(getBaseProductId, getReviews
69
78
 
70
79
  /**
71
80
  * Retrieves the total number of reviews for a current product.
72
- * @param {Object} state The current application state.
73
- * @return {number|null} The total number of reviews.
81
+ * @param state The current application state.
82
+ * @returns The total number of reviews.
74
83
  */
75
84
  export const getReviewsTotalCount = createSelector(getCollectionForCurrentBaseProduct, collection => {
76
- if (!collection || !collection.totalReviewCount) {
85
+ if (!collection || typeof collection.totalReviewCount !== 'number') {
77
86
  return null;
78
87
  }
79
88
  return collection.totalReviewCount;
80
89
  });
81
90
  /**
82
91
  * Retrieves the total number of currently fetched reviews for a current product.
83
- * @param {Object} state The current application state.
84
- * @return {number|null} The current number of fetched reviews.
92
+ * @param state The current application state.
93
+ * @returns The current number of fetched reviews.
85
94
  */
86
95
  export const getCurrentReviewCount = createSelector(getCollectionForCurrentBaseProduct, collection => {
87
96
  if (!collection || !collection.reviews) {
@@ -90,17 +99,95 @@ export const getCurrentReviewCount = createSelector(getCollectionForCurrentBaseP
90
99
  return collection.reviews.length;
91
100
  });
92
101
 
102
+ /**
103
+ * Retrieves the sort of the last review list request of the current base product.
104
+ * @param state The current application state.
105
+ * @returns The sort, or the default sort when the list was not requested yet.
106
+ */
107
+ export const getReviewListSort = createSelector(getCollectionForCurrentBaseProduct, collection => collection?.requestSort ?? SORT_DATE_DESC);
108
+
109
+ /**
110
+ * Retrieves the filters of the last review list request of the current base product.
111
+ * @param state The current application state.
112
+ * @returns The active filters; empty when the list is unfiltered or was not requested yet.
113
+ */
114
+ export const getReviewListFilters = createSelector(getCollectionForCurrentBaseProduct, collection => collection?.requestFilters ?? NO_FILTERS);
115
+
116
+ /**
117
+ * Whether the last request asked for the first page of another sort or filter than the stored
118
+ * reviews were loaded with. The stored reviews then do not belong to the requested list.
119
+ * @param state The current application state.
120
+ * @returns True from the request until its reviews were received, also after it failed.
121
+ */
122
+ export const isReviewListQueryChanged = createSelector(getCollectionForCurrentBaseProduct, collection => !!collection && collection.requestOffset === 0 && (collection.requestSort !== collection.sort || !areReviewFiltersEqual(collection.requestFilters, collection.filters)));
123
+
124
+ /**
125
+ * Whether the review list of the current base product has a further page.
126
+ * @param state The current application state.
127
+ * @returns True when the provider returned a cursor for the next page or, with offset
128
+ * pagination, fewer reviews are loaded than the list contains.
129
+ */
130
+ export const hasMoreReviews = createSelector(getCollectionForCurrentBaseProduct, getCurrentReviewCount, getReviewsTotalCount, isReviewCursorPagination, (collection, count, totalCount, isCursor) => {
131
+ if (!count) {
132
+ return false;
133
+ }
134
+ if (isCursor) {
135
+ return !!collection?.after;
136
+ }
137
+ return typeof totalCount === 'number' && count < totalCount;
138
+ });
139
+
93
140
  /**
94
141
  * Retrieves the information if reviews are currently fetched.
95
- * @param {Object} state The current application state.
96
- * @return {bool} The boolean information if reviews are currently being fetched.
142
+ * @param state The current application state.
143
+ * @returns The boolean information if reviews are currently being fetched.
97
144
  */
98
145
  export const getReviewsFetchingState = createSelector(getCollectionForCurrentBaseProduct, collection => collection && collection.isFetching);
99
146
 
147
+ /**
148
+ * Whether the review list of the current base product was not requested yet.
149
+ * @param state The current application state.
150
+ * @returns True when no list collection exists.
151
+ */
152
+ export const isReviewListMissing = createSelector(getCollectionForCurrentBaseProduct, collection => collection === null);
153
+
154
+ /**
155
+ * Whether the last review list request of the current base product failed.
156
+ * @param state The current application state.
157
+ * @returns True when the last request failed, also when earlier pages are still stored.
158
+ */
159
+ export const hasReviewListError = createSelector(getCollectionForCurrentBaseProduct, collection => !!collection && !collection.isFetching && collection.expires === 0);
160
+
161
+ /**
162
+ * Whether the review list of the current base product has to be requested again.
163
+ * Not memoized, because the result depends on the current time.
164
+ * @param state The current application state.
165
+ * @param props The product id and an optional variant id.
166
+ * @returns True when the list is missing, failed or expired, false while a request runs.
167
+ */
168
+ export const isReviewListStale = (state, props) => {
169
+ const collection = getCollectionForCurrentBaseProduct(state, props);
170
+ return !collection || !collection.isFetching && (collection.expires ?? 0) <= Date.now();
171
+ };
172
+
173
+ /**
174
+ * Whether the review list of the current base product is still loading.
175
+ * @param state The current application state.
176
+ * @returns True while a request runs or before the first response, unless the last request failed.
177
+ */
178
+ export const isReviewListLoading = createSelector(getCollectionForCurrentBaseProduct, hasReviewListError, (collection, hasError) => !!collection?.isFetching || !collection?.reviews && !hasError);
179
+
180
+ /**
181
+ * Retrieves the offset of the last review list request of the current base product.
182
+ * @param state The current application state.
183
+ * @returns The offset, or 0 when the list was not requested yet.
184
+ */
185
+ export const getReviewListRequestOffset = createSelector(getCollectionForCurrentBaseProduct, collection => collection?.requestOffset ?? 0);
186
+
100
187
  /**
101
188
  * Select the user reviews state.
102
- * @param {Object} state The current application state.
103
- * @return {Object} The user reviews collection stored as productId => review.
189
+ * @param state The current application state.
190
+ * @returns The user reviews collection stored as productId => review.
104
191
  */
105
192
  const getUserReviewsByProductId = createSelector(getReviewsState, state => state.userReviewsByProductId);
106
193
 
@@ -108,61 +195,85 @@ const getUserReviewsByProductId = createSelector(getReviewsState, state => state
108
195
  * Retrieves a user review for a product.
109
196
  */
110
197
  export const getUserReviewForProduct = createSelector(getUserReviewsByProductId, getReviews, (state, props = {}) => props.productId, (userReviews, allReviews, productId) => {
111
- if (!userReviews || !userReviews[productId] || !allReviews[userReviews[productId].review]) {
198
+ const userReview = userReviews && userReviews[productId];
199
+ if (!userReview || !allReviews[userReview.review]) {
112
200
  return {};
113
201
  }
114
202
  return {
115
- ...allReviews[userReviews[productId].review]
203
+ ...allReviews[userReview.review]
116
204
  };
117
205
  });
118
206
 
119
207
  /**
120
208
  * Gets user reviews fetching state. Only the first fetch is considered.
121
- * @return {bool} True if user review for current product is being fetched.
209
+ * @returns True if user review for current product is being fetched.
122
210
  */
123
211
  export const getUserReviewFirstFetchState = createSelector(getBaseProductId, getUserReviewsByProductId, (productId, userReviews) => !!(userReviews && productId && userReviews[productId] && !userReviews[productId].review && userReviews[productId].isFetching));
124
212
 
125
213
  /**
126
214
  * Get a user name for the review form.
127
- * @param {Object} state The state.
128
- * @returns {string} A user name.
215
+ * @param state The state.
216
+ * @returns A user name.
129
217
  */
130
- export const getDefaultAuthorName = state => isUserLoggedIn && state.user.data && state.user.data.firstName ? `${state.user.data.firstName} ${state.user.data.lastName}` : '';
218
+ export const getDefaultAuthorName = state => isUserLoggedIn(state) && state.user.data && state.user.data.firstName ? `${state.user.data.firstName} ${state.user.data.lastName}` : '';
131
219
 
132
220
  /**
133
- * Retrieves the current product reviews.
134
- * When the user review is available, it will always be the first entry.
135
- * @param {Object} state The current application state.
136
- * @return {Array|null} The reviews for a product.
221
+ * Retrieves the current product reviews in the order returned by the pipeline.
222
+ * @param state The current application state.
223
+ * @returns The reviews for a product.
137
224
  */
138
- export const getProductReviews = createSelector(getCollectionForCurrentBaseProduct, getReviews, getUserReviewForProduct, (collection, allReviews, userReview) => {
225
+ export const getProductReviews = createSelector(getCollectionForCurrentBaseProduct, getReviews, (collection, allReviews) => {
139
226
  if (!collection || !collection.reviews) {
140
227
  return [];
141
228
  }
142
- const reviews = collection.reviews.map(id => allReviews[id]);
143
- // There is no user review. Returning only from reviews collection.
144
- if (!userReview.id) {
145
- return reviews;
146
- }
147
-
148
- // User review always on top. Avoid duplicates.
149
- return [userReview].concat(reviews.filter(r => r.id !== userReview.id));
229
+ return collection.reviews.map(id => allReviews[id]);
150
230
  });
151
231
 
152
232
  /**
153
- * Retrieves the current product reviews excerpt.
154
- * When user review is available, it will always be the first entry.
155
- * @param {Object} state The current application state.
156
- * @return {Array|null} The reviews for a product
233
+ * Retrieves the current product reviews excerpt in the order returned by the pipeline.
234
+ * @param state The current application state.
235
+ * @returns The reviews for a product
157
236
  */
158
- export const getProductReviewsExcerpt = createSelector(getBaseProductId, getProductReviewsExcerptState, getReviews, getUserReviewForProduct, (productId, productReviewsState, reviewsState, userReview) => {
237
+ export const getProductReviewsExcerpt = createSelector(getBaseProductId, getProductReviewsExcerptState, getReviews, (productId, productReviewsState, reviewsState) => {
159
238
  const collection = productReviewsState[productId];
160
239
  if (!collection || !collection.reviews) {
161
240
  return null;
162
241
  }
163
- const reviews = collection.reviews.map(id => reviewsState[id]);
164
- if (!userReview.id) {
165
- return reviews;
166
- }
167
- return [userReview].concat(reviews.filter(r => r.id !== userReview.id)).slice(0, REVIEW_PREVIEW_COUNT);
168
- });
242
+ return collection.reviews.map(id => reviewsState[id]);
243
+ });
244
+
245
+ /**
246
+ * Retrieves the review preview collection of the current base product.
247
+ * @param state The current application state.
248
+ * @returns The collection, or null when the preview was not requested yet.
249
+ */
250
+ const getProductReviewsExcerptCollection = createSelector(getBaseProductId, getProductReviewsExcerptState, (productId, productReviewsState) => productReviewsState[productId] || null);
251
+
252
+ /**
253
+ * Whether the review preview of the current base product was not requested yet.
254
+ * @param state The current application state.
255
+ * @returns True when no preview collection exists.
256
+ */
257
+ export const isProductReviewsExcerptMissing = createSelector(getProductReviewsExcerptCollection, collection => collection === null);
258
+
259
+ /**
260
+ * Whether the last review preview request of the current base product failed before any
261
+ * reviews were received.
262
+ * @param state The current application state.
263
+ * @returns True when the preview request failed.
264
+ */
265
+ export const hasProductReviewsExcerptError = createSelector(getProductReviewsExcerptCollection, collection => !!collection && !collection.isFetching && !collection.reviews && collection.requestId !== undefined);
266
+
267
+ /**
268
+ * Whether the review preview of the current base product is still loading.
269
+ * @param state The current application state.
270
+ * @returns True while a request runs or before the first response, unless the last request failed.
271
+ */
272
+ export const isProductReviewsExcerptLoading = createSelector(getProductReviewsExcerptCollection, hasProductReviewsExcerptError, (collection, hasError) => !!collection?.isFetching || !collection?.reviews && !hasError);
273
+
274
+ /**
275
+ * Whether the review preview of the current base product is requested without stored reviews.
276
+ * @param state The current application state.
277
+ * @returns True while a request runs and no response was stored yet; false for a refresh.
278
+ */
279
+ export const isProductReviewsExcerptPending = createSelector(getProductReviewsExcerptCollection, collection => !!collection?.isFetching && !collection.reviews);
@@ -0,0 +1,9 @@
1
+ import type { ReviewId, ReviewVote, OwnReviewVotesState } from '../types/reviews';
2
+ /**
3
+ * Selects the vote the user of this device gave for a review.
4
+ * @param state The application state.
5
+ * @param reviewId The ID of the review.
6
+ * @returns The vote, or null when the user did not vote on the review.
7
+ */
8
+ export declare const getOwnReviewVote: (state: OwnReviewVotesState, reviewId: ReviewId) => ReviewVote | null;
9
+ //# sourceMappingURL=ownReviewVotes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownReviewVotes.d.ts","sourceRoot":"","sources":["../../../reviews/selectors/ownReviewVotes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAElF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,OAAO,mBAAmB,EAC1B,UAAU,QAAQ,KACjB,UAAU,GAAG,IAEf,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Selects the vote the user of this device gave for a review.
3
+ * @param state The application state.
4
+ * @param reviewId The ID of the review.
5
+ * @returns The vote, or null when the user did not vote on the review.
6
+ */
7
+ export const getOwnReviewVote = (state, reviewId) => state?.ownReviewVotes?.[String(reviewId)] ?? null;
@@ -0,0 +1,63 @@
1
+ import type { ReviewFilterOption } from '../types/reviews';
2
+ import type { ReviewPaginationType, ReviewSettingsSliceState, ReviewSettingsState } from '../types/reviewSettings';
3
+ /**
4
+ * Retrieves the review settings slice, falling back to an empty slice when absent.
5
+ * @param state The application state.
6
+ * @returns The review settings slice.
7
+ */
8
+ export declare const getReviewSettingsState: (state: ReviewSettingsState) => ReviewSettingsSliceState;
9
+ export declare const getReviewFeatures: ((state: ReviewSettingsState) => string[]) & import("reselect").OutputSelectorFields<(args_0: ReviewSettingsSliceState) => string[], {
10
+ clearCache: () => void;
11
+ }> & {
12
+ clearCache: () => void;
13
+ };
14
+ /**
15
+ * Selects the pagination mode from the settings only. Returns `undefined` while unknown;
16
+ * never derived from `features` and never defaulted to offset.
17
+ */
18
+ export declare const getReviewPaginationType: ((state: ReviewSettingsState) => ReviewPaginationType | undefined) & import("reselect").OutputSelectorFields<(args_0: ReviewSettingsSliceState) => ReviewPaginationType | undefined, {
19
+ clearCache: () => void;
20
+ }> & {
21
+ clearCache: () => void;
22
+ };
23
+ export declare const getReviewCustomFields: ((state: ReviewSettingsState) => unknown[]) & import("reselect").OutputSelectorFields<(args_0: ReviewSettingsSliceState) => unknown[], {
24
+ clearCache: () => void;
25
+ }> & {
26
+ clearCache: () => void;
27
+ };
28
+ /**
29
+ * Selects the sort values the provider supports, limited to the sort values the PWA knows
30
+ * and kept in the provider's order.
31
+ */
32
+ export declare const getReviewSortOptions: ((state: ReviewSettingsState) => string[]) & import("reselect").OutputSelectorFields<(args_0: ReviewSettingsSliceState) => string[], {
33
+ clearCache: () => void;
34
+ }> & {
35
+ clearCache: () => void;
36
+ };
37
+ /**
38
+ * Selects the list filters the provider supports, in the order the PWA defines them.
39
+ */
40
+ export declare const getReviewFilterOptions: ((state: ReviewSettingsState) => ReviewFilterOption[]) & import("reselect").OutputSelectorFields<(args_0: string[]) => ReviewFilterOption[], {
41
+ clearCache: () => void;
42
+ }> & {
43
+ clearCache: () => void;
44
+ };
45
+ export declare const isFetchingReviewSettings: ((state: ReviewSettingsState) => boolean) & import("reselect").OutputSelectorFields<(args_0: ReviewSettingsSliceState) => boolean, {
46
+ clearCache: () => void;
47
+ }> & {
48
+ clearCache: () => void;
49
+ };
50
+ export declare const isReviewCursorPagination: ((state: ReviewSettingsState) => boolean) & import("reselect").OutputSelectorFields<(args_0: ReviewPaginationType | undefined) => boolean, {
51
+ clearCache: () => void;
52
+ }> & {
53
+ clearCache: () => void;
54
+ };
55
+ /**
56
+ * Whether the active provider reports the given capability. A capability missing from the
57
+ * settings enables nothing.
58
+ * @param state The application state.
59
+ * @param feature The capability identifier to check.
60
+ * @returns Whether the capability is enabled.
61
+ */
62
+ export declare const hasReviewFeature: (state: ReviewSettingsState, feature: string) => boolean;
63
+ //# sourceMappingURL=reviewSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewSettings.d.ts","sourceRoot":"","sources":["../../../reviews/selectors/reviewSettings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AAIjC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,mBAAmB,KAAG,wBAClB,CAAC;AAEnD,eAAO,MAAM,iBAAiB;;;;CAG7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB;;;;CAGnC,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;CAGjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;CAKhC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;CAGpC,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;CAGpC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,mBAAmB,EAAE,SAAS,MAAM,KAAG,OACnC,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { createSelector } from 'reselect';
2
+ import { PAGINATION_TYPE_CURSOR, REVIEW_FILTERS, REVIEW_SORT_OPTIONS } from "../constants";
3
+ const EMPTY_SETTINGS = {};
4
+
5
+ /**
6
+ * Retrieves the review settings slice, falling back to an empty slice when absent.
7
+ * @param state The application state.
8
+ * @returns The review settings slice.
9
+ */
10
+ export const getReviewSettingsState = state => state?.reviews?.reviewSettings ?? EMPTY_SETTINGS;
11
+ export const getReviewFeatures = createSelector(getReviewSettingsState, settings => settings.features ?? []);
12
+
13
+ /**
14
+ * Selects the pagination mode from the settings only. Returns `undefined` while unknown;
15
+ * never derived from `features` and never defaulted to offset.
16
+ */
17
+ export const getReviewPaginationType = createSelector(getReviewSettingsState, settings => settings.paginationType);
18
+ export const getReviewCustomFields = createSelector(getReviewSettingsState, settings => settings.customFields ?? []);
19
+
20
+ /**
21
+ * Selects the sort values the provider supports, limited to the sort values the PWA knows
22
+ * and kept in the provider's order.
23
+ */
24
+ export const getReviewSortOptions = createSelector(getReviewSettingsState, settings => Array.isArray(settings.sortOptions) ? settings.sortOptions.filter(option => REVIEW_SORT_OPTIONS.includes(option)) : []);
25
+
26
+ /**
27
+ * Selects the list filters the provider supports, in the order the PWA defines them.
28
+ */
29
+ export const getReviewFilterOptions = createSelector(getReviewFeatures, features => REVIEW_FILTERS.filter(filter => features.includes(filter.param)));
30
+ export const isFetchingReviewSettings = createSelector(getReviewSettingsState, settings => settings.isFetching ?? false);
31
+ export const isReviewCursorPagination = createSelector(getReviewPaginationType, paginationType => paginationType === PAGINATION_TYPE_CURSOR);
32
+
33
+ /**
34
+ * Whether the active provider reports the given capability. A capability missing from the
35
+ * settings enables nothing.
36
+ * @param state The application state.
37
+ * @param feature The capability identifier to check.
38
+ * @returns Whether the capability is enabled.
39
+ */
40
+ export const hasReviewFeature = (state, feature) => getReviewFeatures(state).includes(feature);
@@ -0,0 +1,18 @@
1
+ import type { ReviewsProductProps, ReviewsSliceState } from '../types/reviews';
2
+ import type { ReviewSummary } from '../types/reviewSummary';
3
+ type SummaryState = {
4
+ product: unknown;
5
+ reviews?: Partial<ReviewsSliceState>;
6
+ };
7
+ /**
8
+ * Selects the review summary of a product. A summary delivered by the review provider wins.
9
+ * Without one the product data is the source, unless the provider reports that it delivers
10
+ * summaries: then the product data describes another dataset and is not used.
11
+ * @param state The application state.
12
+ * @param props The product id and an optional variant id, which takes precedence for the
13
+ * product data. Provider summaries are stored by the product id.
14
+ * @returns The summary, or null when none is available.
15
+ */
16
+ export declare const getReviewSummary: (state: SummaryState, props?: ReviewsProductProps) => ReviewSummary | null;
17
+ export {};
18
+ //# sourceMappingURL=reviewSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewSummary.d.ts","sourceRoot":"","sources":["../../../reviews/selectors/reviewSummary.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACtC,CAAC;AAsCF;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,YAAY,EAAE,QAAO,mBAAwB,yBAcpF,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { createSelector } from 'reselect';
2
+ import { getProductRating as getProductRatingSelector } from "../../product/selectors/product";
3
+ import { REVIEW_FEATURE_RATING_SUMMARY } from "../constants";
4
+ import { hasReviewFeature } from "./reviewSettings";
5
+ const getProductRating = getProductRatingSelector;
6
+
7
+ /**
8
+ * Normalizes a raw rating value.
9
+ * @param value A raw rating value from the product data.
10
+ * @returns The value when it is a finite number, otherwise null.
11
+ */
12
+ const toFiniteNumber = value => typeof value === 'number' && Number.isFinite(value) ? value : null;
13
+
14
+ /**
15
+ * Selects the review summary of a product from its product data.
16
+ */
17
+ const getProductReviewSummary = createSelector(getProductRating, rating => {
18
+ if (!rating) {
19
+ return null;
20
+ }
21
+ return {
22
+ average: toFiniteNumber(rating.average),
23
+ count: toFiniteNumber(rating.count)
24
+ };
25
+ });
26
+
27
+ /**
28
+ * Selects the review summary of a product. A summary delivered by the review provider wins.
29
+ * Without one the product data is the source, unless the provider reports that it delivers
30
+ * summaries: then the product data describes another dataset and is not used.
31
+ * @param state The application state.
32
+ * @param props The product id and an optional variant id, which takes precedence for the
33
+ * product data. Provider summaries are stored by the product id.
34
+ * @returns The summary, or null when none is available.
35
+ */
36
+ export const getReviewSummary = (state, props = {}) => {
37
+ const providerSummary = props.productId ? state.reviews?.reviewSummariesByProductId?.[props.productId] : undefined;
38
+ if (providerSummary) {
39
+ return providerSummary;
40
+ }
41
+ if (hasReviewFeature(state, REVIEW_FEATURE_RATING_SUMMARY)) {
42
+ return null;
43
+ }
44
+ return getProductReviewSummary(state, props);
45
+ };
@@ -1,4 +1,7 @@
1
1
  import { main$ } from '@shopgate/pwa-common/streams/main';
2
+ import { RESET_APP } from '@shopgate/pwa-common/constants/ActionTypes';
3
+ import { RESET_APP_REDUCERS } from '@shopgate/pwa-common/constants/Configuration';
4
+ import configuration from '@shopgate/pwa-common/collections/Configuration';
2
5
  import { routeWillEnter$, routeWillLeave$ } from '@shopgate/pwa-common/streams/router';
3
6
  import { ITEM_PATH, RECEIVE_PRODUCT, RECEIVE_PRODUCT_CACHED } from '@shopgate/pwa-common-commerce/product/constants';
4
7
  import { REQUEST_SUBMIT_REVIEW, RECEIVE_SUBMIT_REVIEW, ERROR_SUBMIT_REVIEW, RESET_SUBMIT_REVIEW } from "../constants";
@@ -42,4 +45,19 @@ export const errorReviewSubmit$ = main$.filter(({
42
45
  }) => [ERROR_SUBMIT_REVIEW, RESET_SUBMIT_REVIEW].includes(action.type));
43
46
  export const shouldFetchReviews$ = main$.filter(({
44
47
  action
45
- }) => action.type === RECEIVE_PRODUCT || action.type === RECEIVE_PRODUCT_CACHED).merge(successReviewSubmit$);
48
+ }) => action.type === RECEIVE_PRODUCT || action.type === RECEIVE_PRODUCT_CACHED).merge(successReviewSubmit$);
49
+
50
+ /**
51
+ * Gets triggered after an app reset cleared the reviews state.
52
+ * A reset without an explicit reducer list uses the configured reset reducers.
53
+ * @type {Observable}
54
+ */
55
+ export const reviewsDidReset$ = main$.filter(({
56
+ action
57
+ }) => {
58
+ if (action.type !== RESET_APP) {
59
+ return false;
60
+ }
61
+ const resetReducers = action.reducers || configuration.get(RESET_APP_REDUCERS);
62
+ return Array.isArray(resetReducers) && resetReducers.includes('reviews');
63
+ });
@@ -1,7 +1,9 @@
1
1
  import appConfig from '@shopgate/pwa-common/helpers/config';
2
+ import { appWillStart$ } from '@shopgate/engage/core/streams';
2
3
  import fetchProductReviews from "../actions/fetchProductReviews";
4
+ import fetchProductReviewSettings from "../actions/fetchProductReviewSettings";
3
5
  import { REVIEW_PREVIEW_COUNT } from "../constants";
4
- import { shouldFetchReviews$ } from "../streams";
6
+ import { reviewsDidReset$, shouldFetchReviews$ } from "../streams";
5
7
 
6
8
  /**
7
9
  * Review subscriptions.
@@ -11,10 +13,21 @@ export default function product(subscribe) {
11
13
  if (!appConfig.hasReviews) {
12
14
  return;
13
15
  }
16
+ subscribe(appWillStart$, ({
17
+ dispatch
18
+ }) => {
19
+ dispatch(fetchProductReviewSettings());
20
+ });
21
+ subscribe(reviewsDidReset$, ({
22
+ dispatch
23
+ }) => {
24
+ dispatch(fetchProductReviewSettings());
25
+ });
14
26
  subscribe(shouldFetchReviews$, ({
15
27
  action,
16
28
  dispatch
17
29
  }) => {
30
+ dispatch(fetchProductReviewSettings());
18
31
  if (action.productData) {
19
32
  const {
20
33
  id,
@@ -0,0 +1,31 @@
1
+ export type ReviewPaginationType = 'offset' | 'cursor';
2
+ /**
3
+ * Provider review capabilities returned by shopgate.catalog.getProductReviewSettings.v1.
4
+ */
5
+ export interface ReviewSettings {
6
+ /** Optional capabilities the provider supports; the PWA only acts on identifiers it knows. */
7
+ features: string[];
8
+ /** Whether the review list paginates by offset or cursor. */
9
+ paginationType: ReviewPaginationType;
10
+ /** Provider custom-field definitions; not consumed for display in the first delivery. */
11
+ customFields: unknown[];
12
+ /** Sort values the provider supports for the review list; unknown values are ignored. */
13
+ sortOptions?: string[];
14
+ }
15
+ /**
16
+ * The review settings redux slice: the loaded settings plus request bookkeeping.
17
+ * Empty before the first successful load, so every settings field is optional here.
18
+ */
19
+ export interface ReviewSettingsSliceState extends Partial<ReviewSettings> {
20
+ isFetching?: boolean;
21
+ expires?: number;
22
+ }
23
+ /**
24
+ * Minimal application state shape the review settings selectors read from.
25
+ */
26
+ export interface ReviewSettingsState {
27
+ reviews?: {
28
+ reviewSettings?: ReviewSettingsSliceState;
29
+ };
30
+ }
31
+ //# sourceMappingURL=reviewSettings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewSettings.d.ts","sourceRoot":"","sources":["../../../reviews/types/reviewSettings.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8FAA8F;IAC9F,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,cAAc,EAAE,oBAAoB,CAAC;IACrC,yFAAyF;IACzF,YAAY,EAAE,OAAO,EAAE,CAAC;IACxB,yFAAyF;IACzF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAyB,SAAQ,OAAO,CAAC,cAAc,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,wBAAwB,CAAC;KAC3C,CAAC;CACH"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The number of ratings per number of stars, from 5 to 1.
3
+ */
4
+ export type ReviewRatingDistribution = Record<'5' | '4' | '3' | '2' | '1', number>;
5
+ /**
6
+ * The review summary of a product, from the review provider or from the product data.
7
+ */
8
+ export interface ReviewSummary {
9
+ /** Average rating on the 0–100 scale; null when not provided. */
10
+ average: number | null;
11
+ /** Number of ratings incl. star-only ratings; differs from the list's totalReviewCount. */
12
+ count: number | null;
13
+ /** Only set when the review provider delivers a complete distribution. */
14
+ distribution?: ReviewRatingDistribution;
15
+ }
16
+ //# sourceMappingURL=reviewSummary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewSummary.d.ts","sourceRoot":"","sources":["../../../reviews/types/reviewSummary.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,MAAM,CAAC,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2FAA2F;IAC3F,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,wBAAwB,CAAC;CACzC"}