Skip to content

Commit

Permalink
Performace and styleized cart. Also crrate buttons to control quantity
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Romero committed Nov 26, 2023
1 parent 6a921aa commit 326ecce
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 73 deletions.
34 changes: 29 additions & 5 deletions apps/expo/src/atoms/cart.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { atom } from "jotai";
import { atomFamily, atomWithStorage, createJSONStorage } from "jotai/utils";
import { singleProductAtomFamily } from "./products";
import { productsAtom, singleProductAtomFamily } from "./products";
import AsyncStorage from "@react-native-async-storage/async-storage";

export const cartAtom = atomWithStorage<Record<number, number>>(
Expand All @@ -15,6 +15,22 @@ export const cartItemQuantityAtomFamily = atomFamily((id: number) =>
}),
);

export const cartTotalAtom = atom(async (get) => {
const currentCart = await get(cartAtom);
const products = get(productsAtom);

const ids = Object.keys(currentCart);
let total = 0;

for (const pr of products) {
if (ids.includes(pr.id.toString())) {
total += (currentCart[pr.id] ?? 0) * pr.price.toNumber();
}
}

return total;
});

export const addItemToCartAtom = atomFamily(
({ id, quantity }: { id: number; quantity: number }) =>
atom(null, async (get, set) => {
Expand All @@ -25,13 +41,21 @@ export const addItemToCartAtom = atomFamily(

const q = currentQuantity ? currentQuantity + quantity : quantity;

if (q <= currentItemStock) {
if (q < 1) {
set(cartAtom, { ...currentCart, [id]: 1 });
} else if (q <= currentItemStock) {
set(cartAtom, { ...currentCart, [id]: q });
} else if (q < 0) {
// prevents negative quantity
set(cartAtom, { ...currentCart, [id]: 0 });
} else {
set(cartAtom, { ...currentCart, [id]: currentItemStock });
}
}),
);

export const deleteItemFromCartAtom = atomFamily(({ id }: { id: number }) =>
atom(null, async (_, set) => {
set(cartAtom, async (prev) => {
const { [id]: toDelete, ...rest } = await prev;

Check warning on line 57 in apps/expo/src/atoms/cart.ts

View workflow job for this annotation

GitHub Actions / build-lint

'toDelete' is assigned a value but never used
return { ...rest };
});
}),
);
60 changes: 54 additions & 6 deletions apps/expo/src/components/CartItem.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,63 @@
import { useAtomValue } from "jotai";
import { useAtomValue, useSetAtom } from "jotai";
import { singleProductAtomFamily } from "../atoms/products";
import { Text } from "react-native";
import { Image, Pressable, Text, View } from "react-native";
import Ionicons from "@expo/vector-icons/Ionicons";
import { addItemToCartAtom, deleteItemFromCartAtom } from "../atoms/cart";
import { memo } from "react";

function CartItem({ id, quantity }: { id: number; quantity: number }) {
const item = useAtomValue(singleProductAtomFamily(id));
const deleteItemFromCart = useSetAtom(deleteItemFromCartAtom({ id }));
const addItemToCart = useSetAtom(addItemToCartAtom({ id, quantity: 1 }));
const removeItemToCart = useSetAtom(addItemToCartAtom({ id, quantity: -1 }));

return (
<Text>
{item?.name} : {quantity}
</Text>
<View className="flex flex-row items-center justify-between rounded border border-gray-200 bg-white shadow">
<View className="w-2/3 flex-row items-center p-2">
<Text className="mr-2 text-lg" numberOfLines={1}>
{quantity}
</Text>
<Image
source={{
uri:
item?.image_url ??
"https://t4.ftcdn.net/jpg/04/00/24/31/360_F_400243185_BOxON3h9avMUX10RsDkt3pJ8iQx72kS3.jpg",
}}
className="h-12 w-12"
resizeMode="contain"
/>
<View>
<Text className="w-40 text-base font-medium" numberOfLines={1}>
{item?.name}
</Text>
<Text className=" text-base font-normal" numberOfLines={1}>
C$ {(item?.price.toNumber() ?? 0) * quantity}
</Text>
</View>
</View>

<View className="flex h-full w-1/3 flex-grow flex-row">
<Pressable
className=" w-1/3 items-center justify-center bg-red-400 "
onPress={deleteItemFromCart}
>
<Ionicons name="trash-outline" size={18} color={"white"} />
</Pressable>
<Pressable
className=" w-1/3 items-center justify-center bg-cyan-600 "
onPress={removeItemToCart}
>
<Ionicons name="remove" size={20} color={"white"} />
</Pressable>
<Pressable
className=" w-1/3 items-center justify-center rounded-r bg-emerald-500"
onPress={addItemToCart}
>
<Ionicons name="add" size={20} color={"white"} />
</Pressable>
</View>
</View>
);
}

export default CartItem;
export default memo(CartItem);
17 changes: 0 additions & 17 deletions apps/expo/src/screens/CartScreen.tsx

This file was deleted.

46 changes: 46 additions & 0 deletions apps/expo/src/screens/cart-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useAtomValue } from "jotai";
import { cartAtom, cartTotalAtom } from "../atoms/cart";
import CartItem from "../components/CartItem";
import { Text, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { useMemo } from "react";

function CartListScreen() {
const cart = useAtomValue(cartAtom);

const data = useMemo(() => Object.entries(cart), [cart]);
const total = useAtomValue(cartTotalAtom);

return (
<View className="flex-1 bg-gray-50">
<View className="mb-2.5 h-12 justify-center border-b border-b-gray-200 bg-white shadow-sm">
<Text className="ml-2.5 text-lg ">Shopping Cart</Text>
</View>

<View className="flex-1 px-2">
<View className=" flex flex-row items-center justify-between">
<Text className="text-lg">
<Text className="text-xl font-medium">Total: </Text>C${" "}
{total.toFixed(2)}
</Text>
</View>

<View className="mt-1.5 mb-2.5 border-b-[0.5px] border-b-gray-400" />
<FlatList
data={data}
renderItem={(item) => (
<CartItem
id={Number(item.item[0])}
key={item.item[0]}
quantity={item.item[1]}
/>
)}
ItemSeparatorComponent={() => <View className="mt-2.5" />}
ListFooterComponent={() => <View className="mt-2.5" />}
/>
</View>
</View>
);
}

export default CartListScreen;
18 changes: 2 additions & 16 deletions apps/expo/src/screens/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import ItemListScreen from "./item-list";
import ProfileScreen from "./profile";
import { SafeAreaView } from "react-native-safe-area-context";
import { useAtomValue } from "jotai";
import { cartAtom } from "../atoms/cart";
import CartItem from "../components/CartItem";
import { HomeTabParamList } from "../types/navigation";
import CartListScreen from "./cart-list";

const Tab = createBottomTabNavigator<HomeTabParamList>();

Expand All @@ -14,23 +12,11 @@ function HomeScreen() {
<SafeAreaView className="h-full">
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Search" component={ItemListScreen} />
<Tab.Screen name="Cart" component={CartScreen} />
<Tab.Screen name="Cart" component={CartListScreen} />
<Tab.Screen name="My Profile" component={ProfileScreen} />
</Tab.Navigator>
</SafeAreaView>
);
}

function CartScreen() {
const cart = useAtomValue(cartAtom);

return (
<>
{Object.entries(cart).map(([key, value]) => (
<CartItem id={Number(key)} key={key} quantity={value} />
))}
</>
);
}

export default HomeScreen;
64 changes: 35 additions & 29 deletions apps/expo/src/screens/single-item.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Image, Pressable, Text, View } from "react-native";
import { useEffect } from "react";
import { memo, useEffect } from "react";
import { useAtomValue, useSetAtom } from "jotai";
import { singleProductAtomFamily } from "../atoms/products";
import { addItemToCartAtom, cartItemQuantityAtomFamily } from "../atoms/cart";
Expand Down Expand Up @@ -88,37 +88,43 @@ function SingleItemScreen({
</View>
</View>

<Pressable
className="mt-2.5 rounded bg-emerald-500 p-2 active:bg-emerald-600"
onPress={() => addToCart()}
>
<Text className="self-center text-base font-medium text-white">
Add to cart
</Text>
</Pressable>
{item.stock.toNumber() !== 0 && (
<>
<Pressable
className="mt-2.5 rounded bg-emerald-500 p-2 active:bg-emerald-600 disabled:bg-emerald-100"
onPress={() => addToCart()}
>
<Text className="self-center text-base font-medium text-white">
Add to cart
</Text>
</Pressable>

{user?.publicMetadata.isAdmin && (
<Pressable
className="mt-2.5 rounded bg-cyan-500 p-2 active:bg-cyan-600"
onPress={() =>
navigation.push("Update Item", { id: route.params.id })
}
>
<Text className="self-center text-base font-medium text-white">
Update Item
</Text>
</Pressable>
{user?.publicMetadata.isAdmin && (
<Pressable
className="mt-2.5 rounded bg-cyan-500 p-2 active:bg-cyan-600"
onPress={() =>
navigation.push("Update Item", { id: route.params.id })
}
>
<Text className="self-center text-base font-medium text-white">
Update Item
</Text>
</Pressable>
)}
<Pressable
className="mt-2.5 rounded border-2 border-emerald-500 p-2"
onPress={() =>
navigation.navigate("HomeScreen", { screen: "Cart" })
}
>
<Text className="self-center text-base font-medium text-emerald-700">
Go to cart
</Text>
</Pressable>
</>
)}
<Pressable
className="mt-2.5 rounded border-2 border-emerald-500 p-2"
onPress={() => navigation.navigate("HomeScreen", { screen: "Cart" })}
>
<Text className="self-center text-base font-medium text-emerald-700">
Go to cart
</Text>
</Pressable>
</View>
);
}

export default SingleItemScreen;
export default memo(SingleItemScreen);

0 comments on commit 326ecce

Please sign in to comment.