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)/(screens)/subscriptions/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

256 lines
9.4 KiB
TypeScript

import { useMemo, useState } from "react";
import { Alert, ScrollView, View } from "react-native";
import { useTranslation } from "react-i18next";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Chip, Segmented } from "@/components/ui/Chip";
import { Field } from "@/components/ui/Field";
import { AppHeader } from "@/components/ui/Header";
import { Screen } from "@/components/ui/Screen";
import { Text } from "@/components/ui/Text";
import { products } from "@/lib/data/products";
import { useFamilyStore } from "@/lib/stores/familyStore";
import { useLocationStore, type Fulfillment, type PaymentMethod } from "@/lib/stores/locationStore";
import { useSubscriptionStore } from "@/lib/stores/subscriptionStore";
import { colors, gutter } from "@/lib/theme";
const FREQUENCIES = [
{ days: 7, label: "Weekly" },
{ days: 14, label: "Every 2 weeks" },
{ days: 30, label: "Monthly" },
{ days: 60, label: "Every 2 months" },
{ days: 90, label: "Every 3 months" },
];
export default function SubscriptionsScreen() {
const { t } = useTranslation();
const items = useSubscriptionStore((s) => s.items);
const add = useSubscriptionStore((s) => s.add);
const pause = useSubscriptionStore((s) => s.pause);
const resume = useSubscriptionStore((s) => s.resume);
const cancel = useSubscriptionStore((s) => s.cancel);
const paymentPref = useLocationStore((s) => s.paymentPreference);
const fulfillmentPref = useLocationStore((s) => s.fulfillment);
const members = useFamilyStore((s) => s.members);
const refillCandidates = useMemo(
() =>
products.filter(
(p) =>
p.requiresPrescription ||
p.category === "diabetic" ||
p.category === "vitamins" ||
p.badge === "bestseller"
),
[]
);
const [showCreate, setShowCreate] = useState(false);
const [productId, setProductId] = useState(refillCandidates[0]?.id ?? products[0].id);
const [qty, setQty] = useState("1");
const [frequencyDays, setFrequencyDays] = useState(30);
const [fulfillment, setFulfillment] = useState<Fulfillment>(fulfillmentPref);
const [payment, setPayment] = useState<PaymentMethod>(
paymentPref === "pay_at_branch" && fulfillmentPref === "delivery"
? "chapa"
: paymentPref
);
const [memberId, setMemberId] = useState<string | null>(null);
const [query, setQuery] = useState("");
const filtered = refillCandidates.filter(
(p) =>
!query ||
p.name.toLowerCase().includes(query.toLowerCase()) ||
p.brand.toLowerCase().includes(query.toLowerCase())
);
const selected = products.find((p) => p.id === productId) ?? products[0];
return (
<Screen>
<AppHeader
title={t("common.subscriptions")}
subtitle="Auto-refill chronic medicines on a schedule"
/>
<ScrollView contentContainerStyle={{ paddingHorizontal: gutter, paddingBottom: 40, gap: 12 }}>
{!showCreate ? (
<Button
title="Create refill subscription"
icon="add-circle-outline"
onPress={() => setShowCreate(true)}
/>
) : (
<Card elevated={false}>
<Text variant="h3" style={{ marginBottom: 12 }}>
New subscription
</Text>
<Field
label="Search product"
value={query}
onChangeText={setQuery}
placeholder="Metformin, Vitamin C…"
icon="search-outline"
/>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={{ marginTop: 12 }}
contentContainerStyle={{ gap: 8 }}
>
{filtered.slice(0, 12).map((p) => (
<Chip
key={p.id}
label={p.name}
selected={productId === p.id}
onPress={() => setProductId(p.id)}
/>
))}
</ScrollView>
<Text variant="caption" color={colors.subtle} style={{ marginTop: 8 }}>
Selected: {selected.name} · {selected.unit}
</Text>
<View style={{ marginTop: 14, gap: 12 }}>
<Field
label="Quantity"
value={qty}
onChangeText={setQty}
keyboardType="number-pad"
/>
<Text variant="label" color={colors.muted}>
Frequency
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{FREQUENCIES.map((f) => (
<Chip
key={f.days}
label={f.label}
selected={frequencyDays === f.days}
onPress={() => setFrequencyDays(f.days)}
/>
))}
</View>
<Text variant="label" color={colors.muted}>
Fulfillment
</Text>
<Segmented
value={fulfillment}
onChange={(v) => {
setFulfillment(v);
if (v === "delivery" && payment === "pay_at_branch") setPayment("chapa");
}}
options={[
{ value: "delivery", label: "Delivery" },
{ value: "pickup", label: "Pickup" },
]}
/>
<Text variant="label" color={colors.muted} style={{ marginTop: 4 }}>
Payment
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
{(
[
"chapa",
"telebirr",
"mpesa",
...(fulfillment === "pickup" ? (["pay_at_branch"] as const) : []),
] as PaymentMethod[]
).map((m) => (
<Chip
key={m}
label={m}
selected={payment === m}
onPress={() => setPayment(m)}
/>
))}
</View>
<Text variant="label" color={colors.muted}>
For family member (optional)
</Text>
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
<Chip label="Me" selected={!memberId} onPress={() => setMemberId(null)} />
{members
.filter((m) => m.status === "accepted")
.map((m) => (
<Chip
key={m.id}
label={m.name}
selected={memberId === m.id}
onPress={() => setMemberId(m.id)}
/>
))}
</View>
<Button
title="Start subscription"
onPress={() => {
add({
productId: selected.id,
productName: selected.name,
quantity: Math.max(1, Number(qty) || 1),
frequencyDays,
fulfillment,
paymentMethod: payment,
memberId,
});
setShowCreate(false);
Alert.alert(
"Subscription active",
`${selected.name} every ${frequencyDays} days via ${fulfillment}.`
);
}}
/>
<Button title="Cancel" variant="ghost" onPress={() => setShowCreate(false)} />
</View>
</Card>
)}
{items.map((s) => (
<Card key={s.id}>
<View style={{ flexDirection: "row", justifyContent: "space-between", gap: 12 }}>
<View style={{ flex: 1 }}>
<Text variant="h3">{s.productName}</Text>
<Text variant="caption" color={colors.subtle} style={{ marginTop: 4 }}>
Every {s.frequencyDays} days · Qty {s.quantity} · {s.fulfillment}
</Text>
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
Next: {new Date(s.nextFulfillmentAt).toLocaleDateString()} · {s.paymentMethod}
{s.memberId
? ` · ${members.find((m) => m.id === s.memberId)?.name ?? "Family"}`
: ""}
</Text>
</View>
<Badge
label={s.status}
tone={
s.status === "active" ? "brand" : s.status === "paused" ? "warn" : "danger"
}
/>
</View>
<View style={{ flexDirection: "row", gap: 10, marginTop: 14 }}>
{s.status === "active" ? (
<Button title="Pause" size="sm" variant="outline" fullWidth={false} onPress={() => pause(s.id)} />
) : s.status === "paused" ? (
<Button title="Resume" size="sm" fullWidth={false} onPress={() => resume(s.id)} />
) : null}
{s.status !== "cancelled" ? (
<Button
title="Cancel"
size="sm"
variant="ghost"
fullWidth={false}
onPress={() => cancel(s.id)}
/>
) : null}
</View>
</Card>
))}
</ScrollView>
</Screen>
);
}