Skip to content

Commit b82cebc

Browse files
author
Sanjeev Yadav
committed
feat: Fetch cart items and display in CartScreen
1 parent 6ed73ba commit b82cebc

File tree

12 files changed

+297
-47
lines changed

12 files changed

+297
-47
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { gql } from '@apollo/client';
2+
3+
export interface CartItemType {
4+
id: number;
5+
product: {
6+
sku: string;
7+
name: string;
8+
small_image: {
9+
url: String;
10+
};
11+
};
12+
prices: {
13+
rowTotal: {
14+
currency: string;
15+
value: number;
16+
};
17+
};
18+
quantity: number;
19+
}
20+
21+
export const CART_ITEMS_FRAGMENT = gql`
22+
fragment CartItemsFragment on Cart {
23+
items {
24+
id
25+
prices {
26+
rowTotal: row_total {
27+
currency
28+
value
29+
}
30+
}
31+
product {
32+
name
33+
sku
34+
small_image {
35+
url
36+
}
37+
}
38+
quantity
39+
}
40+
}
41+
`;

src/apollo/queries/getCart.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
import { gql } from '@apollo/client';
2+
import { CartItemType, CART_ITEMS_FRAGMENT } from './cartItemsFragment';
23

34
export interface GetCartDataType {
45
customerCart: CartType;
56
}
67

78
export interface CartType {
89
id: string;
10+
items: Array<CartItemType>;
11+
prices: {
12+
grandTotal: {
13+
value: number;
14+
currency: string;
15+
};
16+
};
917
totalQuantity: number;
1018
}
1119

1220
export const GET_CART = gql`
1321
query GetCart {
1422
customerCart {
1523
id
24+
...CartItemsFragment
25+
prices {
26+
grandTotal: grand_total {
27+
value
28+
currency
29+
}
30+
}
1631
totalQuantity: total_quantity
1732
}
1833
}
34+
${CART_ITEMS_FRAGMENT}
1935
`;

src/components/ProductListItem/ProductListItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useContext } from 'react';
22
import { View, StyleSheet, TouchableOpacity } from 'react-native';
33
import { Text, Image, ThemeContext } from 'react-native-elements';
44
import { ProductInListType } from '../../apollo/queries/productsFragment';
5-
import { getPriceStringFromPriceRange } from '../../logic';
5+
import { formatPrice } from '../../logic';
66
import { DIMENS } from '../../constants';
77

