1 | import React, { Component, ComponentClass } from 'react';
|
2 | import { compose } from 'redux';
|
3 | import { cloneDeep, get, isEqual, isFunction, set } from 'lodash-es';
|
4 |
|
5 | import {
|
6 | CommerceTypes,
|
7 | FetchDataFunction,
|
8 | withCommerceData,
|
9 | WithCommerceProps,
|
10 | WithCommerceProviderProps,
|
11 | withReviewData,
|
12 | WithReviewProps,
|
13 | WithReviewState
|
14 | } from '@brandingbrand/fscommerce';
|
15 |
|
16 |
|
17 | export type CommerceToReviewMapFunction<
|
18 | T extends CommerceTypes.Product = CommerceTypes.Product
|
19 | > = (product: T) => string;
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | export interface WithProductDetailProviderProps<
|
27 | T extends CommerceTypes.Product = CommerceTypes.Product
|
28 | > extends WithCommerceProviderProps<T>, WithReviewProps {
|
29 | commerceToReviewMap: string | CommerceToReviewMapFunction<T>;
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export type WithProductDetailProps<
|
38 | T extends CommerceTypes.Product = CommerceTypes.Product
|
39 | > = WithCommerceProps<T> & WithReviewState;
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | export type WithProductDetailState<
|
48 | T extends CommerceTypes.Product = CommerceTypes.Product
|
49 | > = Pick<WithCommerceProps<T>, 'commerceData'>;
|
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 |
|
77 | export default function withProductDetailData<
|
78 | P,
|
79 | T extends CommerceTypes.Product = CommerceTypes.Product
|
80 | >(fetchProduct: FetchDataFunction<P, T>, fetchReview: Function): ProductDetailWrapper<P, T> {
|
81 | type ResultProps = P &
|
82 | WithProductDetailProviderProps<T> &
|
83 | WithCommerceProps<T> &
|
84 | WithReviewState;
|
85 |
|
86 | |
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | return (WrappedComponent: ComponentClass<P & WithProductDetailProps<T>>) => {
|
95 | class ProductDetailProvider extends Component<ResultProps, WithProductDetailState<T>> {
|
96 |
|
97 | componentWillReceiveProps(nextProps: ResultProps): void {
|
98 | const { commerceData, commerceToReviewMap, reviewProviderDoUpdate } = this.props;
|
99 |
|
100 | const getReviewId = (product: T) => {
|
101 | if (isFunction(commerceToReviewMap)) {
|
102 | return commerceToReviewMap(product);
|
103 | } else if ('string' === typeof commerceToReviewMap) {
|
104 | return get(product, commerceToReviewMap);
|
105 | }
|
106 |
|
107 |
|
108 | return product.id;
|
109 | };
|
110 |
|
111 | if (nextProps.commerceData) {
|
112 | if (!isEqual(commerceData, nextProps.commerceData) && reviewProviderDoUpdate) {
|
113 |
|
114 | const ids = getReviewId(nextProps.commerceData);
|
115 | reviewProviderDoUpdate({ ids });
|
116 | } else if (nextProps.reviewsData && nextProps.reviewsData.length) {
|
117 |
|
118 | const newCommerceData = cloneDeep(nextProps.commerceData);
|
119 | set(newCommerceData, 'review', nextProps.reviewsData[0]);
|
120 | this.setState({ commerceData: newCommerceData });
|
121 | }
|
122 | }
|
123 | }
|
124 |
|
125 | render(): JSX.Element {
|
126 | const {
|
127 | commerceToReviewMap,
|
128 | ...props
|
129 | } = this.props as any;
|
130 |
|
131 | return (
|
132 | <WrappedComponent
|
133 | {...props}
|
134 | commerceData={(this.state && this.state.commerceData) || this.props.commerceData}
|
135 | />
|
136 | );
|
137 | }
|
138 | }
|
139 |
|
140 | return compose<ComponentClass<P & WithProductDetailProviderProps<T>>>(
|
141 | withCommerceData<P, T>(fetchProduct),
|
142 | withReviewData(fetchReview)
|
143 | )(ProductDetailProvider);
|
144 | };
|
145 | }
|