Skip to content

Commit 8313b73

Browse files
author
Sanjeev Yadav
committed
feat: Display selected configurable product image and price in UI
1 parent 185612f commit 8313b73

File tree

6 files changed

+144
-31
lines changed

6 files changed

+144
-31
lines changed

src/apollo/queries/configurableProductFragment.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import { gql } from '@apollo/client';
2+
import {
3+
MEDIA_GALLERY_FRAGMENT,
4+
MediaGalleryItemType,
5+
} from './mediaGalleryFragment';
6+
import { PriceRangeType, PRODUCT_PRICE_FRAGMENT } from './productPriceFragment';
27

38
export interface ConfigurableOptionType {
49
id: number;
@@ -17,6 +22,22 @@ export interface ConfigurableProductOptionValueType {
1722
};
1823
}
1924

25+
export interface ConfigurableProductVariant {
26+
attributes: Array<ConfigurableProductVariantAttribute>;
27+
product: ConfigurableProductVariantProduct;
28+
}
29+
30+
export interface ConfigurableProductVariantAttribute {
31+
code: string;
32+
valueIndex: number;
33+
}
34+
35+
export interface ConfigurableProductVariantProduct {
36+
sku: string;
37+
mediaGallery: Array<MediaGalleryItemType>;
38+
priceRange: PriceRangeType;
39+
}
40+
2041
export const CONFIGURABLE_PRODUCT_FRAGMENT = gql`
2142
fragment ConfigurableProduct on ConfigurableProduct {
2243
configurableOptions: configurable_options {
@@ -32,5 +53,18 @@ export const CONFIGURABLE_PRODUCT_FRAGMENT = gql`
3253
}
3354
}
3455
}
56+
variants {
57+
attributes {
58+
code
59+
valueIndex: value_index
60+
}
61+
product {
62+
sku
63+
...MediaGallery
64+
...ProductPrice
65+
}
66+
}
3567
}
68+
${MEDIA_GALLERY_FRAGMENT}
69+
${PRODUCT_PRICE_FRAGMENT}
3670
`;

src/apollo/queries/getProductDetails.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { gql } from '@apollo/client';
22
import {
33
ConfigurableOptionType,
4+
ConfigurableProductVariant,
45
CONFIGURABLE_PRODUCT_FRAGMENT,
56
} from './configurableProductFragment';
67
import {
78
MEDIA_GALLERY_FRAGMENT,
89
MediaGalleryItemType,
910
} from './mediaGalleryFragment';
10-
import { PriceRangeType } from './productsFragment';
11+
import { PriceRangeType, PRODUCT_PRICE_FRAGMENT } from './productPriceFragment';
1112

1213
export interface GetProductDetailsVars {
1314
sku: string;
@@ -43,6 +44,7 @@ export interface ConfigurableProductDetailsType
4344
extends ProductInterfaceDetailsType {
4445
type: ProductTypeEnum.CONFIGURED;
4546
configurableOptions: Array<ConfigurableOptionType>;
47+
variants: Array<ConfigurableProductVariant>;
4648
}
4749

4850
export interface GroupedProductDetailsType extends ProductInterfaceDetailsType {
@@ -66,19 +68,13 @@ export const GET_PRODUCT_DETAILS = gql`
6668
html
6769
}
6870
type: __typename
69-
priceRange: price_range {
70-
maximumPrice: maximum_price {
71-
finalPrice: final_price {
72-
currency
73-
value
74-
}
75-
}
76-
}
71+
...ProductPrice
7772
...MediaGallery
7873
...ConfigurableProduct
7974
}
8075
}
8176
}
77+
${PRODUCT_PRICE_FRAGMENT}
8278
${MEDIA_GALLERY_FRAGMENT}
8379
${CONFIGURABLE_PRODUCT_FRAGMENT}
8480
`;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { gql } from '@apollo/client';
2+
3+
export interface PriceRangeType {
4+
maximumPrice: {
5+
finalPrice: {
6+
currency: string;
7+
value: number;
8+
};
9+
};
10+
}
11+
12+
export const PRODUCT_PRICE_FRAGMENT = gql`
13+
fragment ProductPrice on ProductInterface {
14+
priceRange: price_range {
15+
maximumPrice: maximum_price {
16+
finalPrice: final_price {
17+
value
18+
currency
19+
}
20+
}
21+
}
22+
}
23+
`;
Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { gql } from '@apollo/client';
2+
import { PRODUCT_PRICE_FRAGMENT } from './productPriceFragment';
3+
import type { PriceRangeType } from './productPriceFragment';
24

35
export interface ProductInListType {
46
id: number;
@@ -10,15 +12,6 @@ export interface ProductInListType {
1012
priceRange: PriceRangeType;
1113
}
1214

13-
export interface PriceRangeType {
14-
maximumPrice: {
15-
finalPrice: {
16-
currency: string;
17-
value: number;
18-
};
19-
};
20-
}
21-
2215
export const PRODUCTS_FRAGMENT = gql`
2316
fragment ProductListFragment on Products {
2417
items {
@@ -28,14 +21,8 @@ export const PRODUCTS_FRAGMENT = gql`
2821
smallImage: small_image {
2922
url
3023
}
31-
priceRange: price_range {
32-
maximumPrice: maximum_price {
33-
finalPrice: final_price {
34-
currency
35-
value
36-
}
37-
}
38-
}
24+
...ProductPrice
3925
}
4026
}
27+
${PRODUCT_PRICE_FRAGMENT}
4128
`;

src/logic/products/useProductDetails.ts

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { useQuery, ApolloError } from '@apollo/client';
2-
import { useState } from 'react';
2+
import { useState, useEffect, useReducer } from 'react';
33
import {
44
GET_PRODUCT_DETAILS,
55
GetProductDetailsVars,
66
ProductDetailsDataType,
77
ProductDetailsType,
8+
ProductTypeEnum,
89
} from '../../apollo/queries/getProductDetails';
10+
import type { ConfigurableProductVariant } from '../../apollo/queries/configurableProductFragment';
11+
import { PriceRangeType } from '../../apollo/queries/productPriceFragment';
12+
import type { MediaGalleryItemType } from '../../apollo/queries/mediaGalleryFragment';
913

1014
interface Props {
1115
sku: string;
@@ -18,19 +22,51 @@ export type HandleSelectedConfigurableOptions = (
1822
valueIndex: number,
1923
) => void;
2024

21-
interface Result {
25+
interface Result extends ProductState {
2226
productDetails?: ProductDetailsType | null;
2327
loading: boolean;
2428
error: ApolloError | undefined;
2529
selectedConfigurableProductOptions: SelectedConfigurableProductOptions;
2630
handleSelectedConfigurableOptions: HandleSelectedConfigurableOptions;
2731
}
2832

33+
interface ProductState {
34+
priceRange: PriceRangeType | null;
35+
mediaGallery: Array<MediaGalleryItemType>;
36+
}
37+
38+
const findSelectedProductVariant = (
39+
selectedConfigurableProductOptions: SelectedConfigurableProductOptions,
40+
productData: ProductDetailsType,
41+
): ConfigurableProductVariant | null => {
42+
if (productData.type !== ProductTypeEnum.CONFIGURED) {
43+
return null;
44+
}
45+
let variants = productData.variants;
46+
Object.keys(selectedConfigurableProductOptions).forEach(code => {
47+
variants = variants.filter(variant => {
48+
const attribute = variant.attributes.find(attr => attr.code === code);
49+
return attribute?.valueIndex === selectedConfigurableProductOptions[code];
50+
});
51+
});
52+
return variants?.[0];
53+
};
54+
2955
export const useProductDetails = ({ sku }: Props): Result => {
3056
const [
3157
selectedConfigurableProductOptions,
3258
setSelectedConfigurableProductOptions,
3359
] = useState<SelectedConfigurableProductOptions>({});
60+
const [
61+
selectedVariant,
62+
setSelectedVariant,
63+
] = useState<ConfigurableProductVariant | null>(null);
64+
const [{ priceRange, mediaGallery }, setState] = useReducer<
65+
React.Reducer<ProductState, ProductState>
66+
>((prevState, newState) => ({ ...prevState, ...newState }), {
67+
priceRange: null,
68+
mediaGallery: [],
69+
});
3470
const { data, loading, error } = useQuery<
3571
ProductDetailsDataType,
3672
GetProductDetailsVars
@@ -40,6 +76,39 @@ export const useProductDetails = ({ sku }: Props): Result => {
4076
},
4177
});
4278

79+
useEffect(() => {
80+
// User has selected configurable options, find the matching simple product
81+
if (
82+
data?.products?.items?.[0] &&
83+
Object.keys(selectedConfigurableProductOptions).length > 0
84+
) {
85+
const variant = findSelectedProductVariant(
86+
selectedConfigurableProductOptions,
87+
data?.products?.items?.[0],
88+
);
89+
setSelectedVariant(variant);
90+
}
91+
}, [data, selectedConfigurableProductOptions]);
92+
93+
useEffect(() => {
94+
if (data?.products?.items?.[0]) {
95+
if (selectedVariant) {
96+
setState({
97+
priceRange: selectedVariant.product.priceRange,
98+
mediaGallery: [
99+
...selectedVariant.product.mediaGallery,
100+
...data?.products?.items?.[0].mediaGallery,
101+
],
102+
});
103+
} else {
104+
setState({
105+
priceRange: data?.products?.items?.[0].priceRange,
106+
mediaGallery: data?.products?.items?.[0].mediaGallery,
107+
});
108+
}
109+
}
110+
}, [data, selectedVariant]);
111+
43112
const handleSelectedConfigurableOptions: HandleSelectedConfigurableOptions = (
44113
optionCode,
45114
valueIndex,
@@ -52,6 +121,8 @@ export const useProductDetails = ({ sku }: Props): Result => {
52121

53122
return {
54123
productDetails: data?.products?.items?.[0],
124+
priceRange,
125+
mediaGallery,
55126
loading,
56127
error,
57128
selectedConfigurableProductOptions,

src/screens/ProductDetailsScreen/ProductDetailsScreen.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const ProductDetailsScreen = ({
4040
const {
4141
error,
4242
loading,
43+
priceRange,
44+
mediaGallery,
4345
productDetails,
4446
selectedConfigurableProductOptions,
4547
handleSelectedConfigurableOptions,
@@ -105,10 +107,10 @@ const ProductDetailsScreen = ({
105107
};
106108

107109
const renderPrice = (): React.ReactNode => {
108-
if (productDetails) {
110+
if (productDetails && priceRange) {
109111
return (
110112
<Text h2 style={styles.price}>
111-
{formatPrice(productDetails.priceRange.maximumPrice.finalPrice)}
113+
{formatPrice(priceRange.maximumPrice.finalPrice)}
112114
</Text>
113115
);
114116
}
@@ -160,7 +162,7 @@ const ProductDetailsScreen = ({
160162
}
161163
>
162164
<View>
163-
<MediaGallery items={productDetails?.mediaGallery ?? []} />
165+
<MediaGallery items={mediaGallery} />
164166
<Text h1 style={styles.name}>
165167
{productDetails?.name}
166168
</Text>

0 commit comments

Comments
 (0)