88
interface Props {
@@ -45,7 +45,7 @@ const ProductListItem = ({
4545
{renderImage()}
4646
<Text style={styles.name}>{item.name}</Text>
4747
<Text style={styles.price}>
48-
{getPriceStringFromPriceRange(item.price_range)}
48+
{formatPrice(item.price_range.maximum_price.final_price)}
4949
</Text>
5050
</View>
5151
</TouchableOpacity>

src/constants/dimens.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ const DIMENS = Object.freeze({
1717
borderWidth: moderateScale(StyleSheet.hairlineWidth),
1818
appbarIconSize: moderateScale(23),
1919
},
20+
cartScreen: {
21+
imageSize: moderateScale(100),
22+
totalPriceFontSize: moderateScale(16),
23+
},
2024
categoryListItem: {
2125
imageWidth: moderateScale(70),
2226
imageHeight: moderateScale(70),

src/i18n/locales/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"lastName": "Last Name",
2323
"register": "Register",
2424
"logout": "Logout",
25+
"quantity": "Qty",
26+
"price": "price",
27+
"total": "Total",
2528
"pluralizationExample": {
2629
"one": "You have 1 item",
2730
"other": "You have %{count} items",
@@ -33,7 +36,10 @@
3336
},
3437
"cartScreen": {
3538
"appbarTitle": "Cart",
36-
"guestUserPromptMessage": "In order to view cart, please login or create new account"
39+
"guestUserPromptMessage": "In order to view cart, please login or create new account",
40+
"cartEmptyTitle": "Hey, it feels so light!",
41+
"cartEmptyMessage": "There is nothing in your bag. Let's add some items.",
42+
"placeOrderButton": "Place Order"
3743
},
3844
"categoriesScreen": {
3945
"appbarTitle": "Category"

src/logic/cart/useCart.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { useEffect } from 'react';
2-
import { useLazyQuery, useMutation, useQuery } from '@apollo/client';
2+
import {
3+
ApolloError,
4+
useLazyQuery,
5+
useMutation,
6+
useQuery,
7+
} from '@apollo/client';
38
import Toast from 'react-native-simple-toast';
49
import {
510
IsLoggedInDataType,
@@ -16,7 +21,9 @@ import { translate } from '../../i18n';
1621

1722
interface Result {
1823
cartData: GetCartDataType | undefined;
19-
loading: boolean;
24+
cartLoading: boolean;
25+
cartError: ApolloError | undefined;
26+
addToCartLoading: boolean;
2027
isLoggedIn: boolean;
2128
addProductsToCart(arg0: CartItemInputType): void;
2229
}
@@ -25,10 +32,11 @@ export const useCart = (): Result => {
2532
const { data: { isLoggedIn = false } = {} } = useQuery<IsLoggedInDataType>(
2633
IS_LOGGED_IN,
2734
);
28-
const [fetchCart, { data: cartData }] = useLazyQuery<GetCartDataType>(
29-
GET_CART,
30-
);
31-
const [_addProductsToCart, { loading, data }] = useMutation<
35+
const [
36+
fetchCart,
37+
{ data: cartData, loading: cartLoading, error: cartError },
38+
] = useLazyQuery<GetCartDataType>(GET_CART);
39+
const [_addProductsToCart, { loading: addToCartLoading, data }] = useMutation<
3240
AddProductsToCartDataType,
3341
AddProductsToCartVars
3442
>(ADD_PRODUCTS_TO_CART, {
@@ -68,6 +76,8 @@ export const useCart = (): Result => {
6876
addProductsToCart,
6977
isLoggedIn,
7078
cartData,
71-
loading,
79+
cartLoading,
80+
cartError,
81+
addToCartLoading,
7282
};
7383
};

src/logic/utils/price.test.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {
2-
getCurrencySymbolFromCode,
3-
getPriceStringFromPriceRange,
4-
} from './price';
1+
import { getCurrencySymbolFromCode, formatPrice } from './price';
52
import { currencySymbols } from '../../../magento.config';
63

74
describe('price.js', () => {
@@ -31,23 +28,19 @@ describe('price.js', () => {
3128
});
3229
});
3330

34-
describe('getPriceStringFromPriceRange()', () => {
31+
describe('formatPrice()', () => {
3532
test('should return correct string', () => {
3633
// Setup
3734
const currencyCode = 'USD';
38-
const price = 29.99;
39-
const priceRange = {
40-
maximum_price: {
41-
final_price: {
42-
currency: currencyCode,
43-
value: price,
44-
},
45-
},
35+
const value = 29.99;
36+
const price = {
37+
currency: currencyCode,
38+
value,
4639
};
47-
const expectedResult = `${currencySymbols[currencyCode]} ${price}`;
40+
const expectedResult = `${currencySymbols[currencyCode]} ${value}`;
4841

4942
// Exercise
50-
const result = getPriceStringFromPriceRange(priceRange);
43+
const result = formatPrice(price);
5144

5245
// Verify
5346
expect(result).toBe(expectedResult);

src/logic/utils/price.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { currencySymbols } from '../../../magento.config';
2-
import { PriceRangeType } from '../../apollo/queries/getCategoryProducts';
32

43
export const getCurrencySymbolFromCode = (currencyCode: string) => {
54
return currencySymbols[currencyCode] ?? currencyCode;
65
};
76

8-
export const getPriceStringFromPriceRange = (
9-
priceRange: PriceRangeType,
10-
): string => {
11-
return `${getCurrencySymbolFromCode(
12-
priceRange.maximum_price.final_price.currency,
13-
)} ${priceRange.maximum_price.final_price.value}`;
14-
};
7+
export const formatPrice = ({
8+
currency,
9+
value,
10+
}: {
11+
currency: string;
12+
value: number;
13+
}): string => `${getCurrencySymbolFromCode(currency)} ${value}`;

src/screens/CartScreen/CartFooter.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useContext, useMemo } from 'react';
2+
import { View, StyleSheet } from 'react-native';
3+
import { Button, Text, ThemeContext } from 'react-native-elements';
4+
import { DIMENS, SPACING } from '../../constants';
5+
import { translate } from '../../i18n';
6+
import { formatPrice } from '../../logic';
7+
8+
type Props = {
9+
grandTotal?: {
10+
currency: string;
11+
value: number;
12+
};
13+
handlePlaceOrder(): void;
14+
};
15+
16+
export const CartFooter = ({
17+
grandTotal,
18+
handlePlaceOrder,
19+
}: Props): React.ReactElement => {
20+
const { theme } = useContext(ThemeContext);
21+
const containerStyle = useMemo(
22+
() => ({
23+
backgroundColor: theme.colors?.white,
24+
borderColor: theme.colors?.divider,
25+
}),
26+
[theme],
27+
);
28+
return (
29+
<View style={[styles.container, containerStyle]}>
30+
{grandTotal && (
31+
<Text h4 h4Style={styles.totalPrice}>{`${translate(
32+
'common.total',
33+
)} : ${formatPrice(grandTotal)}`}</Text>
34+
)}
35+
<Button
36+
containerStyle={styles.placeOrder}
37+
title={translate('cartScreen.placeOrderButton')}
38+
onPress={handlePlaceOrder}
39+
/>
40+
</View>
41+
);
42+
};
43+
44+
const styles = StyleSheet.create({
45+
container: {
46+
flexDirection: 'row',
47+
alignItems: 'center',
48+
padding: SPACING.small,
49+
borderTopWidth: DIMENS.common.borderWidth,
50+
},
51+
totalPrice: {
52+
fontSize: DIMENS.cartScreen.totalPriceFontSize,
53+
},
54+
placeOrder: {
55+
flex: 1,
56+
marginStart: SPACING.large,
57+
},
58+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
import { Image, ListItem } from 'react-native-elements';
4+
import { translate } from '../../i18n';
5+
import { DIMENS } from '../../constants';
6+
import { CartItemType } from '../../apollo/queries/cartItemsFragment';
7+
import { formatPrice } from '../../logic';
8+
9+
type Props = {
10+
item: CartItemType;
11+
index: number;
12+
onPress(arg0: number): void;
13+
onRemovePress(arg0: Number): void;
14+
};
15+
16+
const CartListItem = ({
17+
item,
18+
index,
19+
onPress,
20+
onRemovePress,
21+
}: Props): React.ReactElement => {
22+
const renderImage = () => {
23+
const uri = `${item.product.small_image.url}?width=${DIMENS.cartScreen.imageSize}`;
24+
return <Image source={{ uri }} style={styles.image} />;
25+
};
26+
27+
return (
28+
<ListItem onPress={() => onPress(index)} bottomDivider>
29+
{renderImage()}
30+
<ListItem.Content>
31+
<ListItem.Title>{item.product.name}</ListItem.Title>
32+
<ListItem.Subtitle>{`${translate('common.quantity')} : ${
33+
item.quantity
34+
}`}</ListItem.Subtitle>
35+
<ListItem.Subtitle>{`${translate('common.price')} : ${formatPrice(
36+
item.prices.rowTotal,
37+
)}`}</ListItem.Subtitle>
38+
</ListItem.Content>
39+
<ListItem.Chevron name="delete" onPress={() => onRemovePress(index)} />
40+
</ListItem>
41+
);
42+
};
43+
44+
const styles = StyleSheet.create({
45+
image: {
46+
height: DIMENS.cartScreen.imageSize,
47+
width: DIMENS.cartScreen.imageSize,
48+
},
49+
});
50+
51+
export default CartListItem;

0 commit comments

Comments
 (0)