Replace the profile tab with prescription management (inline reminders and family invite), add a shared tab top bar, separate notifications from orders, and support any-branch guest checkout with GSH codes. Co-authored-by: Cursor <cursoragent@cursor.com>
160 lines
5.8 KiB
TypeScript
160 lines
5.8 KiB
TypeScript
import { Ionicons } from "@expo/vector-icons";
|
|
import { router } from "expo-router";
|
|
import { Pressable, 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 { EmptyState } from "@/components/ui/EmptyState";
|
|
import { AppHeader } from "@/components/ui/Header";
|
|
import { Screen } from "@/components/ui/Screen";
|
|
import { Text } from "@/components/ui/Text";
|
|
import { routes } from "@/lib/routes";
|
|
import {
|
|
useNotificationStore,
|
|
type NotificationKind,
|
|
} from "@/lib/stores/notificationStore";
|
|
import { colors, gutter } from "@/lib/theme";
|
|
|
|
const kindMeta: Record<
|
|
NotificationKind,
|
|
{ label: string; icon: keyof typeof Ionicons.glyphMap; tone: "brand" | "info" | "warn" | "neutral" | "danger" }
|
|
> = {
|
|
order: { label: "Order", icon: "cube-outline", tone: "info" },
|
|
rx: { label: "Rx", icon: "document-text-outline", tone: "warn" },
|
|
dose: { label: "Dose", icon: "alarm-outline", tone: "brand" },
|
|
loyalty: { label: "Loyalty", icon: "star-outline", tone: "brand" },
|
|
promo: { label: "Promo", icon: "pricetag-outline", tone: "neutral" },
|
|
system: { label: "Tips", icon: "information-circle-outline", tone: "neutral" },
|
|
};
|
|
|
|
export default function NotificationsScreen() {
|
|
const { t } = useTranslation();
|
|
const items = useNotificationStore((s) => s.items);
|
|
const markRead = useNotificationStore((s) => s.markRead);
|
|
const markAllRead = useNotificationStore((s) => s.markAllRead);
|
|
const remove = useNotificationStore((s) => s.remove);
|
|
const unread = items.filter((n) => !n.read).length;
|
|
|
|
return (
|
|
<Screen>
|
|
<AppHeader
|
|
title={t("common.notifications")}
|
|
subtitle={
|
|
unread
|
|
? `${unread} unread · separate from your order history`
|
|
: "Alerts for doses, Rx, loyalty and orders"
|
|
}
|
|
right={
|
|
unread ? (
|
|
<Pressable onPress={markAllRead} hitSlop={8}>
|
|
<Text variant="label" color={colors.brandText}>
|
|
Read all
|
|
</Text>
|
|
</Pressable>
|
|
) : undefined
|
|
}
|
|
/>
|
|
<ScrollView
|
|
contentContainerStyle={{
|
|
paddingHorizontal: gutter,
|
|
paddingBottom: 40,
|
|
gap: 10,
|
|
}}
|
|
>
|
|
<Card elevated={false} tone="brand">
|
|
<Text variant="bodySm" color={colors.muted}>
|
|
This inbox is not your Orders list. Order history lives under Profile → Orders.
|
|
Notifications include dose reminders, Rx updates, loyalty, promos, and status
|
|
alerts that may deep-link into an order.
|
|
</Text>
|
|
<Button
|
|
title="Open order history"
|
|
variant="outline"
|
|
size="md"
|
|
fullWidth={false}
|
|
style={{ marginTop: 12 }}
|
|
onPress={() => router.push(routes.orders)}
|
|
/>
|
|
</Card>
|
|
|
|
{items.length === 0 ? (
|
|
<EmptyState
|
|
icon="notifications-outline"
|
|
title="No notifications"
|
|
body="Dose reminders, Rx status, and order alerts will show up here."
|
|
actionTitle="Browse shop"
|
|
onAction={() => router.push(routes.shop)}
|
|
/>
|
|
) : (
|
|
items.map((n) => {
|
|
const meta = kindMeta[n.kind];
|
|
return (
|
|
<Pressable
|
|
key={n.id}
|
|
onPress={() => {
|
|
markRead(n.id);
|
|
if (n.href) router.push(n.href as never);
|
|
}}
|
|
style={{
|
|
backgroundColor: n.read ? colors.surface : colors.brandWash,
|
|
borderRadius: 18,
|
|
borderWidth: 1,
|
|
borderColor: n.read ? colors.line : colors.brandBorder,
|
|
padding: 14,
|
|
}}
|
|
>
|
|
<View style={{ flexDirection: "row", gap: 12, alignItems: "flex-start" }}>
|
|
<View
|
|
style={{
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 12,
|
|
backgroundColor: colors.surface,
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
borderWidth: 1,
|
|
borderColor: colors.line,
|
|
}}
|
|
>
|
|
<Ionicons name={meta.icon} size={18} color={colors.brandText} />
|
|
</View>
|
|
<View style={{ flex: 1 }}>
|
|
<View
|
|
style={{
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
gap: 8,
|
|
flexWrap: "wrap",
|
|
}}
|
|
>
|
|
<Badge label={meta.label} tone={meta.tone} />
|
|
{!n.read ? <Badge label="New" solid /> : null}
|
|
</View>
|
|
<Text variant="h3" style={{ marginTop: 8 }}>
|
|
{n.title}
|
|
</Text>
|
|
<Text variant="bodySm" color={colors.muted} style={{ marginTop: 4 }}>
|
|
{n.body}
|
|
</Text>
|
|
<Text variant="caption" color={colors.subtle} style={{ marginTop: 8 }}>
|
|
{new Date(n.createdAt).toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<Pressable
|
|
onPress={() => remove(n.id)}
|
|
hitSlop={8}
|
|
accessibilityLabel="Dismiss"
|
|
>
|
|
<Ionicons name="close" size={18} color={colors.subtle} />
|
|
</Pressable>
|
|
</View>
|
|
</Pressable>
|
|
);
|
|
})
|
|
)}
|
|
</ScrollView>
|
|
</Screen>
|
|
);
|
|
}
|