1 | import React, { Component, ComponentClass } from 'react';
|
2 | import { compose } from 'redux';
|
3 | import { cloneDeep, isEqual, set } from 'lodash-es';
|
4 |
|
5 | import {
|
6 | CommerceTypes,
|
7 | FetchDataFunction,
|
8 | ReviewDataSource,
|
9 | ReviewTypes,
|
10 | withCommerceData,
|
11 | WithCommerceProps,
|
12 | WithCommerceProviderProps
|
13 | } from '@brandingbrand/fscommerce';
|
14 |
|
15 |
|
16 | export type CommerceToReviewMapFunction<
|
17 | T extends CommerceTypes.Product = CommerceTypes.Product
|
18 | > = (product: T) => string;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | export interface WithProductDetailProviderProps<
|
26 | T extends CommerceTypes.Product = CommerceTypes.Product
|
27 | > extends WithCommerceProviderProps<T> {
|
28 | commerceToReviewMap: keyof T | CommerceToReviewMapFunction<T>;
|
29 | reviewDataSource?: ReviewDataSource;
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export type WithProductDetailProps<
|
38 | T extends CommerceTypes.Product = CommerceTypes.Product
|
39 | > = WithCommerceProps<T> & { reviewsData?: ReviewTypes.ReviewDetails[] };
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | export type WithProductDetailState<
|
48 | T extends CommerceTypes.Product = CommerceTypes.Product
|
49 | > = Pick<WithCommerceProps<T>, 'commerceData'> & { reviewsData?: ReviewTypes.ReviewDetails[] };
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | export type ProductDetailWrapper<P, T extends CommerceTypes.Product = CommerceTypes.Product> = (
|
62 | WrappedComponent: ComponentClass<P & WithProductDetailProps<T>>
|
63 | ) => ComponentClass<P & WithProductDetailProviderProps<T>>;
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | export default function withProductDetailData<
|
77 | P,
|
78 | T extends CommerceTypes.Product = CommerceTypes.Product
|
79 | >(fetchProduct: FetchDataFunction<P, T>): ProductDetailWrapper<P, T> {
|
80 | type ResultProps = P &
|
81 | WithProductDetailProviderProps<T> &
|
82 | WithCommerceProps<T>;
|
83 |
|
84 | |
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | return (WrappedComponent: ComponentClass<P & WithProductDetailProps<T>>) => {
|
93 | class ProductDetailProvider extends Component<ResultProps, WithProductDetailState<T>> {
|
94 | async componentDidUpdate(prevProps: ResultProps): Promise<void> {
|
95 | const { commerceToReviewMap, reviewDataSource } = this.props;
|
96 |
|
97 |
|
98 | const commerceData = this.props.commerceData as T | undefined;
|
99 |
|
100 | if (commerceData === undefined || reviewDataSource === undefined) {
|
101 | return;
|
102 | }
|
103 |
|
104 | if (!isEqual(prevProps.commerceData, commerceData)) {
|
105 |
|
106 |
|
107 | const ids = reviewDataSource.productIdMapper<T>(
|
108 | [commerceData],
|
109 | commerceToReviewMap
|
110 | );
|
111 |
|
112 | const reviewsData = await reviewDataSource.fetchReviewDetails({ ids });
|
113 |
|
114 |
|
115 | const newCommerceData = cloneDeep(commerceData);
|
116 | set(newCommerceData, 'review', reviewsData[0]);
|
117 | this.setState({
|
118 | commerceData: newCommerceData,
|
119 | reviewsData
|
120 | });
|
121 | }
|
122 | }
|
123 |
|
124 | render(): JSX.Element {
|
125 | const {
|
126 | commerceToReviewMap,
|
127 | ...props
|
128 | } = this.props as any;
|
129 |
|
130 | return (
|
131 | <WrappedComponent
|
132 | {...props}
|
133 | commerceData={(this.state && this.state.commerceData) || this.props.commerceData}
|
134 | reviewsData={(this.state && this.state.reviewsData)}
|
135 | />
|
136 | );
|
137 | }
|
138 | }
|
139 |
|
140 | return compose<ComponentClass<P & WithProductDetailProviderProps<T>>>(
|
141 | withCommerceData<P, T>(fetchProduct)
|
142 | )(ProductDetailProvider);
|
143 | };
|
144 | }
|