Skip to content

Commit

Permalink
Basic single-item screen created and and root stack navigator
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Romero committed Nov 22, 2023
1 parent f0db81d commit 7c770ee
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 44 deletions.
3 changes: 3 additions & 0 deletions apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@clerk/clerk-expo": "^0.17.7",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"@react-navigation/stack": "^6.3.20",
"@shopify/flash-list": "^1.4.0",
"@tanstack/react-query": "^4.16.1",
"@trpc/client": "^10.1.0",
Expand All @@ -33,6 +35,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.70.5",
"react-native-gesture-handler": "~2.9.0",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-web": "~0.18.10"
Expand Down
39 changes: 30 additions & 9 deletions apps/expo/src/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
import React from "react";
import React, { ReactNode } from "react";
import "react-native-gesture-handler";

import { ClerkProvider } from "@clerk/clerk-expo";
import { tokenCache } from "./utils/cache";
import Constants from "expo-constants";
import HomeScreen from "./screens/home";
import { TRPCProvider } from "./utils/trpc";
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { NavigationContainer, NavigationProp } from "@react-navigation/native";
import SingleItemScreen from "./screens/single-item";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const Tab = createBottomTabNavigator();
export type MainNavigationStackParams = {
HomeScreen: undefined;
SingleItemScreen: { id: number };
};

export type MainNavigationStack = NavigationProp<MainNavigationStackParams>;

const Stack = createNativeStackNavigator<MainNavigationStackParams>();

function App() {
return (
<Providers>
<Stack.Navigator
screenOptions={{
headerTintColor: "black",
animation: "slide_from_bottom",
}}
>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
<Stack.Screen name="SingleItemScreen" component={SingleItemScreen} />
</Stack.Navigator>
</Providers>
);
}

function Providers({ children }: { children: ReactNode }) {
return (
<ClerkProvider
publishableKey={Constants.expoConfig?.extra?.CLERK_PUBLISHABLE_KEY}
tokenCache={tokenCache}
>
<TRPCProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Selector" component={HomeScreen} />
</Tab.Navigator>
</NavigationContainer>
<NavigationContainer>{children}</NavigationContainer>
</TRPCProvider>
</ClerkProvider>
);
Expand Down
12 changes: 9 additions & 3 deletions apps/expo/src/components/ProductCard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import type { AppRouter } from "@acme/api";
import { useNavigation } from "@react-navigation/native";
import type { inferProcedureOutput } from "@trpc/server";
import { memo } from "react";
import { View, Image, Text } from "react-native";
import { View, Image, Text, TouchableOpacity } from "react-native";
import { MainNavigationStack } from "../_app";

export interface ProductCardProps {
item: inferProcedureOutput<AppRouter["item"]["byId"]>;
}

function ProductCard({ item }: ProductCardProps) {
const hasStock = item.stock.greaterThan(0);
const navigator = useNavigation<MainNavigationStack>();

return (
<View className="flex flex-row items-center rounded border border-gray-200 bg-white p-2 py-4 shadow">
<TouchableOpacity
className="flex flex-row items-center rounded border border-gray-200 bg-white p-2 py-4 shadow"
onPress={() => navigator.navigate("SingleItemScreen", { id: item.id })}
>
<Image
source={{ uri: item.image_url ?? "" }}
className="h-20 w-20"
Expand Down Expand Up @@ -40,7 +46,7 @@ function ProductCard({ item }: ProductCardProps) {
)}
</View>
</View>
</View>
</TouchableOpacity>
);
}

Expand Down
38 changes: 7 additions & 31 deletions apps/expo/src/screens/home.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,13 @@
import React, { useMemo, useState } from "react";
import { FlatList, View, TextInput } from "react-native";
import { trpc } from "../utils/trpc";
import ProductCard from "../components/ProductCard";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import ItemListScreen from "./item-list";

