This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Mob/app/(root)/(tabs)/index.tsx
kirukib 7310a349c5 Add Gishen mobile app with complete demo shopping and care flows.
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>
2026-08-07 01:21:32 +03:00

248 lines
8.9 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 { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
import { Avatar } from "@/components/ui/Avatar";
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 { Text } from "@/components/ui/Text";
import { products } from "@/lib/data/products";
import { categories, routes } from "@/lib/routes";
import { useAuthStore } from "@/lib/stores/authStore";
import { usePrescriptionStore } from "@/lib/stores/prescriptionStore";
import { colors, gutter, space } from "@/lib/theme";
export default function HomeScreen() {
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const user = useAuthStore((s) => s.user);
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>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{
paddingTop: insets.top + 12,
paddingBottom: TAB_BAR_INSET + 24,
paddingHorizontal: gutter,
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Pressable
onPress={() => router.push(routes.profile)}
style={{ flexDirection: "row", alignItems: "center", gap: 12 }}
>
<Avatar name={user?.name} uri={user?.avatarUri} size={46} />
<View>
<Text variant="caption" color={colors.subtle}>
Good day
</Text>
<Text variant="h3">{user?.name?.split(" ")[0] || "Guest"}</Text>
</View>
</Pressable>
<Pressable
onPress={() => router.push(routes.orders)}
style={{
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: colors.surface,
borderWidth: 1,
borderColor: colors.line,
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons name="notifications-outline" size={20} color={colors.ink} />
</Pressable>
</View>
<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>
);
}