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>
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
import { router } from "expo-router";
|
|
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 { AppHeader } from "@/components/ui/Header";
|
|
import { Screen } from "@/components/ui/Screen";
|
|
import { Text } from "@/components/ui/Text";
|
|
import { useFamilyStore } from "@/lib/stores/familyStore";
|
|
import { colors, gutter } from "@/lib/theme";
|
|
import { routes } from "@/lib/routes";
|
|
|
|
export default function FamilyScreen() {
|
|
const { t } = useTranslation();
|
|
const members = useFamilyStore((s) => s.members);
|
|
const inviteCode = useFamilyStore((s) => s.inviteCode);
|
|
const acceptInvite = useFamilyStore((s) => s.acceptInvite);
|
|
const removeMember = useFamilyStore((s) => s.removeMember);
|
|
const setActiveMember = useFamilyStore((s) => s.setActiveMember);
|
|
const activeMemberId = useFamilyStore((s) => s.activeMemberId);
|
|
|
|
return (
|
|
<Screen>
|
|
<AppHeader title={t("common.family")} subtitle="Manage members and shared care" />
|
|
<ScrollView contentContainerStyle={{ paddingHorizontal: gutter, paddingBottom: 40, gap: 12 }}>
|
|
<Card tone="brand" elevated={false}>
|
|
<Text variant="caption" color={colors.muted}>
|
|
Your invite code
|
|
</Text>
|
|
<Text variant="h1" style={{ marginTop: 4 }}>
|
|
{inviteCode}
|
|
</Text>
|
|
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 6 }}>
|
|
Share this code so family can join your care circle.
|
|
</Text>
|
|
</Card>
|
|
|
|
<Button
|
|
title={t("common.inviteFamily")}
|
|
icon="person-add-outline"
|
|
onPress={() => router.push(routes.familyInvite)}
|
|
/>
|
|
|
|
{members.map((m) => (
|
|
<Card
|
|
key={m.id}
|
|
onPress={() => setActiveMember(m.id === activeMemberId ? null : m.id)}
|
|
style={{
|
|
borderColor: activeMemberId === m.id ? colors.brand : colors.line,
|
|
borderWidth: activeMemberId === m.id ? 1.5 : 1,
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: "row", justifyContent: "space-between", gap: 12 }}>
|
|
<View style={{ flex: 1 }}>
|
|
<Text variant="h3">{m.name}</Text>
|
|
<Text variant="caption" color={colors.subtle} style={{ marginTop: 4 }}>
|
|
{m.relationship}
|
|
{m.phone ? ` · ${m.phone}` : ""}
|
|
</Text>
|
|
</View>
|
|
<Badge
|
|
label={m.status}
|
|
tone={m.status === "accepted" ? "brand" : "warn"}
|
|
/>
|
|
</View>
|
|
<View style={{ flexDirection: "row", gap: 16, marginTop: 12 }}>
|
|
{m.status === "pending" ? (
|
|
<Button
|
|
title="Accept"
|
|
size="sm"
|
|
fullWidth={false}
|
|
onPress={() => acceptInvite(m.id)}
|
|
/>
|
|
) : null}
|
|
<Button
|
|
title="Remove"
|
|
size="sm"
|
|
variant="ghost"
|
|
fullWidth={false}
|
|
onPress={() =>
|
|
Alert.alert("Remove member?", m.name, [
|
|
{ text: "Cancel", style: "cancel" },
|
|
{ text: "Remove", style: "destructive", onPress: () => removeMember(m.id) },
|
|
])
|
|
}
|
|
/>
|
|
</View>
|
|
</Card>
|
|
))}
|
|
</ScrollView>
|
|
</Screen>
|
|
);
|
|
}
|