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)/cart.tsx
kirukib 858972b5ca Add multi-provider login and guest pharmacist order codes.
Guests can generate a shareable GSH code for admin fulfillment; sign-in now supports Google, email, phone, and Telegram demos, with the backend spec updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 16:11:23 +03:00

186 lines
6.9 KiB
TypeScript

import { Ionicons } from "@expo/vector-icons";
import { router } from "expo-router";
import {
Alert,
Image,
Pressable,
ScrollView,
TextInput,
View,
} from "react-native";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/Button";
import { Card, InfoRow } from "@/components/ui/Card";
import { EmptyState } from "@/components/ui/EmptyState";
import { QtyStepper } from "@/components/ui/QtyStepper";
import { Screen, TAB_BAR_INSET } from "@/components/ui/Screen";
import { StickyBar } from "@/components/ui/StickyBar";
import { TabTopBar } from "@/components/ui/TabTopBar";
import { Text } from "@/components/ui/Text";
import { DELIVERY_FEE, DEMO_VOUCHER, voucherDiscount } from "@/lib/commerce";
import { routes } from "@/lib/routes";
import { useAuthStore } from "@/lib/stores/authStore";
import { useCartStore } from "@/lib/stores/cartStore";
import { colors, fonts, gutter, radii } from "@/lib/theme";
import { formatETB } from "@/lib/utils";
export default function CartScreen() {
const { t } = useTranslation();
const items = useCartStore((s) => s.items);
const setQuantity = useCartStore((s) => s.setQuantity);
const removeItem = useCartStore((s) => s.removeItem);
const subtotal = useCartStore((s) => s.subtotal());
const voucher = useCartStore((s) => s.voucherCode);
const setVoucher = useCartStore((s) => s.setVoucher);
const isGuest = !!useAuthStore((s) => s.user?.isGuest);
const delivery = items.length ? DELIVERY_FEE : 0;
const discount = voucherDiscount(subtotal, voucher);
const total = subtotal + delivery - discount;
return (
<Screen>
<TabTopBar subtitle={t("common.cart")} />
<ScrollView
contentContainerStyle={{
paddingBottom: TAB_BAR_INSET + 100,
paddingHorizontal: gutter,
}}
>
<Text variant="h1">{t("common.cart")}</Text>
<Text variant="bodySm" color={colors.subtle} style={{ marginTop: 4 }}>
{items.length} item{items.length === 1 ? "" : "s"} in your bag
{isGuest ? " · Guest checkout creates a pharmacist code" : ""}
</Text>
{items.length === 0 ? (
<EmptyState
icon="bag-handle-outline"
title={t("common.emptyCart")}
body="Browse the shop and add medicines or wellness products."
actionTitle="Explore products"
onAction={() => router.push(routes.shop)}
/>
) : (
<View style={{ marginTop: 16, gap: 12 }}>
{items.map((item) => (
<Card key={item.productId} elevated>
<View style={{ flexDirection: "row", gap: 12 }}>
<Image
source={{ uri: item.image }}
style={{
width: 72,
height: 72,
borderRadius: radii.sm,
backgroundColor: colors.surfaceAlt,
}}
resizeMode="contain"
/>
<View style={{ flex: 1 }}>
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text variant="h3" style={{ flex: 1 }} numberOfLines={2}>
{item.name}
</Text>
<Pressable onPress={() => removeItem(item.productId)} hitSlop={8}>
<Ionicons name="trash-outline" size={18} color={colors.subtle} />
</Pressable>
</View>
<Text variant="caption" color={colors.subtle} style={{ marginTop: 2 }}>
{item.unit}
{item.requiresPrescription ? " · RX" : ""}
</Text>
<View
style={{
marginTop: 10,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text variant="price">{formatETB(item.price)}</Text>
<QtyStepper
size="sm"
value={item.quantity}
onChange={(n) => setQuantity(item.productId, n)}
min={0}
/>
</View>
</View>
</View>
</Card>
))}
<Pressable onPress={() => router.push(routes.shop)}>
<Text variant="label" color={colors.brandText}>
+ Add product
</Text>
</Pressable>
<Card elevated={false} style={{ marginTop: 8 }}>
<Text variant="h3" style={{ marginBottom: 8 }}>
Voucher
</Text>
<View
style={{
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: colors.line,
borderRadius: radii.pill,
paddingLeft: 14,
paddingRight: 6,
height: 48,
}}
>
<TextInput
value={voucher ?? ""}
onChangeText={(v) => setVoucher(v || null)}
placeholder="Try GISHEN10"
placeholderTextColor={colors.subtle}
style={{
flex: 1,
fontFamily: fonts.regular,
fontSize: 14,
color: colors.ink,
}}
/>
<Button
title="Apply"
size="sm"
fullWidth={false}
onPress={() => {
if ((voucher ?? "").toUpperCase() === DEMO_VOUCHER) {
setVoucher(DEMO_VOUCHER);
Alert.alert("Applied", "10% off your basket.");
} else {
Alert.alert("Invalid code", `Use ${DEMO_VOUCHER} for a demo discount.`);
}
}}
/>
</View>
</Card>
<Card elevated={false}>
<InfoRow label="Payment" value={formatETB(subtotal)} />
<InfoRow label="Delivery" value={formatETB(delivery)} />
{discount > 0 ? (
<InfoRow label="Discount" value={`- ${formatETB(discount)}`} valueColor={colors.brandText} />
) : null}
<InfoRow label={t("common.total")} value={formatETB(total)} emphasis last />
</Card>
</View>
)}
</ScrollView>
{items.length > 0 ? (
<StickyBar>
<Button
title={isGuest ? "Get pharmacist code" : t("common.payNow")}
iconRight="arrow-forward"
onPress={() => router.push(routes.checkout)}
/>
</StickyBar>
) : null}
</Screen>
);
}