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>
168 lines
5.9 KiB
TypeScript
168 lines
5.9 KiB
TypeScript
import { Share, ScrollView, View } from "react-native";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Badge } from "@/components/ui/Badge";
|
|
import { Button } from "@/components/ui/Button";
|
|
import { Card, InfoRow, Section } from "@/components/ui/Card";
|
|
import { AppHeader } from "@/components/ui/Header";
|
|
import { Screen } from "@/components/ui/Screen";
|
|
import { Text } from "@/components/ui/Text";
|
|
import { DEMO_VOUCHER } from "@/lib/commerce";
|
|
import { useAuthStore } from "@/lib/stores/authStore";
|
|
import { useCartStore } from "@/lib/stores/cartStore";
|
|
import { colors, gutter } from "@/lib/theme";
|
|
import { router } from "expo-router";
|
|
import { routes } from "@/lib/routes";
|
|
import { Alert } from "react-native";
|
|
|
|
const tiers = [
|
|
{ id: "bronze", min: 0, perks: "1 pt per 10 ETB · Birthday surprise" },
|
|
{ id: "silver", min: 300, perks: "1.25x points · Free delivery over 800 ETB" },
|
|
{ id: "gold", min: 1000, perks: "1.5x points · Priority pharmacist consult" },
|
|
] as const;
|
|
|
|
export default function LoyaltyScreen() {
|
|
const { t } = useTranslation();
|
|
const user = useAuthStore((s) => s.user);
|
|
const ledger = useAuthStore((s) => s.ledger);
|
|
const redeemPoints = useAuthStore((s) => s.redeemPoints);
|
|
const setVoucher = useCartStore((s) => s.setVoucher);
|
|
|
|
const points = user?.points ?? 0;
|
|
const next = tiers.find((tier) => points < tier.min) ?? null;
|
|
const progress = next
|
|
? Math.min(1, points / next.min)
|
|
: 1;
|
|
|
|
return (
|
|
<Screen>
|
|
<AppHeader title={t("common.loyalty")} subtitle="Earn points on every paid order" />
|
|
<ScrollView contentContainerStyle={{ paddingHorizontal: gutter, paddingBottom: 40, gap: 14 }}>
|
|
<Card tone="brand" elevated={false}>
|
|
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
|
|
<View>
|
|
<Text variant="caption" color={colors.muted}>
|
|
Available points
|
|
</Text>
|
|
<Text variant="display" style={{ marginTop: 4 }}>
|
|
{points}
|
|
</Text>
|
|
</View>
|
|
<Badge label={(user?.tier ?? "bronze").toUpperCase()} solid />
|
|
</View>
|
|
<View
|
|
style={{
|
|
marginTop: 16,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: "rgba(16,20,16,0.12)",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
width: `${Math.round(progress * 100)}%`,
|
|
height: "100%",
|
|
backgroundColor: colors.ink,
|
|
}}
|
|
/>
|
|
</View>
|
|
<Text variant="caption" color={colors.muted} style={{ marginTop: 8 }}>
|
|
{next
|
|
? `${next.min - points} pts to ${next.id}`
|
|
: "You've reached Gold — enjoy premium perks"}
|
|
</Text>
|
|
</Card>
|
|
|
|
<Section title="How tiers work">
|
|
<Card elevated={false}>
|
|
{tiers.map((tier, i) => (
|
|
<InfoRow
|
|
key={tier.id}
|
|
label={`${tier.id} · ${tier.min}+ pts`}
|
|
value={tier.perks}
|
|
last={i === tiers.length - 1}
|
|
/>
|
|
))}
|
|
</Card>
|
|
</Section>
|
|
|
|
<Card elevated={false}>
|
|
<Text variant="h3">Redeem rewards</Text>
|
|
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
|
|
Redeem 100 points for a {DEMO_VOUCHER} voucher (10% off your next order).
|
|
</Text>
|
|
{user?.activeVoucher ? (
|
|
<Badge
|
|
label={`Active voucher: ${user.activeVoucher}`}
|
|
tone="brand"
|
|
style={{ marginTop: 12 }}
|
|
/>
|
|
) : null}
|
|
<Button
|
|
title="Redeem 100 pts"
|
|
style={{ marginTop: 14 }}
|
|
disabled={!user || user.isGuest || points < 100}
|
|
onPress={() => {
|
|
const ok = redeemPoints(100);
|
|
if (!ok) {
|
|
Alert.alert("Not enough points", "You need at least 100 points.");
|
|
return;
|
|
}
|
|
setVoucher(DEMO_VOUCHER);
|
|
Alert.alert(
|
|
"Voucher unlocked",
|
|
`${DEMO_VOUCHER} is ready in your cart / checkout.`,
|
|
[
|
|
{ text: "Go to cart", onPress: () => router.push(routes.cart) },
|
|
{ text: "OK" },
|
|
]
|
|
);
|
|
}}
|
|
/>
|
|
</Card>
|
|
|
|
<Card elevated={false}>
|
|
<Text variant="h3">{t("common.referral")}</Text>
|
|
<Text variant="priceLg" style={{ marginTop: 8 }}>
|
|
{user?.referralCode ?? "GISHEN-GUEST"}
|
|
</Text>
|
|
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
|
|
Friends get 50 welcome points. You get 100 when they place their first order.
|
|
</Text>
|
|
<Button
|
|
title="Share code"
|
|
variant="outline"
|
|
icon="share-outline"
|
|
style={{ marginTop: 14 }}
|
|
onPress={() =>
|
|
Share.share({
|
|
message: `Join Gishen Pharmacy with my code ${user?.referralCode ?? ""}. Healthcare delivered to your doorstep.`,
|
|
})
|
|
}
|
|
/>
|
|
</Card>
|
|
|
|
<Section title="Points history">
|
|
<Card elevated={false}>
|
|
{ledger.length === 0 ? (
|
|
<Text variant="bodySm" color={colors.muted}>
|
|
Points from orders will appear here.
|
|
</Text>
|
|
) : (
|
|
ledger.map((entry, i) => (
|
|
<InfoRow
|
|
key={entry.id}
|
|
label={`${entry.label} · ${new Date(entry.at).toLocaleDateString()}`}
|
|
value={`${entry.delta > 0 ? "+" : ""}${entry.delta}`}
|
|
valueColor={entry.delta >= 0 ? colors.brandText : colors.danger}
|
|
last={i === ledger.length - 1}
|
|
/>
|
|
))
|
|
)}
|
|
</Card>
|
|
</Section>
|
|
</ScrollView>
|
|
</Screen>
|
|
);
|
|
}
|