Skip to content

Commit

Permalink
Cart and Create Order
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Romero committed Nov 26, 2023
1 parent dc68076 commit 94500c8
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 40 deletions.
Binary file added apps/expo/assets/emptyCart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 28 additions & 13 deletions apps/expo/src/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { RootStackParamList } from "./types/navigation";
import CreateItemScreen from "./screens/create-item";
import jotaiStore from "./atoms/store";
import UpdateItemScreen from "./screens/update-item";
import AuthenticationScreenModal from "./screens/auth-modal";

const Stack = createNativeStackNavigator<RootStackParamList>();

Expand All @@ -26,7 +27,7 @@ function App() {
}

function RootNavigator() {
const { user } = useUser();
const { user, isSignedIn } = useUser();

return (
<Stack.Navigator
Expand All @@ -35,18 +36,32 @@ function RootNavigator() {
animation: "slide_from_bottom",
}}
>
<Stack.Screen
name="HomeScreen"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="SingleItemScreen" component={SingleItemScreen} />
{user?.publicMetadata.isAdmin && (
<>
<Stack.Screen name="Create Item" component={CreateItemScreen} />
<Stack.Screen name="Update Item" component={UpdateItemScreen} />
</>
)}
<Stack.Group>
<Stack.Screen
name="HomeScreen"
component={HomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="SingleItemScreen" component={SingleItemScreen} />
{user?.publicMetadata.isAdmin && (
<>
<Stack.Screen name="Create Item" component={CreateItemScreen} />
<Stack.Screen name="Update Item" component={UpdateItemScreen} />
</>
)}
</Stack.Group>

<Stack.Group
screenOptions={{
presentation: "transparentModal",
headerShown: false,
animation: "fade_from_bottom",
}}
>
{!isSignedIn && (
<Stack.Screen name="Sign In" component={AuthenticationScreenModal} />
)}
</Stack.Group>
</Stack.Navigator>
);
}
Expand Down
12 changes: 9 additions & 3 deletions apps/expo/src/components/CartItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ 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";
import { useNavigation } from "@react-navigation/native";

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 }));

const navigation = useNavigation();

return (
<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}>
<Pressable
className="w-2/3 flex-row items-center p-2"
onPress={() => navigation.navigate("SingleItemScreen", { id: id })}
>
<Text className="mr-2 text-base" numberOfLines={1}>
{quantity}
</Text>
<Image
Expand All @@ -34,7 +40,7 @@ function CartItem({ id, quantity }: { id: number; quantity: number }) {
C$ {(item?.price.toNumber() ?? 0) * quantity}
</Text>
</View>
</View>
</Pressable>

<View className="flex h-full w-1/3 flex-grow flex-row">
<Pressable
Expand Down
26 changes: 26 additions & 0 deletions apps/expo/src/screens/auth-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Pressable } from "react-native";
import type { RootStackScreenProps } from "../types/navigation";
import AuthenticationCard from "../components/auth/AuthenticationCard";

function AuthenticationScreenModal({
navigation,
}: RootStackScreenProps<"Sign In">) {
return (
<Pressable
className="h-full justify-center bg-gray-600 px-2"
style={{ backgroundColor: "rgba(75, 85, 99, .3)" }}
onPressOut={() => navigation.pop(1)}
onPress={(event) => {
if (event.target == event.currentTarget) {
navigation.pop(1);
}
}}
>
<Pressable>
<AuthenticationCard />
</Pressable>
</Pressable>
);
}

export default AuthenticationScreenModal;
102 changes: 79 additions & 23 deletions apps/expo/src/screens/cart-list.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,102 @@
import { useAtomValue } from "jotai";
import { useAtom, useAtomValue } from "jotai";
import { cartAtom, cartTotalAtom } from "../atoms/cart";
import CartItem from "../components/CartItem";
import { Text, View } from "react-native";
import { Image, Pressable, Text, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { useMemo } from "react";
import { HomeTabScreenProps } from "../types/navigation";
import { useAuth } from "@clerk/clerk-expo";
import { trpc } from "../utils/trpc";

function CartListScreen() {
function CartListScreen({ navigation }: HomeTabScreenProps<"Cart">) {
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="flex-1 bg-white">
<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>

<CartHeader navigation={navigation} />
<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]}

{data.length > 0 ? (
<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" />}
contentContainerStyle={{ justifyContent: "center" }}
/>
) : (
<View className="h-full items-center justify-center pb-48">
<Image
source={require("../../assets/emptyCart.png")}
className="h-72 w-72 "
/>
)}
ItemSeparatorComponent={() => <View className="mt-2.5" />}
ListFooterComponent={() => <View className="mt-2.5" />}
/>
<Text className="text-xl text-gray-700">Your cart is empty</Text>
</View>
)}
</View>
</View>
);
}

function CartHeader({
navigation,
}: {
navigation: HomeTabScreenProps<"Cart">["navigation"];
}) {
const { isSignedIn } = useAuth();
const total = useAtomValue(cartTotalAtom);
const [cart, setCart] = useAtom(cartAtom);
const { mutate, isLoading } = trpc.order.create.useMutation();

const onCreateOrder = () => {
const cartEntries = Object.entries(cart).map(([key, value]) => ({
id: Number(key),
quantity: value,
}));

if (cartEntries.length > 0) {
const input = {
total: total,
items: cartEntries,
};
mutate(input, {
onSuccess: () => {
setCart({});
},
});
}
};

return (
<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>
<Pressable
className="rounded bg-emerald-500 p-1.5 active:bg-emerald-600"
onPress={!isSignedIn ? () => navigation.push("Sign In") : onCreateOrder}
disabled={isLoading}
>
<Text className="text-white">
{isSignedIn ? "Create Order" : "Log In to Buy"}
</Text>
</Pressable>
</View>
);
}

export default CartListScreen;
1 change: 1 addition & 0 deletions apps/expo/src/types/navigation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type RootStackParamList = {
SingleItemScreen: { id: number };
"Create Item": undefined;
"Update Item": { id: number };
"Sign In": undefined;
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { router } from "../trpc";
import { itemRouter } from "./item";
import { authRouter } from "./auth";
import { orderRouter } from "./order";

export const appRouter = router({
item: itemRouter,
auth: authRouter,
order: orderRouter
});

// export type definition of API
Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/router/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ export const itemRouter = router({
)
.mutation(({ ctx, input }) => {
return ctx.prisma.items.create({
data: input,
data: {
name: input.name,
price: input.price,
stock: input.stock,
description: input.description,
categoryId: input.categoryId
},
include: {
category: {
select: {
Expand Down
48 changes: 48 additions & 0 deletions packages/api/src/router/order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { router, protectedProcedure } from "../trpc";
import { z } from "zod";

export const orderRouter = router({
create: protectedProcedure
.input(
z.object({
total: z.number().nonnegative(),
items: z
.object({
id: z.number().positive(),
quantity: z.number().nonnegative(),
})
.array(),
}),
)
.mutation(async ({ ctx, input }) => {
const itemIds = input.items.map((item) => item.id);
const items = await ctx.prisma.items.findMany({
where: {
id: { in: itemIds },
},
});

const q: Record<number, number> = {};
input.items.forEach((item) => {
q[item.id] = item.quantity;
});

return await ctx.prisma.orders.create({
data: {
status: "Placed",
total: input.total,
user_id: ctx.auth.userId,
orderitems: {
create: items.map((item) => ({
name: item.name,
price: item.price,
quantity: q[item.id] ?? 0,
})),
},
},
include: {
orderitems: true,
},
});
}),
});

0 comments on commit 94500c8

Please sign in to comment.