import * as Notifications from "expo-notifications"; import Constants from "expo-constants"; import { router } from "expo-router"; import { useEffect, useRef, useState } from "react"; import { AppState, Platform, type AppStateStatus } from "react-native"; import { ensureNotificationPermissions, syncInvoiceSendReminders, } from "@/lib/invoice-send-reminders"; import { api } from "@/lib/trpc"; function openInvoiceFromNotification( data: Record | undefined, ) { if (data?.type !== "invoice-send-reminder") return; const invoiceId = data.invoiceId; if (typeof invoiceId !== "string" || !invoiceId) return; router.push(`/(app)/invoices/${invoiceId}`); } /** Schedules local iOS/Android notifications for draft invoice send reminders. */ export function InvoiceReminderSync() { const utils = api.useUtils(); const invoicesQuery = api.invoices.getAll.useQuery( { status: "draft" }, { staleTime: 60_000 }, ); const wasBackgrounded = useRef(false); const [remotePushReady, setRemotePushReady] = useState(false); const registerPushToken = api.notifications.registerPushToken.useMutation(); useEffect(() => { if (Platform.OS !== "ios" && Platform.OS !== "android") return; void (async () => { if (!(await ensureNotificationPermissions())) return; const projectId = Constants.easConfig?.projectId ?? (Constants.expoConfig?.extra?.eas as { projectId?: string } | undefined) ?.projectId; if (!projectId) return; const { data: token } = await Notifications.getExpoPushTokenAsync({ projectId, }); await registerPushToken.mutateAsync({ token, platform: Platform.OS }); setRemotePushReady(true); })().catch(() => { // Local reminders remain available when remote push registration is unavailable. }); }, [registerPushToken]); useEffect(() => { if (!invoicesQuery.data) return; void syncInvoiceSendReminders(remotePushReady ? [] : invoicesQuery.data); }, [invoicesQuery.data, remotePushReady]); useEffect(() => { const subscription = AppState.addEventListener( "change", (nextState: AppStateStatus) => { if (nextState === "background" || nextState === "inactive") { wasBackgrounded.current = true; return; } if (nextState !== "active" || !wasBackgrounded.current) return; wasBackgrounded.current = false; void utils.invoices.getAll.invalidate({ status: "draft" }); }, ); return () => subscription.remove(); }, [utils.invoices.getAll]); useEffect(() => { const responseSubscription = Notifications.addNotificationResponseReceivedListener((response) => { openInvoiceFromNotification( response.notification.request.content.data as Record, ); }); void Notifications.getLastNotificationResponseAsync().then((response) => { if (!response) return; openInvoiceFromNotification( response.notification.request.content.data as Record, ); }); return () => responseSubscription.remove(); }, []); return null; }