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>
144 lines
5.2 KiB
TypeScript
144 lines
5.2 KiB
TypeScript
import { Linking, ScrollView, View } from "react-native";
|
||
import { useState } from "react";
|
||
import { Alert } from "react-native";
|
||
import { useTranslation } from "react-i18next";
|
||
import { Badge } from "@/components/ui/Badge";
|
||
import { Button, IconButton } from "@/components/ui/Button";
|
||
import { Card } from "@/components/ui/Card";
|
||
import { Chip } from "@/components/ui/Chip";
|
||
import { Field, SwitchRow } from "@/components/ui/Field";
|
||
import { AppHeader } from "@/components/ui/Header";
|
||
import { Screen } from "@/components/ui/Screen";
|
||
import { Text } from "@/components/ui/Text";
|
||
import { useEmergencyContactStore } from "@/lib/stores/emergencyContactStore";
|
||
import { colors, gutter } from "@/lib/theme";
|
||
|
||
const RELATIONSHIPS = ["spouse", "parent", "child", "sibling", "friend", "other"] as const;
|
||
|
||
export default function EmergencyContactsScreen() {
|
||
const { t } = useTranslation();
|
||
const contacts = useEmergencyContactStore((s) => s.contacts);
|
||
const add = useEmergencyContactStore((s) => s.add);
|
||
const remove = useEmergencyContactStore((s) => s.remove);
|
||
const grace = useEmergencyContactStore((s) => s.graceMinutesDefault);
|
||
const setGrace = useEmergencyContactStore((s) => s.setGrace);
|
||
const escalationEnabled = useEmergencyContactStore((s) => s.escalationEnabled);
|
||
const setEscalationEnabled = useEmergencyContactStore((s) => s.setEscalationEnabled);
|
||
const [name, setName] = useState("");
|
||
const [phone, setPhone] = useState("");
|
||
const [relationship, setRelationship] =
|
||
useState<(typeof RELATIONSHIPS)[number]>("spouse");
|
||
|
||
return (
|
||
<Screen>
|
||
<AppHeader
|
||
title={t("common.emergencyContacts")}
|
||
subtitle="Notified if a dose isn’t confirmed in time"
|
||
/>
|
||
<ScrollView
|
||
contentContainerStyle={{
|
||
paddingHorizontal: gutter,
|
||
paddingBottom: 40,
|
||
gap: 14,
|
||
}}
|
||
>
|
||
<Card padded={false}>
|
||
<SwitchRow
|
||
icon="notifications-outline"
|
||
title="Enable escalation"
|
||
subtitle={t("common.escalation")}
|
||
value={escalationEnabled}
|
||
onValueChange={setEscalationEnabled}
|
||
/>
|
||
<View style={{ padding: 16, paddingTop: 4 }}>
|
||
<Field
|
||
label={t("common.graceMinutes")}
|
||
value={String(grace)}
|
||
keyboardType="number-pad"
|
||
onChangeText={(v) => setGrace(Number(v) || 30)}
|
||
hint="Minutes to wait before notifying your contact"
|
||
/>
|
||
</View>
|
||
</Card>
|
||
|
||
<Card>
|
||
<Text variant="h3">Add contact</Text>
|
||
<View style={{ gap: 12, marginTop: 12 }}>
|
||
<Field label="Name" value={name} onChangeText={setName} icon="person-outline" />
|
||
<Field
|
||
label="Phone"
|
||
value={phone}
|
||
onChangeText={setPhone}
|
||
keyboardType="phone-pad"
|
||
icon="call-outline"
|
||
/>
|
||
<Text variant="label" color={colors.muted}>
|
||
Relationship
|
||
</Text>
|
||
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
||
{RELATIONSHIPS.map((r) => (
|
||
<Chip
|
||
key={r}
|
||
label={r}
|
||
selected={relationship === r}
|
||
onPress={() => setRelationship(r)}
|
||
size="sm"
|
||
/>
|
||
))}
|
||
</View>
|
||
<Button
|
||
title="Add contact"
|
||
onPress={() => {
|
||
if (!name.trim() || !phone.trim()) {
|
||
Alert.alert("Required", "Name and phone are required.");
|
||
return;
|
||
}
|
||
add({ name: name.trim(), phone: phone.trim(), relationship });
|
||
setName("");
|
||
setPhone("");
|
||
Alert.alert("Added", `${name.trim()} can receive missed-dose alerts.`);
|
||
}}
|
||
/>
|
||
</View>
|
||
</Card>
|
||
|
||
<Text variant="h3">{contacts.length ? "Your contacts" : "No contacts yet"}</Text>
|
||
{contacts.map((c) => (
|
||
<Card key={c.id}>
|
||
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
||
<View style={{ flex: 1 }}>
|
||
<Text variant="h3">{c.name}</Text>
|
||
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 2 }}>
|
||
{c.phone}
|
||
</Text>
|
||
<View style={{ marginTop: 8 }}>
|
||
<Badge label={c.relationship} tone="neutral" />
|
||
</View>
|
||
</View>
|
||
<IconButton
|
||
icon="call-outline"
|
||
onPress={() => Linking.openURL(`tel:${c.phone}`)}
|
||
accessibilityLabel="Call"
|
||
/>
|
||
<IconButton
|
||
icon="trash-outline"
|
||
onPress={() =>
|
||
Alert.alert("Remove contact?", c.name, [
|
||
{ text: "Cancel", style: "cancel" },
|
||
{
|
||
text: "Remove",
|
||
style: "destructive",
|
||
onPress: () => remove(c.id),
|
||
},
|
||
])
|
||
}
|
||
accessibilityLabel="Remove"
|
||
/>
|
||
</View>
|
||
</Card>
|
||
))}
|
||
</ScrollView>
|
||
</Screen>
|
||
);
|
||
}
|