mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-01 18:19:43 +00:00
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:
1 parent
8a44fef764
commit
c8aaa8967d
12 files changed
+1117
-266
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { View, StyleSheet, Text } from 'react-native';
|
||||
import { colors, spacing, typography } from '../../../theme';
|
||||
import { useNotificationPreferencesStore } from '../../../store';
|
||||
import { useAuthStore } from '../../../store';
|
||||
import { useNotificationPreferencesStore, useAuthStore } from '../../../store';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
import { SwitchItem } from './SwitchItem';
|
||||
|
||||
@@ -19,7 +18,7 @@ export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
|
||||
onPushToggle,
|
||||
delay = 360,
|
||||
}) => {
|
||||
const { user } = useAuthStore();
|
||||
const { user, partner } = useAuthStore();
|
||||
const { preferences, isUpdating, fetchPreferences, updatePreference } = useNotificationPreferencesStore();
|
||||
|
||||
// Fetch notification preferences on mount when user is available
|
||||
@@ -39,6 +38,8 @@ export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
|
||||
onNewPacksToggle: (value: boolean) => updatePreference('new_packs_enabled', value),
|
||||
onStreakMilestonesToggle: (value: boolean) => updatePreference('streak_milestones_enabled', value),
|
||||
onWeeklySummaryToggle: (value: boolean) => updatePreference('weekly_summary_enabled', value),
|
||||
onUnpairedRemindersToggle: (value: boolean) => updatePreference('unpaired_reminders_enabled', value),
|
||||
onCatchupRemindersToggle: (value: boolean) => updatePreference('catchup_reminders_enabled', value),
|
||||
}), [updatePreference]);
|
||||
|
||||
return (
|
||||
@@ -145,6 +146,36 @@ export const NotificationSettings: React.FC<NotificationSettingsProps> = ({
|
||||
onValueChange={handlers.onWeeklySummaryToggle}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
|
||||
{partner && (
|
||||
<>
|
||||
<View style={styles.itemDivider} />
|
||||
|
||||
<SwitchItem
|
||||
icon="time-outline"
|
||||
label="Catch-up Reminders"
|
||||
description="Reminders when your partner is ahead"
|
||||
value={preferences.catchup_reminders_enabled}
|
||||
onValueChange={handlers.onCatchupRemindersToggle}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!partner && (
|
||||
<>
|
||||
<View style={styles.itemDivider} />
|
||||
|
||||
<SwitchItem
|
||||
icon="people-outline"
|
||||
label="Pairing Reminders"
|
||||
description="Reminders to share your invite code"
|
||||
value={preferences.unpaired_reminders_enabled}
|
||||
onValueChange={handlers.onUnpairedRemindersToggle}
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</SettingsSection>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -12,6 +12,8 @@ export interface NotificationPreferences {
|
||||
new_packs_enabled: boolean;
|
||||
streak_milestones_enabled: boolean;
|
||||
weekly_summary_enabled: boolean;
|
||||
unpaired_reminders_enabled: boolean;
|
||||
catchup_reminders_enabled: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user