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)/(screens)/notifications.tsx
kirukib 5391780b9b Ship Rx Care tab, notifications inbox, and guest pickup codes.
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>
2026-08-07 15:35:51 +03:00

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>
);
}