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>
142 lines
3.8 KiB
TypeScript
142 lines
3.8 KiB
TypeScript
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||
import { create } from "zustand";
|
||
import { createJSONStorage, persist } from "zustand/middleware";
|
||
|
||
export type NotificationKind =
|
||
| "order"
|
||
| "rx"
|
||
| "dose"
|
||
| "loyalty"
|
||
| "promo"
|
||
| "system";
|
||
|
||
export type AppNotification = {
|
||
id: string;
|
||
kind: NotificationKind;
|
||
title: string;
|
||
body: string;
|
||
createdAt: string;
|
||
read: boolean;
|
||
/** Deep-link target inside the app */
|
||
href?: string | null;
|
||
};
|
||
|
||
type NotificationState = {
|
||
items: AppNotification[];
|
||
unreadCount: () => number;
|
||
markRead: (id: string) => void;
|
||
markAllRead: () => void;
|
||
remove: (id: string) => void;
|
||
push: (
|
||
n: Omit<AppNotification, "id" | "createdAt" | "read"> & {
|
||
id?: string;
|
||
createdAt?: string;
|
||
read?: boolean;
|
||
}
|
||
) => AppNotification;
|
||
};
|
||
|
||
function hoursAgo(n: number) {
|
||
const d = new Date();
|
||
d.setHours(d.getHours() - n);
|
||
return d.toISOString();
|
||
}
|
||
|
||
export const seedNotifications: AppNotification[] = [
|
||
{
|
||
id: "ntf_dose_1",
|
||
kind: "dose",
|
||
title: "Time for Metformin 500mg",
|
||
body: "Confirm your 20:00 dose or snooze for 10 minutes.",
|
||
createdAt: hoursAgo(1),
|
||
read: false,
|
||
href: "/(root)/(screens)/prescription/calendar/rx_demo_metformin",
|
||
},
|
||
{
|
||
id: "ntf_rx_1",
|
||
kind: "rx",
|
||
title: "Prescription under review",
|
||
body: "A pharmacist is checking your Azithromycin Rx. We’ll notify you when it’s ready.",
|
||
createdAt: hoursAgo(5),
|
||
read: false,
|
||
href: "/(root)/(screens)/prescription/rx_demo_azitro",
|
||
},
|
||
{
|
||
id: "ntf_order_1",
|
||
kind: "order",
|
||
title: "Rider is on the way",
|
||
body: "Your Bole delivery is out for delivery. Track live progress anytime.",
|
||
createdAt: hoursAgo(8),
|
||
read: false,
|
||
href: "/(root)/(screens)/tracking/ord_demo_active",
|
||
},
|
||
{
|
||
id: "ntf_loyalty_1",
|
||
kind: "loyalty",
|
||
title: "120 points ready to redeem",
|
||
body: "Redeem 100 pts for voucher GISHEN10 at checkout.",
|
||
createdAt: hoursAgo(26),
|
||
read: true,
|
||
href: "/(root)/(screens)/loyalty",
|
||
},
|
||
{
|
||
id: "ntf_promo_1",
|
||
kind: "promo",
|
||
title: "Weekend wellness picks",
|
||
body: "Save on vitamins and personal care this week at any Gishen branch.",
|
||
createdAt: hoursAgo(40),
|
||
read: true,
|
||
href: "/(root)/(screens)/shop",
|
||
},
|
||
{
|
||
id: "ntf_system_1",
|
||
kind: "system",
|
||
title: "Guest pickup tip",
|
||
body: "Shopping as a guest? Use your quick pickup code at any branch counter.",
|
||
createdAt: hoursAgo(72),
|
||
read: true,
|
||
href: null,
|
||
},
|
||
];
|
||
|
||
export const useNotificationStore = create<NotificationState>()(
|
||
persist(
|
||
(set, get) => ({
|
||
items: seedNotifications,
|
||
unreadCount: () => get().items.filter((n) => !n.read).length,
|
||
markRead: (id) =>
|
||
set({
|
||
items: get().items.map((n) => (n.id === id ? { ...n, read: true } : n)),
|
||
}),
|
||
markAllRead: () =>
|
||
set({ items: get().items.map((n) => ({ ...n, read: true })) }),
|
||
remove: (id) => set({ items: get().items.filter((n) => n.id !== id) }),
|
||
push: (payload) => {
|
||
const item: AppNotification = {
|
||
id: payload.id ?? `ntf_${Date.now()}`,
|
||
createdAt: payload.createdAt ?? new Date().toISOString(),
|
||
read: payload.read ?? false,
|
||
kind: payload.kind,
|
||
title: payload.title,
|
||
body: payload.body,
|
||
href: payload.href ?? null,
|
||
};
|
||
set({ items: [item, ...get().items] });
|
||
return item;
|
||
},
|
||
}),
|
||
{
|
||
name: "gishen-notifications",
|
||
storage: createJSONStorage(() => AsyncStorage),
|
||
version: 1,
|
||
migrate: (persisted: unknown) => {
|
||
const state = (persisted ?? {}) as Partial<NotificationState>;
|
||
if (!state.items?.length) {
|
||
return { ...state, items: seedNotifications };
|
||
}
|
||
return state;
|
||
},
|
||
}
|
||
)
|
||
);
|