Ship Expo Router screens, seeded stores, and polished UX so auth, shop, checkout, prescriptions, loyalty, family, and account processes work end-to-end offline. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
import { useLocalSearchParams } from "expo-router";
|
|
import { useMemo, useState } from "react";
|
|
import { FlatList, useWindowDimensions, View } from "react-native";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Chip, ChipRow } from "@/components/ui/Chip";
|
|
import { EmptyState } from "@/components/ui/EmptyState";
|
|
import { AppHeader } from "@/components/ui/Header";
|
|
import { ProductCard, GRID_CARD_GAP } from "@/components/ui/ProductCard";
|
|
import { Screen } from "@/components/ui/Screen";
|
|
import { SearchField } from "@/components/ui/SearchField";
|
|
import { products } from "@/lib/data/products";
|
|
import { categories } from "@/lib/routes";
|
|
import { gutter } from "@/lib/theme";
|
|
|
|
export default function ShopScreen() {
|
|
const { t } = useTranslation();
|
|
const params = useLocalSearchParams<{ q?: string; category?: string }>();
|
|
const [q, setQ] = useState(params.q ?? "");
|
|
const [category, setCategory] = useState(params.category ?? "");
|
|
const { width } = useWindowDimensions();
|
|
const cardWidth = (width - gutter * 2 - GRID_CARD_GAP) / 2;
|
|
|
|
const data = useMemo(() => {
|
|
return products.filter((p) => {
|
|
const catOk = !category || p.category === category;
|
|
const qOk =
|
|
!q ||
|
|
p.name.toLowerCase().includes(q.toLowerCase()) ||
|
|
p.brand.toLowerCase().includes(q.toLowerCase()) ||
|
|
p.description.toLowerCase().includes(q.toLowerCase());
|
|
return catOk && qOk;
|
|
});
|
|
}, [q, category]);
|
|
|
|
return (
|
|
<Screen>
|
|
<AppHeader
|
|
large
|
|
back
|
|
title="Explore products"
|
|
subtitle="Trusted medicines, vitamins and care essentials"
|
|
/>
|
|
<View style={{ paddingHorizontal: gutter, marginBottom: 12 }}>
|
|
<SearchField
|
|
value={q}
|
|
onChangeText={setQ}
|
|
onClear={() => setQ("")}
|
|
placeholder={t("common.search")}
|
|
/>
|
|
</View>
|
|
<ChipRow gutters={gutter}>
|
|
<Chip
|
|
label="All"
|
|
selected={!category}
|
|
onPress={() => setCategory("")}
|
|
/>
|
|
{categories.map((c) => (
|
|
<Chip
|
|
key={c.id}
|
|
label={t(c.labelKey)}
|
|
selected={category === c.id}
|
|
onPress={() => setCategory(c.id)}
|
|
icon={c.icon}
|
|
/>
|
|
))}
|
|
</ChipRow>
|
|
<FlatList
|
|
data={data}
|
|
numColumns={2}
|
|
keyExtractor={(i) => i.id}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: gutter,
|
|
paddingTop: 16,
|
|
paddingBottom: 40,
|
|
gap: GRID_CARD_GAP,
|
|
}}
|
|
columnWrapperStyle={{ gap: GRID_CARD_GAP }}
|
|
ListEmptyComponent={
|
|
<EmptyState
|
|
icon="search-outline"
|
|
title="No matches"
|
|
body="Try another medicine name, brand or category."
|
|
compact
|
|
/>
|
|
}
|
|
renderItem={({ item }) => <ProductCard product={item} width={cardWidth} />}
|
|
/>
|
|
</Screen>
|
|
);
|
|
}
|