Replace the profile tab with prescription management (inline reminders and family invite), add a shared tab top bar, separate notifications from orders, and support any-branch guest checkout with GSH codes. Co-authored-by: Cursor <cursoragent@cursor.com>
208 lines
7.5 KiB
TypeScript
208 lines
7.5 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
|
import { router } from "expo-router";
|
|
import { useMemo, useState } from "react";
|
|
import { FlatList, Pressable, ScrollView, View } from "react-native";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import { ActionTile, CategoryTile } from "@/components/ui/CategoryTile";
|
|
import { Card, Section } from "@/components/ui/Card";
|
|
import { ProductRow, ProductTile } from "@/components/ui/ProductCard";
|
|
import { Screen, TAB_BAR_INSET } from "@/components/ui/Screen";
|
|
import { SearchAction, SearchField } from "@/components/ui/SearchField";
|
|
import { TabTopBar } from "@/components/ui/TabTopBar";
|
|
import { Text } from "@/components/ui/Text";
|
|
import { products } from "@/lib/data/products";
|
|
import { categories, routes } from "@/lib/routes";
|
|
import { usePrescriptionStore } from "@/lib/stores/prescriptionStore";
|
|
import { colors, gutter, space } from "@/lib/theme";
|
|
|
|
export default function HomeScreen() {
|
|
const { t } = useTranslation();
|
|
const [category, setCategory] = useState("medicines");
|
|
const prescriptions = usePrescriptionStore((s) => s.prescriptions);
|
|
|
|
const filtered = useMemo(
|
|
() => products.filter((p) => p.category === category),
|
|
[category]
|
|
);
|
|
|
|
const popular = useMemo(
|
|
() =>
|
|
[...products]
|
|
.filter((p) => p.badge === "bestseller" || p.rating >= 4.6)
|
|
.slice(0, 6),
|
|
[]
|
|
);
|
|
|
|
const refillSoon = prescriptions.find((p) => {
|
|
if (!p.lastFilledAt || !p.daysSupply) return false;
|
|
const due = new Date(p.lastFilledAt);
|
|
due.setDate(due.getDate() + p.daysSupply);
|
|
const days = (due.getTime() - Date.now()) / (1000 * 60 * 60 * 24);
|
|
return days <= 5;
|
|
});
|
|
|
|
const pendingRx = prescriptions.find(
|
|
(p) => p.status === "under_review" || p.status === "submitted"
|
|
);
|
|
|
|
return (
|
|
<Screen>
|
|
<TabTopBar />
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={{
|
|
paddingBottom: TAB_BAR_INSET + 24,
|
|
paddingHorizontal: gutter,
|
|
}}
|
|
>
|
|
<Text variant="display" style={{ marginTop: 22 }}>
|
|
{t("common.trustedTitle")}
|
|
</Text>
|
|
<Text variant="display" color={colors.brandText} style={{ marginTop: -2 }}>
|
|
{t("common.trustedSubtitle")}
|
|
</Text>
|
|
|
|
<View style={{ marginTop: 18 }}>
|
|
<SearchField
|
|
readOnlyPress={() => router.push(routes.search)}
|
|
trailing={
|
|
<SearchAction
|
|
icon="scan-outline"
|
|
label={t("common.scan")}
|
|
onPress={() => router.push(routes.rxScan)}
|
|
/>
|
|
}
|
|
/>
|
|
</View>
|
|
|
|
{(refillSoon || pendingRx) && (
|
|
<Pressable
|
|
onPress={() => router.push(routes.rxList)}
|
|
style={{ marginTop: 14 }}
|
|
>
|
|
<Card tone="brand" elevated={false}>
|
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
|
<View
|
|
style={{
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 12,
|
|
backgroundColor: colors.brandSoft,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
}}
|
|
>
|
|
<Ionicons name="medical-outline" size={20} color={colors.brandText} />
|
|
</View>
|
|
<View style={{ flex: 1 }}>
|
|
<Text variant="h3">
|
|
{refillSoon
|
|
? `${t("common.refillDue")}: ${refillSoon.medicineName}`
|
|
: `Rx under review: ${pendingRx?.medicineName}`}
|
|
</Text>
|
|
<Text variant="caption" color={colors.muted} style={{ marginTop: 2 }}>
|
|
Tap to manage prescriptions
|
|
</Text>
|
|
</View>
|
|
<Ionicons name="chevron-forward" size={18} color={colors.brandText} />
|
|
</View>
|
|
</Card>
|
|
</Pressable>
|
|
)}
|
|
|
|
<View style={{ flexDirection: "row", gap: 12, marginTop: 18 }}>
|
|
<ActionTile
|
|
icon="cloud-upload-outline"
|
|
title="Upload Rx"
|
|
subtitle="Photo or PDF"
|
|
tone="brand"
|
|
onPress={() => router.push(routes.rxScan)}
|
|
/>
|
|
<ActionTile
|
|
icon="camera-outline"
|
|
title="Scan Rx"
|
|
subtitle="Camera scan"
|
|
tone="surface"
|
|
onPress={() => router.push(routes.rxScan)}
|
|
/>
|
|
</View>
|
|
|
|
<Section title={t("common.categories")} action={t("common.seeAll")} onAction={() => router.push(routes.shop)}>
|
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
{categories.map((c) => (
|
|
<View key={c.id} style={{ marginRight: 10 }}>
|
|
<CategoryTile
|
|
icon={c.icon}
|
|
label={t(c.labelKey)}
|
|
selected={category === c.id}
|
|
onPress={() => setCategory(c.id)}
|
|
/>
|
|
</View>
|
|
))}
|
|
</ScrollView>
|
|
</Section>
|
|
|
|
<Section title="Today's specials" action={t("common.seeAll")} onAction={() => router.push(routes.shop)}>
|
|
<FlatList
|
|
horizontal
|
|
data={filtered.filter((p) => p.badge === "sale" || p.compareAt).slice(0, 8)}
|
|
keyExtractor={(item) => item.id}
|
|
showsHorizontalScrollIndicator={false}
|
|
ItemSeparatorComponent={() => <View style={{ width: space.md }} />}
|
|
renderItem={({ item }) => <ProductTile product={item} />}
|
|
ListEmptyComponent={
|
|
<FlatList
|
|
horizontal
|
|
data={filtered.slice(0, 6)}
|
|
keyExtractor={(item) => item.id}
|
|
showsHorizontalScrollIndicator={false}
|
|
ItemSeparatorComponent={() => <View style={{ width: space.md }} />}
|
|
renderItem={({ item }) => <ProductTile product={item} />}
|
|
/>
|
|
}
|
|
/>
|
|
</Section>
|
|
|
|
<Section title="Most popular" action={t("common.seeAll")} onAction={() => router.push(routes.shop)}>
|
|
<View style={{ gap: 10 }}>
|
|
{popular.map((p) => (
|
|
<ProductRow key={p.id} product={p} />
|
|
))}
|
|
</View>
|
|
</Section>
|
|
|
|
<Section title={t("common.prescriptions")} action="View all" onAction={() => router.push(routes.rxList)}>
|
|
<View style={{ gap: 10 }}>
|
|
{prescriptions.slice(0, 2).map((rx) => (
|
|
<Card
|
|
key={rx.id}
|
|
onPress={() => router.push(routes.rxDetail(rx.id))}
|
|
>
|
|
<View style={{ flexDirection: "row", justifyContent: "space-between", gap: 12 }}>
|
|
<View style={{ flex: 1 }}>
|
|
<Text variant="h3">{rx.medicineName}</Text>
|
|
<Text variant="caption" color={colors.subtle} style={{ marginTop: 4 }}>
|
|
{rx.patientName} · {rx.daysSupply} day supply
|
|
</Text>
|
|
</View>
|
|
<Badge
|
|
label={rx.status.replace("_", " ")}
|
|
tone={
|
|
rx.status === "filled" || rx.status === "approved"
|
|
? "brand"
|
|
: rx.status === "under_review"
|
|
? "warn"
|
|
: "neutral"
|
|
}
|
|
/>
|
|
</View>
|
|
</Card>
|
|
))}
|
|
</View>
|
|
</Section>
|
|
</ScrollView>
|
|
</Screen>
|
|
);
|
|
}
|