Skip to content

Commit

Permalink
Update Product
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Romero committed Nov 26, 2023
1 parent 12ed81b commit 6a921aa
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
2 changes: 2 additions & 0 deletions apps/expo/src/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Provider } from "jotai";
import type { RootStackParamList } from "./types/navigation";
import CreateItemScreen from "./screens/create-item";
import jotaiStore from "./atoms/store";
import UpdateItemScreen from "./screens/update-item";

const Stack = createNativeStackNavigator<RootStackParamList>();

Expand Down Expand Up @@ -43,6 +44,7 @@ function RootNavigator() {
{user?.publicMetadata.isAdmin && (
<>
<Stack.Screen name="Create Item" component={CreateItemScreen} />
<Stack.Screen name="Update Item" component={UpdateItemScreen} />
</>
)}
</Stack.Navigator>
Expand Down
16 changes: 16 additions & 0 deletions apps/expo/src/screens/single-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAtomValue, useSetAtom } from "jotai";
import { singleProductAtomFamily } from "../atoms/products";
import { addItemToCartAtom, cartItemQuantityAtomFamily } from "../atoms/cart";
import type { RootStackScreenProps } from "../types/navigation";
import { useUser } from "@clerk/clerk-expo";

function SingleItemScreen({
route,
Expand All @@ -16,6 +17,7 @@ function SingleItemScreen({
const addToCart = useSetAtom(
addItemToCartAtom({ id: route.params.id, quantity: 1 }),
);
const { user } = useUser();

useEffect(() => {
navigation.setOptions({ title: item?.name ?? "Loading Item" });
Expand Down Expand Up @@ -85,6 +87,7 @@ function SingleItemScreen({
)}
</View>
</View>

<Pressable
className="mt-2.5 rounded bg-emerald-500 p-2 active:bg-emerald-600"
onPress={() => addToCart()}
Expand All @@ -93,6 +96,19 @@ function SingleItemScreen({
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>
)}
<Pressable
className="mt-2.5 rounded border-2 border-emerald-500 p-2"
onPress={() => navigation.navigate("HomeScreen", { screen: "Cart" })}
Expand Down
192 changes: 192 additions & 0 deletions apps/expo/src/screens/update-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { ActivityIndicator, Pressable, Text, View } from "react-native";
import type { RootStackScreenProps } from "../types/navigation";
import TextInput from "../components/core/TextInput";
import { trpc } from "../utils/trpc";
import { z } from "zod";
import { productsAtom, singleProductAtomFamily } from "../atoms/products";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import BannerError from "../components/core/BannerError";
import jotaiStore from "../atoms/store";
import { useAtomValue } from "jotai";
import { useMemo } from "react";

const getUpdateItemFormValidator = (name: string) => {
const allItems = jotaiStore.get(productsAtom);

return z.object({
name: z
.string()
.min(1, "Nombre no puede estar vacio")
.refine(
(val) =>
allItems.filter(
(product) => product.name === val && product.name !== name,
).length === 0,
{ message: "Name Already Exists" },
)
.optional(),
price: z.string(),
stock: z.string(),
description: z.string().optional(),
});
};

export type UpdateItemFormSchema = Zod.infer<
ReturnType<typeof getUpdateItemFormValidator>
>;

function UpdateItemScreen({
navigation,
route,
}: RootStackScreenProps<"Update Item">) {
const { mutate, isLoading } = trpc.item.update.useMutation();
const trpcUtils = trpc.useContext();

const item = useAtomValue(singleProductAtomFamily(route.params.id));
const validator = useMemo(
() => getUpdateItemFormValidator(item?.name ?? ""),
[item?.name],
);
if (!item) return null;

const { control, handleSubmit, setError, formState, reset } =
useForm<UpdateItemFormSchema>({
resolver: zodResolver(validator),
mode: "onBlur",
defaultValues: {
name: item.name,
price: item.price.toFixed(2),
stock: item.stock.toFixed(2),
description: item.description ?? "",
},
});

const onSubmit = handleSubmit((vals) => {
mutate(
{
id: route.params.id,
name: vals.name,
price: vals.price ? Number(vals.price) : undefined,
stock: vals.stock ? Number(vals.stock) : undefined,
description: vals.description,
},
{
onError: (err) => {
setError("root", {
message: JSON.parse(err.message)[0].message,
});
},
onSuccess: (vals) => {
trpcUtils.item.all.setData(undefined, (prev) =>
prev?.map((v) => {
if (v.id === route.params.id) {
return vals;
}
return v;
}),
);
trpcUtils.item.all.invalidate();
reset();
navigation.pop(1);
},
},
);
});

return (
<View className="mt-2.5 h-full flex-1 bg-gray-50 px-2">
<View className="rounded-md border border-gray-200 bg-white p-4 shadow ">
{formState.errors.root && (
<BannerError error={formState.errors.root.message} />
)}

<Text>Name</Text>
<Controller
control={control}
name="name"
render={({ field: { onBlur, onChange, value } }) => (
<TextInput
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder={item.name}
error={formState.errors.name?.message}
/>
)}
/>
<Text>Price</Text>
<Controller
control={control}
name="price"
render={({ field: { onBlur, onChange, value } }) => (
<TextInput
onBlur={onBlur}
onChangeText={(text) => {
const validated = text.match(/^(\d*\.{0,1}\d{0,2}$)/);
if (validated) {
onChange(text);
}
}}
value={value}
placeholder={item.price.toFixed(2)}
error={formState.errors.price?.message}
keyboardType="numeric"
/>
)}
/>
<Text>Initial Stock</Text>
<Controller
control={control}
name="stock"
render={({ field: { onBlur, onChange, value } }) => (
<TextInput
onBlur={onBlur}
onChangeText={(text) => {
const validated = text.match(/^(\d*\.{0,1}\d{0,2}$)/);
if (validated) {
onChange(text);
}
}}
value={value}
placeholder={item.stock.toFixed(2)}
error={formState.errors.stock?.message}
keyboardType="numeric"
/>
)}
/>
<Text>Description</Text>
<Controller
control={control}
name="description"
render={({ field: { onBlur, onChange, value } }) => (
<TextInput
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder={item.description ?? undefined}
error={formState.errors.description?.message}
multiline
className="pr-3"
/>
)}
/>
<Pressable
className="mt-2.5 rounded bg-cyan-500 p-2 active:bg-cyan-600"
onPress={onSubmit}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color={"white"} className="self-center" />
) : (
<Text className="self-center font-medium text-white">
Update Item
</Text>
)}
</Pressable>
</View>
</View>
);
}

export default UpdateItemScreen;
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 @@ -9,6 +9,7 @@ export type RootStackParamList = {
HomeScreen: NavigatorScreenParams<HomeTabParamList>;
SingleItemScreen: { id: number };
"Create Item": undefined;
"Update Item": { id: number };
};

export type RootStackScreenProps<T extends keyof RootStackParamList> =
Expand Down

0 comments on commit 6a921aa

Please sign in to comment.