function HomeScreen() {
const { data } = trpc.item.all.useQuery();

const [nameFilter, setNameFilter] = useState("");

const filteredData = useMemo(
() =>
data?.filter((item) =>
item.name.toLowerCase().includes(nameFilter.toLocaleLowerCase()),
) ?? [],
[nameFilter, data],
);
const Tab = createBottomTabNavigator();

function HomeScreen() {
return (
<View className="flex-1 bg-gray-50 px-2">
<TextInput
className="my-2 rounded border border-gray-200 bg-white py-1.5 pl-3 shadow-lg"
placeholder="Busca un Producto"
onChangeText={setNameFilter}
/>
<FlatList
data={filteredData}
renderItem={(item) => (
<ProductCard item={item.item} key={item.item.id} />
)}
ItemSeparatorComponent={() => <View className="mt-2.5" />}
ListFooterComponent={() => <View className="mt-2.5" />}
/>
</View>
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Selector" component={ItemListScreen} />
</Tab.Navigator>
);
}

Expand Down
38 changes: 38 additions & 0 deletions apps/expo/src/screens/item-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useMemo, useState } from "react";
import { FlatList, View, TextInput } from "react-native";
import { trpc } from "../utils/trpc";
import ProductCard from "../components/ProductCard";

function ItemListScreen() {
const { data } = trpc.item.all.useQuery();

const [nameFilter, setNameFilter] = useState("");

const filteredData = useMemo(
() =>
data?.filter((item) =>
item.name.toLowerCase().includes(nameFilter.toLocaleLowerCase()),
) ?? [],
[nameFilter, data],
);

return (
<View className="flex-1 bg-gray-50 px-2">
<TextInput
className="my-2 rounded border border-gray-200 bg-white py-1.5 pl-3 shadow-lg"
placeholder="Busca un Producto"
onChangeText={setNameFilter}
/>
<FlatList
data={filteredData}
renderItem={(item) => (
<ProductCard item={item.item} key={item.item.id} />
)}
ItemSeparatorComponent={() => <View className="mt-2.5" />}
ListFooterComponent={() => <View className="mt-2.5" />}
/>
</View>
);
}

export default ItemListScreen;
75 changes: 75 additions & 0 deletions apps/expo/src/screens/single-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { StackScreenProps } from "@react-navigation/stack";
import { MainNavigationStack, MainNavigationStackParams } from "../_app";
import { ActivityIndicator, Image, Text, View } from "react-native";

Check warning on line 3 in apps/expo/src/screens/single-item.tsx

View workflow job for this annotation

GitHub Actions / build-lint

'ActivityIndicator' is defined but never used
import { trpc } from "../utils/trpc";
import { useNavigation } from "@react-navigation/native";
import { useEffect } from "react";

export type SingleItemScreenProps = StackScreenProps<
MainNavigationStackParams,
"SingleItemScreen"
>;

function SingleItemScreen({ route }: SingleItemScreenProps) {
const nav = useNavigation<MainNavigationStack>();
const { data } = trpc.item.byId.useQuery({ id: route.params.id });

useEffect(() => {
nav.setOptions({ title: data?.name ?? "Loading Item" });
}, [data?.name]);

return (
<View className="h-full bg-white px-2 pt-4">
{data?.image_url ? (
<Image
source={{ uri: data?.image_url }}
resizeMode="contain"
className="h-48 "
/>
) : (
<View className="margin-0 h-48 w-48 items-center justify-center self-center rounded border border-gray-200 bg-gray-50">
<Text className="text-lg text-gray-500">No Image</Text>
</View>
)}

<View className="flex gap-1.5 pl-4 pt-2.5">
<View>
<Text className="text-lg font-semibold">
Price:
{data?.price ? (
<Text className="font-normal"> C$ {data.price.toNumber()}</Text>
) : (
<Text className="text-base font-normal">Free</Text>
)}
</Text>
</View>
<View>
<Text className="text-lg font-semibold">
Stock:
{data?.stock ? (
<Text className="font-normal">
{" "}
{data.stock.toNumber().toFixed(2)}
</Text>
) : (
<Text className="text-base font-normal">0.00</Text>
)}
</Text>
</View>
<View>
<Text className="text-lg font-semibold">Description:</Text>

{data?.description ? (
<Text className="text-lg">{data?.description}</Text>
) : (
<Text className="text-lg text-gray-500">
There is no description available
</Text>
)}
</View>
</View>
</View>
);
}

export default SingleItemScreen;
2 changes: 1 addition & 1 deletion packages/api/src/router/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const itemRouter = router({

byId: publicProcedure
.input(z.object({ id: z.number().positive() }))
.query(({ ctx, input }) => {
.query(async ({ ctx, input }) => {
return ctx.prisma.items.findFirstOrThrow({
where: {
id: input.id,
Expand Down
Loading

0 comments on commit 7c770ee

Please sign in to comment.