Skip to content

Commit

Permalink
feat: product on search screen
Browse files Browse the repository at this point in the history
  • Loading branch information
mrevanzak committed Nov 4, 2023
1 parent 4aa33a9 commit 6399ddb
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 22 deletions.
3 changes: 2 additions & 1 deletion apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.1",
"react-native-ui-lib": "^7.9.1",
"superjson": "1.13.1"
"superjson": "1.13.1",
"zustand": "^4.4.6"
},
"devDependencies": {
"@babel/core": "^7.23.2",
Expand Down
65 changes: 58 additions & 7 deletions apps/expo/src/app/(app)/search.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,66 @@
import { api } from "@/utils/api";
import React from "react";
import { Text, View } from "react-native-ui-lib";
import { Avatar, BorderRadiuses, Card, Text, View } from "react-native-ui-lib";
import { api } from "@/utils/api";
import rupiahFormatter from "@/utils/rupiahFormatter";
import { FlashList } from "@shopify/flash-list";
import { useSearchStore } from "@/lib/stores/useSearchStore";
import { useDebouncedValue } from "@/lib/hooks/useDebouncedValues";

export default function SearchScreen() {
const { data, isLoading, error } = api.product.getProduct.useQuery();

console.log(data, isLoading, error);
const search = useSearchStore((state) => state.search);
const [debouncedSearch] = useDebouncedValue(search, 500);
const { data, isFetching, refetch } = api.product.getProduct.useQuery(debouncedSearch);

return (
<View flex-1 bg-primary>
<View bg-white br50 absF padding-s4>
<Text primary text65>Produk</Text>
<View bg-white br50 absF padding-s4 className="rounded-b-none">
<Text primary text65>
Produk
</Text>
<FlashList
data={data}
numColumns={2}
estimatedItemSize={200}
onRefresh={() => refetch()}
refreshing={isFetching}
renderItem={({ item }) => {
return (
<Card flex-1 margin-8 enableShadow>
<Card.Image
source={{ uri: item.image }}
height={250}
borderRadius={BorderRadiuses.br60}
/>
<View
padding-s2
absH
bg-black
br50
className="bottom-0 opacity-70"
>
<Text white>{item.name}</Text>
<Text white>{rupiahFormatter(item.price)}</Text>
<View row right centerV>
<Avatar
source={{ uri: item.user.imageUrl }}
animate
useAutoColors
size={28}
/>
<View padding-s2>
<Text text100 white>
{item.user.name}
</Text>
<Text text100 white>
{item.user.email}
</Text>
</View>
</View>
</View>
</Card>
);
}}
/>
</View>
</View>
);
Expand Down
14 changes: 5 additions & 9 deletions apps/expo/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ import { Link, usePathname } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons";
import type { BottomTabHeaderProps } from "@react-navigation/bottom-tabs";
import type { NativeStackHeaderProps } from "@react-navigation/native-stack";
import { useSearchStore } from "@/lib/stores/useSearchStore";

export default function Header(
props: BottomTabHeaderProps | NativeStackHeaderProps,
) {
const pathname = usePathname();
const isSearchScreen = pathname === "/search";

const setSearch = useSearchStore((state) => state.setSearch);

return (
<SafeAreaView className="flex-row bg-[#157DC1]" {...props}>
{isSearchScreen && (
<Link href="/(tabs)/home" asChild>
<Link href="/(app)/(tabs)/home" asChild>
<Button
avoidInnerPadding
animateLayout
Expand Down Expand Up @@ -50,14 +53,7 @@ export default function Header(
marginLeft: 10,
}}
readonly={!isSearchScreen}
// onChangeText={onChangeText}
enableErrors
// validate={["required", "email", (value) => value.length > 6]}
// validationMessage={[
// "Field is required",
// "Email is invalid",
// "Password is too short",
// ]}
onChangeText={setSearch}
maxLength={30}
/>
</Button>
Expand Down
36 changes: 36 additions & 0 deletions apps/expo/src/lib/hooks/useDebouncedValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useRef, useState } from "react";

export function useDebouncedValue<T = unknown>(
value: T,
wait: number,
options = { leading: false },
) {
const [_value, setValue] = useState(value);
const mountedRef = useRef(false);
const timeoutRef = useRef<number | null>(null);
const cooldownRef = useRef(false);

const cancel = () => window.clearTimeout(timeoutRef.current!);

useEffect(() => {
if (mountedRef.current) {
if (!cooldownRef.current && options.leading) {
cooldownRef.current = true;
setValue(value);
} else {
cancel();
timeoutRef.current = window.setTimeout(() => {
cooldownRef.current = false;
setValue(value);
}, wait);
}
}
}, [value, options.leading, wait]);

useEffect(() => {
mountedRef.current = true;
return cancel;
}, []);

return [_value, cancel] as const;
}
11 changes: 11 additions & 0 deletions apps/expo/src/lib/stores/useSearchStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { create } from "zustand";

interface SearchState {
search: string;
setSearch: (search: string) => void;
}

export const useSearchStore = create<SearchState>()((set) => ({
search: "",
setSearch: (search) => set({ search }),
}));
7 changes: 7 additions & 0 deletions apps/expo/src/utils/rupiahFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function rupiahFormatter(value: number): string {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
minimumFractionDigits: 0,
}).format(value);
}
17 changes: 13 additions & 4 deletions packages/api/src/router/product.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { z } from "zod";

import { createTRPCRouter, protectedProcedure } from "../trpc";

export const productRouter = createTRPCRouter({
getProduct: protectedProcedure.query(({ ctx }) => {
return ctx.db.query.products.findMany();
}),
getProduct: protectedProcedure
.input(z.string())
.query(({ input, ctx }) => {
return ctx.db.query.products.findMany({
with: {
user: true,
},
where: (products, { like }) => like(products.name, `%${input.toLowerCase()}%`),
});
}),
});
2 changes: 1 addition & 1 deletion packages/db/schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const users = mySqlTable("user", {
email: varchar("email", { length: 255 })
.notNull()
.unique(),
imageUrl: varchar("image_url", { length: 255 }),
imageUrl: varchar("image_url", { length: 255 }).notNull(),
});

export const usersRelations = relations(users, ({ many }) => ({
Expand Down
23 changes: 23 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6399ddb

Please sign in to comment.