feat: add catchup and unpaired reminder notifications

Add automatic push notifications for behind partners (escalating: 24h then
every 3 days) and daily unpaired user reminders. Includes edge functions,
migrations, notification preference toggles, and UI improvements to pairing
and swipe screens.
This commit is contained in:
jjbrunton committed 2026-01-31 23:38:27 +00:00
1 parent 8a44fef764
commit c8aaa8967d
12 files changed
+1117 -266

No files matched your search

@@ -27,6 +27,11 @@ interface SwipeInfoStateLayoutProps {
onPress: () => void;
variant?: "primary" | "secondary";
};
secondaryAction?: {
label: string;
onPress: () => void;
icon?: keyof typeof Ionicons.glyphMap;
};
}
export const SwipeInfoStateLayout = ({
@@ -41,6 +46,7 @@ export const SwipeInfoStateLayout = ({
separatorVariant = "rose",
labelStyle,
action,
secondaryAction,
}: SwipeInfoStateLayoutProps) => (
<GradientBackground>
<View style={styles.centerContainer}>
@@ -88,6 +94,17 @@ export const SwipeInfoStateLayout = ({
{action.label}
</GlassButton>
)}
{secondaryAction && (
<GlassButton
variant="secondary"
onPress={secondaryAction.onPress}
style={{ marginTop: spacing.sm }}
icon={secondaryAction.icon ? <Ionicons name={secondaryAction.icon} size={18} color={colors.text} /> : undefined}
>
{secondaryAction.label}
</GlassButton>
)}
</Animated.View>
</View>
</GradientBackground>
@@ -1,4 +1,7 @@
import { Share } from "react-native";
import { colors } from "../../../theme";
import { useAuthStore } from "../../../store";
import { Events } from "../../../lib/analytics";
import { SwipeInfoStateLayout } from "./SwipeInfoStateLayout";
interface SwipeNoPartnerStateProps {
@@ -8,6 +11,19 @@ interface SwipeNoPartnerStateProps {
export const SwipeNoPartnerState = ({ hasCouple, onPairPress }: SwipeNoPartnerStateProps) => {
const accent = colors.premium.rose;
const { couple } = useAuthStore();
const handleShareCode = async () => {
if (!couple?.invite_code) return;
try {
await Share.share({
message: `Join me on Sauci! Download the app at https://sauci.app and use my invite code to pair up: ${couple.invite_code}`,
});
Events.codeShared();
} catch (error) {
console.error(error);
}
};
return (
<SwipeInfoStateLayout
@@ -30,6 +46,11 @@ export const SwipeNoPartnerState = ({ hasCouple, onPairPress }: SwipeNoPartnerSt
label: hasCouple ? "View Invite Code" : "Pair Now",
onPress: onPairPress,
}}
secondaryAction={hasCouple && couple?.invite_code ? {
label: "Share Code",
onPress: handleShareCode,
icon: "share-outline",
} : undefined}
/>
);
};